diff --git a/meetings/frey/meeting-2021-04-26.odp b/meetings/frey/meeting-2021-04-26.odp new file mode 100644 index 0000000..828aafe Binary files /dev/null and b/meetings/frey/meeting-2021-04-26.odp differ diff --git a/meetings/frey/meeting-2021-05-10.odp b/meetings/frey/meeting-2021-05-10.odp new file mode 100644 index 0000000..967dad1 Binary files /dev/null and b/meetings/frey/meeting-2021-05-10.odp differ diff --git a/meetings/frey/meeting-2021-05-25.odp b/meetings/frey/meeting-2021-05-25.odp new file mode 100644 index 0000000..2dda44b Binary files /dev/null and b/meetings/frey/meeting-2021-05-25.odp differ diff --git a/queue-exp/eiffel-hierarchical.py b/queue-exp/eiffel-hierarchical.py old mode 100644 new mode 100755 index 0f9794b..c475f76 --- a/queue-exp/eiffel-hierarchical.py +++ b/queue-exp/eiffel-hierarchical.py @@ -1,4 +1,5 @@ -# -*- coding: utf-8 -*- +#!/usr/bin/env python3 +# coding: utf-8 -*- # # eiffel-hiearch.py # @@ -84,26 +85,26 @@ def dump(self): if __name__ == "__main__": pkts = [ - Packet(1, 1, 1), - Packet(1, 2, 1), - Packet(1, 3, 1), - Packet(2, 1, 1), - Packet(2, 2, 1), - Packet(2, 3, 1), - Packet(2, 4, 1), - Packet(2, 5, 1), - Packet(3, 1, 1), - Packet(3, 2, 1), - Packet(3, 3, 1), - Packet(4, 1, 1), - Packet(4, 2, 1), - Packet(4, 3, 1), - Packet(4, 4, 1), - Packet(4, 5, 1), - Packet(4, 6, 1), - Packet(4, 7, 1), - Packet(4, 8, 1), - Packet(4, 9, 1), - Packet(4, 10, 1), + Packet(flow=1, idn=1, length=1), + Packet(flow=1, idn=2, length=1), + Packet(flow=1, idn=3, length=1), + Packet(flow=2, idn=1, length=1), + Packet(flow=2, idn=2, length=1), + Packet(flow=2, idn=3, length=1), + Packet(flow=2, idn=4, length=1), + Packet(flow=2, idn=5, length=1), + Packet(flow=3, idn=1, length=1), + Packet(flow=3, idn=2, length=1), + Packet(flow=3, idn=3, length=1), + Packet(flow=4, idn=1, length=1), + Packet(flow=4, idn=2, length=1), + Packet(flow=4, idn=3, length=1), + Packet(flow=4, idn=4, length=1), + Packet(flow=4, idn=5, length=1), + Packet(flow=4, idn=6, length=1), + Packet(flow=4, idn=7, length=1), + Packet(flow=4, idn=8, length=1), + Packet(flow=4, idn=9, length=1), + Packet(flow=4, idn=10, length=1), ] Runner(pkts, Policy()).run() diff --git a/queue-exp/eiffel-procedural.py b/queue-exp/eiffel-procedural.py old mode 100644 new mode 100755 index 8ac87bd..bb1c11d --- a/queue-exp/eiffel-procedural.py +++ b/queue-exp/eiffel-procedural.py @@ -1,4 +1,5 @@ -# -*- coding: utf-8 -*- +#!/usr/bin/env python3 +# coding: utf-8 -*- # # eiffel-procedural.py # @@ -89,26 +90,26 @@ def dump(self): if __name__ == "__main__": pkts = [ - Packet(1, 1, 1), - Packet(1, 2, 1), - Packet(1, 3, 1), - Packet(2, 1, 1), - Packet(2, 2, 1), - Packet(2, 3, 1), - Packet(2, 4, 1), - Packet(2, 5, 1), - Packet(3, 1, 1), - Packet(3, 2, 1), - Packet(3, 3, 1), - Packet(4, 1, 1), - Packet(4, 2, 1), - Packet(4, 3, 1), - Packet(4, 4, 1), - Packet(4, 5, 1), - Packet(4, 6, 1), - Packet(4, 7, 1), - Packet(4, 8, 1), - Packet(4, 9, 1), - Packet(4, 10, 1), + Packet(flow=1, idn=1, length=1), + Packet(flow=1, idn=2, length=1), + Packet(flow=1, idn=3, length=1), + Packet(flow=2, idn=1, length=1), + Packet(flow=2, idn=2, length=1), + Packet(flow=2, idn=3, length=1), + Packet(flow=2, idn=4, length=1), + Packet(flow=2, idn=5, length=1), + Packet(flow=3, idn=1, length=1), + Packet(flow=3, idn=2, length=1), + Packet(flow=3, idn=3, length=1), + Packet(flow=4, idn=1, length=1), + Packet(flow=4, idn=2, length=1), + Packet(flow=4, idn=3, length=1), + Packet(flow=4, idn=4, length=1), + Packet(flow=4, idn=5, length=1), + Packet(flow=4, idn=6, length=1), + Packet(flow=4, idn=7, length=1), + Packet(flow=4, idn=8, length=1), + Packet(flow=4, idn=9, length=1), + Packet(flow=4, idn=10, length=1), ] Runner(pkts, Policy()).run() diff --git a/queue-exp/eiffel.py b/queue-exp/eiffel.py old mode 100644 new mode 100755 index cc8e2d8..073816d --- a/queue-exp/eiffel.py +++ b/queue-exp/eiffel.py @@ -1,4 +1,5 @@ -# -*- coding: utf-8 -*- +#!/usr/bin/env python3 +# coding: utf-8 -*- # # eiffel.py # @@ -119,12 +120,12 @@ def get_rank(self, _flow): if __name__ == "__main__": pkts = [ - Packet(1, 1, 1), - Packet(1, 2, 1), - Packet(1, 3, 1), - Packet(2, 1, 1), - Packet(2, 2, 1), - Packet(2, 3, 1), + Packet(flow=1, idn=1, length=1), + Packet(flow=1, idn=2, length=1), + Packet(flow=1, idn=3, length=1), + Packet(flow=2, idn=1, length=1), + Packet(flow=2, idn=2, length=1), + Packet(flow=2, idn=3, length=1), ] Runner(pkts, Fifo()).run() Runner(pkts, Stfq()).run() diff --git a/queue-exp/pifo-basic.py b/queue-exp/pifo-basic.py deleted file mode 100644 index 24d50bc..0000000 --- a/queue-exp/pifo-basic.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- -# -# eiffel-experiments.py -# -# Author: Toke Høiland-Jørgensen (toke@toke.dk) -# Date: 17 May 2021 -# Copyright (c) 2021, Toke Høiland-Jørgensen -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -from pifo_lib import Packet, Runner, Pifo - - -class Fifo(Pifo): - def get_rank(self, item): - return self.qlen - - -class Stfq(Pifo): - def __init__(self): - super().__init__() - self.last_finish = {} - self.virt_time = 0 - - def get_rank(self, pkt): - f = pkt.flow - if f in self.last_finish: - r = max(self.virt_time, self.last_finish[f]) - else: - r = self.virt_time - self.last_finish[f] = r + pkt.length - return r - -if __name__ == "__main__": - pkts = [ - Packet(1, 1, 2), - Packet(1, 2, 2), - Packet(2, 1, 1), - Packet(2, 2, 1), - Packet(2, 3, 1), - ] - Runner(pkts, Fifo()).run() - Runner(pkts, Stfq()).run() diff --git a/queue-exp/pifo_fifo.py b/queue-exp/pifo_fifo.py new file mode 100755 index 0000000..ce7b69e --- /dev/null +++ b/queue-exp/pifo_fifo.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +# coding: utf-8 -*- +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# pifo-fifo.py + +"""First in, first out (FIFO) + +The FIFO scheduling algorithm preserves the order of the scheduled packets. This +implementation is here for completeness and uses a PIFO. It is here to help +people understand how to add new scheduling algorithms to this framework. +""" + +__copyright__ = """ +Copyright (c) 2021, Toke Høiland-Jørgensen +Copyright (c) 2021, Frey Alfredsson +""" + +__license__ = """ +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" + +from sched_lib import Packet, Runner, Pifo, SchedulingAlgorithm + + +class Fifo(SchedulingAlgorithm): + """First in, first out (FIFO)""" + + def __init__(self, name=None): + super().__init__(name) + self._pifo = Pifo() + + def get_rank(self, _): + """Rank the items in FIFO order.""" + return self._pifo.qlen + + def enqueue(self, ref, item): + rank = self.get_rank(item) + self._pifo.enqueue(ref, rank) + + def dequeue(self): + return self._pifo.dequeue() + + def dump(self): + self._pifo.dump() + + +if __name__ == "__main__": + pkts = [ + Packet(flow=1, idn=1, length=2), + Packet(flow=1, idn=2, length=2), + Packet(flow=2, idn=1, length=1), + Packet(flow=2, idn=2, length=1), + Packet(flow=2, idn=3, length=1), + ] + Runner(pkts, Fifo()).run() diff --git a/queue-exp/pifo_hpfq.py b/queue-exp/pifo_hpfq.py new file mode 100755 index 0000000..92eb5d8 --- /dev/null +++ b/queue-exp/pifo_hpfq.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +# coding: utf-8 -*- +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# pifo-hpfq.py + +"""HierarchicalPacket Fair Queueing (HPFQ) + +This scheduling algorithm is mentioned in the paper "Programmable packet +scheduling at line rate" by Sivaraman, Anirudh, et al. It creates a hierarchy of +WFQ schedulers. The central scheduler is called root and contains references to +other WFQ schedulers. Those two WFQ schedulers are called left and right. We +chose that packets with flow ids lower than ten go into the left scheduler in +our implementation. In contrast, the others go into the right scheduler.""" + +__copyright__ = """ +Copyright (c) 2021, Toke Høiland-Jørgensen +Copyright (c) 2021, Frey Alfredsson +""" + +__license__ = """ +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" + +from sched_lib import Packet, Runner, SchedulingAlgorithm +from pifo_wfq import Wfq + + +class Hpfq(SchedulingAlgorithm): + """HierarchicalPacket Fair Queueing (HPFQ)""" + + def __init__(self, name=None): + super().__init__(name) + self._root = Wfq("root") + self._left = Wfq("Left") + self._right = Wfq("Right") + + def enqueue(self, ref, item): + queue = None + if item.flow < 10: + self._left.enqueue(ref, item) + queue = self._left + else: + self._right.enqueue(ref, item) + queue = self._right + + self._root.enqueue(queue, item) + + def dequeue(self): + queue = self._root.dequeue() + return queue.dequeue() if queue is not None else None + + def dump(self): + print(" Root:") + self._root.dump() + print(" Left:") + self._left.dump() + print(" Right:") + self._right.dump() + + +if __name__ == "__main__": + pkts = [ + Packet(flow=1, idn=1, length=200), + Packet(flow=1, idn=2, length=200), + Packet(flow=10, idn=1, length=200), + Packet(flow=10, idn=2, length=200), + Packet(flow=2, idn=1, length=100), + Packet(flow=2, idn=2, length=100), + Packet(flow=2, idn=3, length=100), + Packet(flow=20, idn=1, length=100), + Packet(flow=20, idn=2, length=100), + Packet(flow=20, idn=3, length=100), + ] + Runner(pkts, Hpfq()).run() diff --git a/queue-exp/pifo_lib.py b/queue-exp/pifo_lib.py deleted file mode 100644 index dff0950..0000000 --- a/queue-exp/pifo_lib.py +++ /dev/null @@ -1,139 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pifo_lib.py -# -# Author: Toke Høiland-Jørgensen (toke@toke.dk) -# Date: 18 May 2021 -# Copyright (c) 2021, Toke Høiland-Jørgensen -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -from pprint import pprint, pformat - - -class Packet: - def __init__(self, flow, idn, length=1): - self.flow = flow - self.idn = idn - self.length = length - self.rank = 0 - - def __repr__(self): - return f"P(F:{self.flow}, I:{self.idn}, L:{self.length})" - - -class Runner: - def __init__(self, pkts, queue): - self.input_pkts = pkts - self.queue = queue - - def run(self): - print(f"Running with queue: {self.queue}") - print(" Inserting packets into queue:") - pprint(self.input_pkts, indent=4) - for p in self.input_pkts: - self.queue.enqueue(p) - print(" Queue state:") - self.queue.dump() - output = [] - for p in self.queue: - output.append(p) - print(" Got packets from queue:") - pprint(output, indent=4) - - -class Queue: - def __init__(self, idx=None): - self._list = [] - self.idx = idx - - def enqueue(self, item): - self._list.append(item) - - def peek(self): - try: - return self._list[0] - except IndexError: - return None - - def dequeue(self): - try: - return self._list.pop(0) - except IndexError: - return None - - def __next__(self): - item = self.dequeue() - if item is None: - raise StopIteration - return item - - def __iter__(self): - return self - - @property - def qlen(self): - return len(self._list) - - def __len__(self): - return self.qlen - - def __repr__(self): - return f"{self.__class__.__name__}({self.idx})" - - def dump(self): - pprint(self._list, indent=4) - - -class Pifo(Queue): - - def enqueue(self, item, rank=None): - if rank is None: - rank = self.get_rank(item) - item.rank = rank - super().enqueue((rank, item)) - self.sort() - - def sort(self): - self._list.sort(key=lambda x: x[0]) - - def dequeue(self): - itm = super().dequeue() - return itm[1] if itm else None - - def peek(self): - itm = super().peek() - return itm[1] if itm else None - - def get_rank(self, item): - raise NotImplementedError - - -class Flow(Queue): - def __init__(self, idx): - super().__init__() - self.idx = idx - self.rank = 0 - - def __repr__(self): - return f"F({self.idx})" - - # Return the length of the first packet in the queue as the "length" of the - # flow. This is not the correct thing to do, but it works as a stopgap - # solution for testing the hierarchical mode, and we're only using - # unit-length for that anyway - @property - def length(self): - itm = self.peek() - return itm.length if itm else 0 diff --git a/queue-exp/pifo_srpt.py b/queue-exp/pifo_srpt.py new file mode 100755 index 0000000..014d406 --- /dev/null +++ b/queue-exp/pifo_srpt.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +# coding: utf-8 -*- +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# pifo-srpt.py + +"""Shortest Remaining Processing Time (SRPT). + +This scheduling algorithm is referenced in companion C++ implementation for the +paper "Programmable packet scheduling at line rate" by Sivaraman, Anirudh, et +al. + +It schedules packets in the order of how much data the flow has left. It assumes +complete knowledge of the flow length. In the real world, this would either need +to be estimated or limited to predictable flows. +""" + +__copyright__ = """ +Copyright (c) 2021 Toke Høiland-Jørgensen +Copyright (c) 2021 Frey Alfredsson +""" + +__license__ = """ +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" + +from sched_lib import Packet, Runner, Pifo, SchedulingAlgorithm +from sched_lib import FlowTracker + + +class Srpt(SchedulingAlgorithm): + """Shortest Remaining Processing Time""" + + def __init__(self, name=None): + super().__init__(name) + self._pifo = Pifo() + self._flow_tracker = FlowTracker() + + self._remains = {} + + # We cheat by accessing the global packet list directly + for pkt in pkts: + if pkt.flow in self._remains.keys(): + self._remains[pkt.flow] += pkt.length + else: + self._remains[pkt.flow] = pkt.length + + def get_rank(self, item): + """Rank the items by their remaining total flow length.""" + + rank = self._remains[item.flow] + self._remains[item.flow] -= item.length + return rank + + def enqueue(self, ref, item): + flow = self._flow_tracker.enqueue(item) + rank = self.get_rank(item) + self._pifo.enqueue(flow, rank) + + def dequeue(self): + flow = self._pifo.dequeue() + item = None + if flow is not None: + item = flow.dequeue() + return item + + def dump(self): + self._pifo.dump() + +if __name__ == "__main__": + pkts = [ + Packet(flow=1, idn=1, length=2), + Packet(flow=1, idn=2, length=2), + Packet(flow=2, idn=1, length=1), + Packet(flow=2, idn=2, length=1), + Packet(flow=2, idn=3, length=1), + ] + Runner(pkts, Srpt()).run() diff --git a/queue-exp/pifo_stfq.py b/queue-exp/pifo_stfq.py new file mode 100755 index 0000000..be91af3 --- /dev/null +++ b/queue-exp/pifo_stfq.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# coding: utf-8 -*- +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# pifo-stfq.py + +"""Start-Time Fair Queuing (STFQ) + +This scheduling algorithm is mentioned in the paper "Programmable packet +scheduling at line rate" by Sivaraman, Anirudh, et al. + +It schedules packets by their start time within a flow. It defines the start +time as the finish time of the last enqueued packet within a flow. +""" + +__copyright__ = """ +Copyright (c) 2021 Toke Høiland-Jørgensen +Copyright (c) 2021 Frey Alfredsson +""" + +__license__ = """ +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" + +from sched_lib import Packet, Runner, Pifo, SchedulingAlgorithm + + +class Stfq(SchedulingAlgorithm): + """Start-Time Fair Queuing (STFQ)""" + + def __init__(self, name=None): + super().__init__(name) + self._pifo = Pifo() + + self._last_finish = {} + self._virt_time = 0 + + def get_rank(self, item): + """Rank the items by their start time, which we calculate from the + finish time of the last packet. + """ + flow_id = item.flow + if flow_id in self._last_finish: + rank = max(self._virt_time, self._last_finish[flow_id]) + else: + rank = self._virt_time + self._last_finish[flow_id] = rank + item.length + return rank + + def enqueue(self, ref, item): + rank = self.get_rank(item) + self._pifo.enqueue(ref, rank) + + def dequeue(self): + return self._pifo.dequeue() + + def dump(self): + self._pifo.dump() + + + +if __name__ == "__main__": + pkts = [ + Packet(flow=1, idn=1, length=2), + Packet(flow=1, idn=2, length=2), + Packet(flow=2, idn=1, length=1), + Packet(flow=2, idn=2, length=1), + Packet(flow=2, idn=3, length=1), + ] + Runner(pkts, Stfq()).run() diff --git a/queue-exp/pifo_wfq.py b/queue-exp/pifo_wfq.py new file mode 100755 index 0000000..85e1b00 --- /dev/null +++ b/queue-exp/pifo_wfq.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +# coding: utf-8 -*- +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# pifo-wfq.py + +"""Weighted Fair Queueing (WFQ) + +This scheduling algorithm is mentioned in the paper "Programmable packet +scheduling at line rate" by Sivaraman, Anirudh, et al. It schedules flows by +giving them a fraction of the capacity using predefined weights. In our example, +we defined flows with an odd number to get a weight of 50 and even numbers to +get 100. +""" + +__copyright__ = """ +Copyright (c) 2021 Toke Høiland-Jørgensen +Copyright (c) 2021 Frey Alfredsson +""" + +__license__ = """ +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" + +from sched_lib import Packet, Runner, Pifo, SchedulingAlgorithm + + +class Wfq(SchedulingAlgorithm): + """Weighted Fair Queueing (WFQ)""" + + def __init__(self, name=None): + super().__init__(name) + self._pifo = Pifo() + self._last_finish = {} + self._virt_time = 0 + + def get_rank(self, item): + """Rank the items by their start time, which we calculate from the + finish time of the last packet. However, we divide the packet's length + with weights to prioritize them. We determine the weights by splitting + the flow-ids into odd and even numbers. + """ + flow = item.flow + weight = 50 if flow % 2 == 1 else 100 + if flow in self._last_finish: + rank = max(self._virt_time, self._last_finish[flow]) + else: + rank = self._virt_time + self._last_finish[flow] = rank + item.length / weight + return rank + + def enqueue(self, ref, item): + rank = self.get_rank(item) + self._pifo.enqueue(ref, rank) + + def dequeue(self): + return self._pifo.dequeue() + + def dump(self): + self._pifo.dump() + + +if __name__ == "__main__": + pkts = [ + Packet(flow=1, idn=1, length=100), + Packet(flow=1, idn=2, length=100), + Packet(flow=1, idn=3, length=100), + Packet(flow=2, idn=1, length=100), + Packet(flow=2, idn=2, length=100), + Packet(flow=2, idn=3, length=100), + ] + Runner(pkts, Wfq()).run() diff --git a/queue-exp/sched_lib.py b/queue-exp/sched_lib.py new file mode 100644 index 0000000..40eb4d3 --- /dev/null +++ b/queue-exp/sched_lib.py @@ -0,0 +1,200 @@ +# -*- coding: utf-8 -*- +# +# pifo_lib.py +# +# Author: Toke Høiland-Jørgensen (toke@toke.dk) +# Date: 18 May 2021 +# Copyright (c) 2021, Toke Høiland-Jørgensen +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from pprint import pprint, pformat + + +class Packet: + def __init__(self, flow, idn, length=1): + self.flow = flow + self.idn = idn + self.length = length + + def __repr__(self): + return f"P(F:{self.flow}, I:{self.idn}, L:{self.length})" + + +class Runner: + """This class is responsible for running a test on a packet scheduling + algorithm. It is accountable for enquing and dequeing packets. For now, it + does so by dequing as many packets as it enqued. In the next iteration, when + we add pacing, it will need to handle virtual time cycling. + """ + + def __init__(self, pkts, scheduler): + self.input_pkts = pkts + self.scheduler = scheduler + + def run(self): + print(f"Running with scheduler: {self.scheduler}") + print(" Inserting packets into scheduler:") + pprint(self.input_pkts, indent=4) + for p in self.input_pkts: + self.scheduler.enqueue(p, p) + print(" Scheduler state:") + self.scheduler.dump() + output = [] + + for p in self.scheduler: + output.append(p) + print(" Got packets from queue:") + pprint(output, indent=4) + + +class SchedulingAlgorithm(): + + """A queuing packet scheduling algorithm requires an abstraction that keeps + the queuing data structure and the algorithm separate. To create a new + Scheduling algorithm, inherit this class, add the scheduling data structures + to the constructor, and implement the constructor, enqueue, dequeue, and the + dump functions. + + Please look at the pifo_fifo.py to see how you implement a FIFO. + """ + + def __init__(self, name=None): + self._name = name + + def enqueue(self, ref, item): + raise NotImplementedError(self.__class__.__name__ + ' missing implementation') + + def dequeue(self): + raise NotImplementedError(self.__class__.__name__ + ' missing implementation') + + def dump(self): + raise NotImplementedError(self.__class__.__name__ + ' missing implementation') + + def __next__(self): + pkt = self.dequeue() + if pkt is None: + raise StopIteration + return pkt + + def __iter__(self): + return self + + def __repr__(self): + result = f"{self.__class__.__name__} - {self.__class__.__doc__}" + if self._name is not None: + result = f"{self._name}: {result}" + return result + + +class Queue: + def __init__(self, idx=None): + self._list = [] + self.idx = idx + + def enqueue(self, ref, rank=None): + self._list.append(ref) + + def peek(self): + try: + return self._list[0] + except IndexError: + return None + + def dequeue(self): + try: + return self._list.pop(0) + except IndexError: + return None + + def __next__(self): + item = self.dequeue() + if item is None: + raise StopIteration + return item + + def __iter__(self): + return self + + @property + def qlen(self): + return len(self._list) + + def __len__(self): + return self.qlen + + def __repr__(self): + return f"{self.__class__.__name__}({self.idx})" + + def dump(self): + pprint(self._list, indent=4) + + +class Pifo(Queue): + def enqueue(self, ref, rank): + if rank is None: + raise ValueError("Rank can't be of value 'None'.") + + super().enqueue((rank, ref)) + self.sort() + + def sort(self): + self._list.sort(key=lambda x: x[0]) + + def dequeue(self): + itm = super().dequeue() + return itm[1] if itm else None + + def peek(self): + itm = super().peek() + return itm[1] if itm else None + + +class Flow(Queue): + def __init__(self, idx): + super().__init__(idx) + + def __repr__(self): + return f"F(I:{self.idx}, Q:{self.qlen}, L:{self.length})" + + @property + def length(self): + result = 0 + for itm in self._list: + result += itm.length if itm else 0 + return result + + +class FlowTracker(): + """This class provides us with the typical operation of keeping track of + flows. Use this class in your scheduling algorithms when your algorithm only + has one type of flows. + """ + + def __init__(self): + self._flows = {} + + def enqueue(self, pkt, flow_id=None): + if not isinstance(pkt, Packet): + raise ValueError(f"Expected a packet, but got '{pkt}' instead.") + if flow_id is None: + flow_id = pkt.flow + if not flow_id in self._flows: + self._flows[flow_id] = Flow(flow_id) + flow = self._flows[flow_id] + flow.enqueue(pkt) + return flow + + def get_flow(self, flow_id): + return self._flows[flow_id]