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]