Files

121 lines
3.3 KiB
Python
Raw Permalink Normal View History

#!/usr/bin/env python
2024-08-28 16:52:02 +02:00
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "matplotlib",
# "pyqt6",
# ]
# ///
2019-11-25 19:32:45 +01:00
"""This program shows parametrized `hyperfine` benchmark results as an
errorbar plot."""
import argparse
2020-09-16 17:16:07 -07:00
import sys
2019-11-25 19:32:45 +01:00
2024-08-28 17:01:09 +02:00
import matplotlib.pyplot as plt
from plot_utils import METRICS, add_metric_argument, load_results
2019-11-25 19:32:45 +01:00
parser = argparse.ArgumentParser(description=__doc__)
add_metric_argument(parser)
2019-11-25 19:32:45 +01:00
parser.add_argument("file", help="JSON file with benchmark results", nargs="+")
2020-04-01 09:35:06 +02:00
parser.add_argument(
"--parameter-name",
metavar="name",
type=str,
2020-09-16 17:16:07 -07:00
help="Deprecated; parameter names are now inferred from benchmark files",
2020-04-01 09:35:06 +02:00
)
2020-04-13 08:19:58 +02:00
parser.add_argument(
"--log-x", help="Use a logarithmic x (parameter) axis", action="store_true"
)
parser.add_argument(
"--log-y",
"--log-time",
dest="log_y",
help="Use a logarithmic metric axis",
action="store_true",
2020-04-13 08:19:58 +02:00
)
2020-04-13 08:28:29 +02:00
parser.add_argument(
"--titles", help="Comma-separated list of titles for the plot legend"
)
2024-08-28 16:41:58 +02:00
parser.add_argument("-o", "--output", help="Save image to the given filename.")
2020-04-13 08:19:58 +02:00
2019-11-25 19:32:45 +01:00
args = parser.parse_args()
metric, datasets = load_results(parser, args.file, args.metric)
metric_info = METRICS[metric]
2020-09-16 17:16:07 -07:00
if args.parameter_name is not None:
sys.stderr.write(
"warning: --parameter-name is deprecated; names are inferred from "
"benchmark results\n"
)
def die(msg):
2024-08-28 16:41:58 +02:00
sys.stderr.write(f"fatal: {msg}\n")
2020-09-16 17:16:07 -07:00
sys.exit(1)
def extract_parameters(results):
"""Return `(parameter_name: str, parameter_values: List[float])`."""
if not results:
die("no benchmark data to plot")
(names, values) = zip(*(unique_parameter(b) for b in results))
names = frozenset(names)
if len(names) != 1:
die(
2024-08-28 16:41:58 +02:00
f"benchmarks must all have the same parameter name, but found: {sorted(names)}"
2020-09-16 17:16:07 -07:00
)
2020-09-17 11:31:27 -07:00
return (next(iter(names)), list(values))
2020-09-16 17:16:07 -07:00
def unique_parameter(benchmark):
2020-09-17 11:31:27 -07:00
"""Return the unique parameter `(name: str, value: float)`, or die."""
2020-09-16 17:16:07 -07:00
params_dict = benchmark.get("parameters", {})
if not params_dict:
die("benchmarks must have exactly one parameter, but found none")
if len(params_dict) > 1:
die(
2024-08-28 16:41:58 +02:00
f"benchmarks must have exactly one parameter, but found multiple: {sorted(params_dict)}"
2020-09-16 17:16:07 -07:00
)
2020-09-17 11:31:27 -07:00
[(name, value)] = params_dict.items()
return (name, float(value["value"]))
2020-09-16 17:16:07 -07:00
parameter_name = None
2019-11-25 19:32:45 +01:00
for results in datasets:
2020-09-16 17:16:07 -07:00
(this_parameter_name, parameter_values) = extract_parameters(results)
if parameter_name is not None and this_parameter_name != parameter_name:
die(
2024-08-28 16:41:58 +02:00
f"files must all have the same parameter name, but found {parameter_name!r} vs. {this_parameter_name!r}"
2020-09-16 17:16:07 -07:00
)
parameter_name = this_parameter_name
summaries = [b["summary"][metric] for b in results]
means = [s["mean"] / metric_info.scale for s in summaries]
stddevs = [
s["stddev"] / metric_info.scale if s["stddev"] is not None else float("nan")
for s in summaries
]
2019-11-25 19:32:45 +01:00
plt.errorbar(x=parameter_values, y=means, yerr=stddevs, capsize=2)
2019-11-25 19:32:45 +01:00
2020-09-16 17:16:07 -07:00
plt.xlabel(parameter_name)
plt.ylabel(metric_info.axis_label)
2020-04-13 08:19:58 +02:00
if args.log_y:
2020-04-13 08:19:58 +02:00
plt.yscale("log")
else:
plt.ylim(0, None)
if args.log_x:
plt.xscale("log")
2020-04-13 08:28:29 +02:00
if args.titles:
plt.legend(args.titles.split(","))
2021-02-16 08:02:11 +01:00
if args.output:
plt.savefig(args.output)
2020-11-17 09:00:20 +01:00
else:
plt.show()