comparison contrib/revsetbenchmarks.py @ 43076:2372284d9457

formatting: blacken the codebase This is using my patch to black (https://github.com/psf/black/pull/826) so we don't un-wrap collection literals. Done with: hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**"' | xargs black -S # skip-blame mass-reformatting only # no-check-commit reformats foo_bar functions Differential Revision: https://phab.mercurial-scm.org/D6971
author Augie Fackler <augie@google.com>
date Sun, 06 Oct 2019 09:45:02 -0400
parents fbb43514f342
children c102b704edb5
comparison
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
14 import os 14 import os
15 import re 15 import re
16 import subprocess 16 import subprocess
17 import sys 17 import sys
18 18
19 DEFAULTVARIANTS = ['plain', 'min', 'max', 'first', 'last', 19 DEFAULTVARIANTS = [
20 'reverse', 'reverse+first', 'reverse+last', 20 'plain',
21 'sort', 'sort+first', 'sort+last'] 21 'min',
22 'max',
23 'first',
24 'last',
25 'reverse',
26 'reverse+first',
27 'reverse+last',
28 'sort',
29 'sort+first',
30 'sort+last',
31 ]
32
22 33
23 def check_output(*args, **kwargs): 34 def check_output(*args, **kwargs):
24 kwargs.setdefault('stderr', subprocess.PIPE) 35 kwargs.setdefault('stderr', subprocess.PIPE)
25 kwargs.setdefault('stdout', subprocess.PIPE) 36 kwargs.setdefault('stdout', subprocess.PIPE)
26 proc = subprocess.Popen(*args, **kwargs) 37 proc = subprocess.Popen(*args, **kwargs)
27 output, error = proc.communicate() 38 output, error = proc.communicate()
28 if proc.returncode != 0: 39 if proc.returncode != 0:
29 raise subprocess.CalledProcessError(proc.returncode, ' '.join(args[0])) 40 raise subprocess.CalledProcessError(proc.returncode, ' '.join(args[0]))
30 return output 41 return output
31 42
43
32 def update(rev): 44 def update(rev):
33 """update the repo to a revision""" 45 """update the repo to a revision"""
34 try: 46 try:
35 subprocess.check_call(['hg', 'update', '--quiet', '--check', str(rev)]) 47 subprocess.check_call(['hg', 'update', '--quiet', '--check', str(rev)])
36 check_output(['make', 'local'], 48 check_output(
37 stderr=None) # suppress output except for error/warning 49 ['make', 'local'], stderr=None
50 ) # suppress output except for error/warning
38 except subprocess.CalledProcessError as exc: 51 except subprocess.CalledProcessError as exc:
39 print('update to revision %s failed, aborting'%rev, file=sys.stderr) 52 print('update to revision %s failed, aborting' % rev, file=sys.stderr)
40 sys.exit(exc.returncode) 53 sys.exit(exc.returncode)
41 54
42 55
43 def hg(cmd, repo=None): 56 def hg(cmd, repo=None):
44 """run a mercurial command 57 """run a mercurial command
46 <cmd> is the list of command + argument, 59 <cmd> is the list of command + argument,
47 <repo> is an optional repository path to run this command in.""" 60 <repo> is an optional repository path to run this command in."""
48 fullcmd = ['./hg'] 61 fullcmd = ['./hg']
49 if repo is not None: 62 if repo is not None:
50 fullcmd += ['-R', repo] 63 fullcmd += ['-R', repo]
51 fullcmd += ['--config', 64 fullcmd += [
52 'extensions.perf=' + os.path.join(contribdir, 'perf.py')] 65 '--config',
66 'extensions.perf=' + os.path.join(contribdir, 'perf.py'),
67 ]
53 fullcmd += cmd 68 fullcmd += cmd
54 return check_output(fullcmd, stderr=subprocess.STDOUT) 69 return check_output(fullcmd, stderr=subprocess.STDOUT)
70
55 71
56 def perf(revset, target=None, contexts=False): 72 def perf(revset, target=None, contexts=False):
57 """run benchmark for this very revset""" 73 """run benchmark for this very revset"""
58 try: 74 try:
59 args = ['perfrevset'] 75 args = ['perfrevset']
62 args.append('--') 78 args.append('--')
63 args.append(revset) 79 args.append(revset)
64 output = hg(args, repo=target) 80 output = hg(args, repo=target)
65 return parseoutput(output) 81 return parseoutput(output)
66 except subprocess.CalledProcessError as exc: 82 except subprocess.CalledProcessError as exc:
67 print('abort: cannot run revset benchmark: %s'%exc.cmd, file=sys.stderr) 83 print(
68 if getattr(exc, 'output', None) is None: # no output before 2.7 84 'abort: cannot run revset benchmark: %s' % exc.cmd, file=sys.stderr
85 )
86 if getattr(exc, 'output', None) is None: # no output before 2.7
69 print('(no output)', file=sys.stderr) 87 print('(no output)', file=sys.stderr)
70 else: 88 else:
71 print(exc.output, file=sys.stderr) 89 print(exc.output, file=sys.stderr)
72 return None 90 return None
73 91
74 outputre = re.compile(br'! wall (\d+.\d+) comb (\d+.\d+) user (\d+.\d+) ' 92
75 br'sys (\d+.\d+) \(best of (\d+)\)') 93 outputre = re.compile(
94 br'! wall (\d+.\d+) comb (\d+.\d+) user (\d+.\d+) '
95 br'sys (\d+.\d+) \(best of (\d+)\)'
96 )
97
76 98
77 def parseoutput(output): 99 def parseoutput(output):
78 """parse a textual output into a dict 100 """parse a textual output into a dict
79 101
80 We cannot just use json because we want to compare with old 102 We cannot just use json because we want to compare with old
83 match = outputre.search(output) 105 match = outputre.search(output)
84 if not match: 106 if not match:
85 print('abort: invalid output:', file=sys.stderr) 107 print('abort: invalid output:', file=sys.stderr)
86 print(output, file=sys.stderr) 108 print(output, file=sys.stderr)
87 sys.exit(1) 109 sys.exit(1)
88 return {'comb': float(match.group(2)), 110 return {
89 'count': int(match.group(5)), 111 'comb': float(match.group(2)),
90 'sys': float(match.group(3)), 112 'count': int(match.group(5)),
91 'user': float(match.group(4)), 113 'sys': float(match.group(3)),
92 'wall': float(match.group(1)), 114 'user': float(match.group(4)),
93 } 115 'wall': float(match.group(1)),
116 }
117
94 118
95 def printrevision(rev): 119 def printrevision(rev):
96 """print data about a revision""" 120 """print data about a revision"""
97 sys.stdout.write("Revision ") 121 sys.stdout.write("Revision ")
98 sys.stdout.flush() 122 sys.stdout.flush()
99 subprocess.check_call(['hg', 'log', '--rev', str(rev), '--template', 123 subprocess.check_call(
100 '{if(tags, " ({tags})")} ' 124 [
101 '{rev}:{node|short}: {desc|firstline}\n']) 125 'hg',
126 'log',
127 '--rev',
128 str(rev),
129 '--template',
130 '{if(tags, " ({tags})")} ' '{rev}:{node|short}: {desc|firstline}\n',
131 ]
132 )
133
102 134
103 def idxwidth(nbidx): 135 def idxwidth(nbidx):
104 """return the max width of number used for index 136 """return the max width of number used for index
105 137
106 This is similar to log10(nbidx), but we use custom code here 138 This is similar to log10(nbidx), but we use custom code here
107 because we start with zero and we'd rather not deal with all the 139 because we start with zero and we'd rather not deal with all the
108 extra rounding business that log10 would imply. 140 extra rounding business that log10 would imply.
109 """ 141 """
110 nbidx -= 1 # starts at 0 142 nbidx -= 1 # starts at 0
111 idxwidth = 0 143 idxwidth = 0
112 while nbidx: 144 while nbidx:
113 idxwidth += 1 145 idxwidth += 1
114 nbidx //= 10 146 nbidx //= 10
115 if not idxwidth: 147 if not idxwidth:
116 idxwidth = 1 148 idxwidth = 1
117 return idxwidth 149 return idxwidth
118 150
151
119 def getfactor(main, other, field, sensitivity=0.05): 152 def getfactor(main, other, field, sensitivity=0.05):
120 """return the relative factor between values for 'field' in main and other 153 """return the relative factor between values for 'field' in main and other
121 154
122 Return None if the factor is insignificant (less than <sensitivity> 155 Return None if the factor is insignificant (less than <sensitivity>
123 variation).""" 156 variation)."""
124 factor = 1 157 factor = 1
125 if main is not None: 158 if main is not None:
126 factor = other[field] / main[field] 159 factor = other[field] / main[field]
127 low, high = 1 - sensitivity, 1 + sensitivity 160 low, high = 1 - sensitivity, 1 + sensitivity
128 if (low < factor < high): 161 if low < factor < high:
129 return None 162 return None
130 return factor 163 return factor
164
131 165
132 def formatfactor(factor): 166 def formatfactor(factor):
133 """format a factor into a 4 char string 167 """format a factor into a 4 char string
134 168
135 22% 169 22%
153 order = int(math.log(factor)) + 1 187 order = int(math.log(factor)) + 1
154 while math.log(factor) > 1: 188 while math.log(factor) > 1:
155 factor //= 0 189 factor //= 0
156 return 'x%ix%i' % (factor, order) 190 return 'x%ix%i' % (factor, order)
157 191
192
158 def formattiming(value): 193 def formattiming(value):
159 """format a value to strictly 8 char, dropping some precision if needed""" 194 """format a value to strictly 8 char, dropping some precision if needed"""
160 if value < 10**7: 195 if value < 10 ** 7:
161 return ('%.6f' % value)[:8] 196 return ('%.6f' % value)[:8]
162 else: 197 else:
163 # value is HUGE very unlikely to happen (4+ month run) 198 # value is HUGE very unlikely to happen (4+ month run)
164 return '%i' % value 199 return '%i' % value
165 200
201
166 _marker = object() 202 _marker = object()
203
204
167 def printresult(variants, idx, data, maxidx, verbose=False, reference=_marker): 205 def printresult(variants, idx, data, maxidx, verbose=False, reference=_marker):
168 """print a line of result to stdout""" 206 """print a line of result to stdout"""
169 mask = '%%0%ii) %%s' % idxwidth(maxidx) 207 mask = '%%0%ii) %%s' % idxwidth(maxidx)
170 208
171 out = [] 209 out = []
182 out.append(formatfactor(factor)) 220 out.append(formatfactor(factor))
183 if verbose: 221 if verbose:
184 out.append(formattiming(data[var]['comb'])) 222 out.append(formattiming(data[var]['comb']))
185 out.append(formattiming(data[var]['user'])) 223 out.append(formattiming(data[var]['user']))
186 out.append(formattiming(data[var]['sys'])) 224 out.append(formattiming(data[var]['sys']))
187 out.append('%6d' % data[var]['count']) 225 out.append('%6d' % data[var]['count'])
188 print(mask % (idx, ' '.join(out))) 226 print(mask % (idx, ' '.join(out)))
227
189 228
190 def printheader(variants, maxidx, verbose=False, relative=False): 229 def printheader(variants, maxidx, verbose=False, relative=False):
191 header = [' ' * (idxwidth(maxidx) + 1)] 230 header = [' ' * (idxwidth(maxidx) + 1)]
192 for var in variants: 231 for var in variants:
193 if not var: 232 if not var:
202 header.append('%-8s' % 'user') 241 header.append('%-8s' % 'user')
203 header.append('%-8s' % 'sys') 242 header.append('%-8s' % 'sys')
204 header.append('%6s' % 'count') 243 header.append('%6s' % 'count')
205 print(' '.join(header)) 244 print(' '.join(header))
206 245
246
207 def getrevs(spec): 247 def getrevs(spec):
208 """get the list of rev matched by a revset""" 248 """get the list of rev matched by a revset"""
209 try: 249 try:
210 out = check_output(['hg', 'log', '--template={rev}\n', '--rev', spec]) 250 out = check_output(['hg', 'log', '--template={rev}\n', '--rev', spec])
211 except subprocess.CalledProcessError as exc: 251 except subprocess.CalledProcessError as exc:
212 print("abort, can't get revision from %s"%spec, file=sys.stderr) 252 print("abort, can't get revision from %s" % spec, file=sys.stderr)
213 sys.exit(exc.returncode) 253 sys.exit(exc.returncode)
214 return [r for r in out.split() if r] 254 return [r for r in out.split() if r]
215 255
216 256
217 def applyvariants(revset, variant): 257 def applyvariants(revset, variant):
219 return revset 259 return revset
220 for var in variant.split('+'): 260 for var in variant.split('+'):
221 revset = '%s(%s)' % (var, revset) 261 revset = '%s(%s)' % (var, revset)
222 return revset 262 return revset
223 263
224 helptext="""This script will run multiple variants of provided revsets using 264
265 helptext = """This script will run multiple variants of provided revsets using
225 different revisions in your mercurial repository. After the benchmark are run 266 different revisions in your mercurial repository. After the benchmark are run
226 summary output is provided. Use it to demonstrate speed improvements or pin 267 summary output is provided. Use it to demonstrate speed improvements or pin
227 point regressions. Revsets to run are specified in a file (or from stdin), one 268 point regressions. Revsets to run are specified in a file (or from stdin), one
228 revsets per line. Line starting with '#' will be ignored, allowing insertion of 269 revsets per line. Line starting with '#' will be ignored, allowing insertion of
229 comments.""" 270 comments."""
230 parser = optparse.OptionParser(usage="usage: %prog [options] <revs>", 271 parser = optparse.OptionParser(
231 description=helptext) 272 usage="usage: %prog [options] <revs>", description=helptext
232 parser.add_option("-f", "--file", 273 )
233 help="read revset from FILE (stdin if omitted)", 274 parser.add_option(
234 metavar="FILE") 275 "-f",
235 parser.add_option("-R", "--repo", 276 "--file",
236 help="run benchmark on REPO", metavar="REPO") 277 help="read revset from FILE (stdin if omitted)",
237 278 metavar="FILE",
238 parser.add_option("-v", "--verbose", 279 )
239 action='store_true', 280 parser.add_option("-R", "--repo", help="run benchmark on REPO", metavar="REPO")
240 help="display all timing data (not just best total time)") 281
241 282 parser.add_option(
242 parser.add_option("", "--variants", 283 "-v",
243 default=','.join(DEFAULTVARIANTS), 284 "--verbose",
244 help="comma separated list of variant to test " 285 action='store_true',
245 "(eg: plain,min,sorted) (plain = no modification)") 286 help="display all timing data (not just best total time)",
246 parser.add_option('', '--contexts', 287 )
247 action='store_true', 288
248 help='obtain changectx from results instead of integer revs') 289 parser.add_option(
290 "",
291 "--variants",
292 default=','.join(DEFAULTVARIANTS),
293 help="comma separated list of variant to test "
294 "(eg: plain,min,sorted) (plain = no modification)",
295 )
296 parser.add_option(
297 '',
298 '--contexts',
299 action='store_true',
300 help='obtain changectx from results instead of integer revs',
301 )
249 302
250 (options, args) = parser.parse_args() 303 (options, args) = parser.parse_args()
251 304
252 if not args: 305 if not args:
253 parser.print_help() 306 parser.print_help()
292 for var in variants: 345 for var in variants:
293 varrset = applyvariants(rset, var) 346 varrset = applyvariants(rset, var)
294 data = perf(varrset, target=options.repo, contexts=options.contexts) 347 data = perf(varrset, target=options.repo, contexts=options.contexts)
295 varres[var] = data 348 varres[var] = data
296 res.append(varres) 349 res.append(varres)
297 printresult(variants, idx, varres, len(revsets), 350 printresult(
298 verbose=options.verbose) 351 variants, idx, varres, len(revsets), verbose=options.verbose
352 )
299 sys.stdout.flush() 353 sys.stdout.flush()
300 print("----------------------------") 354 print("----------------------------")
301 355
302 356
303 print(""" 357 print(
358 """
304 359
305 Result by revset 360 Result by revset
306 ================ 361 ================
307 """) 362 """
363 )
308 364
309 print('Revision:') 365 print('Revision:')
310 for idx, rev in enumerate(revs): 366 for idx, rev in enumerate(revs):
311 sys.stdout.write('%i) ' % idx) 367 sys.stdout.write('%i) ' % idx)
312 sys.stdout.flush() 368 sys.stdout.flush()
319 375
320 print("revset #%i: %s" % (ridx, rset)) 376 print("revset #%i: %s" % (ridx, rset))
321 printheader(variants, len(results), verbose=options.verbose, relative=True) 377 printheader(variants, len(results), verbose=options.verbose, relative=True)
322 ref = None 378 ref = None
323 for idx, data in enumerate(results): 379 for idx, data in enumerate(results):
324 printresult(variants, idx, data[ridx], len(results), 380 printresult(
325 verbose=options.verbose, reference=ref) 381 variants,
382 idx,
383 data[ridx],
384 len(results),
385 verbose=options.verbose,
386 reference=ref,
387 )
326 ref = data[ridx] 388 ref = data[ridx]
327 print() 389 print()