1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
|
#!/usr/bin/env python3
import argparse
import git
import json
import pathlib
import sys
import time
from typing import List
VERBOSE: bool = False
TIME_MULTI: dict = {"ns": 1, "us": 1000, "ms": 1000 * 1000, "s": 1000 * 1000 * 1000}
def log(msg: str) -> None:
if VERBOSE:
print(msg)
def warning(msg: str) -> None:
print("\033[1;93m" + msg + "\033[0m")
def error(msg: str) -> None:
print("\033[1;31m" + msg + "\033[0m")
def sort_commits_by_tree_order(repo: git.Repo, commit_hashes: List[str]) -> List[str]:
target_hashes = set(commit_hashes)
ordered_commits = list(
repo.iter_commits(rev="HEAD", topo_order=True, reverse=False)
)
# Build position mapping for target commits
commit_positions = {}
for idx, commit in enumerate(ordered_commits):
commit_hash = commit.hexsha
if commit_hash in target_hashes:
commit_positions[commit_hash] = idx
# Sort input hashes by their topological position
# Handle missing commits by putting them at the end
def sort_key(commit_hash: str) -> int:
return commit_positions.get(commit_hash, len(ordered_commits))
return sorted(commit_hashes, key=sort_key)
def main() -> None:
global VERBOSE
benchmarks = {"commits": [], "results": {}}
commits = []
benchNames = []
parser = argparse.ArgumentParser(
prog="benchreport",
description="Generate an HTML report for bpfilter benchmarks",
)
parser.add_argument(
"-s", "--sources", default=".", help="Sources directory containing .git"
)
parser.add_argument(
"-r", "--results", help="Directory containing the benchmark results"
)
parser.add_argument("-t", "--template", help="HTML report template file")
parser.add_argument("-o", "--output", help="Output HTML file")
parser.add_argument(
"-v", "--verbose", action="store_true", help="Produce a verbose output"
)
args = parser.parse_args()
VERBOSE = args.verbose
files = list(pathlib.Path(args.results).glob("*.json"))
if not files:
warning(f"No benchmark results found in '{args.results}', ignoring")
sys.exit(0)
for file in files:
with open(file, "r", encoding="utf-8") as f:
log(f"Reading benchmark results from {file}")
d = json.load(f)
gitrev = d["context"]["gitrev"]
commits.append(gitrev)
for bench in d["benchmarks"]:
benchNames.append(bench["name"])
if bench["name"] not in benchmarks["results"]:
benchmarks["results"][bench["name"]] = {}
benchmarks["results"][bench["name"]][gitrev] = {
"iters": bench["iterations"],
"time": bench["real_time"] * TIME_MULTI[bench["time_unit"]],
}
nInsn = bench.get("nInsn", None)
if nInsn:
benchmarks["results"][bench["name"]][gitrev]["nInsn"] = nInsn
repo = git.Repo.init(args.sources)
commits = sort_commits_by_tree_order(repo, commits)
for commit in commits:
try:
message = repo.commit(commit).message
date = repo.commit(commit).committed_date
except:
message = "<Not committed yet>"
date = int(time.time())
benchmarks["commits"].append(
{
"sha": commit,
"date": date,
"message": message,
}
)
benchmarks["benchNames"] = list(dict.fromkeys(benchNames))
with open(args.output, "w", encoding="utf-8") as f:
with open(args.template, "r", encoding="utf-8") as template_file:
template = template_file.read()
f.write(template.replace("{{ DATA }}", json.dumps(benchmarks)))
log(f"Benchmark report generated at {args.output}")
if __name__ == "__main__":
main()
|