Beispiel #1
0
class QueueingSystem(Model):
    """QueueingSystem model represents a simple queue */*/1/N or */*/1,
    where any distribution can be used for arrival and service times.

    This model consists of four children:
    - `queue`: a model representing the packets queue (`Queue`)
    - `source`: a model representing the packets source (`Source`)
    - `server`: a model representing the packets server (`Server`)
    - `sink`: a model which collects served packets (`Sink`)
    """
    def __init__(self, sim):
        super().__init__(sim)

        arrival = sim.params.arrival
        service = sim.params.service
        queue_capacity = sim.params.queue_capacity

        self.children['queue'] = Queue(sim, queue_capacity)
        self.children['source'] = Source(sim, arrival, index=0)
        self.children['server'] = Server(sim, service)
        self.children['sink'] = Sink(sim)

        # Building connections:
        self.source.connections['queue'] = self.queue
        self.queue.connections['server'] = self.server
        self.server.connections['queue'] = self.queue
        self.server.connections['next'] = self.sink

        # Statistics:
        self.system_size_trace = Trace()
        self.system_size_trace.record(self.sim.stime, 0)
        self.system_wait_intervals = Statistic()

    @property
    def queue(self):
        return self.children['queue']

    @property
    def source(self):
        return self.children['source']

    @property
    def server(self):
        return self.children['server']

    @property
    def sink(self):
        return self.children['sink']

    @property
    def system_size(self):
        return self.queue.size + (1 if self.server.busy else 0)

    def update_system_size(self, index=0):
        assert index == 0
        self.system_size_trace.record(self.sim.stime, self.system_size)

    def add_system_wait_interval(self, value, index=0):
        assert index == 0
        self.system_wait_intervals.append(value)
class Server(Model):
    """Server module represents a packet server with exponential service time.

    Connections: queue, sink

    Handlers:
    - on_service_end(): called upon service timeout

    Methods:
    - start_service(): start new packet service; generate error if busy.

    Statistics:
    - delays: `Statistic`, stores a set of service intervals
    - busy_trace: `Trace`, stores a vector of server busy status

    Parent: `QueueingSystem`
    """
    def __init__(self, sim, service_mean):
        super().__init__(sim)
        self.__service_mean = service_mean
        self.__busy = False
        # Statistics:
        self.delays = Statistic()
        self.busy_trace = Trace()
        self.busy_trace.record(self.sim.stime, 0)
    
    @property
    def service_mean(self):
        return self.__service_mean
    
    @property
    def busy(self):
        return self.__busy
    
    def on_service_end(self):
        assert self.__busy
        queue = self.connections['queue'].module
        self.__busy = False
        self.busy_trace.record(self.sim.stime, 0)
        if queue.size > 0:
            queue.pop()
            self.start_service()
        self.connections['sink'].module.receive_packet()
        self.parent.update_system_size()

    def start_service(self):
        assert not self.__busy
        delay = exponential(self.service_mean)
        self.sim.schedule(delay, self.on_service_end)
        self.delays.append(delay)
        self.__busy = True
        self.busy_trace.record(self.sim.stime, 1)
Beispiel #3
0
def test_statistic_append_adds_values():
    st = Statistic()
    st.append(1)
    st.append(20)
    assert st.as_tuple() == (1, 20)
Beispiel #4
0
def test_statistic_copy_data_content_instead_of_pointer():
    data = [1, 2]
    st = Statistic(data)
    st.append(3)
    assert st.as_tuple() == (1, 2, 3)
    assert data == [1, 2]
Beispiel #5
0
class Server(Model):
    """Server module represents a packet server with exponential service time.

    Connections: queue, sink

    Handlers:
    - on_service_end(): called upon service timeout

    Methods:
    - start_service(): start new packet service; generate error if busy.

    Statistics:
    - delays: `Statistic`, stores a set of service intervals
    - busy_trace: `Trace`, stores a vector of server busy status
    - num_served: `int`

    Parent: `QueueingSystem`
    """
    def __init__(self, sim, service_time, index=0):
        super().__init__(sim)
        self.service_time = service_time
        self.packet = None
        self.index = index

        # Statistics:
        self.service_intervals = Statistic()
        self.busy_trace = Trace()
        self.busy_trace.record(self.sim.stime, 0)
        self.departure_intervals = Intervals()
        self.departure_intervals.record(sim.stime)
        self.num_served = 0

    @property
    def busy(self):
        return self.packet is not None

    @property
    def ready(self):
        return self.packet is None

    def handle_service_end(self):
        assert self.busy
        stime = self.sim.stime

        self.connections['next'].send(self.packet)
        self.parent.add_system_wait_interval(stime - self.packet.arrived_at,
                                             self.index)

        self.packet = None

        self.busy_trace.record(stime, 0)
        self.departure_intervals.record(stime)
        self.num_served += 1

        # Requesting next packet from the queue:
        queue = self.connections['queue'].module
        if queue.size > 0:
            self.serve(queue.pop())

        self.parent.update_system_size(self.index)

    def _start_service(self):
        delay = self.service_time()
        self.sim.schedule(delay, self.handle_service_end)
        return delay

    def serve(self, packet):
        assert not self.busy
        self.packet = packet
        delay = self._start_service()
        self.service_intervals.append(delay)
        self.busy_trace.record(self.sim.stime, 1)
Beispiel #6
0
class Queue(Model):
    """Queue module represents the packets queue, stores only current size.

    Connections: server

    Methods and properties:
    - push(): increase the queue size
    - pop(): decrease the queue size
    - size: get current queue size

    Statistics:
    - size_trace: Trace, holding the history of the queue size updates
    - num_arrived: `int`
    - num_dropped: `int`
    - drop_ratio: `float`
    """
    def __init__(self, sim, capacity, index=0):
        super().__init__(sim)
        self.__capacity = capacity
        self.packets = deque()
        self.index = index
        # Statistics:
        self.size_trace = Trace()
        self.size_trace.record(self.sim.stime, 0)
        self.num_dropped = 0
        self.arrival_intervals = Intervals()
        self.arrival_intervals.record(sim.stime)
        self.num_arrived = 0
        self.wait_intervals = Statistic()

    @property
    def capacity(self):
        return self.__capacity

    @property
    def size(self):
        return len(self.packets)

    @property
    def drop_ratio(self):
        if self.num_arrived == 0:
            return 0
        return self.num_dropped / self.num_arrived

    def handle_message(self, message, connection=None, sender=None):
        self.push(message)

    def push(self, packet):
        self.arrival_intervals.record(self.sim.stime)
        self.num_arrived += 1
        server = self.connections['server'].module

        packet.arrived_at = self.sim.stime

        if self.size == 0 and not server.busy:
            server.serve(packet)
            self.wait_intervals.append(self.sim.stime - packet.arrived_at)

        elif self.capacity is None or self.size < self.capacity:
            self.packets.append(packet)
            self.size_trace.record(self.sim.stime, self.size)

        else:
            self.num_dropped += 1

        self.parent.update_system_size(self.index)

    def pop(self):
        packet = self.packets.popleft()
        self.size_trace.record(self.sim.stime, self.size)
        self.wait_intervals.append(self.sim.stime - packet.arrived_at)
        return packet

    def __str__(self):
        return f'Queue({self.size})'
class WiredTransceiver(Model):
    """This module models a simple wired full-duplex transceiver.

    `WiredTransceiver` simulates a simple wired network card. It sends packets
    in `WireFrame`s instances with constant bitrate. Each frame can have
    a preamble and some header. After transmission, `WiredTransceiver` waits
    IFS before sending another frame.

    `WiredTransceiver` receives data from the queue. Upon construction, it
    schedules `_start()` call, which will request data from the queue. Note
    that `'queue'` connection MUST be added right after creation to make this
    work. Upon `_start()` call, `WiredTransceiver` will request packets with
    `queue.get_next(self)` call.

    `WiredTransceiver` requests other packets when TX operation finishes and
    it waits IFS. To request additional packets, it calls `queue.get_next()`.

    `WiredTransceiver` is expected to have the only one peer, which typically
    is another `WiredTransceiver` module. They MUST be connected with `'peer'`
    bi-directional connection. Reception is performed in two steps: when
    the frame reaches the transceiver, it marks RX as busy, and schedules
    frame reception end at `frame.duration`. Upon frame RX end, RX is marked
    as ready.

    If `WiredTransceiver` receives a packet from the `Queue` during another
    TX operation running, it will throw a `RuntimeError` exception.

    `WiredTransceiver` needs three connections:

    - `'peer'`: a bi-directional connection to another transceiver (mandatory);
    - `'queue'`: a bi-directional connection to the packets queue (mandatory);
    - `'up'`: a bi-directional connection to the switch (optional).

    If `'up'` connection is defined, the received packets are sent through it.
    Otherwise, they are silently dropped. Typically, `'up'` connects a
    `NetworkSwitch` module, or parent `NetworkInterface`.
    """
    def __init__(self, sim, bitrate=inf, header_size=0, preamble=0, ifs=0):
        super().__init__(sim)
        self.bitrate = bitrate
        self.header_size = header_size
        self.preamble = preamble
        self.ifs = ifs
        # State variables:
        self.__started = False
        self.__tx_frame = None
        self.__wait_ifs = False
        self.__rx_frame = None
        # Statistics:
        self.__num_received_frames = 0
        self.__num_received_bits = 0
        self.__rx_busy_trace = Trace()
        self.__rx_busy_trace.record(0, 0)
        self.__num_transmitted_packets = 0
        self.__num_transmitted_bits = 0
        self.__tx_busy_trace = Trace()
        self.__tx_busy_trace.record(0, 0)
        self.__service_time = Statistic()
        self.__service_started_at = None
        # Initialization:
        self.sim.schedule(self.sim.stime, self.start)

    @property
    def started(self):
        return self.__started

    @property
    def tx_ready(self):
        return not self.tx_busy

    @property
    def tx_busy(self):
        return self.__tx_frame is not None or self.__wait_ifs

    @property
    def rx_ready(self):
        return self.__rx_frame is None

    @property
    def rx_busy(self):
        return not self.rx_ready

    @property
    def num_received_frames(self):
        return self.__num_received_frames

    @property
    def num_received_bits(self):
        return self.__num_received_bits

    @property
    def rx_busy_trace(self):
        return self.__rx_busy_trace

    @property
    def num_transmitted_packets(self):
        return self.__num_transmitted_packets

    @property
    def num_transmitted_bits(self):
        return self.__num_transmitted_bits

    @property
    def tx_busy_trace(self):
        return self.__tx_busy_trace

    @property
    def service_time(self):
        return self.__service_time

    @property
    def tx_frame(self):
        return self.__tx_frame

    def start(self):
        self.connections['queue'].module.get_next(self)
        self.__started = True

    def handle_message(self, message, connection=None, sender=None):
        if connection.name == 'queue':
            if self.tx_busy:
                raise RuntimeError(
                    'new NetworkPacket while another TX running')
            duration = ((self.header_size + message.size) / self.bitrate +
                        self.preamble)
            frame = WireFrame(packet=message,
                              duration=duration,
                              header_size=self.header_size,
                              preamble=self.preamble)
            self.connections['peer'].send(frame)
            self.sim.schedule(duration, self.handle_tx_end)
            self.__tx_frame = frame
            self.__tx_busy_trace.record(self.sim.stime, 1)
            self.__service_started_at = self.sim.stime
            self.sim.logger.debug(f'start transmitting frame {frame}',
                                  src=self)
        elif connection.name == 'peer':
            self.sim.schedule(message.duration,
                              self.handle_rx_end,
                              args=(message, ))
            self.__rx_frame = message
            self.__rx_busy_trace.record(self.sim.stime, 1)
            self.sim.logger.debug(f'start receiving frame {message}', src=self)

    def handle_tx_end(self):
        self.sim.schedule(self.ifs, self.handle_ifs_end)
        # Record statistics:
        self.__num_transmitted_packets += 1
        self.__num_transmitted_bits += self.__tx_frame.size
        # Update state variables:
        self.__wait_ifs = True
        self.__tx_frame = None
        self.sim.logger.debug(f'finish transmitting, waiting IFS', src=self)

    def handle_ifs_end(self):
        self.__wait_ifs = False
        self.connections['queue'].module.get_next(self)
        # Record statistics:
        self.__tx_busy_trace.record(self.sim.stime, 0)
        self.__service_time.append(self.sim.stime - self.__service_started_at)
        self.__service_started_at = None
        self.sim.logger.debug(f'IFS end, ready to transmit', src=self)

    def handle_rx_end(self, frame):
        if 'up' in self.connections:
            self.connections['up'].send(frame.packet)
        self.__rx_frame = None
        self.__num_received_frames += 1
        self.__num_received_bits += frame.size
        self.__rx_busy_trace.record(self.sim.stime, 0)
        self.sim.logger.debug(f'finish receiving frame', src=self)

    def __str__(self):
        prefix = f'{self.parent}.' if self.parent else ''
        return f'{prefix}RxTx'
class Transmitter(Model):
    """Models transmitter module at MAC layer.

    Connections:
    - `'channel'`: mandatory, to `Channel` instance;
    - `'radio'`: mandatory, to `Radio` module
    - `'queue'`: optional, to `Queue` module
    """
    class State(Enum):
        IDLE = 0
        BUSY = 1
        BACKOFF = 2
        TX = 3
        WAIT_ACK = 4

    def __init__(
        self,
        sim,
        address=None,
        phy_header_size=None,
        mac_header_size=None,
        ack_size=None,
        bitrate=None,
        preamble=None,
        max_propagation=0,
    ):
        super().__init__(sim)

        # Properties:
        self.__address = address
        self.__phy_header_size = (phy_header_size if phy_header_size
                                  is not None else sim.params.phy_header_size)
        self.__mac_header_size = (mac_header_size if mac_header_size
                                  is not None else sim.params.mac_header_size)
        self.__bitrate = bitrate if bitrate is not None else sim.params.bitrate
        self.__preamble = (preamble
                           if preamble is not None else sim.params.preamble)
        self.__max_propagation = max_propagation
        self.__ack_size = (ack_size
                           if ack_size is not None else sim.params.ack_size)

        # State variables:
        self.timeout = None
        self.cw = 65536
        self.backoff = -1
        self.num_retries = None
        self.pdu = None
        self.__state = Transmitter.State.IDLE
        self.__seqn = 0

        # Statistics:
        self.backoff_vector = Statistic()
        self.__start_service_time = None
        self.service_time = Statistic()
        self.num_sent = 0
        self.num_retries_vector = Statistic()
        self.__busy_trace = Trace()
        self.__busy_trace.record(sim.stime, 0)

        # Initialize:
        sim.schedule(0, self.start)

    @property
    def state(self):
        return self.__state

    @state.setter
    def state(self, state):
        if self.state != state:
            self.sim.logger.debug(f'{self.state.name} -> {state.name}',
                                  src=self)
        self.__state = state

    @property
    def address(self):
        return self.__address

    @address.setter
    def address(self, address):
        self.__address = address

    @property
    def phy_header_size(self):
        return self.__phy_header_size

    @property
    def mac_header_size(self):
        return self.__mac_header_size

    @property
    def ack_size(self):
        return self.__ack_size

    @property
    def bitrate(self):
        return self.__bitrate

    @property
    def preamble(self):
        return self.__preamble

    @property
    def max_propagation(self):
        return self.__max_propagation

    @property
    def busy_trace(self):
        return self.__busy_trace

    @property
    def channel(self):
        return self.connections['channel'].module

    @property
    def radio(self):
        return self.connections['radio'].module

    @property
    def queue(self):
        return self.connections['queue'].module

    def start(self):
        self.queue.get_next(self)

    def handle_message(self, packet, connection=None, sender=None):
        if connection.name == 'queue':
            assert self.state == Transmitter.State.IDLE

            self.cw = self.sim.params.cwmin
            self.backoff = randint(0, self.cw)
            self.num_retries = 1

            #
            # Create the PDU:
            #
            self.pdu = DataPDU(packet,
                               seqn=self.__seqn,
                               header_size=self.phy_header_size +
                               self.mac_header_size,
                               sender_address=self.address,
                               receiver_address=packet.receiver_address)
            self.__seqn += 1

            self.__start_service_time = self.sim.stime
            self.backoff_vector.append(self.backoff)
            self.__busy_trace.record(self.sim.stime, 1)

            self.sim.logger.debug(
                f'backoff={self.backoff}; CW={self.cw},NR={self.num_retries}',
                src=self)

            if self.channel.is_busy:
                self.state = Transmitter.State.BUSY
            else:
                self.state = Transmitter.State.BACKOFF
                self.timeout = self.sim.schedule(self.sim.params.difs,
                                                 self.handle_backoff_timeout)
        else:
            raise RuntimeError(
                f'unexpected handle_message({packet}, connection={connection}, '
                f'sender={sender}) call')

    def channel_ready(self):
        if self.state == Transmitter.State.BUSY:
            self.timeout = self.sim.schedule(self.sim.params.difs,
                                             self.handle_backoff_timeout)
            self.state = Transmitter.State.BACKOFF

    def channel_busy(self):
        if self.state == Transmitter.State.BACKOFF:
            self.sim.cancel(self.timeout)
            self.state = Transmitter.State.BUSY

    def finish_transmit(self):
        if self.state == Transmitter.State.TX:
            self.sim.logger.debug('TX finished', src=self)
            ack_duration = (
                (self.ack_size + self.mac_header_size + self.phy_header_size) /
                self.bitrate + self.preamble + 6 * self.max_propagation)
            self.timeout = self.sim.schedule(
                self.sim.params.sifs + ack_duration, self.handle_ack_timeout)
            self.state = Transmitter.State.WAIT_ACK

    def acknowledged(self):
        if self.state == Transmitter.State.WAIT_ACK:
            self.sim.logger.debug('received ACK', src=self)

            self.sim.cancel(self.timeout)
            self.pdu = None
            self.state = Transmitter.State.IDLE

            self.num_sent += 1
            self.service_time.append(self.sim.stime -
                                     self.__start_service_time)
            self.__start_service_time = None
            self.num_retries_vector.append(self.num_retries)
            self.num_retries = None
            self.__busy_trace.record(self.sim.stime, 0)

            #
            # IMPORTANT: Informing the queue that we can handle the next packet
            #
            self.queue.get_next(self)

    def handle_ack_timeout(self):
        assert self.state == Transmitter.State.WAIT_ACK
        self.num_retries += 1
        self.cw = min(2 * self.cw, self.sim.params.cwmax)
        self.backoff = randint(0, self.cw)

        self.backoff_vector.append(self.backoff)

        self.sim.logger.debug(
            f'backoff={self.backoff}; CW={self.cw}, NR={self.num_retries})',
            src=self)

        if self.channel.is_busy:
            self.state = Transmitter.State.BUSY
        else:
            self.state = Transmitter.State.BACKOFF
            self.timeout = self.sim.schedule(self.sim.params.difs,
                                             self.handle_backoff_timeout)

    def handle_backoff_timeout(self):
        if self.backoff == 0:
            self.state = Transmitter.State.TX
            self.sim.logger.debug(f'transmitting {self.pdu}', src=self)
            self.radio.transmit(self.pdu)
        else:
            assert self.backoff > 0
            self.backoff -= 1
            self.timeout = self.sim.schedule(self.sim.params.slot,
                                             self.handle_backoff_timeout)
            self.sim.logger.debug(f'backoff := {self.backoff}', src=self)

    def __str__(self):
        prefix = f'{self.parent}.' if self.parent else ''
        return f'{prefix}transmitter'