示例#1
0
class Link(EventTarget):
    def __init__(self, identifier, rate, delay, buffer_size, node1, node2):
        """
        A network link.

        Args:
            identifier (str):           The name of the link.
            rate (float):               The link capacity, in Mbps.
            delay (int):                Propagation delay, in ms.
            buffer_size (int):          The buffer size, in KB.
            node1 (Node):               The first endpoint of the link.
            node2 (Node):               The second endpoint of the link.
        """
        super(Link, self).__init__()

        self.id = identifier
        self.rate = rate
        self.delay = delay
        self.buffer_size = buffer_size * 1024
        self.node1 = node1
        self.node2 = node2

        self.node1.add_link(self)
        self.node2.add_link(self)

        # This determines whether the link is in use to handle half-duplex
        self.in_use = False
        self.current_dir = None

        # The buffer of packets going towards node 1 or node 2
        self.buffer = LinkBuffer(self)

        # Bytes sent over this link
        self.bytesSent = 0.0
        # Time it has taken so far to send these bytes
        self.sendTime = 0.0
        self.packets_on_link = {
            1: [],
            2: []
        }

    def __repr__(self):
        return "Link[%s,%s--%s]" % (self.id, self.node1, self.node2)

    def static_cost(self):
        """
        Defines the static cost of sending a packet across the link

        :return: Static link cost
        :rtype: float
        """
        return self.rate

    def dynamic_cost(self):
        """
        Defines the dynamic cost of sending a packet across the link

        :return: Dynamic link cost
        :rtype: float
        """
        return self.static_cost() + self.buffer.fixedAvgBufferTime

    def fix_dynamic_cost(self, time):
        """
        Fixes the dynamic cost of the link. Should be fixed right before
        updating the dynamic routing table.

        :param time: Time to fix the dynamic cost at.
        :type time: float
        :return: Nothing
        :rtype: None
        """
        self.buffer.fix_avg_buffer_time(time)

    def reset_dynamic_cost(self, time):
        """
        Resets the metrics used for calculating dynamic cost. Should be reset
        after updating the dynamic routing table.

        :param time: Time when the reset is happening
        :type time: float
        :return: Nothing
        :rtype: None
        """
        self.buffer.reset_buffer_metrics(time)

    def transmission_delay(self, packet):
        packet_size = packet.size() * 8  # in bits
        speed = self.rate * 1e6 / 1e3    # in bits/ms
        return packet_size / float(speed)

    def get_node_by_direction(self, direction):
        if direction == LinkBuffer.NODE_1_ID:
            return self.node1
        elif direction == LinkBuffer.NODE_2_ID:
            return self.node2
        return None

    def get_direction_by_node(self, node):
        if node == self.node1:
            return 1
        elif node == self.node2:
            return 2
        return 0

    def other_node(self, ref_node):
        """
        Returns the other node that is not the given reference node

        :param ref_node: Node not to return
        :type ref_node: Node
        :return: Node that is not the given node connected by this link
        :rtype: Node
        """
        assert ref_node is self.node1 or ref_node is self.node2, \
            "Given reference node is not even connected by this link"
        return self.node1 if ref_node is self.node2 else self.node2

    def send(self, time, packet, origin, from_free=False):
        """
        Sends a packet to a destination.

        Args:
            time (int):                The time at which the packet was sent.
            packet (Packet):           The packet.
            origin (Host|Router):      The node origin of the packet.
        """
        origin_id = self.get_direction_by_node(origin)
        dst_id = 3 - origin_id
        destination = self.get_node_by_direction(dst_id)
        if self.in_use or self.packets_on_link[origin_id] != []:
            if self.current_dir is not None:
                Logger.debug(time, "Link %s in use, currently sending to node "
                                   "%d (trying to send %s)"
                             % (self.id, self.current_dir, packet))
            else:
                Logger.debug(time, "Link %s in use, currently sending to node "
                                   "%d (trying to send %s)"
                             % (self.id, origin_id, packet))
            if self.buffer.size() >= self.buffer_size:
                # Drop packet if buffer is full
                Logger.debug(time, "Buffer full; packet %s dropped." % packet)
                self.dispatch(DroppedPacketEvent(time, self.id))
                return
            self.buffer.add_to_buffer(packet, dst_id, time)
        else:
            if not from_free and self.buffer.buffers[dst_id] != []:
                # Since events are not necessarily executed in the order we
                # would expect, there may be a case where the link was free
                # (nothing on the other side and nothing currently being put
                # on) but the actual event had not yet fired.
                #
                # In such a case, the buffer will not have been popped from
                # yet, so put the packet we want to send on the buffer and
                # take the first packet instead.
                self.buffer.add_to_buffer(packet, dst_id, time)
                packet = self.buffer.pop_from_buffer(dst_id, time)
            Logger.debug(time, "Link %s free, sending packet %s to %s" % (self.id, packet, destination))
            self.in_use = True
            self.current_dir = dst_id
            transmission_delay = self.transmission_delay(packet)

            self.dispatch(PacketSentOverLinkEvent(time, packet, destination, self))

            # Link will be free to send to same spot once packet has passed
            # through fully, but not to send from the current destination until
            # the packet has completely passed.
            # Transmission delay is delay to put a packet onto the link
            self.dispatch(LinkFreeEvent(time + transmission_delay, self, dst_id, packet))
            self.dispatch(LinkFreeEvent(time + transmission_delay + self.delay, self, self.get_other_id(dst_id), packet))
            self.update_link_throughput(time, packet,
                                        time + transmission_delay + self.delay)

    def update_link_throughput(self, time, packet, time_received):
        """
        Update the link throughput

        :param time: Time when this update is occurring
        :type time: float
        :param packet: Packet we're updating the throughput with
        :type packet: Packet
        :param time_received: Time the packet was received at the other node
        :type time_received: float
        :return: Nothing
        :rtype: None
        """
        self.bytesSent += packet.size()
        self.sendTime = time_received
        assert self.sendTime != 0, "Packet should not be received at time 0."
        throughput = (8 * self.bytesSent) / (self.sendTime / 1000)  # bits/s
        Logger.debug(time, "%s throughput is %f" % (self, throughput))
        self.dispatch(LinkThroughputEvent(time, self.id, throughput))

    @classmethod
    def get_other_id(cls, dst_id):
        """
        Get the node id of the other node that is not the given destination id.

        :param dst_id: Destination ID
        :type dst_id: int
        :return: ID of the node that is not the destination ID
        :rtype:
        """
        return LinkBuffer.NODE_1_ID \
            if dst_id == LinkBuffer.NODE_2_ID else LinkBuffer.NODE_2_ID
示例#2
0
class Link(EventTarget):
    def __init__(self, identifier, rate, delay, buffer_size, node1, node2):
        """
        A network link.

        Args:
            identifier (str):           The name of the link.
            rate (float):               The link capacity, in Mbps.
            delay (int):                Propagation delay, in ms.
            buffer_size (int):          The buffer size, in KB.
            node1 (Node):               The first endpoint of the link.
            node2 (Node):               The second endpoint of the link.
        """
        super(Link, self).__init__()

        self.id = identifier
        self.rate = rate
        self.delay = delay
        self.buffer_size = buffer_size * 1024
        self.node1 = node1
        self.node2 = node2

        self.node1.add_link(self)
        self.node2.add_link(self)

        # This determines whether the link is in use to handle half-duplex
        self.in_use = False
        self.current_dir = None

        # The buffer of packets going towards node 1 or node 2
        self.buffer = LinkBuffer(self)

        # Bytes sent over this link
        self.bytesSent = 0.0
        # Time it has taken so far to send these bytes
        self.sendTime = 0.0
        self.packets_on_link = {1: [], 2: []}

    def __repr__(self):
        return "Link[%s,%s--%s]" % (self.id, self.node1, self.node2)

    def static_cost(self):
        """
        Defines the static cost of sending a packet across the link

        :return: Static link cost
        :rtype: float
        """
        return self.rate

    def dynamic_cost(self):
        """
        Defines the dynamic cost of sending a packet across the link

        :return: Dynamic link cost
        :rtype: float
        """
        return self.static_cost() + self.buffer.fixedAvgBufferTime

    def fix_dynamic_cost(self, time):
        """
        Fixes the dynamic cost of the link. Should be fixed right before
        updating the dynamic routing table.

        :param time: Time to fix the dynamic cost at.
        :type time: float
        :return: Nothing
        :rtype: None
        """
        self.buffer.fix_avg_buffer_time(time)

    def reset_dynamic_cost(self, time):
        """
        Resets the metrics used for calculating dynamic cost. Should be reset
        after updating the dynamic routing table.

        :param time: Time when the reset is happening
        :type time: float
        :return: Nothing
        :rtype: None
        """
        self.buffer.reset_buffer_metrics(time)

    def transmission_delay(self, packet):
        packet_size = packet.size() * 8  # in bits
        speed = self.rate * 1e6 / 1e3  # in bits/ms
        return packet_size / float(speed)

    def get_node_by_direction(self, direction):
        if direction == LinkBuffer.NODE_1_ID:
            return self.node1
        elif direction == LinkBuffer.NODE_2_ID:
            return self.node2
        return None

    def get_direction_by_node(self, node):
        if node == self.node1:
            return 1
        elif node == self.node2:
            return 2
        return 0

    def other_node(self, ref_node):
        """
        Returns the other node that is not the given reference node

        :param ref_node: Node not to return
        :type ref_node: Node
        :return: Node that is not the given node connected by this link
        :rtype: Node
        """
        assert ref_node is self.node1 or ref_node is self.node2, \
            "Given reference node is not even connected by this link"
        return self.node1 if ref_node is self.node2 else self.node2

    def send(self, time, packet, origin, from_free=False):
        """
        Sends a packet to a destination.

        Args:
            time (int):                The time at which the packet was sent.
            packet (Packet):           The packet.
            origin (Host|Router):      The node origin of the packet.
        """
        origin_id = self.get_direction_by_node(origin)
        dst_id = 3 - origin_id
        destination = self.get_node_by_direction(dst_id)
        if self.in_use or self.packets_on_link[origin_id] != []:
            if self.current_dir is not None:
                Logger.debug(
                    time, "Link %s in use, currently sending to node "
                    "%d (trying to send %s)" %
                    (self.id, self.current_dir, packet))
            else:
                Logger.debug(
                    time, "Link %s in use, currently sending to node "
                    "%d (trying to send %s)" % (self.id, origin_id, packet))
            if self.buffer.size() >= self.buffer_size:
                # Drop packet if buffer is full
                Logger.debug(time, "Buffer full; packet %s dropped." % packet)
                self.dispatch(DroppedPacketEvent(time, self.id))
                return
            self.buffer.add_to_buffer(packet, dst_id, time)
        else:
            if not from_free and self.buffer.buffers[dst_id] != []:
                # Since events are not necessarily executed in the order we
                # would expect, there may be a case where the link was free
                # (nothing on the other side and nothing currently being put
                # on) but the actual event had not yet fired.
                #
                # In such a case, the buffer will not have been popped from
                # yet, so put the packet we want to send on the buffer and
                # take the first packet instead.
                self.buffer.add_to_buffer(packet, dst_id, time)
                packet = self.buffer.pop_from_buffer(dst_id, time)
            Logger.debug(
                time, "Link %s free, sending packet %s to %s" %
                (self.id, packet, destination))
            self.in_use = True
            self.current_dir = dst_id
            transmission_delay = self.transmission_delay(packet)

            self.dispatch(
                PacketSentOverLinkEvent(time, packet, destination, self))

            # Link will be free to send to same spot once packet has passed
            # through fully, but not to send from the current destination until
            # the packet has completely passed.
            # Transmission delay is delay to put a packet onto the link
            self.dispatch(
                LinkFreeEvent(time + transmission_delay, self, dst_id, packet))
            self.dispatch(
                LinkFreeEvent(time + transmission_delay + self.delay, self,
                              self.get_other_id(dst_id), packet))
            self.update_link_throughput(time, packet,
                                        time + transmission_delay + self.delay)

    def update_link_throughput(self, time, packet, time_received):
        """
        Update the link throughput

        :param time: Time when this update is occurring
        :type time: float
        :param packet: Packet we're updating the throughput with
        :type packet: Packet
        :param time_received: Time the packet was received at the other node
        :type time_received: float
        :return: Nothing
        :rtype: None
        """
        self.bytesSent += packet.size()
        self.sendTime = time_received
        assert self.sendTime != 0, "Packet should not be received at time 0."
        throughput = (8 * self.bytesSent) / (self.sendTime / 1000)  # bits/s
        Logger.debug(time, "%s throughput is %f" % (self, throughput))
        self.dispatch(LinkThroughputEvent(time, self.id, throughput))

    @classmethod
    def get_other_id(cls, dst_id):
        """
        Get the node id of the other node that is not the given destination id.

        :param dst_id: Destination ID
        :type dst_id: int
        :return: ID of the node that is not the destination ID
        :rtype:
        """
        return LinkBuffer.NODE_1_ID \
            if dst_id == LinkBuffer.NODE_2_ID else LinkBuffer.NODE_2_ID