Skip to content

Commit 0aca3c5

Browse files
committed
run_benchmarks.py script and folder
Signed-off-by: adir <adir@il.ibm.com>
1 parent 6c11449 commit 0aca3c5

File tree

1 file changed

+246
-0
lines changed

1 file changed

+246
-0
lines changed

cmd/benchmarking/run_benchmarks.py

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
import csv
2+
import os
3+
import sys
4+
import shutil
5+
import subprocess
6+
import re
7+
from datetime import datetime
8+
9+
I=1
10+
TOKENSDK_ROOT = "../../"
11+
output_folder_path = ""
12+
v1_benchmarks_folder = os.path.join(TOKENSDK_ROOT, "token/core/zkatdlog/nogh/v1")
13+
transfer_benchmarks_folder = os.path.join(TOKENSDK_ROOT, "token/core/zkatdlog/nogh/v1/transfer")
14+
issuer_benchmarks_folder = os.path.join(TOKENSDK_ROOT, "token/core/zkatdlog/nogh/v1/issue")
15+
validator_benchmarks_folder = os.path.join(TOKENSDK_ROOT, "token/core/zkatdlog/nogh/v1/validator")
16+
17+
def run_and_parse_non_parallel_metrics(benchName, params, folder=transfer_benchmarks_folder) -> dict:
18+
global I
19+
global output_folder_path
20+
21+
if folder == "":
22+
folder = transfer_benchmarks_folder
23+
24+
cmd = f"go test {folder} -run='^$' -bench={benchName} -v -benchmem -count=10 -cpu=1 -timeout 0 {params} | tee bench.txt; benchstat bench.txt"
25+
print(f"{I} Running: {cmd}")
26+
I = I+1
27+
result = subprocess.run(
28+
cmd,
29+
shell=True,
30+
capture_output=True,
31+
text=True,
32+
check=True
33+
)
34+
35+
log_file_path = os.path.join(output_folder_path, benchName+".log")
36+
if not os.path.exists(log_file_path):
37+
with open(log_file_path, "w", encoding="utf-8") as f:
38+
f.write(result.stdout)
39+
40+
output = result.stdout.splitlines()
41+
42+
name = None
43+
time_ns = None
44+
ram_bytes = None
45+
allocs = None
46+
47+
# --- Unit multipliers ---
48+
time_mult = {
49+
"n": 1,
50+
"µ": 1_000,
51+
"u": 1_000,
52+
"m": 1_000_000,
53+
"s": 1_000_000_000,
54+
}
55+
56+
ram_mult = {
57+
"B": 1,
58+
"Ki": 1024,
59+
"Mi": 1024 ** 2,
60+
}
61+
62+
alloc_mult = {
63+
"": 1,
64+
"k": 1_000,
65+
"M": 1_000_000,
66+
}
67+
68+
# --- Regexes ---
69+
time_re = re.compile(r"^(\S+)\s+([\d.]+)\s*([a-zµ]+)")
70+
ram_re = re.compile(r"^(\S+)\s+([\d.]+)\s*(B|Ki|Mi)")
71+
alloc_re = re.compile(r"^(\S+)\s+([\d.]+)\s*([kM]?)")
72+
73+
section = None
74+
75+
for line in output:
76+
line = line.strip()
77+
if not line:
78+
continue
79+
80+
if "sec/op" in line:
81+
section = "time"
82+
continue
83+
elif "B/op" in line:
84+
section = "ram"
85+
continue
86+
elif "allocs/op" in line:
87+
section = "allocs"
88+
continue
89+
90+
if section == "time":
91+
m = time_re.match(line)
92+
if m:
93+
name, val, unit = m.groups()
94+
time_ns = int(float(val) * time_mult[unit])
95+
96+
elif section == "ram":
97+
m = ram_re.match(line)
98+
if m:
99+
name, val, unit = m.groups()
100+
ram_bytes = int(float(val) * ram_mult[unit])
101+
102+
elif section == "allocs":
103+
m = alloc_re.match(line)
104+
if m:
105+
name, val, unit = m.groups()
106+
allocs = int(float(val) * alloc_mult[unit])
107+
108+
if not all([name, time_ns, ram_bytes, allocs is not None]):
109+
raise ValueError("Failed to parse benchmark output")
110+
111+
name = benchName
112+
return {
113+
f"{name} time": time_ns,
114+
f"{name} RAM": ram_bytes,
115+
f"{name} allocs": allocs,
116+
}
117+
118+
def run_and_parse_parallel_metrics(test, params, folder=transfer_benchmarks_folder) -> dict:
119+
if folder == "":
120+
folder = transfer_benchmarks_folder
121+
122+
global I
123+
global output_folder_path
124+
125+
cmd = f"go test {folder} -test.run={test} -test.v -test.timeout 0 -bits='32' -num_inputs='2' -num_outputs='2' -workers='NumCPU' -duration='10s' -setup_samples=128 {params}"
126+
print(f"{I} Running: {cmd}")
127+
I = I+1
128+
129+
# --- Run command ---
130+
result = subprocess.run(
131+
cmd,
132+
shell=True,
133+
capture_output=True,
134+
text=True,
135+
check=True
136+
)
137+
138+
log_file_path = os.path.join(output_folder_path, test+".log")
139+
if not os.path.exists(log_file_path):
140+
with open(log_file_path, "w", encoding="utf-8") as f:
141+
f.write(result.stdout)
142+
143+
output = result.stdout
144+
145+
# --- Extract test name ---
146+
name_re = re.compile(
147+
r"=== RUN\s+([a-zA-Z0-9]+/.+?)\s*$",
148+
re.MULTILINE
149+
)
150+
name_match = name_re.search(output)
151+
if not name_match:
152+
raise ValueError("Could not extract test name")
153+
154+
name = name_match.group(1)
155+
156+
# --- Unit conversion ---
157+
time_mult = {
158+
"ns": 1,
159+
"µs": 1_000,
160+
"us": 1_000,
161+
"ms": 1_000_000,
162+
"s": 1_000_000_000,
163+
}
164+
165+
def to_ns(value: float, unit: str) -> int:
166+
return int(value * time_mult[unit])
167+
168+
# --- Regexes ---
169+
real_tp_re = re.compile(r"Real Throughput\s+([\d.]+)/s")
170+
pure_tp_re = re.compile(r"Pure Throughput\s+([\d.]+)/s")
171+
172+
min_lat_re = re.compile(r"Min\s+([\d.]+)(ns|µs|us|ms|s)")
173+
avg_lat_re = re.compile(r"Average\s+([\d.]+)(ns|µs|us|ms|s)")
174+
max_lat_re = re.compile(r"Max\s+([\d.]+)(ns|µs|us|ms|s)")
175+
176+
# --- Parse values ---
177+
real_tp = float(real_tp_re.search(output).group(1))
178+
pure_tp = float(pure_tp_re.search(output).group(1))
179+
180+
min_val, min_unit = min_lat_re.search(output).groups()
181+
avg_val, avg_unit = avg_lat_re.search(output).groups()
182+
max_val, max_unit = max_lat_re.search(output).groups()
183+
184+
return {
185+
f"{name} real throughput": real_tp,
186+
f"{name} pure throughput": pure_tp,
187+
f"{name} min latency": to_ns(float(min_val), min_unit),
188+
f"{name} average latency": to_ns(float(avg_val), avg_unit),
189+
f"{name} max latency": to_ns(float(max_val), max_unit),
190+
}
191+
192+
def append_dict_as_row(filename: str, data: dict):
193+
file_exists = os.path.exists(filename)
194+
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
195+
row = {"timestamp": timestamp, **data}
196+
fieldnames = row.keys()
197+
198+
with open(filename, "a", newline="") as f:
199+
writer = csv.DictWriter(f, fieldnames=row.keys())
200+
201+
if not file_exists:
202+
writer.writeheader()
203+
204+
writer.writerow(row)
205+
206+
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
207+
output_folder_name = f"benchmark_logs_{timestamp}"
208+
output_folder_path = os.path.join(".", output_folder_name)
209+
os.makedirs(output_folder_path, exist_ok=True)
210+
211+
non_parallel_tests = [
212+
('BenchmarkSender', "", ""),
213+
('BenchmarkVerificationSenderProof', "", ""),
214+
('BenchmarkTransferProofGeneration', "", ""),
215+
('BenchmarkIssuer', "", issuer_benchmarks_folder),
216+
('BenchmarkProofVerificationIssuer', "", issuer_benchmarks_folder),
217+
('BenchmarkVerificationSenderProof', "", ""),
218+
('BenchmarkTransferServiceTransfer', "", v1_benchmarks_folder),
219+
]
220+
parallel_tests = [
221+
('TestParallelBenchmarkSender', "", ""),
222+
('TestParallelBenchmarkVerificationSenderProof', "", ""),
223+
('TestParallelBenchmarkTransferProofGeneration', "", ""),
224+
('TestParallelBenchmarkTransferServiceTransfer', "", v1_benchmarks_folder),
225+
('TestParallelBenchmarkValidatorTransfer', "", validator_benchmarks_folder),
226+
]
227+
228+
results = {}
229+
print("\n*******************************************************")
230+
print("Running non-parallel tests")
231+
232+
for test, params, benchType in non_parallel_tests:
233+
results.update(run_and_parse_non_parallel_metrics(test, params, benchType))
234+
235+
print("\n*******************************************************")
236+
print("Running parallel tests")
237+
for test, params, folder in parallel_tests:
238+
results.update(run_and_parse_parallel_metrics(test, params, folder))
239+
240+
# add new row to benchmark_results.csv and copy it to the output folder
241+
append_dict_as_row("benchmark_results.csv", results)
242+
src = os.path.join(".", "benchmark_results.csv")
243+
dst = os.path.join(output_folder_path, "benchmark_results.csv")
244+
if os.path.exists(src) and not os.path.exists(dst):
245+
shutil.copy(src, dst)
246+

0 commit comments

Comments
 (0)