112c66251SAmir Ayupov#!/usr/bin/env python3 212c66251SAmir Ayupovimport argparse 312c66251SAmir Ayupovimport csv 412c66251SAmir Ayupovimport re 512c66251SAmir Ayupovimport sys 612c66251SAmir Ayupovimport os 712c66251SAmir Ayupovfrom statistics import geometric_mean 812c66251SAmir Ayupov 912c66251SAmir AyupovTIMING_LOG_RE = re.compile(r"(.*)/(.*).tmp(.*)") 1012c66251SAmir Ayupov 1112c66251SAmir Ayupov 1212c66251SAmir Ayupovdef main(): 1312c66251SAmir Ayupov parser = argparse.ArgumentParser( 1412c66251SAmir Ayupov description="BOLT NFC stat parser", 1512c66251SAmir Ayupov formatter_class=argparse.ArgumentDefaultsHelpFormatter, 1612c66251SAmir Ayupov ) 1712c66251SAmir Ayupov parser.add_argument( 1812c66251SAmir Ayupov "input", nargs="+", help="timing.log files produced by llvm-bolt-wrapper" 1912c66251SAmir Ayupov ) 2012c66251SAmir Ayupov parser.add_argument( 2112c66251SAmir Ayupov "--check_longer_than", 22*9584f583SAmir Ayupov default=2, 2312c66251SAmir Ayupov type=float, 2412c66251SAmir Ayupov help="Only warn on tests longer than X seconds for at least one side", 2512c66251SAmir Ayupov ) 2612c66251SAmir Ayupov parser.add_argument( 2712c66251SAmir Ayupov "--threshold_single", 2812c66251SAmir Ayupov default=10, 2912c66251SAmir Ayupov type=float, 3012c66251SAmir Ayupov help="Threshold for a single test result swing, abs percent", 3112c66251SAmir Ayupov ), 3212c66251SAmir Ayupov parser.add_argument( 3312c66251SAmir Ayupov "--threshold_agg", 3412c66251SAmir Ayupov default=5, 3512c66251SAmir Ayupov type=float, 3612c66251SAmir Ayupov help="Threshold for geomean test results swing, abs percent", 3712c66251SAmir Ayupov ), 3812c66251SAmir Ayupov parser.add_argument("--verbose", "-v", action="store_true") 3912c66251SAmir Ayupov args = parser.parse_args() 4012c66251SAmir Ayupov 4112c66251SAmir Ayupov def fmt_delta(value, exc_threshold, above_bound=True): 4212c66251SAmir Ayupov formatted_value = format(value, "+.2%") 4312c66251SAmir Ayupov if not above_bound: 4412c66251SAmir Ayupov formatted_value += "?" 4512c66251SAmir Ayupov elif exc_threshold and sys.stdout.isatty(): # terminal supports colors 4612c66251SAmir Ayupov return f"\033[1m{formatted_value}\033[0m" 4712c66251SAmir Ayupov return formatted_value 4812c66251SAmir Ayupov 4912c66251SAmir Ayupov # Ratios for geomean computation 5012c66251SAmir Ayupov time_ratios = [] 5112c66251SAmir Ayupov mem_ratios = [] 5212c66251SAmir Ayupov # Whether any test exceeds the single test threshold (mem or time) 5312c66251SAmir Ayupov threshold_single = False 5412c66251SAmir Ayupov # Whether geomean exceeds aggregate test threshold (mem or time) 5512c66251SAmir Ayupov threshold_agg = False 5612c66251SAmir Ayupov 5712c66251SAmir Ayupov if args.verbose: 5812c66251SAmir Ayupov print(f"# Individual test threshold: +-{args.threshold_single}%") 5912c66251SAmir Ayupov print(f"# Aggregate (geomean) test threshold: +-{args.threshold_agg}%") 6012c66251SAmir Ayupov print( 6112c66251SAmir Ayupov f"# Checking time swings for tests with runtime >" 6212c66251SAmir Ayupov f"{args.check_longer_than}s - otherwise marked as ?" 6312c66251SAmir Ayupov ) 6412c66251SAmir Ayupov print("Test/binary BOLT_wall_time BOLT_max_rss") 6512c66251SAmir Ayupov 6612c66251SAmir Ayupov for input_file in args.input: 6712c66251SAmir Ayupov input_dir = os.path.dirname(input_file) 6812c66251SAmir Ayupov with open(input_file) as timing_file: 6912c66251SAmir Ayupov timing_reader = csv.reader(timing_file, delimiter=";") 7012c66251SAmir Ayupov for row in timing_reader: 7112c66251SAmir Ayupov test_name = row[0] 7212c66251SAmir Ayupov m = TIMING_LOG_RE.match(row[0]) 7312c66251SAmir Ayupov if m: 7412c66251SAmir Ayupov test_name = f"{input_dir}/{m.groups()[1]}/{m.groups()[2]}" 7512c66251SAmir Ayupov else: 7612c66251SAmir Ayupov # Prepend input dir to unparsed test name 7712c66251SAmir Ayupov test_name = input_dir + "#" + test_name 7812c66251SAmir Ayupov time_a, time_b = float(row[1]), float(row[3]) 7912c66251SAmir Ayupov mem_a, mem_b = int(row[2]), int(row[4]) 8012c66251SAmir Ayupov # Check if time is above bound for at least one side 8112c66251SAmir Ayupov time_above_bound = any( 8212c66251SAmir Ayupov [x > args.check_longer_than for x in [time_a, time_b]] 8312c66251SAmir Ayupov ) 8412c66251SAmir Ayupov # Compute B/A ratios (for % delta and geomean) 8512c66251SAmir Ayupov time_ratio = time_b / time_a if time_a else float('nan') 8612c66251SAmir Ayupov mem_ratio = mem_b / mem_a if mem_a else float('nan') 8712c66251SAmir Ayupov # Keep ratios for geomean 8812c66251SAmir Ayupov if time_above_bound and time_ratio > 0: # must be >0 for gmean 8912c66251SAmir Ayupov time_ratios += [time_ratio] 9012c66251SAmir Ayupov mem_ratios += [mem_ratio] 9112c66251SAmir Ayupov # Deltas: (B/A)-1 = (B-A)/A 9212c66251SAmir Ayupov time_delta = time_ratio - 1 9312c66251SAmir Ayupov mem_delta = mem_ratio - 1 9412c66251SAmir Ayupov # Check individual test results vs single test threshold 9512c66251SAmir Ayupov time_exc = ( 9612c66251SAmir Ayupov 100.0 * abs(time_delta) > args.threshold_single and time_above_bound 9712c66251SAmir Ayupov ) 9812c66251SAmir Ayupov mem_exc = 100.0 * abs(mem_delta) > args.threshold_single 9912c66251SAmir Ayupov if time_exc or mem_exc: 10012c66251SAmir Ayupov threshold_single = True 10112c66251SAmir Ayupov # Print deltas with formatting in verbose mode 10212c66251SAmir Ayupov if args.verbose or time_exc or mem_exc: 10312c66251SAmir Ayupov print( 10412c66251SAmir Ayupov test_name, 10512c66251SAmir Ayupov fmt_delta(time_delta, time_exc, time_above_bound), 10612c66251SAmir Ayupov fmt_delta(mem_delta, mem_exc), 10712c66251SAmir Ayupov ) 10812c66251SAmir Ayupov 10912c66251SAmir Ayupov time_gmean_delta = geometric_mean(time_ratios) - 1 11012c66251SAmir Ayupov mem_gmean_delta = geometric_mean(mem_ratios) - 1 11112c66251SAmir Ayupov time_agg_threshold = 100.0 * abs(time_gmean_delta) > args.threshold_agg 11212c66251SAmir Ayupov mem_agg_threshold = 100.0 * abs(mem_gmean_delta) > args.threshold_agg 11312c66251SAmir Ayupov if time_agg_threshold or mem_agg_threshold: 11412c66251SAmir Ayupov threshold_agg = True 11512c66251SAmir Ayupov if time_agg_threshold or mem_agg_threshold or args.verbose: 11612c66251SAmir Ayupov print( 11712c66251SAmir Ayupov "Geomean", 11812c66251SAmir Ayupov fmt_delta(time_gmean_delta, time_agg_threshold), 11912c66251SAmir Ayupov fmt_delta(mem_gmean_delta, mem_agg_threshold), 12012c66251SAmir Ayupov ) 12112c66251SAmir Ayupov exit(threshold_single or threshold_agg) 12212c66251SAmir Ayupov 12312c66251SAmir Ayupov 12412c66251SAmir Ayupovif __name__ == "__main__": 12512c66251SAmir Ayupov main() 126