##// END OF EJS Templates
revsetbenchmarks: use combination variants in default set...
Pierre-Yves David -
r25544:e66f1707 default
parent child Browse files
Show More
@@ -1,302 +1,304
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 11 import sys
12 12 import os
13 13 import re
14 14 import math
15 15 from subprocess import check_call, Popen, CalledProcessError, STDOUT, PIPE
16 16 # cannot use argparse, python 2.7 only
17 17 from optparse import OptionParser
18 18
19 DEFAULTVARIANTS = ['plain', 'min', 'max', 'first', 'last', 'reverse', 'sort']
19 DEFAULTVARIANTS = ['plain', 'min', 'max', 'first', 'last',
20 'reverse', 'reverse+first', 'reverse+last',
21 'sort', 'sort+first', 'sort+last']
20 22
21 23 def check_output(*args, **kwargs):
22 24 kwargs.setdefault('stderr', PIPE)
23 25 kwargs.setdefault('stdout', PIPE)
24 26 proc = Popen(*args, **kwargs)
25 27 output, error = proc.communicate()
26 28 if proc.returncode != 0:
27 29 raise CalledProcessError(proc.returncode, ' '.join(args[0]))
28 30 return output
29 31
30 32 def update(rev):
31 33 """update the repo to a revision"""
32 34 try:
33 35 check_call(['hg', 'update', '--quiet', '--check', str(rev)])
34 36 except CalledProcessError, exc:
35 37 print >> sys.stderr, 'update to revision %s failed, aborting' % rev
36 38 sys.exit(exc.returncode)
37 39
38 40
39 41 def hg(cmd, repo=None):
40 42 """run a mercurial command
41 43
42 44 <cmd> is the list of command + argument,
43 45 <repo> is an optional repository path to run this command in."""
44 46 fullcmd = ['./hg']
45 47 if repo is not None:
46 48 fullcmd += ['-R', repo]
47 49 fullcmd += ['--config',
48 50 'extensions.perf=' + os.path.join(contribdir, 'perf.py')]
49 51 fullcmd += cmd
50 52 return check_output(fullcmd, stderr=STDOUT)
51 53
52 54 def perf(revset, target=None):
53 55 """run benchmark for this very revset"""
54 56 try:
55 57 output = hg(['perfrevset', revset], repo=target)
56 58 return parseoutput(output)
57 59 except CalledProcessError, exc:
58 60 print >> sys.stderr, 'abort: cannot run revset benchmark: %s' % exc.cmd
59 61 if exc.output is None:
60 62 print >> sys.stderr, '(no ouput)'
61 63 else:
62 64 print >> sys.stderr, exc.output
63 65 sys.exit(exc.returncode)
64 66
65 67 outputre = re.compile(r'! wall (\d+.\d+) comb (\d+.\d+) user (\d+.\d+) '
66 68 'sys (\d+.\d+) \(best of (\d+)\)')
67 69
68 70 def parseoutput(output):
69 71 """parse a textual output into a dict
70 72
71 73 We cannot just use json because we want to compare with old
72 74 versions of Mercurial that may not support json output.
73 75 """
74 76 match = outputre.search(output)
75 77 if not match:
76 78 print >> sys.stderr, 'abort: invalid output:'
77 79 print >> sys.stderr, output
78 80 sys.exit(1)
79 81 return {'comb': float(match.group(2)),
80 82 'count': int(match.group(5)),
81 83 'sys': float(match.group(3)),
82 84 'user': float(match.group(4)),
83 85 'wall': float(match.group(1)),
84 86 }
85 87
86 88 def printrevision(rev):
87 89 """print data about a revision"""
88 90 sys.stdout.write("Revision ")
89 91 sys.stdout.flush()
90 92 check_call(['hg', 'log', '--rev', str(rev), '--template',
91 93 '{rev}:{node|short}: {desc|firstline}\n'])
92 94
93 95 def idxwidth(nbidx):
94 96 """return the max width of number used for index
95 97
96 98 This is similar to log10(nbidx), but we use custom code here
97 99 because we start with zero and we'd rather not deal with all the
98 100 extra rounding business that log10 would imply.
99 101 """
100 102 nbidx -= 1 # starts at 0
101 103 idxwidth = 0
102 104 while nbidx:
103 105 idxwidth += 1
104 106 nbidx //= 10
105 107 if not idxwidth:
106 108 idxwidth = 1
107 109 return idxwidth
108 110
109 111 def getfactor(main, other, field, sensitivity=0.05):
110 112 """return the relative factor between values for 'field' in main and other
111 113
112 114 Return None if the factor is insignicant (less than <sensitivity>
113 115 variation)."""
114 116 factor = 1
115 117 if main is not None:
116 118 factor = other[field] / main[field]
117 119 low, high = 1 - sensitivity, 1 + sensitivity
118 120 if (low < factor < high):
119 121 return None
120 122 return factor
121 123
122 124 def formatfactor(factor):
123 125 """format a factor into a 4 char string
124 126
125 127 22%
126 128 156%
127 129 x2.4
128 130 x23
129 131 x789
130 132 x1e4
131 133 x5x7
132 134
133 135 """
134 136 if factor is None:
135 137 return ' '
136 138 elif factor < 2:
137 139 return '%3i%%' % (factor * 100)
138 140 elif factor < 10:
139 141 return 'x%3.1f' % factor
140 142 elif factor < 1000:
141 143 return '%4s' % ('x%i' % factor)
142 144 else:
143 145 order = int(math.log(factor)) + 1
144 146 while 1 < math.log(factor):
145 147 factor //= 0
146 148 return 'x%ix%i' % (factor, order)
147 149
148 150 def formattiming(value):
149 151 """format a value to strictly 8 char, dropping some precision if needed"""
150 152 if value < 10**7:
151 153 return ('%.6f' % value)[:8]
152 154 else:
153 155 # value is HUGE very unlikely to happen (4+ month run)
154 156 return '%i' % value
155 157
156 158 _marker = object()
157 159 def printresult(variants, idx, data, maxidx, verbose=False, reference=_marker):
158 160 """print a line of result to stdout"""
159 161 mask = '%%0%ii) %%s' % idxwidth(maxidx)
160 162 out = []
161 163 for var in variants:
162 164 out.append(formattiming(data[var]['wall']))
163 165 if reference is not _marker:
164 166 factor = None
165 167 if reference is not None:
166 168 factor = getfactor(reference[var], data[var], 'wall')
167 169 out.append(formatfactor(factor))
168 170 if verbose:
169 171 out.append(formattiming(data[var]['comb']))
170 172 out.append(formattiming(data[var]['user']))
171 173 out.append(formattiming(data[var]['sys']))
172 174 out.append('%6d' % data[var]['count'])
173 175 print mask % (idx, ' '.join(out))
174 176
175 177 def printheader(variants, maxidx, verbose=False, relative=False):
176 178 header = [' ' * (idxwidth(maxidx) + 1)]
177 179 for var in variants:
178 180 if not var:
179 181 var = 'iter'
180 182 if 8 < len(var):
181 183 var = var[:3] + '..' + var[-3:]
182 184 header.append('%-8s' % var)
183 185 if relative:
184 186 header.append(' ')
185 187 if verbose:
186 188 header.append('%-8s' % 'comb')
187 189 header.append('%-8s' % 'user')
188 190 header.append('%-8s' % 'sys')
189 191 header.append('%6s' % 'count')
190 192 print ' '.join(header)
191 193
192 194 def getrevs(spec):
193 195 """get the list of rev matched by a revset"""
194 196 try:
195 197 out = check_output(['hg', 'log', '--template={rev}\n', '--rev', spec])
196 198 except CalledProcessError, exc:
197 199 print >> sys.stderr, "abort, can't get revision from %s" % spec
198 200 sys.exit(exc.returncode)
199 201 return [r for r in out.split() if r]
200 202
201 203
202 204 def applyvariants(revset, variant):
203 205 if variant == 'plain':
204 206 return revset
205 207 for var in variant.split('+'):
206 208 revset = '%s(%s)' % (var, revset)
207 209 return revset
208 210
209 211
210 212 parser = OptionParser(usage="usage: %prog [options] <revs>")
211 213 parser.add_option("-f", "--file",
212 214 help="read revset from FILE (stdin if omitted)",
213 215 metavar="FILE")
214 216 parser.add_option("-R", "--repo",
215 217 help="run benchmark on REPO", metavar="REPO")
216 218
217 219 parser.add_option("-v", "--verbose",
218 220 action='store_true',
219 221 help="display all timing data (not just best total time)")
220 222
221 223 parser.add_option("", "--variants",
222 224 default=','.join(DEFAULTVARIANTS),
223 225 help="comma separated list of variant to test "
224 226 "(eg: plain,min,sorted) (plain = no modification)")
225 227
226 228 (options, args) = parser.parse_args()
227 229
228 230 if not args:
229 231 parser.print_help()
230 232 sys.exit(255)
231 233
232 234 # the directory where both this script and the perf.py extension live.
233 235 contribdir = os.path.dirname(__file__)
234 236
235 237 revsetsfile = sys.stdin
236 238 if options.file:
237 239 revsetsfile = open(options.file)
238 240
239 241 revsets = [l.strip() for l in revsetsfile if not l.startswith('#')]
240 242
241 243 print "Revsets to benchmark"
242 244 print "----------------------------"
243 245
244 246 for idx, rset in enumerate(revsets):
245 247 print "%i) %s" % (idx, rset)
246 248
247 249 print "----------------------------"
248 250 print
249 251
250 252 revs = []
251 253 for a in args:
252 254 revs.extend(getrevs(a))
253 255
254 256 variants = options.variants.split(',')
255 257
256 258 results = []
257 259 for r in revs:
258 260 print "----------------------------"
259 261 printrevision(r)
260 262 print "----------------------------"
261 263 update(r)
262 264 res = []
263 265 results.append(res)
264 266 printheader(variants, len(revsets), verbose=options.verbose)
265 267 for idx, rset in enumerate(revsets):
266 268 varres = {}
267 269 for var in variants:
268 270 varrset = applyvariants(rset, var)
269 271 data = perf(varrset, target=options.repo)
270 272 varres[var] = data
271 273 res.append(varres)
272 274 printresult(variants, idx, varres, len(revsets),
273 275 verbose=options.verbose)
274 276 sys.stdout.flush()
275 277 print "----------------------------"
276 278
277 279
278 280 print """
279 281
280 282 Result by revset
281 283 ================
282 284 """
283 285
284 286 print 'Revision:'
285 287 for idx, rev in enumerate(revs):
286 288 sys.stdout.write('%i) ' % idx)
287 289 sys.stdout.flush()
288 290 printrevision(rev)
289 291
290 292 print
291 293 print
292 294
293 295 for ridx, rset in enumerate(revsets):
294 296
295 297 print "revset #%i: %s" % (ridx, rset)
296 298 printheader(variants, len(results), verbose=options.verbose, relative=True)
297 299 ref = None
298 300 for idx, data in enumerate(results):
299 301 printresult(variants, idx, data[ridx], len(results),
300 302 verbose=options.verbose, reference=ref)
301 303 ref = data[ridx]
302 304 print
General Comments 0
You need to be logged in to leave comments. Login now