##// END OF EJS Templates
contrib: revsetbenchmarks use absolute_import and print_function
Pulkit Goyal -
r28564:6d7da090 default
parent child Browse files
Show More
@@ -1,325 +1,334
1 1 #!/usr/bin/env python
2 2
3 3 # Measure the performance of a list of revsets against multiple revisions
4 4 # defined by parameter. Checkout one by one and run perfrevset with every
5 5 # revset in the list to benchmark its performance.
6 6 #
7 7 # You should run this from the root of your mercurial repository.
8 8 #
9 9 # call with --help for details
10 10
11 import sys
11 from __future__ import absolute_import, print_function
12 import math
12 13 import os
13 14 import re
14 import math
15 from subprocess import check_call, Popen, CalledProcessError, STDOUT, PIPE
15 import sys
16 from subprocess import (
17 CalledProcessError,
18 check_call,
19 PIPE,
20 Popen,
21 STDOUT,
22 )
16 23 # cannot use argparse, python 2.7 only
17 from optparse import OptionParser
24 from optparse import (
25 OptionParser,
26 )
18 27
19 28 DEFAULTVARIANTS = ['plain', 'min', 'max', 'first', 'last',
20 29 'reverse', 'reverse+first', 'reverse+last',
21 30 'sort', 'sort+first', 'sort+last']
22 31
23 32 def check_output(*args, **kwargs):
24 33 kwargs.setdefault('stderr', PIPE)
25 34 kwargs.setdefault('stdout', PIPE)
26 35 proc = Popen(*args, **kwargs)
27 36 output, error = proc.communicate()
28 37 if proc.returncode != 0:
29 38 raise CalledProcessError(proc.returncode, ' '.join(args[0]))
30 39 return output
31 40
32 41 def update(rev):
33 42 """update the repo to a revision"""
34 43 try:
35 44 check_call(['hg', 'update', '--quiet', '--check', str(rev)])
36 45 check_output(['make', 'local'],
37 46 stderr=None) # suppress output except for error/warning
38 47 except CalledProcessError as exc:
39 print >> sys.stderr, 'update to revision %s failed, aborting' % rev
48 print('update to revision %s failed, aborting'%rev, file=sys.stderr)
40 49 sys.exit(exc.returncode)
41 50
42 51
43 52 def hg(cmd, repo=None):
44 53 """run a mercurial command
45 54
46 55 <cmd> is the list of command + argument,
47 56 <repo> is an optional repository path to run this command in."""
48 57 fullcmd = ['./hg']
49 58 if repo is not None:
50 59 fullcmd += ['-R', repo]
51 60 fullcmd += ['--config',
52 61 'extensions.perf=' + os.path.join(contribdir, 'perf.py')]
53 62 fullcmd += cmd
54 63 return check_output(fullcmd, stderr=STDOUT)
55 64
56 65 def perf(revset, target=None, contexts=False):
57 66 """run benchmark for this very revset"""
58 67 try:
59 68 args = ['perfrevset', revset]
60 69 if contexts:
61 70 args.append('--contexts')
62 71 output = hg(args, repo=target)
63 72 return parseoutput(output)
64 73 except CalledProcessError as exc:
65 print >> sys.stderr, 'abort: cannot run revset benchmark: %s' % exc.cmd
74 print('abort: cannot run revset benchmark: %s'%exc.cmd, file=sys.stderr)
66 75 if getattr(exc, 'output', None) is None: # no output before 2.7
67 print >> sys.stderr, '(no output)'
76 print('(no output)', file=sys.stderr)
68 77 else:
69 print >> sys.stderr, exc.output
78 print(exc.output, file=sys.stderr)
70 79 return None
71 80
72 81 outputre = re.compile(r'! wall (\d+.\d+) comb (\d+.\d+) user (\d+.\d+) '
73 82 'sys (\d+.\d+) \(best of (\d+)\)')
74 83
75 84 def parseoutput(output):
76 85 """parse a textual output into a dict
77 86
78 87 We cannot just use json because we want to compare with old
79 88 versions of Mercurial that may not support json output.
80 89 """
81 90 match = outputre.search(output)
82 91 if not match:
83 print >> sys.stderr, 'abort: invalid output:'
84 print >> sys.stderr, output
92 print('abort: invalid output:', file=sys.stderr)
93 print(output, file=sys.stderr)
85 94 sys.exit(1)
86 95 return {'comb': float(match.group(2)),
87 96 'count': int(match.group(5)),
88 97 'sys': float(match.group(3)),
89 98 'user': float(match.group(4)),
90 99 'wall': float(match.group(1)),
91 100 }
92 101
93 102 def printrevision(rev):
94 103 """print data about a revision"""
95 104 sys.stdout.write("Revision ")
96 105 sys.stdout.flush()
97 106 check_call(['hg', 'log', '--rev', str(rev), '--template',
98 107 '{if(tags, " ({tags})")} '
99 108 '{rev}:{node|short}: {desc|firstline}\n'])
100 109
101 110 def idxwidth(nbidx):
102 111 """return the max width of number used for index
103 112
104 113 This is similar to log10(nbidx), but we use custom code here
105 114 because we start with zero and we'd rather not deal with all the
106 115 extra rounding business that log10 would imply.
107 116 """
108 117 nbidx -= 1 # starts at 0
109 118 idxwidth = 0
110 119 while nbidx:
111 120 idxwidth += 1
112 121 nbidx //= 10
113 122 if not idxwidth:
114 123 idxwidth = 1
115 124 return idxwidth
116 125
117 126 def getfactor(main, other, field, sensitivity=0.05):
118 127 """return the relative factor between values for 'field' in main and other
119 128
120 129 Return None if the factor is insignificant (less than <sensitivity>
121 130 variation)."""
122 131 factor = 1
123 132 if main is not None:
124 133 factor = other[field] / main[field]
125 134 low, high = 1 - sensitivity, 1 + sensitivity
126 135 if (low < factor < high):
127 136 return None
128 137 return factor
129 138
130 139 def formatfactor(factor):
131 140 """format a factor into a 4 char string
132 141
133 142 22%
134 143 156%
135 144 x2.4
136 145 x23
137 146 x789
138 147 x1e4
139 148 x5x7
140 149
141 150 """
142 151 if factor is None:
143 152 return ' '
144 153 elif factor < 2:
145 154 return '%3i%%' % (factor * 100)
146 155 elif factor < 10:
147 156 return 'x%3.1f' % factor
148 157 elif factor < 1000:
149 158 return '%4s' % ('x%i' % factor)
150 159 else:
151 160 order = int(math.log(factor)) + 1
152 161 while 1 < math.log(factor):
153 162 factor //= 0
154 163 return 'x%ix%i' % (factor, order)
155 164
156 165 def formattiming(value):
157 166 """format a value to strictly 8 char, dropping some precision if needed"""
158 167 if value < 10**7:
159 168 return ('%.6f' % value)[:8]
160 169 else:
161 170 # value is HUGE very unlikely to happen (4+ month run)
162 171 return '%i' % value
163 172
164 173 _marker = object()
165 174 def printresult(variants, idx, data, maxidx, verbose=False, reference=_marker):
166 175 """print a line of result to stdout"""
167 176 mask = '%%0%ii) %%s' % idxwidth(maxidx)
168 177
169 178 out = []
170 179 for var in variants:
171 180 if data[var] is None:
172 181 out.append('error ')
173 182 out.append(' ' * 4)
174 183 continue
175 184 out.append(formattiming(data[var]['wall']))
176 185 if reference is not _marker:
177 186 factor = None
178 187 if reference is not None:
179 188 factor = getfactor(reference[var], data[var], 'wall')
180 189 out.append(formatfactor(factor))
181 190 if verbose:
182 191 out.append(formattiming(data[var]['comb']))
183 192 out.append(formattiming(data[var]['user']))
184 193 out.append(formattiming(data[var]['sys']))
185 194 out.append('%6d' % data[var]['count'])
186 print mask % (idx, ' '.join(out))
195 print(mask % (idx, ' '.join(out)))
187 196
188 197 def printheader(variants, maxidx, verbose=False, relative=False):
189 198 header = [' ' * (idxwidth(maxidx) + 1)]
190 199 for var in variants:
191 200 if not var:
192 201 var = 'iter'
193 202 if 8 < len(var):
194 203 var = var[:3] + '..' + var[-3:]
195 204 header.append('%-8s' % var)
196 205 if relative:
197 206 header.append(' ')
198 207 if verbose:
199 208 header.append('%-8s' % 'comb')
200 209 header.append('%-8s' % 'user')
201 210 header.append('%-8s' % 'sys')
202 211 header.append('%6s' % 'count')
203 print ' '.join(header)
212 print(' '.join(header))
204 213
205 214 def getrevs(spec):
206 215 """get the list of rev matched by a revset"""
207 216 try:
208 217 out = check_output(['hg', 'log', '--template={rev}\n', '--rev', spec])
209 218 except CalledProcessError as exc:
210 print >> sys.stderr, "abort, can't get revision from %s" % spec
219 print("abort, can't get revision from %s"%spec, file=sys.stderr)
211 220 sys.exit(exc.returncode)
212 221 return [r for r in out.split() if r]
213 222
214 223
215 224 def applyvariants(revset, variant):
216 225 if variant == 'plain':
217 226 return revset
218 227 for var in variant.split('+'):
219 228 revset = '%s(%s)' % (var, revset)
220 229 return revset
221 230
222 231 helptext="""This script will run multiple variants of provided revsets using
223 232 different revisions in your mercurial repository. After the benchmark are run
224 233 summary output is provided. Use it to demonstrate speed improvements or pin
225 234 point regressions. Revsets to run are specified in a file (or from stdin), one
226 235 revsets per line. Line starting with '#' will be ignored, allowing insertion of
227 236 comments."""
228 237 parser = OptionParser(usage="usage: %prog [options] <revs>",
229 238 description=helptext)
230 239 parser.add_option("-f", "--file",
231 240 help="read revset from FILE (stdin if omitted)",
232 241 metavar="FILE")
233 242 parser.add_option("-R", "--repo",
234 243 help="run benchmark on REPO", metavar="REPO")
235 244
236 245 parser.add_option("-v", "--verbose",
237 246 action='store_true',
238 247 help="display all timing data (not just best total time)")
239 248
240 249 parser.add_option("", "--variants",
241 250 default=','.join(DEFAULTVARIANTS),
242 251 help="comma separated list of variant to test "
243 252 "(eg: plain,min,sorted) (plain = no modification)")
244 253 parser.add_option('', '--contexts',
245 254 action='store_true',
246 255 help='obtain changectx from results instead of integer revs')
247 256
248 257 (options, args) = parser.parse_args()
249 258
250 259 if not args:
251 260 parser.print_help()
252 261 sys.exit(255)
253 262
254 263 # the directory where both this script and the perf.py extension live.
255 264 contribdir = os.path.dirname(__file__)
256 265
257 266 revsetsfile = sys.stdin
258 267 if options.file:
259 268 revsetsfile = open(options.file)
260 269
261 270 revsets = [l.strip() for l in revsetsfile if not l.startswith('#')]
262 271 revsets = [l for l in revsets if l]
263 272
264 print "Revsets to benchmark"
265 print "----------------------------"
273 print("Revsets to benchmark")
274 print("----------------------------")
266 275
267 276 for idx, rset in enumerate(revsets):
268 print "%i) %s" % (idx, rset)
277 print("%i) %s" % (idx, rset))
269 278
270 print "----------------------------"
271 print
279 print("----------------------------")
280 print()
272 281
273 282 revs = []
274 283 for a in args:
275 284 revs.extend(getrevs(a))
276 285
277 286 variants = options.variants.split(',')
278 287
279 288 results = []
280 289 for r in revs:
281 print "----------------------------"
290 print("----------------------------")
282 291 printrevision(r)
283 print "----------------------------"
292 print("----------------------------")
284 293 update(r)
285 294 res = []
286 295 results.append(res)
287 296 printheader(variants, len(revsets), verbose=options.verbose)
288 297 for idx, rset in enumerate(revsets):
289 298 varres = {}
290 299 for var in variants:
291 300 varrset = applyvariants(rset, var)
292 301 data = perf(varrset, target=options.repo, contexts=options.contexts)
293 302 varres[var] = data
294 303 res.append(varres)
295 304 printresult(variants, idx, varres, len(revsets),
296 305 verbose=options.verbose)
297 306 sys.stdout.flush()
298 print "----------------------------"
307 print("----------------------------")
299 308
300 309
301 print """
310 print("""
302 311
303 312 Result by revset
304 313 ================
305 """
314 """)
306 315
307 print 'Revision:'
316 print('Revision:')
308 317 for idx, rev in enumerate(revs):
309 318 sys.stdout.write('%i) ' % idx)
310 319 sys.stdout.flush()
311 320 printrevision(rev)
312 321
313 print
314 print
322 print()
323 print()
315 324
316 325 for ridx, rset in enumerate(revsets):
317 326
318 print "revset #%i: %s" % (ridx, rset)
327 print("revset #%i: %s" % (ridx, rset))
319 328 printheader(variants, len(results), verbose=options.verbose, relative=True)
320 329 ref = None
321 330 for idx, data in enumerate(results):
322 331 printresult(variants, idx, data[ridx], len(results),
323 332 verbose=options.verbose, reference=ref)
324 333 ref = data[ridx]
325 print
334 print()
@@ -1,122 +1,120
1 1 #require test-repo
2 2
3 3 $ cd "$TESTDIR"/..
4 4
5 5 $ hg files 'set:(**.py)' | sed 's|\\|/|g' | xargs python contrib/check-py3-compat.py
6 6 contrib/import-checker.py not using absolute_import
7 7 contrib/import-checker.py requires print_function
8 contrib/revsetbenchmarks.py not using absolute_import
9 contrib/revsetbenchmarks.py requires print_function
10 8 doc/check-seclevel.py not using absolute_import
11 9 doc/gendoc.py not using absolute_import
12 10 doc/hgmanpage.py not using absolute_import
13 11 hgext/color.py not using absolute_import
14 12 hgext/eol.py not using absolute_import
15 13 hgext/extdiff.py not using absolute_import
16 14 hgext/factotum.py not using absolute_import
17 15 hgext/fetch.py not using absolute_import
18 16 hgext/fsmonitor/pywatchman/__init__.py not using absolute_import
19 17 hgext/fsmonitor/pywatchman/__init__.py requires print_function
20 18 hgext/fsmonitor/pywatchman/capabilities.py not using absolute_import
21 19 hgext/fsmonitor/pywatchman/pybser.py not using absolute_import
22 20 hgext/gpg.py not using absolute_import
23 21 hgext/graphlog.py not using absolute_import
24 22 hgext/hgcia.py not using absolute_import
25 23 hgext/hgk.py not using absolute_import
26 24 hgext/highlight/__init__.py not using absolute_import
27 25 hgext/highlight/highlight.py not using absolute_import
28 26 hgext/histedit.py not using absolute_import
29 27 hgext/largefiles/__init__.py not using absolute_import
30 28 hgext/largefiles/basestore.py not using absolute_import
31 29 hgext/largefiles/lfcommands.py not using absolute_import
32 30 hgext/largefiles/lfutil.py not using absolute_import
33 31 hgext/largefiles/localstore.py not using absolute_import
34 32 hgext/largefiles/overrides.py not using absolute_import
35 33 hgext/largefiles/proto.py not using absolute_import
36 34 hgext/largefiles/remotestore.py not using absolute_import
37 35 hgext/largefiles/reposetup.py not using absolute_import
38 36 hgext/largefiles/uisetup.py not using absolute_import
39 37 hgext/largefiles/wirestore.py not using absolute_import
40 38 hgext/mq.py not using absolute_import
41 39 hgext/rebase.py not using absolute_import
42 40 hgext/share.py not using absolute_import
43 41 hgext/win32text.py not using absolute_import
44 42 i18n/check-translation.py not using absolute_import
45 43 i18n/polib.py not using absolute_import
46 44 setup.py not using absolute_import
47 45 tests/filterpyflakes.py requires print_function
48 46 tests/generate-working-copy-states.py requires print_function
49 47 tests/get-with-headers.py requires print_function
50 48 tests/heredoctest.py requires print_function
51 49 tests/hypothesishelpers.py not using absolute_import
52 50 tests/hypothesishelpers.py requires print_function
53 51 tests/killdaemons.py not using absolute_import
54 52 tests/md5sum.py not using absolute_import
55 53 tests/mockblackbox.py not using absolute_import
56 54 tests/printenv.py not using absolute_import
57 55 tests/readlink.py not using absolute_import
58 56 tests/readlink.py requires print_function
59 57 tests/revlog-formatv0.py not using absolute_import
60 58 tests/run-tests.py not using absolute_import
61 59 tests/seq.py not using absolute_import
62 60 tests/seq.py requires print_function
63 61 tests/silenttestrunner.py not using absolute_import
64 62 tests/silenttestrunner.py requires print_function
65 63 tests/sitecustomize.py not using absolute_import
66 64 tests/svn-safe-append.py not using absolute_import
67 65 tests/svnxml.py not using absolute_import
68 66 tests/test-ancestor.py requires print_function
69 67 tests/test-atomictempfile.py not using absolute_import
70 68 tests/test-batching.py not using absolute_import
71 69 tests/test-batching.py requires print_function
72 70 tests/test-bdiff.py not using absolute_import
73 71 tests/test-bdiff.py requires print_function
74 72 tests/test-context.py not using absolute_import
75 73 tests/test-context.py requires print_function
76 74 tests/test-demandimport.py not using absolute_import
77 75 tests/test-demandimport.py requires print_function
78 76 tests/test-doctest.py not using absolute_import
79 77 tests/test-duplicateoptions.py not using absolute_import
80 78 tests/test-duplicateoptions.py requires print_function
81 79 tests/test-filecache.py not using absolute_import
82 80 tests/test-filecache.py requires print_function
83 81 tests/test-filelog.py not using absolute_import
84 82 tests/test-filelog.py requires print_function
85 83 tests/test-hg-parseurl.py not using absolute_import
86 84 tests/test-hg-parseurl.py requires print_function
87 85 tests/test-hgweb-auth.py not using absolute_import
88 86 tests/test-hgweb-auth.py requires print_function
89 87 tests/test-hgwebdir-paths.py not using absolute_import
90 88 tests/test-hybridencode.py not using absolute_import
91 89 tests/test-hybridencode.py requires print_function
92 90 tests/test-lrucachedict.py not using absolute_import
93 91 tests/test-lrucachedict.py requires print_function
94 92 tests/test-manifest.py not using absolute_import
95 93 tests/test-minirst.py not using absolute_import
96 94 tests/test-minirst.py requires print_function
97 95 tests/test-parseindex2.py not using absolute_import
98 96 tests/test-parseindex2.py requires print_function
99 97 tests/test-pathencode.py not using absolute_import
100 98 tests/test-pathencode.py requires print_function
101 99 tests/test-propertycache.py not using absolute_import
102 100 tests/test-propertycache.py requires print_function
103 101 tests/test-revlog-ancestry.py not using absolute_import
104 102 tests/test-revlog-ancestry.py requires print_function
105 103 tests/test-run-tests.py not using absolute_import
106 104 tests/test-simplemerge.py not using absolute_import
107 105 tests/test-status-inprocess.py not using absolute_import
108 106 tests/test-status-inprocess.py requires print_function
109 107 tests/test-symlink-os-yes-fs-no.py not using absolute_import
110 108 tests/test-trusted.py not using absolute_import
111 109 tests/test-trusted.py requires print_function
112 110 tests/test-ui-color.py not using absolute_import
113 111 tests/test-ui-color.py requires print_function
114 112 tests/test-ui-config.py not using absolute_import
115 113 tests/test-ui-config.py requires print_function
116 114 tests/test-ui-verbosity.py not using absolute_import
117 115 tests/test-ui-verbosity.py requires print_function
118 116 tests/test-url.py not using absolute_import
119 117 tests/test-url.py requires print_function
120 118 tests/test-walkrepo.py requires print_function
121 119 tests/test-wireproto.py requires print_function
122 120 tests/tinyproxy.py requires print_function
General Comments 0
You need to be logged in to leave comments. Login now