def generate_customers(self, generation_date): print('Generating customers') start_time = time() for group_name in user_groups_info: group_info = user_groups_info[group_name] size = group_info['size'] customer_type = group_info['customer_type'] if customer_type == 'individual': age_distribution = Distribution(info=distributions_info['age'][group_info['ages']]) gender_distribution = distribution_from_list(distributions_info['gender'][group_info['gender']]) for i in range(size): if customer_type == 'individual': age = int(age_distribution.get_value(return_array=False)) gender = gender_distribution.get_value(return_array=False) customer = random_individual(generation_date, age, gender) else: customer = random_organization(generation_date) customer.cluster_id = group_info['cluster_id'] c = SimulatedCustomer(customer, group_info['agreements'], self.main_session, self.system, verbose=True) c.generate_hierarchy(generation_date) self.customers.append(c) self.main_session.commit() end_time = time() print('Customers generation done in %f seconds' % (end_time-start_time))
class FSMNode(Module): """ Implement a Finite State Machine initialized with a transition table that maps pairs (State, Event) to an action and an event transition. For internal state S0 and event E0 there is a transition function T such that T:(S0, E0) -> S1 changes the internal state to S1. To avoid an anti-pattern the current state is not accessible from within a transition function. """ # transmission speed parameter (bits per second) DATARATE = "datarate" # queue size QUEUE = "queue" # inter-arrival distribution (seconds) INTERARRIVAL = "interarrival" # packet size distribution (bytes) SIZE = "size" # processing time distribution (seconds) PROC_TIME = "processing" # max available time slots MAXSLOTS = "maxslots" STAY = -1 def __init__(self, config, channel, x, y): """ :param initialState: The state the FSM has to start from :param transitions: A dictionary that maps pairs (State, Event) to a transition function. The transition function is defined as t(E) -> E where E is the event to handle. The FSM will move to the state returned by the function. If the function returns None, the FSM remains in the same state. """ Module.__init__(self) # load configuration parameters self.datarate = config.get_param(FSMNode.DATARATE) self.queue_size = config.get_param(FSMNode.QUEUE) self.interarrival = Distribution(config.get_param(FSMNode.INTERARRIVAL)) self.size = Distribution(config.get_param(FSMNode.SIZE)) self.proc_time = Distribution(config.get_param(FSMNode.PROC_TIME)) # a slot lasts the maximum time a packet would take to be transmitted max_pkt_time = (config.get_param(FSMNode.SIZE)[Distribution.MAX] * 8) / self.datarate prop_delay = (config.get_param(Channel.PAR_RANGE)) / Channel.SOL self.slot_duration = max_pkt_time + prop_delay # the slots distribution for a node self.slots = Distribution({"distribution" : "unif", "int" : True, "min" : 0, "max" : config.get_param(FSMNode.MAXSLOTS) }) # save position self.x = x self.y = y # save channel self.channel = channel # queue of packets to be sent self.queue = [] def set_transitions(self, initialState, transitions): self.state = initialState self.transitions = transitions def initialize(self): """ Initialization. Starts node operation by scheduling the first packet """ self.schedule_next_arrival() def schedule_next_arrival(self): """ Schedules a new arrival event """ # extract random value for next arrival arrival = self.interarrival.get_value() # draw packet size from the distribution packet_size = self.size.get_value() # generate an event setting this node as destination event = Event(self.sim.get_time() + arrival, Events.PACKET_ARRIVAL, self, self, packet_size) self.sim.schedule_event(event) def handle_event(self, event): """ Handles the given event with a mapped transition function. If there is no mapping an exeception will be raised. :param event: the event to handle """ # Log current state self.logger.log_state(self, self.state) # On arrival always try to enqueue and schedule next packet arrival if(event.get_type() == Events.PACKET_ARRIVAL): self.schedule_next_arrival() # If enqueue fails don't notify the node and remain in the # same state if(not self.enqueue_arrived(event)): return else: event = Event(event.get_time(), Events.PACKET_ENQUEUED, event.get_destination(), event.get_source(), event.get_obj()) key = (self.state, event.get_type()) if key not in self.transitions.keys(): raise AssertionError("Unhandled event %s in state %s" % (event.get_type(), self.state)) action = self.transitions[key] nextState = action(event) if(nextState is None): raise AssertionError("Unknown next state for transition" + " function %s" % action.__name__) if(nextState is not FSMNode.STAY): self.state = nextState def enqueue_arrived(self, event): packet_size = event.get_obj() self.logger.log_arrival(self, packet_size) if self.queue_size == 0 or len(self.queue) < self.queue_size: # if queue size is infinite or there is still space self.queue.append(packet_size) self.logger.log_queue_length(self, len(self.queue)) return True else: # if there is no space left, we drop the packet and log self.logger.log_queue_drop(self, packet_size) return False def stay(self, event): return self.STAY def transmit(self): assert(len(self.queue) > 0) packet_size = self.queue.pop(0) self.logger.log_queue_length(self, len(self.queue)) duration = packet_size * 8 / self.datarate # transmit packet packet = Packet(packet_size, duration) if(packet.get_id() == 19): pass self.channel.start_transmission(self, packet) # schedule end of transmission end_tx = Event(self.sim.get_time() + duration, Events.END_TX, self, self, packet) self.sim.schedule_event(end_tx) def get_posx(self): """ Returns x position :returns: x position in meters """ return self.x def get_posy(self): """ Returns y position :returns: y position in meters """ return self.y
class Node(Module): """ This class implements a node capable of communicating with other devices """ # transmission speed parameter (bits per second) DATARATE = "datarate" # queue size QUEUE = "queue" # inter-arrival distribution (seconds) INTERARRIVAL = "interarrival" # packet size distribution (bytes) SIZE = "size" # processing time distribution (seconds) PROC_TIME = "processing" # max packet size (bytes) MAXSIZE = "maxsize" # list of possible states for this node IDLE = 0 TX = 1 RX = 2 PROC = 3 def __init__(self, config, channel, x, y): """ Constructor. :param config: the set of configs loaded by the simulator :param channel: the channel to which frames are sent :param x: x position :param y: y position """ Module.__init__(self) # load configuration parameters self.datarate = config.get_param(Node.DATARATE) self.queue_size = config.get_param(Node.QUEUE) self.interarrival = Distribution(config.get_param(Node.INTERARRIVAL)) self.size = Distribution(config.get_param(Node.SIZE)) self.proc_time = Distribution(config.get_param(Node.PROC_TIME)) self.maxsize = config.get_param(Node.MAXSIZE) # queue of packets to be sent self.queue = [] # current state self.state = Node.IDLE self.logger.log_state(self, Node.IDLE) # save position self.x = x self.y = y # save channel self.channel = channel # current packet being either sent or received self.current_pkt = None # count the number of frames currently under reception self.receiving_count = 0 # timeout event used to avoid being stuck in the RX state self.timeout_event = None # timeout time for the rx timeout event. set as the time needed to # transmit a packet of the maximum size plus a small amount of 10 # microseconds self.timeout_time = self.maxsize * 8.0 / self.datarate + 10e-6 def initialize(self): """ Initialization. Starts node operation by scheduling the first packet """ self.schedule_next_arrival() def handle_event(self, event): """ Handles events notified to the node :param event: the event """ if event.get_type() == Events.PACKET_ARRIVAL: self.handle_arrival() elif event.get_type() == Events.START_RX: self.handle_start_rx(event) elif event.get_type() == Events.END_RX: self.handle_end_rx(event) elif event.get_type() == Events.END_TX: self.handle_end_tx(event) elif event.get_type() == Events.END_PROC: self.handle_end_proc(event) elif event.get_type() == Events.RX_TIMEOUT: self.handle_rx_timeout(event) else: print( "Node %d has received a notification for event type %d which" " can't be handled", (self.get_id(), event.get_type())) sys.exit(1) def schedule_next_arrival(self): """ Schedules a new arrival event """ # extract random value for next arrival arrival = self.interarrival.get_value() # generate an event setting this node as destination event = Event(self.sim.get_time() + arrival, Events.PACKET_ARRIVAL, self, self) self.sim.schedule_event(event) def handle_arrival(self): """ Handles a packet arrival """ # draw packet size from the distribution packet_size = self.size.get_value() # log the arrival self.logger.log_arrival(self, packet_size) if self.state == Node.IDLE: # if we are in a idle state, then there must be no packets in the # queue assert (len(self.queue) == 0) # if current state is IDLE and there are no packets in the queue, we # can start transmitting self.transmit_packet(packet_size) self.state = Node.TX self.logger.log_state(self, Node.TX) else: # if we are either transmitting or receiving, packet must be queued if self.queue_size == 0 or len(self.queue) < self.queue_size: # if queue size is infinite or there is still space self.queue.append(packet_size) self.logger.log_queue_length(self, len(self.queue)) else: # if there is no space left, we drop the packet and log self.logger.log_queue_drop(self, packet_size) # schedule next arrival self.schedule_next_arrival() def handle_start_rx(self, event): """ Handles beginning of a frame reception :param event: the RX event including the frame being received """ new_packet = event.get_obj() if self.state == Node.IDLE: if self.receiving_count == 0: # node is idle: it will try to receive this packet assert (self.current_pkt is None) new_packet.set_state(Packet.PKT_RECEIVING) self.current_pkt = new_packet self.state = Node.RX assert (self.timeout_event is None) # create and schedule the RX timeout self.timeout_event = Event( self.sim.get_time() + self.timeout_time, Events.RX_TIMEOUT, self, self, None) self.sim.schedule_event(self.timeout_event) self.logger.log_state(self, Node.RX) else: # there is another signal in the air but we are IDLE. this # happens if we start receiving a frame while transmitting # another. when we are done with the transmission we assume we # are not able to detect that there is another frame in the air # (we are not doing carrier sensing). In this case we assume we # are not able to detect the new one and set that to corrupted new_packet.set_state(Packet.PKT_CORRUPTED) else: # node is either receiving or transmitting if self.state == Node.RX and self.current_pkt is not None: # the frame we are currently receiving is corrupted by a # collision, if we have one self.current_pkt.set_state(Packet.PKT_CORRUPTED) # the same holds for the new incoming packet. either if we are in # the RX, TX, or PROC state, we won't be able to decode it new_packet.set_state(Packet.PKT_CORRUPTED) # in any case, we schedule a new event to handle the end of this frame end_rx = Event(self.sim.get_time() + new_packet.get_duration(), Events.END_RX, self, self, new_packet) self.sim.schedule_event(end_rx) # count this as currently being received self.receiving_count = self.receiving_count + 1 def handle_end_rx(self, event): """ Handles the end of a reception :param event: the END_RX event """ packet = event.get_obj() # if the packet that ends is the one that we are trying to receive, but # we are not in the RX state, then something is very wrong if self.current_pkt is not None and \ packet.get_id() == self.current_pkt.get_id(): assert (self.state == Node.RX) if self.state == Node.RX: if packet.get_state() == Packet.PKT_RECEIVING: # the packet is not in a corrupted state: we succesfully # received it packet.set_state(Packet.PKT_RECEIVED) # just to be sure: we can only correctly receive the packet we # were trying to decode assert (packet.get_id() == self.current_pkt.get_id()) # we might be in RX state but have no current packet. this can # happen when a packet overlaps with the current one being received # and the one being received terminates earlier. we assume to stay # in the RX state because we are not able to detect the end of the # frame if self.current_pkt is not None and \ packet.get_id() == self.current_pkt.get_id(): self.current_pkt = None if self.receiving_count == 1: # this is the only frame currently in the air, move to PROC # before restarting operations self.switch_to_proc() # delete the timeout event self.sim.cancel_event(self.timeout_event) self.timeout_event = None self.receiving_count = self.receiving_count - 1 # log packet self.logger.log_packet(event.get_source(), self, packet) def switch_to_proc(self): """ Switches to the processing state and schedules the end_proc event """ proc_time = self.proc_time.get_value() proc = Event(self.sim.get_time() + proc_time, Events.END_PROC, self, self) self.sim.schedule_event(proc) self.state = Node.PROC self.logger.log_state(self, Node.PROC) def handle_rx_timeout(self, event): """ Handles RX timeout :param event: the RX_TIMEOUT event """ # when this event happens, we can only be in RX state, otherwise # something is wrong assert (self.state == Node.RX) # in addition, the timeout should be longer than any possible packet, # meaning that we must not be receiving a packet when the timeout occurs assert (self.current_pkt is None) # the timeout forces us to switch to the PROC state self.switch_to_proc() self.timeout_event = None def handle_end_tx(self, event): """ Handles the end of a transmission done by this node :param event: the END_TX event """ assert (self.state == Node.TX) assert (self.current_pkt is not None) assert (self.current_pkt.get_id() == event.get_obj().get_id()) self.current_pkt = None # the only thing to do here is to move to the PROC state self.switch_to_proc() def handle_end_proc(self, event): """ Handles the end of the processing period, resuming operations :param event: the END_PROC event """ assert (self.state == Node.PROC) if len(self.queue) == 0: # resuming operations but nothing to transmit. back to IDLE self.state = Node.IDLE self.logger.log_state(self, Node.IDLE) else: # there is a packet ready, trasmit it packet_size = self.queue.pop(0) self.transmit_packet(packet_size) self.state = Node.TX self.logger.log_state(self, Node.TX) self.logger.log_queue_length(self, len(self.queue)) def transmit_packet(self, packet_size): """ Generates, sends, and schedules end of transmission of a new packet :param packet_size: size of the packet to send in bytes """ assert (self.current_pkt is None) duration = packet_size * 8 / self.datarate # transmit packet packet = Packet(packet_size, duration) self.channel.start_transmission(self, packet) # schedule end of transmission end_tx = Event(self.sim.get_time() + duration, Events.END_TX, self, self, packet) self.sim.schedule_event(end_tx) self.current_pkt = packet def get_posx(self): """ Returns x position :returns: x position in meters """ return self.x def get_posy(self): """ Returns y position :returns: y position in meters """ return self.y
class Node(Module): """ This class implements a node capable of communicating with other devices """ # transmission speed parameter (bits per second) DATARATE = "datarate" # queue size QUEUE = "queue" # inter-arrival distribution (seconds) INTERARRIVAL = "interarrival" # packet size distribution (bytes) SIZE = "size" # processing time distribution (seconds) PROC_TIME = "processing" # max packet size (bytes) MAXSIZE = "maxsize" # type of propagation PROPAGATION = "propagation" # p-persistence PERSISTENCE = "persistence" # list of possible states for this node IDLE = 0 TX = 1 RX = 2 PROC = 3 WC = 4 # waiting for the channel to get free (then transmit immediately) WT = 5 # waiting random exp time to transmit (NB: channel may NOT be free) def __init__(self, config, channel, x, y): """ Constructor. :param config: the set of configs loaded by the simulator :param channel: the channel to which frames are sent :param x: x position :param y: y position """ Module.__init__(self) # load configuration parameters self.datarate = config.get_param(Node.DATARATE) self.queue_size = config.get_param(Node.QUEUE) self.interarrival = Distribution(config.get_param(Node.INTERARRIVAL)) self.size = Distribution(config.get_param(Node.SIZE)) self.proc_time = Distribution(config.get_param(Node.PROC_TIME)) self.maxsize = config.get_param(Node.MAXSIZE) # queue of packets to be sent self.queue = [] # current state self.state = Node.IDLE self.logger.log_state(self, Node.IDLE) # save position self.x = x self.y = y # save channel self.channel = channel # current packet being either sent or received self.current_pkt = None # count the number of frames currently under reception self.receiving_count = 0 # timeout event used to avoid being stuck in the RX state self.timeout_rx_event = None # timeout used for the p-persistence self.timeout_wt_event = None # time needed to transmit a packet with the maximum size self.packet_max_tx_time = self.maxsize * 8.0 / self.datarate # p-persistence probability [simple carrier sensing] self.p_persistence = float(config.get_param(Node.PERSISTENCE)) # timeout time for the rx timeout event. set as the time needed to # transmit a packet of the maximum size plus a small amount of 10 # microseconds self.timeout_time = self.packet_max_tx_time + 10e-6 # determine the type of propagation.. self.realistic_propagation = config.get_param( Node.PROPAGATION) == "realistic" def initialize(self): """ Initialization. Starts node operation by scheduling the first packet """ self.schedule_next_arrival() def schedule_next_arrival(self): """ Schedules a new arrival event """ # extract random value for next arrival arrival = self.interarrival.get_value() # generate an event setting this node as destination event = Event(self.sim.get_time() + arrival, Event.PACKET_ARRIVAL, self, self) self.sim.schedule_event(event) def handle_event(self, event): """ Handles events notified to the node :param event: the event """ if event.get_type() == Event.PACKET_ARRIVAL: self.handle_arrival() elif event.get_type() == Event.START_RX: self.handle_start_rx(event) elif event.get_type() == Event.END_RX: self.handle_end_rx(event) elif event.get_type() == Event.END_TX: self.handle_end_tx(event) elif event.get_type() == Event.END_PROC: self.handle_end_proc(event) elif event.get_type() == Event.RX_TIMEOUT: self.handle_rx_timeout(event) elif event.get_type() == Event.WT_TIMEOUT: self.handle_wt_timeout(event) else: print( "Node %d has received a notification for event type %d which" " can't be handled", (self.get_id(), event.get_type())) sys.exit(1) def handle_arrival(self): """ Handles a packet arrival """ # draw packet size from the distribution packet_size = self.size.get_value() # log the arrival self.logger.log_arrival(self, packet_size) # if IDLE -> transit if self.state == Node.IDLE: # if we are in a idle state, there must be no packets in the queue assert (len(self.queue) == 0) # if current state is IDLE and there are no packets in the queue, we # can start transmitting self.transmit_packet(packet_size) self.change_state(Node.TX) else: # if we are doing something, packet must be queued if self.queue_size == 0 or len(self.queue) < self.queue_size: # if queue size is infinite or there is still space self.queue.append(packet_size) self.logger.log_queue_length(self, len(self.queue)) else: # if there is no space left, we drop the packet and log self.logger.log_queue_drop(self, packet_size) # schedule next arrival self.schedule_next_arrival() def handle_start_rx(self, event): """ Handles beginning of a frame reception :param event: the RX event including the frame being received """ new_packet = event.get_obj() # node is idle: it will try to receive this packet if self.state == Node.IDLE: # with carrier sensing, this should be the only possibility assert self.receiving_count == 0 self.receive_packet(new_packet) # I am waiting to transmit... but I can receive packets # receive the packet only if it is the only one in the air elif self.state == Node.WT and self.receiving_count == 0: # delete the timeout self.sim.cancel_event(self.timeout_wt_event) self.timeout_wt_event = None # receive the packet self.receive_packet(new_packet) else: # node is doing something if self.state == Node.RX and self.current_pkt is not None: # the frame we are currently receiving is corrupted by a # collision, if we have one self.current_pkt.set_state(Packet.PKT_CORRUPTED) # the same holds for the new incoming packet. # if we are NOT in IDLE we won't be able to decode it new_packet.set_state(Packet.PKT_CORRUPTED) # in any case, we schedule a new event to handle the end of this frame end_rx = Event(self.sim.get_time() + new_packet.get_duration(), Event.END_RX, self, self, new_packet) self.sim.schedule_event(end_rx) # count this as currently being received self.receiving_count += 1 def handle_end_rx(self, event): """ Handles the end of a reception :param event: the END_RX event """ # with carrier sensing, this event should not happen in IDLE # also, I am receiving at least one packet assert (self.state != self.IDLE) assert (self.receiving_count >= 1) packet = event.get_obj() # if the packet that ends is the one that we are trying to receive, but # we are not in the RX state, then something is very wrong if self.current_pkt is not None and \ packet.get_id() == self.current_pkt.get_id(): assert (self.state == Node.RX) # ignore the packet if in some state other than RX if self.state == Node.RX: if packet.get_state() == Packet.PKT_RECEIVING: # "Realistic Propagation" model # if here, there was NO collision... the packet may be good # extract: random ~ Unif(0,1) if self.realistic_propagation: random = Uniform(0, 1).get_value() prob_correct = packet.get_prob_correct() if random >= prob_correct: # the packet is not in a corrupted state: # we successfully received it packet.set_state(Packet.PKT_RECEIVED) else: # we were unlucky: the channel corrupted the packet packet.set_state(Packet.PKT_CORRUPTED_BY_CHANNEL) # original propagation model else: # the packet is not in a corrupted state: # we successfully received it packet.set_state(Packet.PKT_RECEIVED) # just to be sure: we can only correctly receive the packet we # were trying to decode assert (packet.get_id() == self.current_pkt.get_id()) # we might be in RX state but have no current packet. this can # happen when a packet overlaps with the current one being received # and the one being received terminates earlier. we assume to stay # in the RX state because we are not able to detect the end of the # frame if self.current_pkt is not None and \ packet.get_id() == self.current_pkt.get_id(): self.current_pkt = None if self.receiving_count == 1: # this is the only frame currently in the air, move to PROC # before restarting operations self.switch_to_proc() # delete the timeout event self.sim.cancel_event(self.timeout_rx_event) self.timeout_rx_event = None # trivial carrier sensing elif self.state == Node.WC: # if count = 1, I am receiving the last packet in the channel # I can exit the carrier sensing and go either to IDLE of TX if self.receiving_count == 1: if len(self.queue) == 0: # resuming operations but nothing to transmit. back to IDLE self.change_state(Node.IDLE) else: # there is a packet ready, transmit it self.dequeue_and_transmit_packer() # remove packet from the channel self.receiving_count -= 1 # log packet self.logger.log_packet(event.get_source(), self, packet) # noinspection PyUnusedLocal def handle_rx_timeout(self, event): """ Handles RX timeout :param event: the RX_TIMEOUT event """ # when this event happens, we can only be in RX state, otherwise # something is wrong assert (self.state == Node.RX) # in addition, the timeout should be longer than any possible packet, # meaning that we must not be receiving a packet when the timeout occurs assert (self.current_pkt is None) # the timeout forces us to switch to the PROC state self.switch_to_proc() self.timeout_rx_event = None def handle_end_tx(self, event): """ Handles the end of a transmission done by this node :param event: the END_TX event """ assert (self.state == Node.TX) assert (self.current_pkt is not None) assert (self.current_pkt.get_id() == event.get_obj().get_id()) self.current_pkt = None # the only thing to do here is to move to the PROC state self.switch_to_proc() # noinspection PyUnusedLocal def handle_end_proc(self, event): """ Handles the end of the processing period, resuming operations :param event: the END_PROC event """ assert (self.state == Node.PROC) assert (self.receiving_count >= 0) assert (len(self.queue) >= 0) # nothing in the air... IDLE / TX if self.receiving_count == 0: if len(self.queue) == 0: # resuming operations but nothing to transmit. back to IDLE self.change_state(Node.IDLE) else: # there is a packet ready, transmit it packet_size = self.queue.pop(0) self.transmit_packet(packet_size) self.change_state(Node.TX) self.logger.log_queue_length(self, len(self.queue)) # something there... do carrier sensing... WC or WT else: # NB: if nothing to transmit, just wait for the channel to get free # to then move to IDLE if len(self.queue) == 0: self.change_state(Node.WC) else: self.schedule_packet_transmission() # noinspection PyUnusedLocal def handle_wt_timeout(self, event): """ Handles the end of the processing period, resuming operations :param event: the WT_TIMEOUT event """ # when this event happens, we can only be in WT state # each time I move out from WT, I cancel the timeout assert (self.state == Node.WT) # remove timeout from node self.timeout_wt_event = None # if the channel is free, I can transmit if self.receiving_count == 0: self.dequeue_and_transmit_packer() # channel is NOT free... repeat the procedure else: self.schedule_packet_transmission() pass def switch_to_proc(self): """ Switches to the processing state and schedules the end_proc event """ proc_time = self.proc_time.get_value() proc = Event(self.sim.get_time() + proc_time, Event.END_PROC, self, self) self.sim.schedule_event(proc) self.change_state(Node.PROC) def receive_packet(self, new_packet): """ Receive a packet. NB: this function assumes that the channel is free! """ assert (self.current_pkt is None) new_packet.set_state(Packet.PKT_RECEIVING) self.current_pkt = new_packet assert (self.timeout_rx_event is None) # create and schedule the RX timeout self.timeout_rx_event = Event(self.sim.get_time() + self.timeout_time, Event.RX_TIMEOUT, self, self, None) self.sim.schedule_event(self.timeout_rx_event) self.change_state(Node.RX) def transmit_packet(self, packet_size): """ Generates, sends, and schedules end of transmission of a new packet :param packet_size: size of the packet to send in bytes """ assert (self.current_pkt is None) duration = packet_size * 8 / self.datarate # transmit packet packet = Packet(packet_size, duration) self.channel.start_transmission(self, packet) # schedule end of transmission end_tx = Event(self.sim.get_time() + duration, Event.END_TX, self, self, packet) self.sim.schedule_event(end_tx) self.current_pkt = packet def dequeue_and_transmit_packer(self): """ Utility method to transmit the next packet in the queue. """ assert (len(self.queue) > 0) packet_size = self.queue.pop(0) self.transmit_packet(packet_size) self.change_state(Node.TX) self.logger.log_queue_length(self, len(self.queue)) def schedule_packet_transmission(self): """ Schedule the next packet transmission, using p-persistence. p is the probability to transmit immediately after the channel gets free (WC). """ assert (len(self.queue) > 0) # simple carrier sensing - p-persistence # extract a random number from a uniform distribution # if number >= p, else schedule transmission # after exponential time random = Uniform(0, 1).get_value() # will transmit this packet immediately when channel gets free if random >= self.p_persistence: self.change_state(Node.WC) # wait random exponential time... then try again # average time is 10 * time to send biggest packet allowed else: max_tx_time = Exp(self.packet_max_tx_time * 10).get_value() self.timeout_wt_event = Event(self.sim.get_time() + max_tx_time, Event.WT_TIMEOUT, self, self) self.sim.schedule_event(self.timeout_wt_event) self.change_state(Node.WT) def change_state(self, state): """ Utility method to change the state of this node. :param state: New state to set. """ if state != Node.WT: assert self.timeout_wt_event is None self.state = state self.logger.log_state(self, state) def get_posx(self): """ Returns x position :returns: x position in meters """ return self.x def get_posy(self): """ Returns y position :returns: y position in meters """ return self.y
class Node(Module): """ This class implements a node capable of communicating with other devices """ # transmission speed parameter (bits per second) DATARATE = "datarate" # queue size QUEUE = "queue" # contention window size WINDOW_SIZE = "window_size" # channel listening time (seconds) LISTENING_TIME = "listening" # inter-arrival distribution (seconds) INTERARRIVAL = "interarrival" # packet size distribution (bytes) SIZE = "size" # processing time distribution (seconds) PROC_TIME = "processing" # max packet size (bytes) MAXSIZE = "maxsize" # list of possible states for this node IDLE = 0 TX = 1 RX = 2 SLOTTING = 3 LISTENING = 4 def __init__(self, config, channel, x, y): """ Constructor. :param config: the set of configs loaded by the simulator :param channel: the channel to which frames are sent :param x: x position :param y: y position """ Module.__init__(self) #Number of slots in the contention window self.window_slots_count = config.get_param(Node.WINDOW_SIZE) #Duration in seconds of the channel listening period self.listening_duration = config.get_param(Node.LISTENING_TIME) #Duration in seconds of each slot self.slot_duration = self.listening_duration # load configuration parameters self.datarate = config.get_param(Node.DATARATE) self.queue_size = config.get_param(Node.QUEUE) self.interarrival = Distribution(config.get_param(Node.INTERARRIVAL)) self.size = Distribution(config.get_param(Node.SIZE)) self.proc_time = Distribution(config.get_param(Node.PROC_TIME)) self.maxsize = config.get_param(Node.MAXSIZE) # queue of packets to be sent self.queue = [] # current state self.state = None self.switch_state(Node.IDLE) # save position self.x = x self.y = y # save channel self.channel = channel #Number of packets being received self.packets_in_air = 0 #Number of window slots we still have to wait before transmitting self.slot_countdown = 0 #First packet in the current sequence of receiving packets self.rx_sequence_first_packet = None #Hook to events in the queue for future manipulation self.end_listenting_event_hook = None self.end_slot_event_hook = None """ Changes state of the reception node Performs necessary logging :param new state: state to which to switch """ def switch_state(self, new_state): self.state = new_state self.logger.log_state(self, self.state) """ Enqueues a new packet in the send buffer. If the buffer is full the packet is dropped Performs necessary logging :param packet_size: integer representing the size of the packet to transmit """ def enqueue_packet_size(self, packet_size): if len(self.queue) < self.queue_size: self.queue.append(packet_size) self.logger.log_queue_length(self, len(self.queue)) else: self.logger.log_queue_drop(self, packet_size) """ Dequeues a packet from the buffer. The buffer must be non empty Performs necessary logging """ def dequeue_packet_size(self): if(len(self.queue) == 0): print("Node %d tried to dequeue a packet from an empty queue", self.get_id()) sys.exit(1) packet_size = self.queue.pop(0) self.logger.log_queue_length(self, len(self.queue)) return packet_size def initialize(self): """ Initialization. Starts node operation by scheduling the first packet """ self.schedule_next_packet_arrival() def handle_event(self, event): """ Handles events notified to the node :param event: the event """ if event.get_type() == Events.END_LISTENING: self.handle_end_listenting(event) elif event.get_type() == Events.NEW_PACKET: self.handle_new_packet(event) elif event.get_type() == Events.END_TX: self.handle_end_tx(event) elif event.get_type() == Events.END_SLOT: self.handle_end_slot(event) elif event.get_type() == Events.START_RX: self.handle_start_rx(event) elif event.get_type() == Events.END_RX: self.handle_end_rx(event) else: print("Node %d has received a notification for event type %d which" " can't be handled", (self.get_id(), event.get_type())) sys.exit(1) """ Schedules the arrival of the next packet """ def schedule_next_packet_arrival(self): # extract random value for next arrival arrival = self.interarrival.get_value() # generate an event setting this node as destination event = Event(self.sim.get_time() + arrival, Events.NEW_PACKET, self, self) self.sim.schedule_event(event) """ Schedules the next end of contention window slot """ def schedule_next_slot_end(self): end_slot_event = Event(self.sim.get_time() + self.slot_duration, Events.END_SLOT, self, self, None) self.end_slot_event_hook = end_slot_event # Saves the event for future cancellation self.sim.schedule_event(end_slot_event) """ Handles the request to transmit a new packet """ def handle_new_packet(self, event): new_packet_size = self.size.get_value() self.logger.log_arrival(self, new_packet_size) #Enqueues or drops packet self.enqueue_packet_size(new_packet_size) self.schedule_next_packet_arrival() #If we are idle then start immediately channel listening, otherwise keep doing what we were doing if self.state == self.IDLE: self.switch_state(self.LISTENING) end_listening_event = Event(self.sim.get_time() + self.listening_duration, Events.END_LISTENING, self, self, None) self.end_listenting_event_hook = end_listening_event # Saves the event for future cancellation self.sim.schedule_event(end_listening_event) """ Handles the end of listenting event """ def handle_end_listenting(self, event): #An end listenting event can arrive only if we were listening #Did you forget to cancel the event? assert(self.state == self.LISTENING) #If we are at this point no packets must be in the air and there must be something to transmit assert(len(self.queue) > 0) assert(self.packets_in_air == 0) #Channel is free, start transmisison self.switch_state(self.TX) self.transmit_next_packet() """ Handles the end of a slot during a contention window """ def handle_end_slot(self, event): #And end slotting event can arrive only if we are in a contention window #Did you forget to cancel the event? assert(self.state == self.SLOTTING) #A contention window cannot be open if there are pakets being received or there is nothing to transmit assert (len(self.queue) > 0) assert(self.packets_in_air == 0) #Counts down a slot and check if we can transmit self.slot_countdown -= 1 if self.slot_countdown == 0: #We won contention and can start transmit self.switch_state(self.TX) self.transmit_next_packet() else: self.schedule_next_slot_end() """ Handles the end of transmission """ def handle_end_tx(self, event): #And end tx event can arrive only if we were transmitting something assert(self.state == self.TX) if len(self.queue) == 0: #No need to open contention window. Decide what to to based on the state of the channel if self.packets_in_air == 0: #Nothing left to do self.switch_state(self.IDLE) else: #The channel is busy, start listenting self.switch_state(self.RX) else: #Need to open a contention window. self.switch_state(self.SLOTTING) self.slot_countdown = randint(0, self.window_slots_count - 1) if self.slot_countdown == 0: # Must transmit immediately without listening. Nothing new can get corrupted self.switch_state(self.TX) self.transmit_next_packet() elif self.packets_in_air > 0: #We must wait at least a slot and realize the channel is busy. No need to schedule slot countdowns, go to RX self.switch_state(self.RX) else: #We must wait but for now the channel is free. Schedule next slot countdown assert(self.packets_in_air == 0) self.schedule_next_slot_end() """ Handles the start of reception of a packet """ def handle_start_rx(self, event): #There is one more receiving packet in the air self.packets_in_air += 1 current_packet = event.get_obj() #Schedules the end of reception end_rx_event = Event(self.sim.get_time() + current_packet.get_duration(), Events.END_RX, self, self, current_packet) self.sim.schedule_event(end_rx_event) if self.packets_in_air == 1: #It was the first packet self.rx_sequence_first_packet = current_packet else: #Other packets were in the air, the current and the first packet are corrupted #First packet might already be completely processed so it could be None if self.rx_sequence_first_packet is not None: self.rx_sequence_first_packet.set_state(Packet.PKT_CORRUPTED) current_packet.set_state(Packet.PKT_CORRUPTED) if self.state == self.IDLE: self.switch_state(self.RX) if self.state == self.LISTENING: assert(self.end_listenting_event_hook is not None) self.sim.cancel_event(self.end_listenting_event_hook) self.switch_state(self.RX) if self.state == self.SLOTTING: assert(self.end_slot_event_hook is not None) self.sim.cancel_event(self.end_slot_event_hook) self.switch_state(self.RX) elif self.state == self.TX: #If I am transmitting there is a collision current_packet.set_state(Packet.PKT_CORRUPTED) def handle_end_rx(self, event): assert(self.state == self.RX or self.state == self.TX) self.packets_in_air -= 1 ended_packet = event.get_obj() #If I ended the reception of the first packet remove it if self.rx_sequence_first_packet is not None and self.rx_sequence_first_packet.get_id() == ended_packet.get_id(): self.rx_sequence_first_packet = None #If the packet was not corrupted then mark it as correctly received if ended_packet.get_state() == Packet.PKT_RECEIVING: ended_packet.set_state(Packet.PKT_RECEIVED) assert(self.state == self.RX) #Cannot correctly receive anything if not in RX self.logger.log_packet(event.get_source(), event.get_destination(), ended_packet) if self.state == self.RX: if self.packets_in_air != 0: #Do nothing, must still rx pass elif self.packets_in_air == 0 and len(self.queue) == 0: #Nothing left to do self.switch_state(self.IDLE) elif self.packets_in_air == 0 and len(self.queue) >= 0: # Need to open a contention window. self.switch_state(self.SLOTTING) self.slot_countdown = randint(0, self.window_slots_count - 1) if self.slot_countdown == 0: # Must transmit immediately without listening. self.switch_state(self.TX) self.transmit_next_packet() else: # We must wait but for now the channel is free. Schedule next slot countdown self.schedule_next_slot_end() """ Sends the first packet in the buffer, schedules end of tx event and performs necessary logging. Must be called in TX state. Buffer must be not empty. """ def transmit_next_packet(self): assert(self.state == self.TX) assert(len(self.queue) > 0) packet_size = self.dequeue_packet_size() duration = packet_size * 8 / self.datarate # transmit packet tx_packet = Packet(packet_size, duration) self.channel.start_transmission(self, tx_packet) # schedule end of transmission end_tx = Event(self.sim.get_time() + duration, Events.END_TX, self, self, tx_packet) self.sim.schedule_event(end_tx) """ Returns x position :returns: x position in meters """ def get_posx(self): return self.x """ Returns y position :returns: y position in meters """ def get_posy(self): return self.y
class Link: """ Class defining a link between nodes this class contains properties for the link like delay or preference and link policy functions Every link have a unique id A single link can have a specific id """ __link_counter = 0 __waiter = 0.001 DELAY = "delay" POLICY_FUNCTION = "policy" MRAI = "mrai" def __init__(self, env, node, resource, properties): """ Creates a link and automatically assign a uniqueid to the link It requires a simpy environment where to operate. It also require a simpy resource to operate correctly and reserve the channel for a message. :param env: Simpy environment :param node: Node which the link is refered to :param resource: unitary resource used to lock the link :param properties: properties of the link in the graphml that needs to be evaluated """ self._id = Link.__link_counter Link.__link_counter += 1 self._env = env self._node = node self._res = resource self._delay = None if Link.DELAY in properties: self._delay = Distribution(json.loads(properties[Link.DELAY])) if Link.POLICY_FUNCTION in properties: self._policy_function = PolicyFunction(properties[Link.POLICY_FUNCTION]) else: self._policy_function = PolicyFunction(PolicyFunction.PASS_EVERYTHING) self._mrai = 30.0 if Link.MRAI in properties: self._mrai = float(properties[Link.MRAI]) self._mrai_active = False self._jitter = Distribution(json.loads('{"distribution": "unif", \ "min": 0, "max": ' + str(self._mrai*0.25) + ', "int": 0.01}')) def _print(self, msg: str) -> None: """_print. Print helper function for the Link All messages will be preceded by the time, the link id and the neighbour connected :param msg: The message that needs to be printed by the link :type msg: str :rtype: None """ if self._node.verbose: print("{}-Lid:{} to {} ".format(self._env.now, self._id, self._node.id) + msg) def transmit(self, msg: Any, delay: float) -> None: """ Actual transmitting function :param msg: message that needs to be trasfered :param delay: time that needs to be waited before the message arrives to the destination """ # Request of the unique resource, if the resource is available # it means the message is the first in the sequence of messages # that needs to be transfered # link:[----msg2----msg1+res--->dst] # Once a node have the resource and has waited for delay time it # can be delivered request = self._res.request() yield self._env.timeout(delay) & request self._print("Transmitting msg: " + str(msg.obj)) self._node.event_store.put(msg) yield self._env.timeout(self.__waiter) self._res.release(request) def tx(self, msg, delay): # pylint: disable=invalid-name """ Transmission function fot the node use this function to trigger a simpy process :param msg: message that needs to be transfered :param delay: delay that needs to be waited """ self._env.process(self.transmit(msg, delay)) def test(self, policy_value: PolicyValue) -> PolicyValue: """test. Test if a policy value is applicable to the policy function of the link :param policy_value: policy value to test :type policy_value: PolicyValue :rtype: PolicyValue """ return self._policy_function[policy_value] @property def id(self): # pylint: disable=invalid-name """ Returns the link id :returns: id of the link """ return self._id @property def node(self): """ Returns the referenced node :returns: node reference """ return self._node @property def delay(self): """ Returns the delay distribution that needs to be used for the link :returns: link delay distribution """ return self._delay @property def mrai(self) -> float: """mrai. Function to get an MRAI value from the distribution If the MRAI is not active will be returned 0 and the MRAI will be activated For all the other iteration the MRAI will return a value equal to the value given minus the jitter :rtype: float """ if not self.mrai_state: self._mrai_active = True return 0 jitter = self._jitter.get_value() return self._mrai - jitter @property def mrai_state(self) -> bool: """mrai_state. Get the MRAI state value :rtype: bool """ return self._mrai_active def mrai_not_active(self) -> None: """mrai_not_active. Disable the current MRAI if the MRAI was already disable this will have no effects :rtype: None """ self._mrai_active = False def __str__(self): """ Prints the link in a human readable format :returns: link in a string format """ res = "id: {} ref_node: {}".format(self._id, self._node.id) return res
class Cluster: """ This class allocates resources on the cluster by calling the cluster API """ SIZE = "size" OFFER = "offer" APPLICATION = "application" ENDPOINT = "endpoint" RESOURCE = "resource" RESOURCE_SCALE = "resource_scale" def __init__(self): self.sim = sim.Sim.Instance() self.logger = self.sim.get_logger() self.requests = [] def initialize(self, config): self.run_number = config.run_number self.application = config.get_param(Cluster.APPLICATION) self.size = Distribution(config.get_param(Cluster.SIZE)) self.offer = Distribution(config.get_param(Cluster.OFFER)) self.resource = config.get_param(Cluster.RESOURCE) self.resource_scale = int(config.get_param(Cluster.RESOURCE_SCALE)) Configuration().host = config.get_param(Cluster.ENDPOINT) self.api = swagger_client.DeploymentsApi() self.nodes_api = swagger_client.NodesApi() self.index = 0 def finalize(self): for app in self.requests: try: self.api.delete_deployment(app) except ApiException: pass def request_allocation(self): app_name = "test-%s-%s" % (self.run_number, self.index) self.index += 1 unity_offer = self.offer.get_value() scaled_size = self.size.get_value() # Offer for the requested size (same magnitude) offer = scaled_size * unity_offer raw_size = scaled_size * self.resource_scale request = swagger_client.DeploymentRequest(self.application, str(offer), [{'name': self.resource, 'amount': str(raw_size) }]) self.requests.append(app_name) # Request an application deployment try: allocation = self.api.put_deployment(app_name, request) allocation.resources[self.resource]['used'] = scaled_size self.logger.log_allocation(allocation, self.resource) return True except ApiException as e: request.resources[0]['amount'] = scaled_size self.logger.log_failure(request) return False def get_expected_deployments(self): total = 0 dep_size = int(self.size.get_mean()) for node in self.nodes_api.get_nodes_collection().nodes: alloc_s = node.resources[self.resource]['allocatable'] / \ self.resource_scale total += alloc_s / dep_size return total def get_allocation_probability(self): nodes = self.nodes_api.get_nodes_collection() # Get smallest amount of free resources on a node remaining = max([node.resources[self.resource]['free'] for node in nodes.nodes]) remaining /= self.resource_scale return self.size.get_probability(0, remaining)