#!/usr/bin/env python3
import argparse
import csv
import re
import sys
import os
from statistics import geometric_mean
TIMING_LOG_RE = re.compile(r"(.*)/(.*).tmp(.*)")
def main():
parser = argparse.ArgumentParser(
description="BOLT NFC stat parser",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument(
"input", nargs="+", help="timing.log files produced by llvm-bolt-wrapper"
)
parser.add_argument(
"--check_longer_than",
default=2,
type=float,
help="Only warn on tests longer than X seconds for at least one side",
)
parser.add_argument(
"--threshold_single",
default=10,
type=float,
help="Threshold for a single test result swing, abs percent",
),
parser.add_argument(
"--threshold_agg",
default=5,
type=float,
help="Threshold for geomean test results swing, abs percent",
),
parser.add_argument("--verbose", "-v", action="store_true")
args = parser.parse_args()
def fmt_delta(value, exc_threshold, above_bound=True):
formatted_value = format(value, "+.2%")
if not above_bound:
formatted_value += "?"
elif exc_threshold and sys.stdout.isatty(): # terminal supports colors
return f"\033[1m{formatted_value}\033[0m"
return formatted_value
# Ratios for geomean computation
time_ratios = []
mem_ratios = []
# Whether any test exceeds the single test threshold (mem or time)
threshold_single = False
# Whether geomean exceeds aggregate test threshold (mem or time)
threshold_agg = False
if args.verbose:
print(f"# Individual test threshold: +-{args.threshold_single}%")
print(f"# Aggregate (geomean) test threshold: +-{args.threshold_agg}%")
print(
f"# Checking time swings for tests with runtime >"
f"{args.check_longer_than}s - otherwise marked as ?"
)
print("Test/binary BOLT_wall_time BOLT_max_rss")
for input_file in args.input:
input_dir = os.path.dirname(input_file)
with open(input_file) as timing_file:
timing_reader = csv.reader(timing_file, delimiter=";")
for row in timing_reader:
test_name = row[0]
m = TIMING_LOG_RE.match(row[0])
if m:
test_name = f"{input_dir}/{m.groups()[1]}/{m.groups()[2]}"
else:
# Prepend input dir to unparsed test name
test_name = input_dir + "#" + test_name
time_a, time_b = float(row[1]), float(row[3])
mem_a, mem_b = int(row[2]), int(row[4])
# Check if time is above bound for at least one side
time_above_bound = any(
[x > args.check_longer_than for x in [time_a, time_b]]
)
# Compute B/A ratios (for % delta and geomean)
time_ratio = time_b / time_a if time_a else float('nan')
mem_ratio = mem_b / mem_a if mem_a else float('nan')
# Keep ratios for geomean
if time_above_bound and time_ratio > 0: # must be >0 for gmean
time_ratios += [time_ratio]
mem_ratios += [mem_ratio]
# Deltas: (B/A)-1 = (B-A)/A
time_delta = time_ratio - 1
mem_delta = mem_ratio - 1
# Check individual test results vs single test threshold
time_exc = (
100.0 * abs(time_delta) > args.threshold_single and time_above_bound
)
mem_exc = 100.0 * abs(mem_delta) > args.threshold_single
if time_exc or mem_exc:
threshold_single = True
# Print deltas with formatting in verbose mode
if args.verbose or time_exc or mem_exc:
print(
test_name,
fmt_delta(time_delta, time_exc, time_above_bound),
fmt_delta(mem_delta, mem_exc),
)
time_gmean_delta = geometric_mean(time_ratios) - 1
mem_gmean_delta = geometric_mean(mem_ratios) - 1
time_agg_threshold = 100.0 * abs(time_gmean_delta) > args.threshold_agg
mem_agg_threshold = 100.0 * abs(mem_gmean_delta) > args.threshold_agg
if time_agg_threshold or mem_agg_threshold:
threshold_agg = True
if time_agg_threshold or mem_agg_threshold or args.verbose:
print(
"Geomean",
fmt_delta(time_gmean_delta, time_agg_threshold),
fmt_delta(mem_gmean_delta, mem_agg_threshold),
)
exit(threshold_single or threshold_agg)
if __name__ == "__main__":
main()