def test_intervals_creation_with_valid_data(timestamps): ints = Intervals(timestamps) _timestamps = asarray([0] + list(timestamps)) assert not ints.empty assert len(ints) == len(timestamps) assert ints.as_tuple() == tuple(_timestamps[1:] - _timestamps[:-1]) assert ints.last == timestamps[-1]
def __init__(self, sim, arrival_mean): super().__init__(sim) self.__arrival_mean = arrival_mean # Statistics: self.intervals = Intervals() # Initialize: self._schedule_next_arrival()
class Source(Model): """Source module represents the traffic source with exponential intervals. Connections: queue Handlers: - handle_timeout(): called upon next arrival timeout Statistics: - intervals: `Intervals`, stores a set of inter-arrival intervals Parent: `QueueingSystem` """ def __init__(self, sim, arrival, index): super().__init__(sim) self.arrival = arrival self.index = index # Statistics: self.intervals = Intervals() self.num_generated = 0 self.delays = Statistic() # Initialize: self._schedule_next_arrival() def handle_timeout(self): packet = Packet(self, self.sim.stime) self.connections['queue'].send(packet) self._schedule_next_arrival() self.num_generated += 1 def _schedule_next_arrival(self): self.intervals.record(self.sim.stime) self.sim.schedule(self.arrival(), self.handle_timeout)
class Source(Model): """Source module represents the traffic source with exponential intervals. Connections: queue Handlers: - on_timeout(): called upon next arrival timeout Statistics: - intervals: `Intervals`, stores a set of inter-arrival intervals Parent: `QueueingSystem` """ def __init__(self, sim, arrival_mean): super().__init__(sim) self.__arrival_mean = arrival_mean # Statistics: self.intervals = Intervals() # Initialize: self._schedule_next_arrival() @property def arrival_mean(self): return self.__arrival_mean def on_timeout(self): queue = self.connections['queue'].module queue.push() self._schedule_next_arrival() def _schedule_next_arrival(self): self.intervals.record(self.sim.stime) self.sim.schedule(exponential(self.arrival_mean), self.on_timeout)
def __init__(self, sim, arrival, index): super().__init__(sim) self.arrival = arrival self.index = index # Statistics: self.intervals = Intervals() self.num_generated = 0 self.delays = Statistic() # Initialize: self._schedule_next_arrival()
def test_record_valid_data(): ints = Intervals() assert ints.as_tuple() == () ints.record(2) assert_almost_equal(ints.as_tuple(), (2, )) ints.record(3.4) assert_almost_equal(ints.as_tuple(), (2, 1.4))
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
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()
class Sink(Model): """Sink module represents the traffic sink and counts arrived packets. Methods: - receive_packet(): called when the server finishes serving packet. """ def __init__(self, sim): super().__init__(sim) # Statistics: self.departures = Intervals() self.departures.record(self.sim.stime) def receive_packet(self): self.departures.record(self.sim.stime)
def __init__(self, arrival_mean, service_mean): # Parameters: self.arrival_mean = arrival_mean self.service_mean = service_mean # System state: self.queue_size = 0 self.server_busy = False # Statistics: self.system_size_trace = Trace() self.server_busy_trace = Trace() self.service_intervals = Statistic() self.arrivals = Intervals() self.departures = Intervals()
class Sink(Model): """Sink module represents the traffic sink and counts arrived packets. Methods: - receive_packet(): called when the server finishes serving packet. """ def __init__(self, sim): super().__init__(sim) # Statistics: self.arrival_intervals = Intervals() self.arrival_intervals.record(self.sim.stime) def handle_message(self, message, connection=None, sender=None): assert isinstance(message, Packet) self.arrival_intervals.record(self.sim.stime) message.source.delays.append(self.sim.stime - message.created_at)
def test_random_source_provides_statistics(): """Validate that `RandomSource` provides statistics. """ intervals = (10, 12, 15, 17) data_size = (123, 453, 245, 321) class TestModel(Model): def __init__(self, sim): super().__init__(sim) self.source = RandomSource( sim, source_id=34, dest_addr=13, data_size=Mock(side_effect=data_size), interval=Mock(side_effect=(intervals + (1000, ))), ) self.network = DummyModel(sim, 'Network') self.source.connections['network'] = self.network ret = simulate(TestModel, stime_limit=sum(intervals)) assert ret.data.source.arrival_intervals.as_tuple() == intervals assert ret.data.source.data_size_stat.as_tuple() == data_size # Also check that we can not replace statistics: with pytest.raises(AttributeError): from pydesim import Intervals ret.data.source.arrival_intervals = Intervals() with pytest.raises(AttributeError): from pydesim import Statistic ret.data.source.data_size_stat = Statistic() # Check that source records the number of packets being sent: assert ret.data.source.num_packets_sent == 4
def __init__(self, sim): super().__init__(sim) self.__source_delays_data = {} self.__source_delays = ReadOnlyDict(self.__source_delays_data) self.__arrival_intervals = Intervals() self.__data_size_stat = Statistic() self.__num_packets_received = 0
def test_controlled_source_provides_statistics(): """Validate that `ControlledSource` provides statistics. """ intervals = (10, 12, 15, 17) data_size = (123, 453, 245, 321) class SourceController(Model): def __init__(self, sim, src): super().__init__(sim) self.iterator = iter(intervals) self.src = src self.sim.schedule(next(self.iterator), self.handle_timeout) def handle_timeout(self): self.src.get_next() try: interval = next(self.iterator) except StopIteration: pass else: self.sim.schedule(interval, self.handle_timeout) class TestModel(Model): def __init__(self, sim): super().__init__(sim) self.source = ControlledSource( sim, source_id=34, dest_addr=13, data_size=Mock(side_effect=data_size), ) self.network = DummyModel(sim, 'Network') self.source.connections['network'] = self.network self.controller = SourceController(sim, self.source) ret = simulate(TestModel, stime_limit=sum(intervals)) assert ret.data.source.data_size_stat.as_tuple() == data_size assert ret.data.source.arrival_intervals.as_tuple() == intervals # Also check that we can not replace statistics: with pytest.raises(AttributeError): from pydesim import Intervals ret.data.source.arrival_intervals = Intervals() with pytest.raises(AttributeError): from pydesim import Statistic ret.data.source.data_size_stat = Statistic() # Check that source records the number of packets being sent: assert ret.data.source.num_packets_sent == 4
def __init__(self, sim, data_size, source_id, dest_addr): """Constructor. :param sim: `pydesim.Simulator` object; :param data_size: callable without arguments, iterable or constant; represents application data size distribution; :param source_id: this source ID (more like IP address, not MAC) :param dest_addr: destination MAC address. """ super().__init__(sim) self.__data_size = data_size self.__source_id = source_id self.__dest_addr = dest_addr # Attempt to build iterators for data size and intervals: try: self.__data_size_iter = iter(self.__data_size) except TypeError: self.__data_size_iter = None # Statistics: self.__arrival_intervals = Intervals() self.__data_size_stat = Statistic() self.__num_packets_sent = 0
def test_record_illegal_types_raises_error(value): ints = Intervals() with pytest.raises(TypeError) as excinfo: ints.record(value) assert 'only numeric values expected' in str(excinfo.value).lower()
def test_record_in_past_raises_error(): ints = Intervals([10]) with pytest.raises(ValueError) as excinfo: ints.record(9.9) assert 'prohibited timestamps from past' in str(excinfo.value).lower()
def __init__(self, sim): super().__init__(sim) # Statistics: self.departures = Intervals() self.departures.record(self.sim.stime)
def test_data_of_illegal_types_causes_type_error(timestamps): with pytest.raises(TypeError) as excinfo: Intervals(timestamps) assert 'only numeric values expected' in str(excinfo.value).lower()
def test_intervals_accept_only_timestamps_in_future(): with pytest.raises(ValueError) as excinfo: Intervals([1, 2, 1.9, 3]) assert 'timestamps must be ascending' in str(excinfo.value).lower()
def test_intervals_copy_list_content_instead_of_pointer(): data = [1, 2] ints = Intervals(data) ints.record(3) assert ints.as_tuple() == (1, 1, 1) assert data == [1, 2]
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})'
def test_empty_intervals_instantiation(): ints = Intervals() assert ints.empty assert len(ints) == 0 assert ints.as_tuple() == () assert ints.last == 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)
def test_as_list(timestamps): ints = Intervals(timestamps) _timestamps = asarray([0] + list(timestamps)) assert ints.as_list() == list(_timestamps[1:] - _timestamps[:-1])
def __init__(self, sim): super().__init__(sim) # Statistics: self.arrival_intervals = Intervals() self.arrival_intervals.record(self.sim.stime)
def test_statistic(data): ints = Intervals(data) stats = ints.statistic() assert_almost_equal(stats.as_tuple(), ints.as_tuple())
def test_sink_module_records_statistics(): """Validate that `Sink` module records various statistics. In this test we check that `Sink` module records end-to-end delays per SourceID, inter-arrival intervals and packet sizes (last two statistics are recorded without splitting by SourceID). It also records the number of received packets. To do this, we define a sample simulation with dummy network layer, two sources and one sink. Each source generates three messages within given intervals and with given sizes. """ delays = [(5, 2, 234), (10, 3, 135)] intervals = [(10, 10, 200), (20, 400, 80)] sizes = [(1234, 1625, 1452), (2534, 2124, 2664)] sids = [23, 14] class DummyNetwork(Model): def __init__(self, sim): super().__init__(sim) def handle_message(self, app_data, **kwargs): self.connections['sink'].send(app_data) class TestModel(Model): def __init__(self, sim): super().__init__(sim) self.sink = Sink(sim) for ds, inter, sid, delay in zip(sizes, intervals, sids, delays): network = DummyNetwork(sim) source = RandomSource(sim, ds, inter, sid, 5) source.connections['network'] = network network.connections['sink'] = self.sink network.connections['sink'].delay = Mock(side_effect=delay) ret = simulate(TestModel) sink = ret.data.sink assert isinstance(sink.source_delays[sids[0]], Statistic) assert sink.source_delays[sids[0]].as_tuple() == delays[0] assert sink.source_delays[sids[1]].as_tuple() == delays[1] # To check `Sink` stores arrival intervals and packet sizes, we estimate # expected arrival times (received_at) and along with it order packet # sizes by their arrival time: indices = [0, 0] gen_times = [cumsum(ints) for ints in intervals] n = len(intervals[0]) received_at, received_sizes = [], [] while any(i < n for i in indices): next_arrivals = [(gen_times[i][j] + delays[i][j]) if j < n else inf for i, j in enumerate(indices)] i = int(argmin(next_arrivals)) received_at.append(next_arrivals[i]) received_sizes.append(sizes[i][indices[i]]) indices[i] += 1 # Since received_at stores arrival timestamps, compute arrival intervals: v = asarray(received_at) arrival_intervals = [v[0]] + list(v[1:] - v[:-1]) # Check that recorded data match the expected: assert isinstance(sink.arrival_intervals, Intervals) assert sink.arrival_intervals.as_tuple() == tuple(arrival_intervals) assert isinstance(sink.data_size_stat, Statistic) assert sink.data_size_stat.as_tuple() == tuple(received_sizes) assert sink.num_packets_received == len(received_sizes) # Check that statistics can not be overwritten: with pytest.raises(AttributeError): sink.arrival_intervals = Intervals() with pytest.raises(AttributeError): sink.data_size_stat = Statistic() with pytest.raises(TypeError): sink.source_delays[sids[0]] = Statistic()