blob: 27dd0dd3580ceab1a3686d0bb44c3b867f5fd858 [file] [log] [blame] [edit]
#!/usr/bin/env python
"""Parse microkernel benchmark results into a more readable table.
This script translates google benchmark framework output for microkernel
benchmarks from the following ordering (from google benchmark framework):
for b in benchmark:
for k in microkernels:
k/b (time) (cpu) ...
To a table, where the rows are microkernels, and the columns are the benchmarks.
It sorts the microkernels by the geomean of the ratio of the microkernel
time in each benchmark to the best time for that benchmark, and colors the data
to make it easier to find the outliers.
Usage example:
bench/vunary_bench | tee /dev/stderr | tools/parse-microkernel-bench.py
"""
import collections
import math
import pathlib
import re
import sys
BLACK = "\033[30m"
RED = "\033[31m"
GREEN = "\033[32m"
YELLOW = "\033[33m"
BLUE = "\033[34m"
MAGENTA = "\033[35m"
CYAN = "\033[36m"
WHITE = "\033[37m"
BOLD = "\033[1m"
RESET = "\033[0m" # Reset to default color
MAX_COLS = 10
def color(r):
thresholds = [
(1.01, WHITE),
(1.1, GREEN),
(1.25, YELLOW),
(2, RED),
]
for threshold, color in thresholds:
if r < threshold:
return color
return MAGENTA
def to_seconds(time, units):
if units == "s":
return time
elif units == "ms":
return time / 1e3
elif units == "us":
return time / 1e6
elif units == "ns":
return time / 1e9
else:
raise ValueError("Unknown units: %s" % units)
def seconds_to_string(t):
if t < 1e-7:
s = "%5.1fns" % (t * 1e9)
elif t < 1e-4:
s = "%5.1fus" % (t * 1e6)
elif t < 1e-1:
s = "%5.1fms" % (t * 1e3)
else:
s = "%5.1fs" % t
return s.rjust(5)
def ratio_to_string(t, best):
r = t / best
if r <= 1.01:
s = seconds_to_string(t)
elif r < 100:
s = "%6.2fx" % r
else:
s = " >100x"
return color(r) + s.rjust(5) + RESET
def prod_kernels(ukernels):
"""Returns the set of kernels that are used in production."""
result = set()
try:
configs = (pathlib.Path(__file__).parent.parent / "src/configs").glob("*.c")
for path in configs:
contents = path.read_text()
for i in ukernels:
if i not in result and i in contents:
result.add(i)
except Exception:
pass
return result
# Parse a row of google benchmark output.
r = re.compile(r"(.*ukernel[^/]*)/([\S]*)\s+(\d\S+) (\S+)\s+(\d\S+)\s+(\S+).*")
# Gather the data into a dictionary {ukernel: {benchmark: time_seconds}}
all_ukernels = collections.defaultdict(dict)
for line in sys.stdin:
m = r.match(line)
if m:
ukernel = m.group(1)
benchmark = m.group(2)
time = float(m.group(3))
units = m.group(4)
# Remove BM_ and parameter prefixes from the ukernel, if any.
if ukernel.startswith("BM_"):
ukernel = ukernel[3:]
ukernel = ukernel.split("/")[-1]
all_ukernels[ukernel][benchmark] = to_seconds(time, units)
while all_ukernels:
# Find the kernels that belong in the next table.
ukernel_prefix = next(iter(all_ukernels.keys())).split("ukernel")[0]
ukernels = {
k: v for k, v in all_ukernels.items() if k.startswith(ukernel_prefix)
}
for i in ukernels.keys():
all_ukernels.pop(i)
# Get the set of all benchmarks represented here.
benchmarks = list(
sorted(set.union(*[set(i.keys()) for i in ukernels.values()]))
)
# Make the table layout and header.
ukernel_width = max([len(i) for i in ukernels.keys()]) + 5
table_format = "| {} | " + " | ".join(["{:>7}" for i in benchmarks]) + " |"
print(
table_format.format(
"ukernel".ljust(ukernel_width), *range(len(benchmarks))
)
)
print(table_format.format("-" * ukernel_width, *["-" * 7] * len(benchmarks)))
# Find the best time for each benchmark.
bests = {}
ratios = {}
for i in benchmarks:
# {ukernel: time} for this benchmark
data = {j: ukernels[j].get(i, 0) for j in ukernels.keys()}
best = min(data.values())
bests[i] = best
ratios[i] = {j: data[j] / best for j in ukernels.keys()}
# Get the subset of ukernels that are used in production.
prod = prod_kernels(ukernels.keys())
cols = {i[1]: [i[0]] for i in enumerate(benchmarks)}
# Sort the kernels by the sum of the log of the ratios of the kernel in each
# benchmark (geomean-ish ordering)
for ukernel, data in sorted(
ukernels.items(),
key=lambda x: sum([math.log(ratios[i].get(x[0], 1)) for i in benchmarks]),
):
# Highlight ukernels found in production configs.
if ukernel in prod:
ukernel = ukernel + "*"
ukernel = BOLD + ukernel.ljust(ukernel_width) + RESET
else:
ukernel = ukernel.ljust(ukernel_width)
# Print the row
cols = [ratio_to_string(data.get(i, 0), bests[i]) for i in benchmarks]
print(table_format.format(ukernel, *cols))
# Print the key for the columns.
print()
for i, benchmark in enumerate(benchmarks):
print("{i}. {benchmark}".format(i=i, benchmark=benchmark))
print()
print()