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