xref: /llvm-project/bolt/utils/nfc-stat-parser.py (revision 9584f5834499e6093797d4a28fde209f927ea556)
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