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))
Exemple #2
0
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
Exemple #3
0
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
Exemple #5
0
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
Exemple #6
0
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
Exemple #7
0
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)