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