Skip to content

Commit d045764

Browse files
authored
Add markdown_results_table benchcomp visualization (rust-lang#2413)
This commit adds a new visualization that writes all results out to a file as a series of tables, one for each metric. For each metric, each row comprises the benchmark name, followed by the values of the metric for each of the variants. This is an example of the output: ``` ## runtime | Benchmark | variant_1 | variant_2 | | --- | --- |--- | | bench_1 | 5 | 10 | | bench_2 | 10 | 5 | ## success | Benchmark | variant_1 | variant_2 | | --- | --- |--- | | bench_1 | True | True | | bench_2 | True | False | ```
1 parent 6f57d12 commit d045764

File tree

4 files changed

+157
-2
lines changed

4 files changed

+157
-2
lines changed

.github/workflows/kani.yml

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,4 +306,20 @@ jobs:
306306
- name: Run benchcomp
307307
run: |
308308
new/tools/benchcomp/bin/benchcomp \
309-
--config new/tools/benchcomp/configs/perf-regression.yaml
309+
--config new/tools/benchcomp/configs/perf-regression.yaml \
310+
run
311+
new/tools/benchcomp/bin/benchcomp \
312+
--config new/tools/benchcomp/configs/perf-regression.yaml \
313+
collate
314+
315+
- name: Perf Regression Results Table
316+
run: |
317+
new/tools/benchcomp/bin/benchcomp \
318+
--config new/tools/benchcomp/configs/perf-regression.yaml \
319+
visualize --only dump_markdown_results_table >> "$GITHUB_STEP_SUMMARY"
320+
321+
- name: Run other visualizations
322+
run: |
323+
new/tools/benchcomp/bin/benchcomp \
324+
--config new/tools/benchcomp/configs/perf-regression.yaml \
325+
visualize --except dump_markdown_results_table

tools/benchcomp/benchcomp/visualizers/__init__.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33

44

55
import dataclasses
6+
import textwrap
67

8+
import jinja2
79
import yaml
810

911
import benchcomp
@@ -76,3 +78,73 @@ def __call__(self, results):
7678
with self.get_out_file() as handle:
7779
print(
7880
yaml.dump(results, default_flow_style=False), file=handle)
81+
82+
83+
84+
class dump_markdown_results_table:
85+
"""Print a Markdown-formatted table displaying benchmark results
86+
87+
The 'out_file' key is mandatory; specify '-' to print to stdout.
88+
89+
Sample configuration:
90+
91+
visualize:
92+
- type: dump_markdown_results_table
93+
out_file: '-'
94+
"""
95+
96+
97+
def __init__(self, out_file):
98+
self.get_out_file = benchcomp.Outfile(out_file)
99+
100+
101+
@staticmethod
102+
def _get_template():
103+
return textwrap.dedent("""\
104+
{% for metric, benchmarks in d["metrics"].items() %}
105+
## {{ metric }}
106+
107+
| Benchmark | {% for variant in d["variants"] %} {{ variant }} |{% endfor %}
108+
| --- | {% for variant in d["variants"] %}--- |{% endfor -%}
109+
{% for bench_name, bench_variants in benchmarks.items () %}
110+
| {{ bench_name }} {% for variant in d["variants"] -%}
111+
| {{ bench_variants[variant] }} {% endfor %}|
112+
{%- endfor %}
113+
{% endfor -%}
114+
""")
115+
116+
117+
@staticmethod
118+
def _get_variant_names(results):
119+
return results.values()[0]["variants"]
120+
121+
122+
@staticmethod
123+
def _organize_results_into_metrics(results):
124+
ret = {metric: {} for metric in results["metrics"]}
125+
for bench, bench_result in results["benchmarks"].items():
126+
for variant, variant_result in bench_result["variants"].items():
127+
for metric, value in variant_result["metrics"].items():
128+
try:
129+
ret[metric][bench][variant] = variant_result["metrics"][metric]
130+
except KeyError:
131+
ret[metric][bench] = {
132+
variant: variant_result["metrics"][metric]
133+
}
134+
return ret
135+
136+
137+
def __call__(self, results):
138+
data = {
139+
"metrics": self._organize_results_into_metrics(results),
140+
"variants": list(results["benchmarks"].values())[0]["variants"],
141+
}
142+
143+
env = jinja2.Environment(
144+
loader=jinja2.BaseLoader, autoescape=jinja2.select_autoescape(
145+
enabled_extensions=("html"),
146+
default_for_string=True))
147+
template = env.from_string(self._get_template())
148+
output = template.render(d=data)[:-1]
149+
with self.get_out_file() as handle:
150+
print(output, file=handle)

tools/benchcomp/configs/perf-regression.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ run:
2929

3030
visualize:
3131
- type: dump_yaml
32-
out_file: '/tmp/result.yaml'
32+
out_file: '-'
33+
34+
- type: dump_markdown_results_table
35+
out_file: '-'
3336

3437
- type: error_on_regression
3538
variant_pairs: [[kani_old, kani_new]]

tools/benchcomp/test/test_regression.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import pathlib
99
import subprocess
1010
import tempfile
11+
import textwrap
1112
import unittest
1213

1314
import yaml
@@ -391,6 +392,69 @@ def test_error_on_regression_visualization_ratio_regressed(self):
391392
run_bc.proc.returncode, 1, msg=run_bc.stderr)
392393

393394

395+
def test_markdown_results_table(self):
396+
"""Run the markdown results table visualization"""
397+
398+
with tempfile.TemporaryDirectory() as tmp:
399+
run_bc = Benchcomp({
400+
"variants": {
401+
"variant_1": {
402+
"config": {
403+
"directory": str(tmp),
404+
"command_line":
405+
"mkdir bench_1 bench_2"
406+
"&& echo true > bench_1/success"
407+
"&& echo true > bench_2/success"
408+
"&& echo 5 > bench_1/runtime"
409+
"&& echo 10 > bench_2/runtime"
410+
},
411+
},
412+
"variant_2": {
413+
"config": {
414+
"directory": str(tmp),
415+
"command_line":
416+
"mkdir bench_1 bench_2"
417+
"&& echo true > bench_1/success"
418+
"&& echo false > bench_2/success"
419+
"&& echo 10 > bench_1/runtime"
420+
"&& echo 5 > bench_2/runtime"
421+
}
422+
}
423+
},
424+
"run": {
425+
"suites": {
426+
"suite_1": {
427+
"parser": { "module": "test_file_to_metric" },
428+
"variants": ["variant_1", "variant_2"]
429+
}
430+
}
431+
},
432+
"visualize": [{
433+
"type": "dump_markdown_results_table",
434+
"out_file": "-",
435+
}]
436+
})
437+
run_bc()
438+
439+
self.assertEqual(run_bc.proc.returncode, 0, msg=run_bc.stderr)
440+
self.assertEqual(
441+
run_bc.stdout, textwrap.dedent("""
442+
## runtime
443+
444+
| Benchmark | variant_1 | variant_2 |
445+
| --- | --- |--- |
446+
| bench_1 | 5 | 10 |
447+
| bench_2 | 10 | 5 |
448+
449+
## success
450+
451+
| Benchmark | variant_1 | variant_2 |
452+
| --- | --- |--- |
453+
| bench_1 | True | True |
454+
| bench_2 | True | False |
455+
"""))
456+
457+
394458
def test_only_dump_yaml(self):
395459
"""Ensure that benchcomp terminates with return code 0 when `--only dump_yaml` is passed, even if the error_on_regression visualization would have resulted in a return code of 1"""
396460

0 commit comments

Comments
 (0)