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