Skip to content

Commit dd5f9c3

Browse files
committed
update benchmark
Signed-off-by: Gregor Zeitlinger <[email protected]>
1 parent 47e2988 commit dd5f9c3

File tree

1 file changed

+161
-0
lines changed

1 file changed

+161
-0
lines changed

.mise/tasks/run_benchmarks.py

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Run benchmarks for the `benchmarks` module, capture JMH text output, and update
4+
any <pre>...</pre> blocks containing "thrpt" under the `benchmarks/` module
5+
(files such as Java sources with embedded example output in javadocs).
6+
7+
Usage: ./scripts/run_benchmarks.py [--mvnw ./mvnw] [--module benchmarks] [--java java] [--jmh-args "-f 1 -wi 0 -i 1"]
8+
9+
By default this will:
10+
- run the maven wrapper to package the benchmarks: `./mvnw -pl benchmarks -am -DskipTests package`
11+
- locate the shaded jar under `benchmarks/target/` (named containing "benchmarks")
12+
- run `java -jar <jar> -rf text` (add extra JMH args with --jmh-args)
13+
- parse the first JMH table (the block starting with the "Benchmark Mode" header)
14+
- update all files under the `benchmarks/` directory which contain a `<pre>` block with the substring "thrpt"
15+
16+
This script is careful to preserve Javadoc comment prefixes like " * " when replacing the
17+
contents of the <pre> block.
18+
"""
19+
20+
import argparse
21+
import glob
22+
import os
23+
import re
24+
import shlex
25+
import subprocess
26+
import sys
27+
from typing import List, Optional
28+
29+
30+
def run_cmd(cmd: List[str], cwd: Optional[str] = None) -> str:
31+
proc = subprocess.run(cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
32+
if proc.returncode != 0:
33+
print(f"Command failed: {' '.join(cmd)}\nExit: {proc.returncode}\nOutput:\n{proc.stdout}")
34+
raise SystemExit(proc.returncode)
35+
return proc.stdout
36+
37+
38+
def build_benchmarks(mvnw: str, module: str) -> None:
39+
print(f"Building Maven module '{module}' using {mvnw} (this may take a while)...")
40+
cmd = [mvnw, '-pl', module, '-am', '-DskipTests', 'package']
41+
run_cmd(cmd)
42+
print("Build completed.")
43+
44+
45+
def find_benchmarks_jar(module: str) -> str:
46+
pattern = os.path.join(module, 'target', '*.jar')
47+
jars = [p for p in glob.glob(pattern) if 'original' not in p and p.endswith('.jar')]
48+
# prefer jar whose basename contains module name
49+
jars_pref = [j for j in jars if module in os.path.basename(j)]
50+
chosen = (jars_pref or jars)[:1]
51+
if not chosen:
52+
raise FileNotFoundError(f"No jar found in {os.path.join(module, 'target')} (tried: {pattern})")
53+
jar = chosen[0]
54+
print(f"Using jar: {jar}")
55+
return jar
56+
57+
58+
def run_jmh(jar: str, java_cmd: str, extra_args: Optional[str]) -> str:
59+
args = [java_cmd, '-jar', jar, '-rf', 'text']
60+
if extra_args:
61+
args += shlex.split(extra_args)
62+
print(f"Running JMH: {' '.join(args)}")
63+
output = run_cmd(args)
64+
print("JMH run completed.")
65+
return output
66+
67+
68+
def extract_first_table(jmh_output: str) -> str:
69+
# Try to extract the first table that starts with "Benchmark" header and continues until a blank line
70+
m = re.search(r'(\nBenchmark\s+Mode[\s\S]*?)(?:\n\s*\n|\Z)', jmh_output)
71+
if not m:
72+
# fallback: collect all lines that contain 'thrpt' plus a header if present
73+
lines = [l for l in jmh_output.splitlines() if 'thrpt' in l]
74+
if not lines:
75+
raise ValueError('Could not find any "thrpt" lines in JMH output')
76+
# try to find header
77+
header = next((l for l in jmh_output.splitlines() if l.startswith('Benchmark') and 'Mode' in l), 'Benchmark Mode Cnt Score Error Units')
78+
return header + '\n' + '\n'.join(lines)
79+
table = m.group(1).strip('\n')
80+
# Ensure we return only the table lines (remove any leading iteration info lines that JMH sometimes prints)
81+
# Normalize spaces: keep as-is
82+
return table
83+
84+
85+
def update_pre_blocks_under_module(module: str, table: str) -> List[str]:
86+
# Find files under module and update any <pre>...</pre> block that contains 'thrpt'
87+
updated_files = []
88+
for path in glob.glob(os.path.join(module, '**'), recursive=True):
89+
if os.path.isdir(path):
90+
continue
91+
try:
92+
with open(path, 'r', encoding='utf-8') as f:
93+
content = f.read()
94+
except Exception:
95+
continue
96+
# quick filter
97+
if '<pre>' not in content or 'thrpt' not in content:
98+
continue
99+
100+
original = content
101+
new_content = content
102+
103+
# Regex to find any line-starting Javadoc prefix like " * " before <pre>
104+
# This will match patterns like: " * <pre>... </pre>" and capture the prefix (e.g. " * ")
105+
pattern = re.compile(r'(?m)^(?P<prefix>[ \t]*\*[ \t]*)<pre>[\s\S]*?</pre>')
106+
107+
def repl(m: re.Match) -> str:
108+
prefix = m.group('prefix')
109+
# Build the new block with the same prefix on each line
110+
lines = table.splitlines()
111+
replaced = prefix + '<pre>\n'
112+
for ln in lines:
113+
replaced += prefix + ln.rstrip() + '\n'
114+
replaced += prefix + '</pre>'
115+
return replaced
116+
117+
new_content, nsubs = pattern.subn(repl, content)
118+
if nsubs > 0 and new_content != original:
119+
with open(path, 'w', encoding='utf-8') as f:
120+
f.write(new_content)
121+
updated_files.append(path)
122+
print(f"Updated {path}: replaced {nsubs} <pre> block(s)")
123+
return updated_files
124+
125+
126+
def main(argv: List[str]):
127+
parser = argparse.ArgumentParser()
128+
parser.add_argument('--mvnw', default='./mvnw', help='Path to maven wrapper')
129+
parser.add_argument('--module', default='benchmarks', help='Module directory to build/run')
130+
parser.add_argument('--java', default='java', help='Java command')
131+
parser.add_argument('--jmh-args', default='', help='Extra arguments to pass to the JMH main (e.g. "-f 1 -wi 0 -i 1")')
132+
args = parser.parse_args(argv)
133+
134+
repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
135+
os.chdir(repo_root)
136+
137+
build_benchmarks(args.mvnw, args.module)
138+
jar = find_benchmarks_jar(args.module)
139+
output = run_jmh(jar, args.java, args.jmh_args)
140+
141+
# Print a short preview of the JMH output
142+
preview = '\n'.join(output.splitlines()[:120])
143+
print('\n--- JMH output preview ---')
144+
print(preview)
145+
print('--- end preview ---\n')
146+
147+
table = extract_first_table(output)
148+
149+
updated = update_pre_blocks_under_module(args.module, table)
150+
151+
if not updated:
152+
print('No files were updated (no <pre> blocks with "thrpt" found under the module).')
153+
else:
154+
print('\nUpdated files:')
155+
for p in updated:
156+
print(' -', p)
157+
158+
159+
if __name__ == '__main__':
160+
main(sys.argv[1:])
161+

0 commit comments

Comments
 (0)