#!/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()