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