github.com/aclements/go-misc@v0.0.0-20240129233631-2f6ede80790c/benchscripts/benchstat2 (about) 1 #!/usr/bin/python3 2 3 import os 4 import sys 5 import tempfile 6 import subprocess 7 import argparse 8 import re 9 10 def expandHash(commits, h): 11 x = None 12 for c in commits: 13 if c.startswith(h): 14 if x != None: 15 raise ValueError("ambiguous commit hash " + h) 16 x = c 17 return x 18 19 def main(): 20 parser = argparse.ArgumentParser(description="disentangle benchmark output") 21 parser.add_argument("-C", metavar="gitdir", help="git repo for resolving commit hashes", default=os.path.expanduser("~/go.dev")) 22 parser.add_argument("-o", metavar="base", help="write output to base-commit.log instead of invoking benchstat") 23 parser.add_argument("-benchsave", action="store_true", help="invoke benchsave instead of benchstat") 24 parser.add_argument("-geomean", action="store_true", help="pass -geomean to benchstat") 25 parser.add_argument("-delta-test", help="pass -delta-test to benchstat") 26 parser.add_argument("logs", nargs="+", help="input benchmark log files") 27 parser.add_argument("commits", nargs="*", help="commits to show") 28 args = parser.parse_args() 29 30 benchstat = args.o == None 31 if benchstat: 32 tmpdir = tempfile.TemporaryDirectory() 33 args.o = os.path.join(tmpdir.name, "out") 34 35 # Separate logs and commits arguments 36 for i, arg in enumerate(args.logs): 37 if re.fullmatch("[0-9a-fA-F]{5,}", arg): 38 args.commits = args.logs[i:] 39 args.logs = args.logs[:i] 40 break 41 if arg == "--": 42 args.commits = args.logs[i+1:] 43 args.logs = args.logs[:i] 44 break 45 46 # Process input files into output files 47 fmap = {} 48 logCommits = set() 49 for inp in args.logs: 50 parseInput(inp, args.o, fmap, logCommits) 51 for f, name in fmap.values(): 52 f.close() 53 54 # Get commit order 55 listArgs = [list(logCommits)] 56 if args.commits: 57 # We want to accept revision list arguments, but keep things 58 # in argument order if there's more than one argument. This 59 # means we have to call rev-list separately for each argument. 60 listArgs = [["--no-walk", c] for c in args.commits] 61 commits = [] 62 for listArg in listArgs: 63 commits += subprocess.check_output(["git", "-C", args.C, "rev-list", "--topo-order", "--reverse"] + listArg, universal_newlines=True).splitlines() 64 order = {cid: i for i, cid in enumerate(commits)} 65 66 # Get names in commit order. 67 if args.commits: 68 names = [args.o + "-" + expandHash(commits, h)[:10] + ".log" for h in commits] 69 else: 70 names = [fmap[cid][1] 71 for cid in sorted(fmap.keys(), key=lambda cid: order[cid])] 72 73 if benchstat: 74 # Invoke benchstat/benchsave 75 try: 76 os.chdir(os.path.dirname(args.o)) 77 if args.benchsave: 78 benchargs = ["benchsave"] 79 else: 80 benchargs = ["benchstat"] 81 if args.geomean: 82 benchargs.append("-geomean") 83 if args.delta_test: 84 benchargs.extend(["-delta-test", args.delta_test]) 85 subprocess.check_call(benchargs + list(map(os.path.basename, names)), 86 stdout=sys.stdout, stderr=sys.stderr) 87 finally: 88 # Allow deletion of temporary directory. 89 os.chdir("/") 90 else: 91 print(" ".join(names)) 92 93 def parseInput(path, outbase, fmap, logCommits): 94 infile = open(path) 95 outfile = None 96 97 f = None 98 for l in infile: 99 if l.startswith("commit: "): 100 chash = l.split()[1].strip() 101 logCommits.add(chash) 102 f, name = fmap.get(chash, (None, None)) 103 if f is None: 104 name = outbase + "-" + chash[:10] + ".log" 105 f = open(name, "w") 106 fmap[chash] = (f, name) 107 elif f: 108 f.write(l) 109 110 main()