Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Queues: PCAP-based benchmarking harness #2358

Merged
merged 25 commits into from
Jan 22, 2025
Merged
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
06bda6b
Add pcap parsing script
polybeandip Oct 15, 2024
0213aff
Refactor queue_call
polybeandip Oct 26, 2024
39a8138
C -> c
polybeandip Oct 27, 2024
61ec5da
Make flow inference a separate component
polybeandip Oct 27, 2024
a41a15d
Remove generate from binheap strict and RR
polybeandip Oct 28, 2024
20352f9
Add strict test for 7 flows
polybeandip Oct 28, 2024
5141884
Fix imports in binheap tests
polybeandip Oct 28, 2024
126bb2d
Rename RR and Strict oracles
polybeandip Oct 28, 2024
0ebd533
Rename binheap (un)tuplify components
polybeandip Oct 28, 2024
789dfcc
Sketch sim_pcap.py
polybeandip Oct 28, 2024
7a66029
More sketching sim_pcap.py and flow_inference.py
polybeandip Oct 30, 2024
31fa876
Move evaluation scripts to separate directory
polybeandip Nov 4, 2024
2dd295f
Pass open files instead of names
polybeandip Nov 4, 2024
614b028
Various tweaks
polybeandip Nov 11, 2024
4b84482
Add --start and --end options to parse_pcap.py
polybeandip Nov 11, 2024
b3c8941
Add CalyxPy state to fud2
polybeandip Nov 11, 2024
33a93d7
Fix fud2 tests
polybeandip Nov 12, 2024
90a59af
Add pkts/bits per sec stats to parse_pcap.py
polybeandip Nov 12, 2024
f330085
Add more PCAP sim logic for binheap rr and strict
polybeandip Nov 12, 2024
5b7ad8b
Factor out flow_inference for strict_or_rr.py
polybeandip Nov 19, 2024
80b0ef4
Comment parse_pcap.py
polybeandip Nov 20, 2024
d2e4aa5
Typo
polybeandip Dec 11, 2024
2600465
Merge branch 'main' into realistic-benchmarking-harness
polybeandip Dec 13, 2024
cf8fd9c
Merge remote-tracking branch 'refs/remotes/origin/realistic-benchmark…
polybeandip Dec 13, 2024
d4692de
Remove assertion_line
polybeandip Jan 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Various tweaks
- finish up plot_pcap_sim.py
- move parse_pcap.py to evaluation/
- revert most changes to queue_call.py
polybeandip committed Nov 11, 2024
commit 614b028ed025ce7a2316a6229d40b9cbf9e827b5
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# =============================================================================
# Usage: python3 parse_pcap.py <PCAP> <Addr2Flow> <Out> [Option]...
# =============================================================================
# Arguments:
# Usage: python3 parse_pcap.py <PCAP> <Out> [Options]...
#
# Parses PCAP files to generate data files
#
# Positional Arguments:
# PCAP Packet Capture to parse
# Addr2Flow JSON mapping MAC addresses to integer flows
# Out Path to save generated .data file
@@ -10,7 +11,7 @@
# -h --help Display this message
#
# --num-packets N No. packets in PCAP to parse
# [default: 1000]
# [default: 500]
#
# --clock-period C Clock period of hardware in ns
# [default: 7]
@@ -21,18 +22,22 @@
# --pop-tick P Time between consecutive pops in ns
# [default: calulated to achieve line rate]
#
# --addr2int A JSON mapping MAC addresses to non-negative integers
# [default: ith encountered address -> i]
#
# --num-flows F No. flows
# The flow of a packet is Addr2Flow[Packet Address] mod F
# [default: max value in Addr2Flow + 1]
# The flow of a packet is Addr2Int[Packet Address] mod F
# [default: max value in Addr2Int + 1]
#
# Example:
# python3 parse_pcap.py example.pcap addr2flow.json example.data --num-packets 500
# python3 parse_pcap.py example.pcap example.data --addr2int addr2int.json --num-packets 250

import sys
import random
import json
import dpkt
import argparse
from contextlib import nullcontext
from calyx.utils import bits_needed

CMD_PUSH = 1
@@ -45,6 +50,10 @@
LINE_RATE = 1 # in Gbit/s


class UnknownAddress(Exception):
pass


class ArgumentParserWithCustomError(argparse.ArgumentParser):
def __init__(self):
super().__init__(add_help=False)
@@ -65,7 +74,6 @@ def parse_cmdline():

parser.add_argument("-h", "--help", action="store_true")
parser.add_argument("PCAP")
parser.add_argument("Addr2Flow")
parser.add_argument("Out")

def check_positive_int(x):
@@ -86,6 +94,7 @@ def check_positive_int(x):
parser.add_argument(
"--pop-tick", type=check_positive_int, action="store", default=POP_TICK
)
parser.add_argument("--addr2int", action="store")
parser.add_argument("--num-flows", type=check_positive_int, action="store")

def check_positive_float(x):
@@ -107,25 +116,44 @@ def check_positive_float(x):
return parser.parse_args()


def parse_pcap(pcap, addr2flow, num_flows):
def parse_pcap(pcap, addr2int, num_flows):
global POP_TICK

def mac_addr(addr):
return ":".join("%02x" % dpkt.compat.compat_ord(b) for b in addr)

offset = None
total_size = 0
make_addr_map = addr2int == None
if make_addr_map:
addr2int = {}
addr_count = 0
for i, (ts, buf) in zip(range(NUM_PKTS), pcap):
if i == 0:
offset = ts

eth = dpkt.ethernet.Ethernet(buf)
addr = mac_addr(eth.src)
if addr not in addr2int:
if make_addr_map:
addr2int[addr] = addr_count
addr_count += 1
else:
raise UnknownAddress(
f"MAC address {addr} for packet {i} not found in Addr2Flow map:\n {addr2int}"
)

total_size += len(buf)

if num_flows is None:
num_flows = max(addr2int[addr] for addr in addr2int) + 1

if POP_TICK is None:
POP_TICK = int((total_size * 8) // (LINE_RATE * NUM_PKTS))

def mac_addr(addr):
return ":".join("%02x" % dpkt.compat.compat_ord(b) for b in addr)

pcap_file.seek(0)
pcap = dpkt.pcap.Reader(pcap_file)
out = {"commands": [], "arrival_cycles": [], "flows": [], "pkt_ids": []}
data = {"commands": [], "arrival_cycles": [], "flows": [], "pkt_ids": []}
prev_time = 0
pkts_in_switch = 0
for i, (ts, buf) in zip(range(NUM_PKTS), pcap):
@@ -135,39 +163,41 @@ def mac_addr(addr):
num_pops = int((time - pop_time) // POP_TICK) if time > pop_time else 0
pkts_in_switch = 0 if pkts_in_switch < num_pops else pkts_in_switch - num_pops
for _ in range(num_pops):
out["commands"].append(CMD_POP)
data["commands"].append(CMD_POP)

pop_cycle = int(pop_time // CLOCK_PERIOD)
out["arrival_cycles"].append(pop_cycle)
data["arrival_cycles"].append(pop_cycle)
pop_time += POP_TICK

out["flows"].append(DONTCARE)
out["pkt_ids"].append(DONTCARE)
data["flows"].append(DONTCARE)
data["pkt_ids"].append(DONTCARE)

eth = dpkt.ethernet.Ethernet(buf)
flow = addr2flow[mac_addr(eth.src)] % num_flows
addr = mac_addr(eth.src)
flow = addr2int[addr] % num_flows
cycle = int(time // CLOCK_PERIOD)
pkt_id = i + 1
pkts_in_switch += 1

out["commands"].append(CMD_PUSH)
out["arrival_cycles"].append(cycle)
out["flows"].append(flow)
out["pkt_ids"].append(i)
data["commands"].append(CMD_PUSH)
data["arrival_cycles"].append(cycle)
data["flows"].append(flow)
data["pkt_ids"].append(pkt_id)

prev_time = time

pop_time = (prev_time % POP_TICK) + prev_time
for _ in range(pkts_in_switch):
out["commands"].append(CMD_POP)
data["commands"].append(CMD_POP)

pop_cycle = int(pop_time // CLOCK_PERIOD)
out["arrival_cycles"].append(pop_cycle)
data["arrival_cycles"].append(pop_cycle)
pop_time += POP_TICK

out["flows"].append(DONTCARE)
out["pkt_ids"].append(DONTCARE)
data["flows"].append(DONTCARE)
data["pkt_ids"].append(DONTCARE)

return out
return data, num_flows, addr2int


def dump_json(data, flow_bits, data_file):
@@ -207,18 +237,21 @@ def format_gen(width):
POP_TICK = opts.pop_tick

with open(opts.PCAP, "rb") as pcap_file:
with open(opts.Addr2Flow) as addr2flow_json:
with (
nullcontext() if opts.addr2int is None else open(opts.addr2int)
) as addr2int_json:
pcap = dpkt.pcap.Reader(pcap_file)
addr2flow = json.load(addr2flow_json)
if opts.num_flows is None:
num_flows = max(addr2flow[addr] for addr in addr2flow) + 1
else:
num_flows = opts.num_flows
data = parse_pcap(pcap, addr2flow, num_flows)
addr2int = None if addr2int_json is None else json.load(addr2int_json)
num_flows = opts.num_flows

num_cmds = len(data["commands"])
print(f'len(data["commands"] = {num_cmds}')
data, num_flows, addr2int = parse_pcap(pcap, addr2int, num_flows)

with open(opts.Out, "w") as data_file:
flow_bits = bits_needed(num_flows - 1)
json = dump_json(data, flow_bits, data_file)

print(f"Number of commands = {len(data['commands'])}")
print("Addresses to flows:")
for addr in addr2int:
print(f"\t{addr} -> {addr2int[addr] % num_flows}")
print(f"Pop tick = {POP_TICK} ns or {POP_TICK / CLOCK_PERIOD} cycles")
106 changes: 104 additions & 2 deletions frontends/queues/evaluation/plot_pcap_sim.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,42 @@
import os
import sys
import json
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Patch

CLOCK_PERIOD = 7 # in ns
NUM_FLOWS = 2


class OutOfColors(Exception):
pass


class Packet:
def __init__(self, id, flow, punch_in, punch_out=None):
self.id = id
self.flow = flow
self.punch_in = punch_in
self.punch_out = punch_out

def color(self):
colors = [
"red",
"skyblue",
"forestgreen",
"lightsalmon",
"dodgerblue",
"darkseagreen",
"orchid",
]
if self.flow < len(colors):
return colors[self.flow]

raise OutOfColors(f"No color for flow {self.flow}; Extend the colors list!")

def __str__(self):
return f"({self.id}, {self.flow}, {self.punch_in}, {self.punch_out})"


def append_path_prefix(file):
@@ -9,8 +46,73 @@ def append_path_prefix(file):


def parse(file):
out[] =
data = json.load(file)
packets = []

for i, cmd in enumerate(data["commands"]):
if cmd == 0:
continue

if __name__ == "__main__":
id = data["values"][i]
flow = data["flows"][i]
punch_in = data["arrival_cycles"][i] * CLOCK_PERIOD
if id in data["ans_mem"]:
j = data["ans_mem"].index(id)
punch_out = data["departure_cycles"][j] * CLOCK_PERIOD
pkt = Packet(id, flow, punch_in, punch_out)
else:
pkt = Packet(id, flow, punch_in)
packets += [pkt]

packets.sort(
key=lambda p: float("inf") if p.punch_out is None else float(p.punch_out)
)

return packets


def draw(packets, name):
fig, ax = plt.subplots(1, 1)
fig.set_size_inches(20, 10, forward=True)
ax.set_ylim(0, len(packets))
ax.axes.yaxis.set_visible(False)

patches = []
labels = []
for i, pkt in enumerate(packets):
color = pkt.color()
if pkt.punch_out is not None:
treetime = pkt.punch_out - pkt.punch_in
handle = ax.broken_barh(
[(pkt.punch_in, treetime)], (i, 1), facecolors=color
)

label = f"Flow {pkt.flow}"
if label not in labels:
patches += [Patch(color=color)]
labels += [label]
else:
treetime = 0
ax.broken_barh([(pkt.punch_in, treetime)], (i, 1), facecolors=color)
ax.text(
x=pkt.punch_in + 0.2,
y=i + 0.7,
s="OVERFLOW",
color="black",
fontsize="x-small",
)
ax.invert_yaxis()
ax.legend(handles=patches, labels=labels)

file = append_path_prefix(name)
plt.savefig(file)
print(f"Generated {file}")


if __name__ == "__main__":
file = sys.argv[1]
basename = os.path.basename(file)
with open(file) as file:
packets = parse(file)
name = basename.split(".")[0] + ".png"
draw(packets, name)
Loading