Skip to content

Commit

Permalink
Queues: Test Oracles for PIEOs and PCQs (#2192)
Browse files Browse the repository at this point in the history
This PR introduces Python test oracles for Push-In-Extract-Out (PIEO)
Queues and Programmable Calendar Queues (PCQs). It also generalizes
parts of the oracle generation framework to account for parameters like
time and rank being passed in directly.

For further info on these structures, please consult the notes that I
and @anshumanmohan's have put together on
[PIEOs](cucapra/packet-scheduling#12),
[PCQs](cucapra/packet-scheduling#15) and
[PIEO
Trees](cucapra/packet-scheduling#16)
which also discuss the PIEO-PCQ implementation I have gone for here.
  • Loading branch information
KabirSamsi authored Jul 15, 2024
1 parent 907bcd1 commit 9135b6f
Show file tree
Hide file tree
Showing 20 changed files with 600,737 additions and 94 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,6 @@ tools/profiler/tmp/
tools/profiler/logs

temp/

# for running a venv
.venv
4 changes: 2 additions & 2 deletions calyx-py/calyx/binheap_oracle.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
if __name__ == "__main__":
max_cmds, len = int(sys.argv[1]), int(sys.argv[2])
keepgoing = "--keepgoing" in sys.argv
commands, values, ranks = queue_util.parse_json()
commands, values, ranks, _ = queue_util.parse_json(True)
binheap = queues.Binheap(len)
ans = queues.operate_queue(binheap, max_cmds, commands, values, ranks=ranks, keepgoing=keepgoing)
queue_util.dump_json(ans, commands, values, ranks=ranks)
queue_util.dump_json(commands, values, ans, ranks=ranks)
4 changes: 2 additions & 2 deletions calyx-py/calyx/fifo_oracle.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
if __name__ == "__main__":
max_cmds, len = int(sys.argv[1]), int(sys.argv[2])
keepgoing = "--keepgoing" in sys.argv
commands, values, _ = queue_util.parse_json()
commands, values, _, _ = queue_util.parse_json()
fifo = queues.Fifo(len)
ans = queues.operate_queue(fifo, max_cmds, commands, values, keepgoing=keepgoing)
queue_util.dump_json(ans, commands, values)
queue_util.dump_json(commands, values, ans)
19 changes: 14 additions & 5 deletions calyx-py/calyx/gen_queue_data_expect.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ for queue_kind in fifo pifo pifo_tree; do
[[ "$queue_kind" != "pifo_tree" ]] && cp ../test/correctness/queues/$queue_kind.expect ../test/correctness/queues/binheap/$queue_kind.expect
done

# Here, we test the queues for non-work-conserving algorithms,
# which are the following:
# - pieo_oracle.py
# - pcq_oracle.py

for queue_kind in pieo pcq nwc_simple; do
python3 queue_data_gen.py $num_cmds --nwc-en > ../test/correctness/queues/$queue_kind.data
cat ../test/correctness/queues/$queue_kind.data | python3 ${queue_kind}_oracle.py $num_cmds $queue_size --keepgoing > ../test/correctness/queues/$queue_kind.expect
done

# For the Binary Heap, we drop piezo mode and enable ranks for data gen and
# use binheap_oracle to generate the expected output
Expand All @@ -31,15 +40,15 @@ cat ../test/correctness/queues/binheap/stable_binheap.data | python3 binheap_ora
# generate the expected output for queues with 2..7 flows. This generates 6 data expect file pairs.

for n in {2..7}; do
python3 queue_data_gen.py $num_cmds > ../test/correctness/queues/rr_queues/rr_queue_${n}flows.data
cat ../test/correctness/queues/rr_queues/rr_queue_${n}flows.data | python3 rrqueue_oracle.py $num_cmds $queue_size $n --keepgoing > ../test/correctness/queues/rr_queues/rr_queue_${n}flows.expect
python3 queue_data_gen.py $num_cmds > ../test/correctness/queues/strict_and_rr_queues/rr_queues/rr_queue_${n}flows.data
cat ../test/correctness/queues/strict_and_rr_queues/rr_queues/rr_queue_${n}flows.data | python3 rr_queue_oracle.py $num_cmds $queue_size $n --keepgoing > ../test/correctness/queues/strict_and_rr_queues/rr_queues/rr_queue_${n}flows.expect
done

# For Strict queues, we use strict_queue_oracle.py to generate the expected output
# for queues with 2..6 flows, each with a different strict ordering. This generates 5
# expect file pairs.

for n in {2..6}; do
python3 queue_data_gen.py $num_cmds > ../test/correctness/queues/strict_queues/strict_${n}flows.data
cat ../test/correctness/queues/strict_queues/strict_${n}flows.data | python3 strict_queue_oracle.py $num_cmds $queue_size $n --keepgoing > ../test/correctness/queues/strict_queues/strict_${n}flows.expect
done
python3 queue_data_gen.py $num_cmds > ../test/correctness/queues/strict_and_rr_queues/strict_queues/strict_${n}flows.data
cat ../test/correctness/queues/strict_and_rr_queues/strict_queues/strict_${n}flows.data | python3 strict_queue_oracle.py $num_cmds $queue_size $n --keepgoing > ../test/correctness/queues/strict_and_rr_queues/strict_queues/strict_${n}flows.expect
done
11 changes: 11 additions & 0 deletions calyx-py/calyx/nwc_simple_oracle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import sys
import calyx.queues as queues
from calyx import queue_util

if __name__ == "__main__":
commands, values, ranks, times = queue_util.parse_json(True, True)
max_cmds, len = int(sys.argv[1]), int(sys.argv[2])
keepgoing = "--keepgoing" in sys.argv
queue = queues.NWCSimple(len)
ans = queues.operate_queue(queue, max_cmds, commands, values, ranks, times=times, keepgoing=keepgoing)
queue_util.dump_json(commands, values, ans, ranks, times)
11 changes: 11 additions & 0 deletions calyx-py/calyx/pcq_oracle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import sys
import calyx.queues as queues
from calyx import queue_util

if __name__ == "__main__":
commands, values, ranks, times = queue_util.parse_json(True, True)
max_cmds, len = int(sys.argv[1]), int(sys.argv[2])
keepgoing = "--keepgoing" in sys.argv
pcq = queues.PCQ(len)
ans = queues.operate_queue(pcq, max_cmds, commands, values, ranks, times=times, keepgoing=keepgoing)
queue_util.dump_json(commands, values, ans, ranks, times)
11 changes: 11 additions & 0 deletions calyx-py/calyx/pieo_oracle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import sys
import calyx.queues as queues
from calyx import queue_util

if __name__ == "__main__":
commands, values, ranks, times = queue_util.parse_json(True, True)
max_cmds, len = int(sys.argv[1]), int(sys.argv[2])
keepgoing = "--keepgoing" in sys.argv
pieo = queues.Pieo(len)
ans = queues.operate_queue(pieo, max_cmds, commands, values, ranks, times=times, keepgoing=keepgoing)
queue_util.dump_json(commands, values, ans, ranks, times)
4 changes: 2 additions & 2 deletions calyx-py/calyx/pifo_oracle.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
if __name__ == "__main__":
max_cmds, len = int(sys.argv[1]), int(sys.argv[2])
keepgoing = "--keepgoing" in sys.argv
commands, values, _ = queue_util.parse_json()
commands, values, _, _ = queue_util.parse_json()

# Our PIFO is simple: it just orchestrates two FIFOs. The boundary is 200.
pifo = queues.Pifo(queues.Fifo(len), queues.Fifo(len), 200, len)

ans = queues.operate_queue(pifo, max_cmds, commands, values, keepgoing=keepgoing)
queue_util.dump_json(ans, commands, values)
queue_util.dump_json(commands, values, ans)
4 changes: 2 additions & 2 deletions calyx-py/calyx/pifo_tree_oracle.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
if __name__ == "__main__":
max_cmds, len = int(sys.argv[1]), int(sys.argv[2])
keepgoing = "--keepgoing" in sys.argv
commands, values, _ = queue_util.parse_json()
commands, values, _, _ = queue_util.parse_json()

# Our PIFO is a little complicated: it is a tree of queues.
# The root has two children, which are PIFOs.
Expand All @@ -28,4 +28,4 @@
)

ans = queues.operate_queue(pifo, max_cmds, commands, values, keepgoing=keepgoing)
queue_util.dump_json(ans, commands, values)
queue_util.dump_json(commands, values, ans)
62 changes: 52 additions & 10 deletions calyx-py/calyx/queue_data_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ def no_err_cmds_list(queue_size, num_cmds):
- No underflows.
- `num_cmds`/2 pushes and `num_cmds`/2 pops.
A combination of the above means that no packet is left unpopped.
This is specifically catered for work-conserving algorithms,
and not for non-work-conserving algorithms.
"""
running_count = 0 # The current size of the queue.
push_goal = int(num_cmds / 2) # How many pushes we want overall.
Expand Down Expand Up @@ -63,29 +65,58 @@ def no_err_cmds_list(queue_size, num_cmds):
return commands


def dump_json(num_cmds, use_rank: bool, no_err: bool, queue_size: Optional[int] = None):
def dump_json(num_cmds, no_err: bool, queue_size: Optional[int]=None, nwc=False, use_ranks=False):
"""Prints a JSON representation of the data to stdout.
The data itself is populated randomly, following certain rules:
- It has three "memories": `commands`, `values`, and `ans_mem`.
- The `commands` memory has `num_cmds` items, which are 0, 1, or 2.
0: pop, 1: peek, 2: push
If the `no_err` flag is set, then items are chosen from 0 and 2 using a helper.
- Optional memories `ranks` and `times` are included for queues primed for non-work-conserving algorithms.
- The `commands` memory has `num_cmds` items, which range from 0-2 for work-conserving policies,
and from 0-4 for non-work-conserving. They are as follows:
FOR WORK-CONSERVING POLICIES
0 : pop
1 : peek
2 : push
FOR NON-WORK-CONSERVING POLICIES
0 : pop by predicate
1 : peek by predicate
2 : push
3 : pop by value
4 : peek by value
If the `no_err` flag is set and the policy is work-conserving,
then items are chosen from 0 and 2 using a helper.
If the `nwc` flag is set to False (marking the policy as work-conserving),
then the predicate is treated as though always true.
- The `values` memory has `num_cmds` items:
random values between 0 and 400.
- The `ranks` memory has `num_cmds` items:
random values between 0 and 400.
- The `times` memory has `num_cmds` items:
random values between 0 and 50.
- The `ans_mem` memory has `num_cmds` items, all zeroes.
- Each memory has a `format` field, which is a format object for a bitvector.
"""

commands = {
"commands": {
"data": (
# The `commands` memory has `num_cmds` items, which are all 0, 1, or 2
# The `commands` memory has `num_cmds` items, which are all 0, 1, or 2 (or 3 and 4, for nwc policies)
no_err_cmds_list(queue_size, num_cmds)
if no_err
# If the `no_err` flag is set, then we use the special helper
# that ensures no overflow or overflow will occur.
if no_err
else [random.randint(0, 2) for _ in range(num_cmds)]
else (
[random.randint(0, 4) for _ in range(num_cmds)]
if nwc
else
[random.randint(0, 2) for _ in range(num_cmds)]
)
),
"format": format_gen(2),
"format": format_gen(3 if nwc else 2),
}
}
values = {
Expand All @@ -104,6 +135,14 @@ def dump_json(num_cmds, use_rank: bool, no_err: bool, queue_size: Optional[int]
"format": format_gen(32),
}
}
times = {
"times": {
"data": [0 if not nwc else random.randint(0, 50) for _ in range(num_cmds)],
# The `times` memory has `num_cmds` items, which are all
# random values between 0 and 50.
"format": format_gen(32),
}
}
ans_mem = {
"ans_mem": {
"data": [0] * num_cmds,
Expand All @@ -112,7 +151,9 @@ def dump_json(num_cmds, use_rank: bool, no_err: bool, queue_size: Optional[int]
}
}

if use_rank:
if nwc:
print(json.dumps(commands | values | ranks | times | ans_mem, indent=2))
elif use_ranks:
print(json.dumps(commands | values | ranks | ans_mem, indent=2))
else:
print(json.dumps(commands | values | ans_mem, indent=2))
Expand All @@ -123,8 +164,9 @@ def dump_json(num_cmds, use_rank: bool, no_err: bool, queue_size: Optional[int]
# This says whether we should use the special no_err helper.
random.seed(5)
num_cmds = int(sys.argv[1])
nwc = "--nwc-en" in sys.argv
no_err = "--no-err" in sys.argv
use_rank = "--use-rank" in sys.argv
if no_err:
queue_size = int(sys.argv[3])
dump_json(num_cmds, use_rank, no_err, queue_size if no_err else None)
dump_json(num_cmds, no_err, queue_size if no_err else None, nwc, use_rank)
44 changes: 23 additions & 21 deletions calyx-py/calyx/queue_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import sys


def parse_json():
def parse_json(parse_ranks=False, parse_times=False):
"""Effectively the opposite of `data_gen`:
Given a JSON file formatted for Calyx purposes, parses it into two lists:
- The `commands` list.
Expand All @@ -12,28 +12,30 @@ def parse_json():
data = json.load(sys.stdin)
commands = data["commands"]["data"]
values = data["values"]["data"]
ranks = None
try:

if parse_ranks:
ranks = data["ranks"]["data"]
except KeyError:
pass
return commands, values, ranks
if parse_times:
times = data["times"]["data"]

#Return tuple of data
return commands, values, (ranks if parse_ranks else None), (times if parse_times else None)


def dump_json(ans_mem, commands, values, ranks=None):
def dump_json(commands, values, ans_mem, ranks=None, times=None):
"""Prints a JSON representation of the data to stdout."""
payload = {}
if ranks == None:
payload = {
"ans_mem": ans_mem,
"commands": commands,
"values": values
}
else:
payload = {
"ans_mem": ans_mem,
"commands": commands,
"ranks": ranks,
"values": values
}

payload = {
"ans_mem": ans_mem,
"commands": commands
}

if ranks:
payload["ranks"] = ranks

payload["values"] = values

if times:
payload["times"] = times

print(json.dumps(payload, indent=2))
Loading

0 comments on commit 9135b6f

Please sign in to comment.