##// END OF EJS Templates
perf: introduce safeattrsetter to replace direct attribute assignment...
FUJIWARA Katsunori -
r30143:2d858c77 default
parent child Browse files
Show More
@@ -1,944 +1,978 b''
1 1 # perf.py - performance test routines
2 2 '''helper extension to measure performance'''
3 3
4 4 # "historical portability" policy of perf.py:
5 5 #
6 6 # We have to do:
7 7 # - make perf.py "loadable" with as wide Mercurial version as possible
8 8 # This doesn't mean that perf commands work correctly with that Mercurial.
9 9 # BTW, perf.py itself has been available since 1.1 (or eb240755386d).
10 10 # - make historical perf command work correctly with as wide Mercurial
11 11 # version as possible
12 12 #
13 13 # We have to do, if possible with reasonable cost:
14 14 # - make recent perf command for historical feature work correctly
15 15 # with early Mercurial
16 16 #
17 17 # We don't have to do:
18 18 # - make perf command for recent feature work correctly with early
19 19 # Mercurial
20 20
21 21 from __future__ import absolute_import
22 22 import functools
23 23 import os
24 24 import random
25 25 import sys
26 26 import time
27 27 from mercurial import (
28 28 changegroup,
29 29 cmdutil,
30 30 commands,
31 31 copies,
32 32 error,
33 33 extensions,
34 34 mdiff,
35 35 merge,
36 36 revlog,
37 37 util,
38 38 )
39 39
40 40 # for "historical portability":
41 41 # try to import modules separately (in dict order), and ignore
42 42 # failure, because these aren't available with early Mercurial
43 43 try:
44 44 from mercurial import branchmap # since 2.5 (or bcee63733aad)
45 45 except ImportError:
46 46 pass
47 47 try:
48 48 from mercurial import obsolete # since 2.3 (or ad0d6c2b3279)
49 49 except ImportError:
50 50 pass
51 51 try:
52 52 from mercurial import repoview # since 2.5 (or 3a6ddacb7198)
53 53 except ImportError:
54 54 pass
55 55 try:
56 56 from mercurial import scmutil # since 1.9 (or 8b252e826c68)
57 57 except ImportError:
58 58 pass
59 59
60 60 # for "historical portability":
61 61 # define util.safehasattr forcibly, because util.safehasattr has been
62 62 # available since 1.9.3 (or 94b200a11cf7)
63 63 _undefined = object()
64 64 def safehasattr(thing, attr):
65 65 return getattr(thing, attr, _undefined) is not _undefined
66 66 setattr(util, 'safehasattr', safehasattr)
67 67
68 68 # for "historical portability":
69 69 # use locally defined empty option list, if formatteropts isn't
70 70 # available, because commands.formatteropts has been available since
71 71 # 3.2 (or 7a7eed5176a4), even though formatting itself has been
72 72 # available since 2.2 (or ae5f92e154d3)
73 73 formatteropts = getattr(commands, "formatteropts", [])
74 74
75 75 # for "historical portability":
76 76 # use locally defined option list, if debugrevlogopts isn't available,
77 77 # because commands.debugrevlogopts has been available since 3.7 (or
78 78 # 5606f7d0d063), even though cmdutil.openrevlog() has been available
79 79 # since 1.9 (or a79fea6b3e77).
80 80 revlogopts = getattr(commands, "debugrevlogopts", [
81 81 ('c', 'changelog', False, ('open changelog')),
82 82 ('m', 'manifest', False, ('open manifest')),
83 83 ('', 'dir', False, ('open directory manifest')),
84 84 ])
85 85
86 86 cmdtable = {}
87 87
88 88 # for "historical portability":
89 89 # define parsealiases locally, because cmdutil.parsealiases has been
90 90 # available since 1.5 (or 6252852b4332)
91 91 def parsealiases(cmd):
92 92 return cmd.lstrip("^").split("|")
93 93
94 94 if safehasattr(cmdutil, 'command'):
95 95 import inspect
96 96 command = cmdutil.command(cmdtable)
97 97 if 'norepo' not in inspect.getargspec(command)[0]:
98 98 # for "historical portability":
99 99 # wrap original cmdutil.command, because "norepo" option has
100 100 # been available since 3.1 (or 75a96326cecb)
101 101 _command = command
102 102 def command(name, options=(), synopsis=None, norepo=False):
103 103 if norepo:
104 104 commands.norepo += ' %s' % ' '.join(parsealiases(name))
105 105 return _command(name, list(options), synopsis)
106 106 else:
107 107 # for "historical portability":
108 108 # define "@command" annotation locally, because cmdutil.command
109 109 # has been available since 1.9 (or 2daa5179e73f)
110 110 def command(name, options=(), synopsis=None, norepo=False):
111 111 def decorator(func):
112 112 if synopsis:
113 113 cmdtable[name] = func, list(options), synopsis
114 114 else:
115 115 cmdtable[name] = func, list(options)
116 116 if norepo:
117 117 commands.norepo += ' %s' % ' '.join(parsealiases(name))
118 118 return func
119 119 return decorator
120 120
121 121 def getlen(ui):
122 122 if ui.configbool("perf", "stub"):
123 123 return lambda x: 1
124 124 return len
125 125
126 126 def gettimer(ui, opts=None):
127 127 """return a timer function and formatter: (timer, formatter)
128 128
129 129 This function exists to gather the creation of formatter in a single
130 130 place instead of duplicating it in all performance commands."""
131 131
132 132 # enforce an idle period before execution to counteract power management
133 133 # experimental config: perf.presleep
134 134 time.sleep(ui.configint("perf", "presleep", 1))
135 135
136 136 if opts is None:
137 137 opts = {}
138 138 # redirect all to stderr
139 139 ui = ui.copy()
140 140 ui.fout = ui.ferr
141 141 # get a formatter
142 142 fm = ui.formatter('perf', opts)
143 143 # stub function, runs code only once instead of in a loop
144 144 # experimental config: perf.stub
145 145 if ui.configbool("perf", "stub"):
146 146 return functools.partial(stub_timer, fm), fm
147 147 return functools.partial(_timer, fm), fm
148 148
149 149 def stub_timer(fm, func, title=None):
150 150 func()
151 151
152 152 def _timer(fm, func, title=None):
153 153 results = []
154 154 begin = time.time()
155 155 count = 0
156 156 while True:
157 157 ostart = os.times()
158 158 cstart = time.time()
159 159 r = func()
160 160 cstop = time.time()
161 161 ostop = os.times()
162 162 count += 1
163 163 a, b = ostart, ostop
164 164 results.append((cstop - cstart, b[0] - a[0], b[1]-a[1]))
165 165 if cstop - begin > 3 and count >= 100:
166 166 break
167 167 if cstop - begin > 10 and count >= 3:
168 168 break
169 169
170 170 fm.startitem()
171 171
172 172 if title:
173 173 fm.write('title', '! %s\n', title)
174 174 if r:
175 175 fm.write('result', '! result: %s\n', r)
176 176 m = min(results)
177 177 fm.plain('!')
178 178 fm.write('wall', ' wall %f', m[0])
179 179 fm.write('comb', ' comb %f', m[1] + m[2])
180 180 fm.write('user', ' user %f', m[1])
181 181 fm.write('sys', ' sys %f', m[2])
182 182 fm.write('count', ' (best of %d)', count)
183 183 fm.plain('\n')
184 184
185 # utilities for historical portability
186
187 def safeattrsetter(obj, name, ignoremissing=False):
188 """Ensure that 'obj' has 'name' attribute before subsequent setattr
189
190 This function is aborted, if 'obj' doesn't have 'name' attribute
191 at runtime. This avoids overlooking removal of an attribute, which
192 breaks assumption of performance measurement, in the future.
193
194 This function returns the object to (1) assign a new value, and
195 (2) restore an original value to the attribute.
196
197 If 'ignoremissing' is true, missing 'name' attribute doesn't cause
198 abortion, and this function returns None. This is useful to
199 examine an attribute, which isn't ensured in all Mercurial
200 versions.
201 """
202 if not util.safehasattr(obj, name):
203 if ignoremissing:
204 return None
205 raise error.Abort(("missing attribute %s of %s might break assumption"
206 " of performance measurement") % (name, obj))
207
208 origvalue = getattr(obj, name)
209 class attrutil(object):
210 def set(self, newvalue):
211 setattr(obj, name, newvalue)
212 def restore(self):
213 setattr(obj, name, origvalue)
214
215 return attrutil()
216
217 # perf commands
218
185 219 @command('perfwalk', formatteropts)
186 220 def perfwalk(ui, repo, *pats, **opts):
187 221 timer, fm = gettimer(ui, opts)
188 222 try:
189 223 m = scmutil.match(repo[None], pats, {})
190 224 timer(lambda: len(list(repo.dirstate.walk(m, [], True, False))))
191 225 except Exception:
192 226 try:
193 227 m = scmutil.match(repo[None], pats, {})
194 228 timer(lambda: len([b for a, b, c in repo.dirstate.statwalk([], m)]))
195 229 except Exception:
196 230 timer(lambda: len(list(cmdutil.walk(repo, pats, {}))))
197 231 fm.end()
198 232
199 233 @command('perfannotate', formatteropts)
200 234 def perfannotate(ui, repo, f, **opts):
201 235 timer, fm = gettimer(ui, opts)
202 236 fc = repo['.'][f]
203 237 timer(lambda: len(fc.annotate(True)))
204 238 fm.end()
205 239
206 240 @command('perfstatus',
207 241 [('u', 'unknown', False,
208 242 'ask status to look for unknown files')] + formatteropts)
209 243 def perfstatus(ui, repo, **opts):
210 244 #m = match.always(repo.root, repo.getcwd())
211 245 #timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
212 246 # False))))
213 247 timer, fm = gettimer(ui, opts)
214 248 timer(lambda: sum(map(len, repo.status(unknown=opts['unknown']))))
215 249 fm.end()
216 250
217 251 @command('perfaddremove', formatteropts)
218 252 def perfaddremove(ui, repo, **opts):
219 253 timer, fm = gettimer(ui, opts)
220 254 try:
221 255 oldquiet = repo.ui.quiet
222 256 repo.ui.quiet = True
223 257 matcher = scmutil.match(repo[None])
224 258 timer(lambda: scmutil.addremove(repo, matcher, "", dry_run=True))
225 259 finally:
226 260 repo.ui.quiet = oldquiet
227 261 fm.end()
228 262
229 263 def clearcaches(cl):
230 264 # behave somewhat consistently across internal API changes
231 265 if util.safehasattr(cl, 'clearcaches'):
232 266 cl.clearcaches()
233 267 elif util.safehasattr(cl, '_nodecache'):
234 268 from mercurial.node import nullid, nullrev
235 269 cl._nodecache = {nullid: nullrev}
236 270 cl._nodepos = None
237 271
238 272 @command('perfheads', formatteropts)
239 273 def perfheads(ui, repo, **opts):
240 274 timer, fm = gettimer(ui, opts)
241 275 cl = repo.changelog
242 276 def d():
243 277 len(cl.headrevs())
244 278 clearcaches(cl)
245 279 timer(d)
246 280 fm.end()
247 281
248 282 @command('perftags', formatteropts)
249 283 def perftags(ui, repo, **opts):
250 284 import mercurial.changelog
251 285 import mercurial.manifest
252 286 timer, fm = gettimer(ui, opts)
253 287 def t():
254 288 repo.changelog = mercurial.changelog.changelog(repo.svfs)
255 289 repo.manifest = mercurial.manifest.manifest(repo.svfs)
256 290 repo._tags = None
257 291 return len(repo.tags())
258 292 timer(t)
259 293 fm.end()
260 294
261 295 @command('perfancestors', formatteropts)
262 296 def perfancestors(ui, repo, **opts):
263 297 timer, fm = gettimer(ui, opts)
264 298 heads = repo.changelog.headrevs()
265 299 def d():
266 300 for a in repo.changelog.ancestors(heads):
267 301 pass
268 302 timer(d)
269 303 fm.end()
270 304
271 305 @command('perfancestorset', formatteropts)
272 306 def perfancestorset(ui, repo, revset, **opts):
273 307 timer, fm = gettimer(ui, opts)
274 308 revs = repo.revs(revset)
275 309 heads = repo.changelog.headrevs()
276 310 def d():
277 311 s = repo.changelog.ancestors(heads)
278 312 for rev in revs:
279 313 rev in s
280 314 timer(d)
281 315 fm.end()
282 316
283 317 @command('perfchangegroupchangelog', formatteropts +
284 318 [('', 'version', '02', 'changegroup version'),
285 319 ('r', 'rev', '', 'revisions to add to changegroup')])
286 320 def perfchangegroupchangelog(ui, repo, version='02', rev=None, **opts):
287 321 """Benchmark producing a changelog group for a changegroup.
288 322
289 323 This measures the time spent processing the changelog during a
290 324 bundle operation. This occurs during `hg bundle` and on a server
291 325 processing a `getbundle` wire protocol request (handles clones
292 326 and pull requests).
293 327
294 328 By default, all revisions are added to the changegroup.
295 329 """
296 330 cl = repo.changelog
297 331 revs = [cl.lookup(r) for r in repo.revs(rev or 'all()')]
298 332 bundler = changegroup.getbundler(version, repo)
299 333
300 334 def lookup(node):
301 335 # The real bundler reads the revision in order to access the
302 336 # manifest node and files list. Do that here.
303 337 cl.read(node)
304 338 return node
305 339
306 340 def d():
307 341 for chunk in bundler.group(revs, cl, lookup):
308 342 pass
309 343
310 344 timer, fm = gettimer(ui, opts)
311 345 timer(d)
312 346 fm.end()
313 347
314 348 @command('perfdirs', formatteropts)
315 349 def perfdirs(ui, repo, **opts):
316 350 timer, fm = gettimer(ui, opts)
317 351 dirstate = repo.dirstate
318 352 'a' in dirstate
319 353 def d():
320 354 dirstate.dirs()
321 355 del dirstate._dirs
322 356 timer(d)
323 357 fm.end()
324 358
325 359 @command('perfdirstate', formatteropts)
326 360 def perfdirstate(ui, repo, **opts):
327 361 timer, fm = gettimer(ui, opts)
328 362 "a" in repo.dirstate
329 363 def d():
330 364 repo.dirstate.invalidate()
331 365 "a" in repo.dirstate
332 366 timer(d)
333 367 fm.end()
334 368
335 369 @command('perfdirstatedirs', formatteropts)
336 370 def perfdirstatedirs(ui, repo, **opts):
337 371 timer, fm = gettimer(ui, opts)
338 372 "a" in repo.dirstate
339 373 def d():
340 374 "a" in repo.dirstate._dirs
341 375 del repo.dirstate._dirs
342 376 timer(d)
343 377 fm.end()
344 378
345 379 @command('perfdirstatefoldmap', formatteropts)
346 380 def perfdirstatefoldmap(ui, repo, **opts):
347 381 timer, fm = gettimer(ui, opts)
348 382 dirstate = repo.dirstate
349 383 'a' in dirstate
350 384 def d():
351 385 dirstate._filefoldmap.get('a')
352 386 del dirstate._filefoldmap
353 387 timer(d)
354 388 fm.end()
355 389
356 390 @command('perfdirfoldmap', formatteropts)
357 391 def perfdirfoldmap(ui, repo, **opts):
358 392 timer, fm = gettimer(ui, opts)
359 393 dirstate = repo.dirstate
360 394 'a' in dirstate
361 395 def d():
362 396 dirstate._dirfoldmap.get('a')
363 397 del dirstate._dirfoldmap
364 398 del dirstate._dirs
365 399 timer(d)
366 400 fm.end()
367 401
368 402 @command('perfdirstatewrite', formatteropts)
369 403 def perfdirstatewrite(ui, repo, **opts):
370 404 timer, fm = gettimer(ui, opts)
371 405 ds = repo.dirstate
372 406 "a" in ds
373 407 def d():
374 408 ds._dirty = True
375 409 ds.write(repo.currenttransaction())
376 410 timer(d)
377 411 fm.end()
378 412
379 413 @command('perfmergecalculate',
380 414 [('r', 'rev', '.', 'rev to merge against')] + formatteropts)
381 415 def perfmergecalculate(ui, repo, rev, **opts):
382 416 timer, fm = gettimer(ui, opts)
383 417 wctx = repo[None]
384 418 rctx = scmutil.revsingle(repo, rev, rev)
385 419 ancestor = wctx.ancestor(rctx)
386 420 # we don't want working dir files to be stat'd in the benchmark, so prime
387 421 # that cache
388 422 wctx.dirty()
389 423 def d():
390 424 # acceptremote is True because we don't want prompts in the middle of
391 425 # our benchmark
392 426 merge.calculateupdates(repo, wctx, rctx, [ancestor], False, False,
393 427 acceptremote=True, followcopies=True)
394 428 timer(d)
395 429 fm.end()
396 430
397 431 @command('perfpathcopies', [], "REV REV")
398 432 def perfpathcopies(ui, repo, rev1, rev2, **opts):
399 433 timer, fm = gettimer(ui, opts)
400 434 ctx1 = scmutil.revsingle(repo, rev1, rev1)
401 435 ctx2 = scmutil.revsingle(repo, rev2, rev2)
402 436 def d():
403 437 copies.pathcopies(ctx1, ctx2)
404 438 timer(d)
405 439 fm.end()
406 440
407 441 @command('perfmanifest', [], 'REV')
408 442 def perfmanifest(ui, repo, rev, **opts):
409 443 timer, fm = gettimer(ui, opts)
410 444 ctx = scmutil.revsingle(repo, rev, rev)
411 445 t = ctx.manifestnode()
412 446 def d():
413 447 repo.manifest.clearcaches()
414 448 repo.manifest.read(t)
415 449 timer(d)
416 450 fm.end()
417 451
418 452 @command('perfchangeset', formatteropts)
419 453 def perfchangeset(ui, repo, rev, **opts):
420 454 timer, fm = gettimer(ui, opts)
421 455 n = repo[rev].node()
422 456 def d():
423 457 repo.changelog.read(n)
424 458 #repo.changelog._cache = None
425 459 timer(d)
426 460 fm.end()
427 461
428 462 @command('perfindex', formatteropts)
429 463 def perfindex(ui, repo, **opts):
430 464 import mercurial.revlog
431 465 timer, fm = gettimer(ui, opts)
432 466 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
433 467 n = repo["tip"].node()
434 468 def d():
435 469 cl = mercurial.revlog.revlog(repo.svfs, "00changelog.i")
436 470 cl.rev(n)
437 471 timer(d)
438 472 fm.end()
439 473
440 474 @command('perfstartup', formatteropts)
441 475 def perfstartup(ui, repo, **opts):
442 476 timer, fm = gettimer(ui, opts)
443 477 cmd = sys.argv[0]
444 478 def d():
445 479 if os.name != 'nt':
446 480 os.system("HGRCPATH= %s version -q > /dev/null" % cmd)
447 481 else:
448 482 os.environ['HGRCPATH'] = ''
449 483 os.system("%s version -q > NUL" % cmd)
450 484 timer(d)
451 485 fm.end()
452 486
453 487 @command('perfparents', formatteropts)
454 488 def perfparents(ui, repo, **opts):
455 489 timer, fm = gettimer(ui, opts)
456 490 # control the number of commits perfparents iterates over
457 491 # experimental config: perf.parentscount
458 492 count = ui.configint("perf", "parentscount", 1000)
459 493 if len(repo.changelog) < count:
460 494 raise error.Abort("repo needs %d commits for this test" % count)
461 495 repo = repo.unfiltered()
462 496 nl = [repo.changelog.node(i) for i in xrange(count)]
463 497 def d():
464 498 for n in nl:
465 499 repo.changelog.parents(n)
466 500 timer(d)
467 501 fm.end()
468 502
469 503 @command('perfctxfiles', formatteropts)
470 504 def perfctxfiles(ui, repo, x, **opts):
471 505 x = int(x)
472 506 timer, fm = gettimer(ui, opts)
473 507 def d():
474 508 len(repo[x].files())
475 509 timer(d)
476 510 fm.end()
477 511
478 512 @command('perfrawfiles', formatteropts)
479 513 def perfrawfiles(ui, repo, x, **opts):
480 514 x = int(x)
481 515 timer, fm = gettimer(ui, opts)
482 516 cl = repo.changelog
483 517 def d():
484 518 len(cl.read(x)[3])
485 519 timer(d)
486 520 fm.end()
487 521
488 522 @command('perflookup', formatteropts)
489 523 def perflookup(ui, repo, rev, **opts):
490 524 timer, fm = gettimer(ui, opts)
491 525 timer(lambda: len(repo.lookup(rev)))
492 526 fm.end()
493 527
494 528 @command('perfrevrange', formatteropts)
495 529 def perfrevrange(ui, repo, *specs, **opts):
496 530 timer, fm = gettimer(ui, opts)
497 531 revrange = scmutil.revrange
498 532 timer(lambda: len(revrange(repo, specs)))
499 533 fm.end()
500 534
501 535 @command('perfnodelookup', formatteropts)
502 536 def perfnodelookup(ui, repo, rev, **opts):
503 537 timer, fm = gettimer(ui, opts)
504 538 import mercurial.revlog
505 539 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
506 540 n = repo[rev].node()
507 541 cl = mercurial.revlog.revlog(repo.svfs, "00changelog.i")
508 542 def d():
509 543 cl.rev(n)
510 544 clearcaches(cl)
511 545 timer(d)
512 546 fm.end()
513 547
514 548 @command('perflog',
515 549 [('', 'rename', False, 'ask log to follow renames')] + formatteropts)
516 550 def perflog(ui, repo, rev=None, **opts):
517 551 if rev is None:
518 552 rev=[]
519 553 timer, fm = gettimer(ui, opts)
520 554 ui.pushbuffer()
521 555 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
522 556 copies=opts.get('rename')))
523 557 ui.popbuffer()
524 558 fm.end()
525 559
526 560 @command('perfmoonwalk', formatteropts)
527 561 def perfmoonwalk(ui, repo, **opts):
528 562 """benchmark walking the changelog backwards
529 563
530 564 This also loads the changelog data for each revision in the changelog.
531 565 """
532 566 timer, fm = gettimer(ui, opts)
533 567 def moonwalk():
534 568 for i in xrange(len(repo), -1, -1):
535 569 ctx = repo[i]
536 570 ctx.branch() # read changelog data (in addition to the index)
537 571 timer(moonwalk)
538 572 fm.end()
539 573
540 574 @command('perftemplating', formatteropts)
541 575 def perftemplating(ui, repo, rev=None, **opts):
542 576 if rev is None:
543 577 rev=[]
544 578 timer, fm = gettimer(ui, opts)
545 579 ui.pushbuffer()
546 580 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
547 581 template='{date|shortdate} [{rev}:{node|short}]'
548 582 ' {author|person}: {desc|firstline}\n'))
549 583 ui.popbuffer()
550 584 fm.end()
551 585
552 586 @command('perfcca', formatteropts)
553 587 def perfcca(ui, repo, **opts):
554 588 timer, fm = gettimer(ui, opts)
555 589 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
556 590 fm.end()
557 591
558 592 @command('perffncacheload', formatteropts)
559 593 def perffncacheload(ui, repo, **opts):
560 594 timer, fm = gettimer(ui, opts)
561 595 s = repo.store
562 596 def d():
563 597 s.fncache._load()
564 598 timer(d)
565 599 fm.end()
566 600
567 601 @command('perffncachewrite', formatteropts)
568 602 def perffncachewrite(ui, repo, **opts):
569 603 timer, fm = gettimer(ui, opts)
570 604 s = repo.store
571 605 s.fncache._load()
572 606 lock = repo.lock()
573 607 tr = repo.transaction('perffncachewrite')
574 608 def d():
575 609 s.fncache._dirty = True
576 610 s.fncache.write(tr)
577 611 timer(d)
578 612 tr.close()
579 613 lock.release()
580 614 fm.end()
581 615
582 616 @command('perffncacheencode', formatteropts)
583 617 def perffncacheencode(ui, repo, **opts):
584 618 timer, fm = gettimer(ui, opts)
585 619 s = repo.store
586 620 s.fncache._load()
587 621 def d():
588 622 for p in s.fncache.entries:
589 623 s.encode(p)
590 624 timer(d)
591 625 fm.end()
592 626
593 627 @command('perfdiffwd', formatteropts)
594 628 def perfdiffwd(ui, repo, **opts):
595 629 """Profile diff of working directory changes"""
596 630 timer, fm = gettimer(ui, opts)
597 631 options = {
598 632 'w': 'ignore_all_space',
599 633 'b': 'ignore_space_change',
600 634 'B': 'ignore_blank_lines',
601 635 }
602 636
603 637 for diffopt in ('', 'w', 'b', 'B', 'wB'):
604 638 opts = dict((options[c], '1') for c in diffopt)
605 639 def d():
606 640 ui.pushbuffer()
607 641 commands.diff(ui, repo, **opts)
608 642 ui.popbuffer()
609 643 title = 'diffopts: %s' % (diffopt and ('-' + diffopt) or 'none')
610 644 timer(d, title)
611 645 fm.end()
612 646
613 647 @command('perfrevlog', revlogopts + formatteropts +
614 648 [('d', 'dist', 100, 'distance between the revisions'),
615 649 ('s', 'startrev', 0, 'revision to start reading at'),
616 650 ('', 'reverse', False, 'read in reverse')],
617 651 '-c|-m|FILE')
618 652 def perfrevlog(ui, repo, file_=None, startrev=0, reverse=False, **opts):
619 653 """Benchmark reading a series of revisions from a revlog.
620 654
621 655 By default, we read every ``-d/--dist`` revision from 0 to tip of
622 656 the specified revlog.
623 657
624 658 The start revision can be defined via ``-s/--startrev``.
625 659 """
626 660 timer, fm = gettimer(ui, opts)
627 661 _len = getlen(ui)
628 662
629 663 def d():
630 664 r = cmdutil.openrevlog(repo, 'perfrevlog', file_, opts)
631 665
632 666 startrev = 0
633 667 endrev = _len(r)
634 668 dist = opts['dist']
635 669
636 670 if reverse:
637 671 startrev, endrev = endrev, startrev
638 672 dist = -1 * dist
639 673
640 674 for x in xrange(startrev, endrev, dist):
641 675 r.revision(r.node(x))
642 676
643 677 timer(d)
644 678 fm.end()
645 679
646 680 @command('perfrevlogrevision', revlogopts + formatteropts +
647 681 [('', 'cache', False, 'use caches instead of clearing')],
648 682 '-c|-m|FILE REV')
649 683 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
650 684 """Benchmark obtaining a revlog revision.
651 685
652 686 Obtaining a revlog revision consists of roughly the following steps:
653 687
654 688 1. Compute the delta chain
655 689 2. Obtain the raw chunks for that delta chain
656 690 3. Decompress each raw chunk
657 691 4. Apply binary patches to obtain fulltext
658 692 5. Verify hash of fulltext
659 693
660 694 This command measures the time spent in each of these phases.
661 695 """
662 696 if opts.get('changelog') or opts.get('manifest'):
663 697 file_, rev = None, file_
664 698 elif rev is None:
665 699 raise error.CommandError('perfrevlogrevision', 'invalid arguments')
666 700
667 701 r = cmdutil.openrevlog(repo, 'perfrevlogrevision', file_, opts)
668 702 node = r.lookup(rev)
669 703 rev = r.rev(node)
670 704
671 705 def dodeltachain(rev):
672 706 if not cache:
673 707 r.clearcaches()
674 708 r._deltachain(rev)
675 709
676 710 def doread(chain):
677 711 if not cache:
678 712 r.clearcaches()
679 713 r._chunkraw(chain[0], chain[-1])
680 714
681 715 def dodecompress(data, chain):
682 716 if not cache:
683 717 r.clearcaches()
684 718
685 719 start = r.start
686 720 length = r.length
687 721 inline = r._inline
688 722 iosize = r._io.size
689 723 buffer = util.buffer
690 724 offset = start(chain[0])
691 725
692 726 for rev in chain:
693 727 chunkstart = start(rev)
694 728 if inline:
695 729 chunkstart += (rev + 1) * iosize
696 730 chunklength = length(rev)
697 731 b = buffer(data, chunkstart - offset, chunklength)
698 732 revlog.decompress(b)
699 733
700 734 def dopatch(text, bins):
701 735 if not cache:
702 736 r.clearcaches()
703 737 mdiff.patches(text, bins)
704 738
705 739 def dohash(text):
706 740 if not cache:
707 741 r.clearcaches()
708 742 r._checkhash(text, node, rev)
709 743
710 744 def dorevision():
711 745 if not cache:
712 746 r.clearcaches()
713 747 r.revision(node)
714 748
715 749 chain = r._deltachain(rev)[0]
716 750 data = r._chunkraw(chain[0], chain[-1])[1]
717 751 bins = r._chunks(chain)
718 752 text = str(bins[0])
719 753 bins = bins[1:]
720 754 text = mdiff.patches(text, bins)
721 755
722 756 benches = [
723 757 (lambda: dorevision(), 'full'),
724 758 (lambda: dodeltachain(rev), 'deltachain'),
725 759 (lambda: doread(chain), 'read'),
726 760 (lambda: dodecompress(data, chain), 'decompress'),
727 761 (lambda: dopatch(text, bins), 'patch'),
728 762 (lambda: dohash(text), 'hash'),
729 763 ]
730 764
731 765 for fn, title in benches:
732 766 timer, fm = gettimer(ui, opts)
733 767 timer(fn, title=title)
734 768 fm.end()
735 769
736 770 @command('perfrevset',
737 771 [('C', 'clear', False, 'clear volatile cache between each call.'),
738 772 ('', 'contexts', False, 'obtain changectx for each revision')]
739 773 + formatteropts, "REVSET")
740 774 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
741 775 """benchmark the execution time of a revset
742 776
743 777 Use the --clean option if need to evaluate the impact of build volatile
744 778 revisions set cache on the revset execution. Volatile cache hold filtered
745 779 and obsolete related cache."""
746 780 timer, fm = gettimer(ui, opts)
747 781 def d():
748 782 if clear:
749 783 repo.invalidatevolatilesets()
750 784 if contexts:
751 785 for ctx in repo.set(expr): pass
752 786 else:
753 787 for r in repo.revs(expr): pass
754 788 timer(d)
755 789 fm.end()
756 790
757 791 @command('perfvolatilesets', formatteropts)
758 792 def perfvolatilesets(ui, repo, *names, **opts):
759 793 """benchmark the computation of various volatile set
760 794
761 795 Volatile set computes element related to filtering and obsolescence."""
762 796 timer, fm = gettimer(ui, opts)
763 797 repo = repo.unfiltered()
764 798
765 799 def getobs(name):
766 800 def d():
767 801 repo.invalidatevolatilesets()
768 802 obsolete.getrevs(repo, name)
769 803 return d
770 804
771 805 allobs = sorted(obsolete.cachefuncs)
772 806 if names:
773 807 allobs = [n for n in allobs if n in names]
774 808
775 809 for name in allobs:
776 810 timer(getobs(name), title=name)
777 811
778 812 def getfiltered(name):
779 813 def d():
780 814 repo.invalidatevolatilesets()
781 815 repoview.filterrevs(repo, name)
782 816 return d
783 817
784 818 allfilter = sorted(repoview.filtertable)
785 819 if names:
786 820 allfilter = [n for n in allfilter if n in names]
787 821
788 822 for name in allfilter:
789 823 timer(getfiltered(name), title=name)
790 824 fm.end()
791 825
792 826 @command('perfbranchmap',
793 827 [('f', 'full', False,
794 828 'Includes build time of subset'),
795 829 ] + formatteropts)
796 830 def perfbranchmap(ui, repo, full=False, **opts):
797 831 """benchmark the update of a branchmap
798 832
799 833 This benchmarks the full repo.branchmap() call with read and write disabled
800 834 """
801 835 timer, fm = gettimer(ui, opts)
802 836 def getbranchmap(filtername):
803 837 """generate a benchmark function for the filtername"""
804 838 if filtername is None:
805 839 view = repo
806 840 else:
807 841 view = repo.filtered(filtername)
808 842 def d():
809 843 if full:
810 844 view._branchcaches.clear()
811 845 else:
812 846 view._branchcaches.pop(filtername, None)
813 847 view.branchmap()
814 848 return d
815 849 # add filter in smaller subset to bigger subset
816 850 possiblefilters = set(repoview.filtertable)
817 851 allfilters = []
818 852 while possiblefilters:
819 853 for name in possiblefilters:
820 854 subset = branchmap.subsettable.get(name)
821 855 if subset not in possiblefilters:
822 856 break
823 857 else:
824 858 assert False, 'subset cycle %s!' % possiblefilters
825 859 allfilters.append(name)
826 860 possiblefilters.remove(name)
827 861
828 862 # warm the cache
829 863 if not full:
830 864 for name in allfilters:
831 865 repo.filtered(name).branchmap()
832 866 # add unfiltered
833 867 allfilters.append(None)
834 868 oldread = branchmap.read
835 869 oldwrite = branchmap.branchcache.write
836 870 try:
837 871 branchmap.read = lambda repo: None
838 872 branchmap.write = lambda repo: None
839 873 for name in allfilters:
840 874 timer(getbranchmap(name), title=str(name))
841 875 finally:
842 876 branchmap.read = oldread
843 877 branchmap.branchcache.write = oldwrite
844 878 fm.end()
845 879
846 880 @command('perfloadmarkers')
847 881 def perfloadmarkers(ui, repo):
848 882 """benchmark the time to parse the on-disk markers for a repo
849 883
850 884 Result is the number of markers in the repo."""
851 885 timer, fm = gettimer(ui)
852 886 timer(lambda: len(obsolete.obsstore(repo.svfs)))
853 887 fm.end()
854 888
855 889 @command('perflrucachedict', formatteropts +
856 890 [('', 'size', 4, 'size of cache'),
857 891 ('', 'gets', 10000, 'number of key lookups'),
858 892 ('', 'sets', 10000, 'number of key sets'),
859 893 ('', 'mixed', 10000, 'number of mixed mode operations'),
860 894 ('', 'mixedgetfreq', 50, 'frequency of get vs set ops in mixed mode')],
861 895 norepo=True)
862 896 def perflrucache(ui, size=4, gets=10000, sets=10000, mixed=10000,
863 897 mixedgetfreq=50, **opts):
864 898 def doinit():
865 899 for i in xrange(10000):
866 900 util.lrucachedict(size)
867 901
868 902 values = []
869 903 for i in xrange(size):
870 904 values.append(random.randint(0, sys.maxint))
871 905
872 906 # Get mode fills the cache and tests raw lookup performance with no
873 907 # eviction.
874 908 getseq = []
875 909 for i in xrange(gets):
876 910 getseq.append(random.choice(values))
877 911
878 912 def dogets():
879 913 d = util.lrucachedict(size)
880 914 for v in values:
881 915 d[v] = v
882 916 for key in getseq:
883 917 value = d[key]
884 918 value # silence pyflakes warning
885 919
886 920 # Set mode tests insertion speed with cache eviction.
887 921 setseq = []
888 922 for i in xrange(sets):
889 923 setseq.append(random.randint(0, sys.maxint))
890 924
891 925 def dosets():
892 926 d = util.lrucachedict(size)
893 927 for v in setseq:
894 928 d[v] = v
895 929
896 930 # Mixed mode randomly performs gets and sets with eviction.
897 931 mixedops = []
898 932 for i in xrange(mixed):
899 933 r = random.randint(0, 100)
900 934 if r < mixedgetfreq:
901 935 op = 0
902 936 else:
903 937 op = 1
904 938
905 939 mixedops.append((op, random.randint(0, size * 2)))
906 940
907 941 def domixed():
908 942 d = util.lrucachedict(size)
909 943
910 944 for op, v in mixedops:
911 945 if op == 0:
912 946 try:
913 947 d[v]
914 948 except KeyError:
915 949 pass
916 950 else:
917 951 d[v] = v
918 952
919 953 benches = [
920 954 (doinit, 'init'),
921 955 (dogets, 'gets'),
922 956 (dosets, 'sets'),
923 957 (domixed, 'mixed')
924 958 ]
925 959
926 960 for fn, title in benches:
927 961 timer, fm = gettimer(ui, opts)
928 962 timer(fn, title=title)
929 963 fm.end()
930 964
931 965 def uisetup(ui):
932 966 if (util.safehasattr(cmdutil, 'openrevlog') and
933 967 not util.safehasattr(commands, 'debugrevlogopts')):
934 968 # for "historical portability":
935 969 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
936 970 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
937 971 # openrevlog() should cause failure, because it has been
938 972 # available since 3.5 (or 49c583ca48c4).
939 973 def openrevlog(orig, repo, cmd, file_, opts):
940 974 if opts.get('dir') and not util.safehasattr(repo, 'dirlog'):
941 975 raise error.Abort("This version doesn't support --dir option",
942 976 hint="use 3.5 or later")
943 977 return orig(repo, cmd, file_, opts)
944 978 extensions.wrapfunction(cmdutil, 'openrevlog', openrevlog)
General Comments 0
You need to be logged in to leave comments. Login now