##// END OF EJS Templates
perfphases: add a flag to also include file access time...
marmoute -
r32732:e36569bd default
parent child Browse files
Show More
@@ -1,1481 +1,1488
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 gc
24 24 import os
25 25 import random
26 26 import struct
27 27 import sys
28 28 import time
29 29 from mercurial import (
30 30 changegroup,
31 31 cmdutil,
32 32 commands,
33 33 copies,
34 34 error,
35 35 extensions,
36 36 mdiff,
37 37 merge,
38 38 revlog,
39 39 util,
40 40 )
41 41
42 42 # for "historical portability":
43 43 # try to import modules separately (in dict order), and ignore
44 44 # failure, because these aren't available with early Mercurial
45 45 try:
46 46 from mercurial import branchmap # since 2.5 (or bcee63733aad)
47 47 except ImportError:
48 48 pass
49 49 try:
50 50 from mercurial import obsolete # since 2.3 (or ad0d6c2b3279)
51 51 except ImportError:
52 52 pass
53 53 try:
54 54 from mercurial import registrar # since 3.7 (or 37d50250b696)
55 55 dir(registrar) # forcibly load it
56 56 except ImportError:
57 57 registrar = None
58 58 try:
59 59 from mercurial import repoview # since 2.5 (or 3a6ddacb7198)
60 60 except ImportError:
61 61 pass
62 62 try:
63 63 from mercurial import scmutil # since 1.9 (or 8b252e826c68)
64 64 except ImportError:
65 65 pass
66 66
67 67 # for "historical portability":
68 68 # define util.safehasattr forcibly, because util.safehasattr has been
69 69 # available since 1.9.3 (or 94b200a11cf7)
70 70 _undefined = object()
71 71 def safehasattr(thing, attr):
72 72 return getattr(thing, attr, _undefined) is not _undefined
73 73 setattr(util, 'safehasattr', safehasattr)
74 74
75 75 # for "historical portability":
76 76 # define util.timer forcibly, because util.timer has been available
77 77 # since ae5d60bb70c9
78 78 if safehasattr(time, 'perf_counter'):
79 79 util.timer = time.perf_counter
80 80 elif os.name == 'nt':
81 81 util.timer = time.clock
82 82 else:
83 83 util.timer = time.time
84 84
85 85 # for "historical portability":
86 86 # use locally defined empty option list, if formatteropts isn't
87 87 # available, because commands.formatteropts has been available since
88 88 # 3.2 (or 7a7eed5176a4), even though formatting itself has been
89 89 # available since 2.2 (or ae5f92e154d3)
90 90 formatteropts = getattr(cmdutil, "formatteropts",
91 91 getattr(commands, "formatteropts", []))
92 92
93 93 # for "historical portability":
94 94 # use locally defined option list, if debugrevlogopts isn't available,
95 95 # because commands.debugrevlogopts has been available since 3.7 (or
96 96 # 5606f7d0d063), even though cmdutil.openrevlog() has been available
97 97 # since 1.9 (or a79fea6b3e77).
98 98 revlogopts = getattr(cmdutil, "debugrevlogopts",
99 99 getattr(commands, "debugrevlogopts", [
100 100 ('c', 'changelog', False, ('open changelog')),
101 101 ('m', 'manifest', False, ('open manifest')),
102 102 ('', 'dir', False, ('open directory manifest')),
103 103 ]))
104 104
105 105 cmdtable = {}
106 106
107 107 # for "historical portability":
108 108 # define parsealiases locally, because cmdutil.parsealiases has been
109 109 # available since 1.5 (or 6252852b4332)
110 110 def parsealiases(cmd):
111 111 return cmd.lstrip("^").split("|")
112 112
113 113 if safehasattr(registrar, 'command'):
114 114 command = registrar.command(cmdtable)
115 115 elif safehasattr(cmdutil, 'command'):
116 116 import inspect
117 117 command = cmdutil.command(cmdtable)
118 118 if 'norepo' not in inspect.getargspec(command)[0]:
119 119 # for "historical portability":
120 120 # wrap original cmdutil.command, because "norepo" option has
121 121 # been available since 3.1 (or 75a96326cecb)
122 122 _command = command
123 123 def command(name, options=(), synopsis=None, norepo=False):
124 124 if norepo:
125 125 commands.norepo += ' %s' % ' '.join(parsealiases(name))
126 126 return _command(name, list(options), synopsis)
127 127 else:
128 128 # for "historical portability":
129 129 # define "@command" annotation locally, because cmdutil.command
130 130 # has been available since 1.9 (or 2daa5179e73f)
131 131 def command(name, options=(), synopsis=None, norepo=False):
132 132 def decorator(func):
133 133 if synopsis:
134 134 cmdtable[name] = func, list(options), synopsis
135 135 else:
136 136 cmdtable[name] = func, list(options)
137 137 if norepo:
138 138 commands.norepo += ' %s' % ' '.join(parsealiases(name))
139 139 return func
140 140 return decorator
141 141
142 142 def getlen(ui):
143 143 if ui.configbool("perf", "stub"):
144 144 return lambda x: 1
145 145 return len
146 146
147 147 def gettimer(ui, opts=None):
148 148 """return a timer function and formatter: (timer, formatter)
149 149
150 150 This function exists to gather the creation of formatter in a single
151 151 place instead of duplicating it in all performance commands."""
152 152
153 153 # enforce an idle period before execution to counteract power management
154 154 # experimental config: perf.presleep
155 155 time.sleep(getint(ui, "perf", "presleep", 1))
156 156
157 157 if opts is None:
158 158 opts = {}
159 159 # redirect all to stderr unless buffer api is in use
160 160 if not ui._buffers:
161 161 ui = ui.copy()
162 162 uifout = safeattrsetter(ui, 'fout', ignoremissing=True)
163 163 if uifout:
164 164 # for "historical portability":
165 165 # ui.fout/ferr have been available since 1.9 (or 4e1ccd4c2b6d)
166 166 uifout.set(ui.ferr)
167 167
168 168 # get a formatter
169 169 uiformatter = getattr(ui, 'formatter', None)
170 170 if uiformatter:
171 171 fm = uiformatter('perf', opts)
172 172 else:
173 173 # for "historical portability":
174 174 # define formatter locally, because ui.formatter has been
175 175 # available since 2.2 (or ae5f92e154d3)
176 176 from mercurial import node
177 177 class defaultformatter(object):
178 178 """Minimized composition of baseformatter and plainformatter
179 179 """
180 180 def __init__(self, ui, topic, opts):
181 181 self._ui = ui
182 182 if ui.debugflag:
183 183 self.hexfunc = node.hex
184 184 else:
185 185 self.hexfunc = node.short
186 186 def __nonzero__(self):
187 187 return False
188 188 __bool__ = __nonzero__
189 189 def startitem(self):
190 190 pass
191 191 def data(self, **data):
192 192 pass
193 193 def write(self, fields, deftext, *fielddata, **opts):
194 194 self._ui.write(deftext % fielddata, **opts)
195 195 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
196 196 if cond:
197 197 self._ui.write(deftext % fielddata, **opts)
198 198 def plain(self, text, **opts):
199 199 self._ui.write(text, **opts)
200 200 def end(self):
201 201 pass
202 202 fm = defaultformatter(ui, 'perf', opts)
203 203
204 204 # stub function, runs code only once instead of in a loop
205 205 # experimental config: perf.stub
206 206 if ui.configbool("perf", "stub"):
207 207 return functools.partial(stub_timer, fm), fm
208 208 return functools.partial(_timer, fm), fm
209 209
210 210 def stub_timer(fm, func, title=None):
211 211 func()
212 212
213 213 def _timer(fm, func, title=None):
214 214 gc.collect()
215 215 results = []
216 216 begin = util.timer()
217 217 count = 0
218 218 while True:
219 219 ostart = os.times()
220 220 cstart = util.timer()
221 221 r = func()
222 222 cstop = util.timer()
223 223 ostop = os.times()
224 224 count += 1
225 225 a, b = ostart, ostop
226 226 results.append((cstop - cstart, b[0] - a[0], b[1]-a[1]))
227 227 if cstop - begin > 3 and count >= 100:
228 228 break
229 229 if cstop - begin > 10 and count >= 3:
230 230 break
231 231
232 232 fm.startitem()
233 233
234 234 if title:
235 235 fm.write('title', '! %s\n', title)
236 236 if r:
237 237 fm.write('result', '! result: %s\n', r)
238 238 m = min(results)
239 239 fm.plain('!')
240 240 fm.write('wall', ' wall %f', m[0])
241 241 fm.write('comb', ' comb %f', m[1] + m[2])
242 242 fm.write('user', ' user %f', m[1])
243 243 fm.write('sys', ' sys %f', m[2])
244 244 fm.write('count', ' (best of %d)', count)
245 245 fm.plain('\n')
246 246
247 247 # utilities for historical portability
248 248
249 249 def getint(ui, section, name, default):
250 250 # for "historical portability":
251 251 # ui.configint has been available since 1.9 (or fa2b596db182)
252 252 v = ui.config(section, name, None)
253 253 if v is None:
254 254 return default
255 255 try:
256 256 return int(v)
257 257 except ValueError:
258 258 raise error.ConfigError(("%s.%s is not an integer ('%s')")
259 259 % (section, name, v))
260 260
261 261 def safeattrsetter(obj, name, ignoremissing=False):
262 262 """Ensure that 'obj' has 'name' attribute before subsequent setattr
263 263
264 264 This function is aborted, if 'obj' doesn't have 'name' attribute
265 265 at runtime. This avoids overlooking removal of an attribute, which
266 266 breaks assumption of performance measurement, in the future.
267 267
268 268 This function returns the object to (1) assign a new value, and
269 269 (2) restore an original value to the attribute.
270 270
271 271 If 'ignoremissing' is true, missing 'name' attribute doesn't cause
272 272 abortion, and this function returns None. This is useful to
273 273 examine an attribute, which isn't ensured in all Mercurial
274 274 versions.
275 275 """
276 276 if not util.safehasattr(obj, name):
277 277 if ignoremissing:
278 278 return None
279 279 raise error.Abort(("missing attribute %s of %s might break assumption"
280 280 " of performance measurement") % (name, obj))
281 281
282 282 origvalue = getattr(obj, name)
283 283 class attrutil(object):
284 284 def set(self, newvalue):
285 285 setattr(obj, name, newvalue)
286 286 def restore(self):
287 287 setattr(obj, name, origvalue)
288 288
289 289 return attrutil()
290 290
291 291 # utilities to examine each internal API changes
292 292
293 293 def getbranchmapsubsettable():
294 294 # for "historical portability":
295 295 # subsettable is defined in:
296 296 # - branchmap since 2.9 (or 175c6fd8cacc)
297 297 # - repoview since 2.5 (or 59a9f18d4587)
298 298 for mod in (branchmap, repoview):
299 299 subsettable = getattr(mod, 'subsettable', None)
300 300 if subsettable:
301 301 return subsettable
302 302
303 303 # bisecting in bcee63733aad::59a9f18d4587 can reach here (both
304 304 # branchmap and repoview modules exist, but subsettable attribute
305 305 # doesn't)
306 306 raise error.Abort(("perfbranchmap not available with this Mercurial"),
307 307 hint="use 2.5 or later")
308 308
309 309 def getsvfs(repo):
310 310 """Return appropriate object to access files under .hg/store
311 311 """
312 312 # for "historical portability":
313 313 # repo.svfs has been available since 2.3 (or 7034365089bf)
314 314 svfs = getattr(repo, 'svfs', None)
315 315 if svfs:
316 316 return svfs
317 317 else:
318 318 return getattr(repo, 'sopener')
319 319
320 320 def getvfs(repo):
321 321 """Return appropriate object to access files under .hg
322 322 """
323 323 # for "historical portability":
324 324 # repo.vfs has been available since 2.3 (or 7034365089bf)
325 325 vfs = getattr(repo, 'vfs', None)
326 326 if vfs:
327 327 return vfs
328 328 else:
329 329 return getattr(repo, 'opener')
330 330
331 331 def repocleartagscachefunc(repo):
332 332 """Return the function to clear tags cache according to repo internal API
333 333 """
334 334 if util.safehasattr(repo, '_tagscache'): # since 2.0 (or 9dca7653b525)
335 335 # in this case, setattr(repo, '_tagscache', None) or so isn't
336 336 # correct way to clear tags cache, because existing code paths
337 337 # expect _tagscache to be a structured object.
338 338 def clearcache():
339 339 # _tagscache has been filteredpropertycache since 2.5 (or
340 340 # 98c867ac1330), and delattr() can't work in such case
341 341 if '_tagscache' in vars(repo):
342 342 del repo.__dict__['_tagscache']
343 343 return clearcache
344 344
345 345 repotags = safeattrsetter(repo, '_tags', ignoremissing=True)
346 346 if repotags: # since 1.4 (or 5614a628d173)
347 347 return lambda : repotags.set(None)
348 348
349 349 repotagscache = safeattrsetter(repo, 'tagscache', ignoremissing=True)
350 350 if repotagscache: # since 0.6 (or d7df759d0e97)
351 351 return lambda : repotagscache.set(None)
352 352
353 353 # Mercurial earlier than 0.6 (or d7df759d0e97) logically reaches
354 354 # this point, but it isn't so problematic, because:
355 355 # - repo.tags of such Mercurial isn't "callable", and repo.tags()
356 356 # in perftags() causes failure soon
357 357 # - perf.py itself has been available since 1.1 (or eb240755386d)
358 358 raise error.Abort(("tags API of this hg command is unknown"))
359 359
360 360 # utilities to clear cache
361 361
362 362 def clearfilecache(repo, attrname):
363 363 unfi = repo.unfiltered()
364 364 if attrname in vars(unfi):
365 365 delattr(unfi, attrname)
366 366 unfi._filecache.pop(attrname, None)
367 367
368 368 # perf commands
369 369
370 370 @command('perfwalk', formatteropts)
371 371 def perfwalk(ui, repo, *pats, **opts):
372 372 timer, fm = gettimer(ui, opts)
373 373 try:
374 374 m = scmutil.match(repo[None], pats, {})
375 375 timer(lambda: len(list(repo.dirstate.walk(m, [], True, False))))
376 376 except Exception:
377 377 try:
378 378 m = scmutil.match(repo[None], pats, {})
379 379 timer(lambda: len([b for a, b, c in repo.dirstate.statwalk([], m)]))
380 380 except Exception:
381 381 timer(lambda: len(list(cmdutil.walk(repo, pats, {}))))
382 382 fm.end()
383 383
384 384 @command('perfannotate', formatteropts)
385 385 def perfannotate(ui, repo, f, **opts):
386 386 timer, fm = gettimer(ui, opts)
387 387 fc = repo['.'][f]
388 388 timer(lambda: len(fc.annotate(True)))
389 389 fm.end()
390 390
391 391 @command('perfstatus',
392 392 [('u', 'unknown', False,
393 393 'ask status to look for unknown files')] + formatteropts)
394 394 def perfstatus(ui, repo, **opts):
395 395 #m = match.always(repo.root, repo.getcwd())
396 396 #timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
397 397 # False))))
398 398 timer, fm = gettimer(ui, opts)
399 399 timer(lambda: sum(map(len, repo.status(unknown=opts['unknown']))))
400 400 fm.end()
401 401
402 402 @command('perfaddremove', formatteropts)
403 403 def perfaddremove(ui, repo, **opts):
404 404 timer, fm = gettimer(ui, opts)
405 405 try:
406 406 oldquiet = repo.ui.quiet
407 407 repo.ui.quiet = True
408 408 matcher = scmutil.match(repo[None])
409 409 timer(lambda: scmutil.addremove(repo, matcher, "", dry_run=True))
410 410 finally:
411 411 repo.ui.quiet = oldquiet
412 412 fm.end()
413 413
414 414 def clearcaches(cl):
415 415 # behave somewhat consistently across internal API changes
416 416 if util.safehasattr(cl, 'clearcaches'):
417 417 cl.clearcaches()
418 418 elif util.safehasattr(cl, '_nodecache'):
419 419 from mercurial.node import nullid, nullrev
420 420 cl._nodecache = {nullid: nullrev}
421 421 cl._nodepos = None
422 422
423 423 @command('perfheads', formatteropts)
424 424 def perfheads(ui, repo, **opts):
425 425 timer, fm = gettimer(ui, opts)
426 426 cl = repo.changelog
427 427 def d():
428 428 len(cl.headrevs())
429 429 clearcaches(cl)
430 430 timer(d)
431 431 fm.end()
432 432
433 433 @command('perftags', formatteropts)
434 434 def perftags(ui, repo, **opts):
435 435 import mercurial.changelog
436 436 import mercurial.manifest
437 437 timer, fm = gettimer(ui, opts)
438 438 svfs = getsvfs(repo)
439 439 repocleartagscache = repocleartagscachefunc(repo)
440 440 def t():
441 441 repo.changelog = mercurial.changelog.changelog(svfs)
442 442 repo.manifestlog = mercurial.manifest.manifestlog(svfs, repo)
443 443 repocleartagscache()
444 444 return len(repo.tags())
445 445 timer(t)
446 446 fm.end()
447 447
448 448 @command('perfancestors', formatteropts)
449 449 def perfancestors(ui, repo, **opts):
450 450 timer, fm = gettimer(ui, opts)
451 451 heads = repo.changelog.headrevs()
452 452 def d():
453 453 for a in repo.changelog.ancestors(heads):
454 454 pass
455 455 timer(d)
456 456 fm.end()
457 457
458 458 @command('perfancestorset', formatteropts)
459 459 def perfancestorset(ui, repo, revset, **opts):
460 460 timer, fm = gettimer(ui, opts)
461 461 revs = repo.revs(revset)
462 462 heads = repo.changelog.headrevs()
463 463 def d():
464 464 s = repo.changelog.ancestors(heads)
465 465 for rev in revs:
466 466 rev in s
467 467 timer(d)
468 468 fm.end()
469 469
470 470 @command('perfchangegroupchangelog', formatteropts +
471 471 [('', 'version', '02', 'changegroup version'),
472 472 ('r', 'rev', '', 'revisions to add to changegroup')])
473 473 def perfchangegroupchangelog(ui, repo, version='02', rev=None, **opts):
474 474 """Benchmark producing a changelog group for a changegroup.
475 475
476 476 This measures the time spent processing the changelog during a
477 477 bundle operation. This occurs during `hg bundle` and on a server
478 478 processing a `getbundle` wire protocol request (handles clones
479 479 and pull requests).
480 480
481 481 By default, all revisions are added to the changegroup.
482 482 """
483 483 cl = repo.changelog
484 484 revs = [cl.lookup(r) for r in repo.revs(rev or 'all()')]
485 485 bundler = changegroup.getbundler(version, repo)
486 486
487 487 def lookup(node):
488 488 # The real bundler reads the revision in order to access the
489 489 # manifest node and files list. Do that here.
490 490 cl.read(node)
491 491 return node
492 492
493 493 def d():
494 494 for chunk in bundler.group(revs, cl, lookup):
495 495 pass
496 496
497 497 timer, fm = gettimer(ui, opts)
498 498 timer(d)
499 499 fm.end()
500 500
501 501 @command('perfdirs', formatteropts)
502 502 def perfdirs(ui, repo, **opts):
503 503 timer, fm = gettimer(ui, opts)
504 504 dirstate = repo.dirstate
505 505 'a' in dirstate
506 506 def d():
507 507 dirstate.dirs()
508 508 del dirstate._dirs
509 509 timer(d)
510 510 fm.end()
511 511
512 512 @command('perfdirstate', formatteropts)
513 513 def perfdirstate(ui, repo, **opts):
514 514 timer, fm = gettimer(ui, opts)
515 515 "a" in repo.dirstate
516 516 def d():
517 517 repo.dirstate.invalidate()
518 518 "a" in repo.dirstate
519 519 timer(d)
520 520 fm.end()
521 521
522 522 @command('perfdirstatedirs', formatteropts)
523 523 def perfdirstatedirs(ui, repo, **opts):
524 524 timer, fm = gettimer(ui, opts)
525 525 "a" in repo.dirstate
526 526 def d():
527 527 "a" in repo.dirstate._dirs
528 528 del repo.dirstate._dirs
529 529 timer(d)
530 530 fm.end()
531 531
532 532 @command('perfdirstatefoldmap', formatteropts)
533 533 def perfdirstatefoldmap(ui, repo, **opts):
534 534 timer, fm = gettimer(ui, opts)
535 535 dirstate = repo.dirstate
536 536 'a' in dirstate
537 537 def d():
538 538 dirstate._filefoldmap.get('a')
539 539 del dirstate._filefoldmap
540 540 timer(d)
541 541 fm.end()
542 542
543 543 @command('perfdirfoldmap', formatteropts)
544 544 def perfdirfoldmap(ui, repo, **opts):
545 545 timer, fm = gettimer(ui, opts)
546 546 dirstate = repo.dirstate
547 547 'a' in dirstate
548 548 def d():
549 549 dirstate._dirfoldmap.get('a')
550 550 del dirstate._dirfoldmap
551 551 del dirstate._dirs
552 552 timer(d)
553 553 fm.end()
554 554
555 555 @command('perfdirstatewrite', formatteropts)
556 556 def perfdirstatewrite(ui, repo, **opts):
557 557 timer, fm = gettimer(ui, opts)
558 558 ds = repo.dirstate
559 559 "a" in ds
560 560 def d():
561 561 ds._dirty = True
562 562 ds.write(repo.currenttransaction())
563 563 timer(d)
564 564 fm.end()
565 565
566 566 @command('perfmergecalculate',
567 567 [('r', 'rev', '.', 'rev to merge against')] + formatteropts)
568 568 def perfmergecalculate(ui, repo, rev, **opts):
569 569 timer, fm = gettimer(ui, opts)
570 570 wctx = repo[None]
571 571 rctx = scmutil.revsingle(repo, rev, rev)
572 572 ancestor = wctx.ancestor(rctx)
573 573 # we don't want working dir files to be stat'd in the benchmark, so prime
574 574 # that cache
575 575 wctx.dirty()
576 576 def d():
577 577 # acceptremote is True because we don't want prompts in the middle of
578 578 # our benchmark
579 579 merge.calculateupdates(repo, wctx, rctx, [ancestor], False, False,
580 580 acceptremote=True, followcopies=True)
581 581 timer(d)
582 582 fm.end()
583 583
584 584 @command('perfpathcopies', [], "REV REV")
585 585 def perfpathcopies(ui, repo, rev1, rev2, **opts):
586 586 timer, fm = gettimer(ui, opts)
587 587 ctx1 = scmutil.revsingle(repo, rev1, rev1)
588 588 ctx2 = scmutil.revsingle(repo, rev2, rev2)
589 589 def d():
590 590 copies.pathcopies(ctx1, ctx2)
591 591 timer(d)
592 592 fm.end()
593 593
594 @command('perfphases', [], "")
594 @command('perfphases',
595 [('', 'full', False, 'include file reading time too'),
596 ], "")
595 597 def perfphases(ui, repo, **opts):
596 598 """benchmark phasesets computation"""
597 599 timer, fm = gettimer(ui, opts)
598 phases = repo._phasecache
600 _phases = repo._phasecache
601 full = opts.get('full')
599 602 def d():
603 phases = _phases
604 if full:
605 clearfilecache(repo, '_phasecache')
606 phases = repo._phasecache
600 607 phases.invalidate()
601 608 phases.loadphaserevs(repo)
602 609 timer(d)
603 610 fm.end()
604 611
605 612 @command('perfmanifest', [], 'REV')
606 613 def perfmanifest(ui, repo, rev, **opts):
607 614 timer, fm = gettimer(ui, opts)
608 615 ctx = scmutil.revsingle(repo, rev, rev)
609 616 t = ctx.manifestnode()
610 617 def d():
611 618 repo.manifestlog.clearcaches()
612 619 repo.manifestlog[t].read()
613 620 timer(d)
614 621 fm.end()
615 622
616 623 @command('perfchangeset', formatteropts)
617 624 def perfchangeset(ui, repo, rev, **opts):
618 625 timer, fm = gettimer(ui, opts)
619 626 n = repo[rev].node()
620 627 def d():
621 628 repo.changelog.read(n)
622 629 #repo.changelog._cache = None
623 630 timer(d)
624 631 fm.end()
625 632
626 633 @command('perfindex', formatteropts)
627 634 def perfindex(ui, repo, **opts):
628 635 import mercurial.revlog
629 636 timer, fm = gettimer(ui, opts)
630 637 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
631 638 n = repo["tip"].node()
632 639 svfs = getsvfs(repo)
633 640 def d():
634 641 cl = mercurial.revlog.revlog(svfs, "00changelog.i")
635 642 cl.rev(n)
636 643 timer(d)
637 644 fm.end()
638 645
639 646 @command('perfstartup', formatteropts)
640 647 def perfstartup(ui, repo, **opts):
641 648 timer, fm = gettimer(ui, opts)
642 649 cmd = sys.argv[0]
643 650 def d():
644 651 if os.name != 'nt':
645 652 os.system("HGRCPATH= %s version -q > /dev/null" % cmd)
646 653 else:
647 654 os.environ['HGRCPATH'] = ''
648 655 os.system("%s version -q > NUL" % cmd)
649 656 timer(d)
650 657 fm.end()
651 658
652 659 @command('perfparents', formatteropts)
653 660 def perfparents(ui, repo, **opts):
654 661 timer, fm = gettimer(ui, opts)
655 662 # control the number of commits perfparents iterates over
656 663 # experimental config: perf.parentscount
657 664 count = getint(ui, "perf", "parentscount", 1000)
658 665 if len(repo.changelog) < count:
659 666 raise error.Abort("repo needs %d commits for this test" % count)
660 667 repo = repo.unfiltered()
661 668 nl = [repo.changelog.node(i) for i in xrange(count)]
662 669 def d():
663 670 for n in nl:
664 671 repo.changelog.parents(n)
665 672 timer(d)
666 673 fm.end()
667 674
668 675 @command('perfctxfiles', formatteropts)
669 676 def perfctxfiles(ui, repo, x, **opts):
670 677 x = int(x)
671 678 timer, fm = gettimer(ui, opts)
672 679 def d():
673 680 len(repo[x].files())
674 681 timer(d)
675 682 fm.end()
676 683
677 684 @command('perfrawfiles', formatteropts)
678 685 def perfrawfiles(ui, repo, x, **opts):
679 686 x = int(x)
680 687 timer, fm = gettimer(ui, opts)
681 688 cl = repo.changelog
682 689 def d():
683 690 len(cl.read(x)[3])
684 691 timer(d)
685 692 fm.end()
686 693
687 694 @command('perflookup', formatteropts)
688 695 def perflookup(ui, repo, rev, **opts):
689 696 timer, fm = gettimer(ui, opts)
690 697 timer(lambda: len(repo.lookup(rev)))
691 698 fm.end()
692 699
693 700 @command('perfrevrange', formatteropts)
694 701 def perfrevrange(ui, repo, *specs, **opts):
695 702 timer, fm = gettimer(ui, opts)
696 703 revrange = scmutil.revrange
697 704 timer(lambda: len(revrange(repo, specs)))
698 705 fm.end()
699 706
700 707 @command('perfnodelookup', formatteropts)
701 708 def perfnodelookup(ui, repo, rev, **opts):
702 709 timer, fm = gettimer(ui, opts)
703 710 import mercurial.revlog
704 711 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
705 712 n = repo[rev].node()
706 713 cl = mercurial.revlog.revlog(getsvfs(repo), "00changelog.i")
707 714 def d():
708 715 cl.rev(n)
709 716 clearcaches(cl)
710 717 timer(d)
711 718 fm.end()
712 719
713 720 @command('perflog',
714 721 [('', 'rename', False, 'ask log to follow renames')] + formatteropts)
715 722 def perflog(ui, repo, rev=None, **opts):
716 723 if rev is None:
717 724 rev=[]
718 725 timer, fm = gettimer(ui, opts)
719 726 ui.pushbuffer()
720 727 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
721 728 copies=opts.get('rename')))
722 729 ui.popbuffer()
723 730 fm.end()
724 731
725 732 @command('perfmoonwalk', formatteropts)
726 733 def perfmoonwalk(ui, repo, **opts):
727 734 """benchmark walking the changelog backwards
728 735
729 736 This also loads the changelog data for each revision in the changelog.
730 737 """
731 738 timer, fm = gettimer(ui, opts)
732 739 def moonwalk():
733 740 for i in xrange(len(repo), -1, -1):
734 741 ctx = repo[i]
735 742 ctx.branch() # read changelog data (in addition to the index)
736 743 timer(moonwalk)
737 744 fm.end()
738 745
739 746 @command('perftemplating', formatteropts)
740 747 def perftemplating(ui, repo, rev=None, **opts):
741 748 if rev is None:
742 749 rev=[]
743 750 timer, fm = gettimer(ui, opts)
744 751 ui.pushbuffer()
745 752 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
746 753 template='{date|shortdate} [{rev}:{node|short}]'
747 754 ' {author|person}: {desc|firstline}\n'))
748 755 ui.popbuffer()
749 756 fm.end()
750 757
751 758 @command('perfcca', formatteropts)
752 759 def perfcca(ui, repo, **opts):
753 760 timer, fm = gettimer(ui, opts)
754 761 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
755 762 fm.end()
756 763
757 764 @command('perffncacheload', formatteropts)
758 765 def perffncacheload(ui, repo, **opts):
759 766 timer, fm = gettimer(ui, opts)
760 767 s = repo.store
761 768 def d():
762 769 s.fncache._load()
763 770 timer(d)
764 771 fm.end()
765 772
766 773 @command('perffncachewrite', formatteropts)
767 774 def perffncachewrite(ui, repo, **opts):
768 775 timer, fm = gettimer(ui, opts)
769 776 s = repo.store
770 777 s.fncache._load()
771 778 lock = repo.lock()
772 779 tr = repo.transaction('perffncachewrite')
773 780 def d():
774 781 s.fncache._dirty = True
775 782 s.fncache.write(tr)
776 783 timer(d)
777 784 tr.close()
778 785 lock.release()
779 786 fm.end()
780 787
781 788 @command('perffncacheencode', formatteropts)
782 789 def perffncacheencode(ui, repo, **opts):
783 790 timer, fm = gettimer(ui, opts)
784 791 s = repo.store
785 792 s.fncache._load()
786 793 def d():
787 794 for p in s.fncache.entries:
788 795 s.encode(p)
789 796 timer(d)
790 797 fm.end()
791 798
792 799 @command('perfbdiff', revlogopts + formatteropts + [
793 800 ('', 'count', 1, 'number of revisions to test (when using --startrev)'),
794 801 ('', 'alldata', False, 'test bdiffs for all associated revisions')],
795 802 '-c|-m|FILE REV')
796 803 def perfbdiff(ui, repo, file_, rev=None, count=None, **opts):
797 804 """benchmark a bdiff between revisions
798 805
799 806 By default, benchmark a bdiff between its delta parent and itself.
800 807
801 808 With ``--count``, benchmark bdiffs between delta parents and self for N
802 809 revisions starting at the specified revision.
803 810
804 811 With ``--alldata``, assume the requested revision is a changeset and
805 812 measure bdiffs for all changes related to that changeset (manifest
806 813 and filelogs).
807 814 """
808 815 if opts['alldata']:
809 816 opts['changelog'] = True
810 817
811 818 if opts.get('changelog') or opts.get('manifest'):
812 819 file_, rev = None, file_
813 820 elif rev is None:
814 821 raise error.CommandError('perfbdiff', 'invalid arguments')
815 822
816 823 textpairs = []
817 824
818 825 r = cmdutil.openrevlog(repo, 'perfbdiff', file_, opts)
819 826
820 827 startrev = r.rev(r.lookup(rev))
821 828 for rev in range(startrev, min(startrev + count, len(r) - 1)):
822 829 if opts['alldata']:
823 830 # Load revisions associated with changeset.
824 831 ctx = repo[rev]
825 832 mtext = repo.manifestlog._revlog.revision(ctx.manifestnode())
826 833 for pctx in ctx.parents():
827 834 pman = repo.manifestlog._revlog.revision(pctx.manifestnode())
828 835 textpairs.append((pman, mtext))
829 836
830 837 # Load filelog revisions by iterating manifest delta.
831 838 man = ctx.manifest()
832 839 pman = ctx.p1().manifest()
833 840 for filename, change in pman.diff(man).items():
834 841 fctx = repo.file(filename)
835 842 f1 = fctx.revision(change[0][0] or -1)
836 843 f2 = fctx.revision(change[1][0] or -1)
837 844 textpairs.append((f1, f2))
838 845 else:
839 846 dp = r.deltaparent(rev)
840 847 textpairs.append((r.revision(dp), r.revision(rev)))
841 848
842 849 def d():
843 850 for pair in textpairs:
844 851 mdiff.textdiff(*pair)
845 852
846 853 timer, fm = gettimer(ui, opts)
847 854 timer(d)
848 855 fm.end()
849 856
850 857 @command('perfdiffwd', formatteropts)
851 858 def perfdiffwd(ui, repo, **opts):
852 859 """Profile diff of working directory changes"""
853 860 timer, fm = gettimer(ui, opts)
854 861 options = {
855 862 'w': 'ignore_all_space',
856 863 'b': 'ignore_space_change',
857 864 'B': 'ignore_blank_lines',
858 865 }
859 866
860 867 for diffopt in ('', 'w', 'b', 'B', 'wB'):
861 868 opts = dict((options[c], '1') for c in diffopt)
862 869 def d():
863 870 ui.pushbuffer()
864 871 commands.diff(ui, repo, **opts)
865 872 ui.popbuffer()
866 873 title = 'diffopts: %s' % (diffopt and ('-' + diffopt) or 'none')
867 874 timer(d, title)
868 875 fm.end()
869 876
870 877 @command('perfrevlogindex', revlogopts + formatteropts,
871 878 '-c|-m|FILE')
872 879 def perfrevlogindex(ui, repo, file_=None, **opts):
873 880 """Benchmark operations against a revlog index.
874 881
875 882 This tests constructing a revlog instance, reading index data,
876 883 parsing index data, and performing various operations related to
877 884 index data.
878 885 """
879 886
880 887 rl = cmdutil.openrevlog(repo, 'perfrevlogindex', file_, opts)
881 888
882 889 opener = getattr(rl, 'opener') # trick linter
883 890 indexfile = rl.indexfile
884 891 data = opener.read(indexfile)
885 892
886 893 header = struct.unpack('>I', data[0:4])[0]
887 894 version = header & 0xFFFF
888 895 if version == 1:
889 896 revlogio = revlog.revlogio()
890 897 inline = header & (1 << 16)
891 898 else:
892 899 raise error.Abort(('unsupported revlog version: %d') % version)
893 900
894 901 rllen = len(rl)
895 902
896 903 node0 = rl.node(0)
897 904 node25 = rl.node(rllen // 4)
898 905 node50 = rl.node(rllen // 2)
899 906 node75 = rl.node(rllen // 4 * 3)
900 907 node100 = rl.node(rllen - 1)
901 908
902 909 allrevs = range(rllen)
903 910 allrevsrev = list(reversed(allrevs))
904 911 allnodes = [rl.node(rev) for rev in range(rllen)]
905 912 allnodesrev = list(reversed(allnodes))
906 913
907 914 def constructor():
908 915 revlog.revlog(opener, indexfile)
909 916
910 917 def read():
911 918 with opener(indexfile) as fh:
912 919 fh.read()
913 920
914 921 def parseindex():
915 922 revlogio.parseindex(data, inline)
916 923
917 924 def getentry(revornode):
918 925 index = revlogio.parseindex(data, inline)[0]
919 926 index[revornode]
920 927
921 928 def getentries(revs, count=1):
922 929 index = revlogio.parseindex(data, inline)[0]
923 930
924 931 for i in range(count):
925 932 for rev in revs:
926 933 index[rev]
927 934
928 935 def resolvenode(node):
929 936 nodemap = revlogio.parseindex(data, inline)[1]
930 937 # This only works for the C code.
931 938 if nodemap is None:
932 939 return
933 940
934 941 try:
935 942 nodemap[node]
936 943 except error.RevlogError:
937 944 pass
938 945
939 946 def resolvenodes(nodes, count=1):
940 947 nodemap = revlogio.parseindex(data, inline)[1]
941 948 if nodemap is None:
942 949 return
943 950
944 951 for i in range(count):
945 952 for node in nodes:
946 953 try:
947 954 nodemap[node]
948 955 except error.RevlogError:
949 956 pass
950 957
951 958 benches = [
952 959 (constructor, 'revlog constructor'),
953 960 (read, 'read'),
954 961 (parseindex, 'create index object'),
955 962 (lambda: getentry(0), 'retrieve index entry for rev 0'),
956 963 (lambda: resolvenode('a' * 20), 'look up missing node'),
957 964 (lambda: resolvenode(node0), 'look up node at rev 0'),
958 965 (lambda: resolvenode(node25), 'look up node at 1/4 len'),
959 966 (lambda: resolvenode(node50), 'look up node at 1/2 len'),
960 967 (lambda: resolvenode(node75), 'look up node at 3/4 len'),
961 968 (lambda: resolvenode(node100), 'look up node at tip'),
962 969 # 2x variation is to measure caching impact.
963 970 (lambda: resolvenodes(allnodes),
964 971 'look up all nodes (forward)'),
965 972 (lambda: resolvenodes(allnodes, 2),
966 973 'look up all nodes 2x (forward)'),
967 974 (lambda: resolvenodes(allnodesrev),
968 975 'look up all nodes (reverse)'),
969 976 (lambda: resolvenodes(allnodesrev, 2),
970 977 'look up all nodes 2x (reverse)'),
971 978 (lambda: getentries(allrevs),
972 979 'retrieve all index entries (forward)'),
973 980 (lambda: getentries(allrevs, 2),
974 981 'retrieve all index entries 2x (forward)'),
975 982 (lambda: getentries(allrevsrev),
976 983 'retrieve all index entries (reverse)'),
977 984 (lambda: getentries(allrevsrev, 2),
978 985 'retrieve all index entries 2x (reverse)'),
979 986 ]
980 987
981 988 for fn, title in benches:
982 989 timer, fm = gettimer(ui, opts)
983 990 timer(fn, title=title)
984 991 fm.end()
985 992
986 993 @command('perfrevlogrevisions', revlogopts + formatteropts +
987 994 [('d', 'dist', 100, 'distance between the revisions'),
988 995 ('s', 'startrev', 0, 'revision to start reading at'),
989 996 ('', 'reverse', False, 'read in reverse')],
990 997 '-c|-m|FILE')
991 998 def perfrevlogrevisions(ui, repo, file_=None, startrev=0, reverse=False,
992 999 **opts):
993 1000 """Benchmark reading a series of revisions from a revlog.
994 1001
995 1002 By default, we read every ``-d/--dist`` revision from 0 to tip of
996 1003 the specified revlog.
997 1004
998 1005 The start revision can be defined via ``-s/--startrev``.
999 1006 """
1000 1007 rl = cmdutil.openrevlog(repo, 'perfrevlogrevisions', file_, opts)
1001 1008 rllen = getlen(ui)(rl)
1002 1009
1003 1010 def d():
1004 1011 rl.clearcaches()
1005 1012
1006 1013 beginrev = startrev
1007 1014 endrev = rllen
1008 1015 dist = opts['dist']
1009 1016
1010 1017 if reverse:
1011 1018 beginrev, endrev = endrev, beginrev
1012 1019 dist = -1 * dist
1013 1020
1014 1021 for x in xrange(beginrev, endrev, dist):
1015 1022 # Old revisions don't support passing int.
1016 1023 n = rl.node(x)
1017 1024 rl.revision(n)
1018 1025
1019 1026 timer, fm = gettimer(ui, opts)
1020 1027 timer(d)
1021 1028 fm.end()
1022 1029
1023 1030 @command('perfrevlogchunks', revlogopts + formatteropts +
1024 1031 [('e', 'engines', '', 'compression engines to use'),
1025 1032 ('s', 'startrev', 0, 'revision to start at')],
1026 1033 '-c|-m|FILE')
1027 1034 def perfrevlogchunks(ui, repo, file_=None, engines=None, startrev=0, **opts):
1028 1035 """Benchmark operations on revlog chunks.
1029 1036
1030 1037 Logically, each revlog is a collection of fulltext revisions. However,
1031 1038 stored within each revlog are "chunks" of possibly compressed data. This
1032 1039 data needs to be read and decompressed or compressed and written.
1033 1040
1034 1041 This command measures the time it takes to read+decompress and recompress
1035 1042 chunks in a revlog. It effectively isolates I/O and compression performance.
1036 1043 For measurements of higher-level operations like resolving revisions,
1037 1044 see ``perfrevlogrevisions`` and ``perfrevlogrevision``.
1038 1045 """
1039 1046 rl = cmdutil.openrevlog(repo, 'perfrevlogchunks', file_, opts)
1040 1047
1041 1048 # _chunkraw was renamed to _getsegmentforrevs.
1042 1049 try:
1043 1050 segmentforrevs = rl._getsegmentforrevs
1044 1051 except AttributeError:
1045 1052 segmentforrevs = rl._chunkraw
1046 1053
1047 1054 # Verify engines argument.
1048 1055 if engines:
1049 1056 engines = set(e.strip() for e in engines.split(','))
1050 1057 for engine in engines:
1051 1058 try:
1052 1059 util.compressionengines[engine]
1053 1060 except KeyError:
1054 1061 raise error.Abort('unknown compression engine: %s' % engine)
1055 1062 else:
1056 1063 engines = []
1057 1064 for e in util.compengines:
1058 1065 engine = util.compengines[e]
1059 1066 try:
1060 1067 if engine.available():
1061 1068 engine.revlogcompressor().compress('dummy')
1062 1069 engines.append(e)
1063 1070 except NotImplementedError:
1064 1071 pass
1065 1072
1066 1073 revs = list(rl.revs(startrev, len(rl) - 1))
1067 1074
1068 1075 def rlfh(rl):
1069 1076 if rl._inline:
1070 1077 return getsvfs(repo)(rl.indexfile)
1071 1078 else:
1072 1079 return getsvfs(repo)(rl.datafile)
1073 1080
1074 1081 def doread():
1075 1082 rl.clearcaches()
1076 1083 for rev in revs:
1077 1084 segmentforrevs(rev, rev)
1078 1085
1079 1086 def doreadcachedfh():
1080 1087 rl.clearcaches()
1081 1088 fh = rlfh(rl)
1082 1089 for rev in revs:
1083 1090 segmentforrevs(rev, rev, df=fh)
1084 1091
1085 1092 def doreadbatch():
1086 1093 rl.clearcaches()
1087 1094 segmentforrevs(revs[0], revs[-1])
1088 1095
1089 1096 def doreadbatchcachedfh():
1090 1097 rl.clearcaches()
1091 1098 fh = rlfh(rl)
1092 1099 segmentforrevs(revs[0], revs[-1], df=fh)
1093 1100
1094 1101 def dochunk():
1095 1102 rl.clearcaches()
1096 1103 fh = rlfh(rl)
1097 1104 for rev in revs:
1098 1105 rl._chunk(rev, df=fh)
1099 1106
1100 1107 chunks = [None]
1101 1108
1102 1109 def dochunkbatch():
1103 1110 rl.clearcaches()
1104 1111 fh = rlfh(rl)
1105 1112 # Save chunks as a side-effect.
1106 1113 chunks[0] = rl._chunks(revs, df=fh)
1107 1114
1108 1115 def docompress(compressor):
1109 1116 rl.clearcaches()
1110 1117
1111 1118 try:
1112 1119 # Swap in the requested compression engine.
1113 1120 oldcompressor = rl._compressor
1114 1121 rl._compressor = compressor
1115 1122 for chunk in chunks[0]:
1116 1123 rl.compress(chunk)
1117 1124 finally:
1118 1125 rl._compressor = oldcompressor
1119 1126
1120 1127 benches = [
1121 1128 (lambda: doread(), 'read'),
1122 1129 (lambda: doreadcachedfh(), 'read w/ reused fd'),
1123 1130 (lambda: doreadbatch(), 'read batch'),
1124 1131 (lambda: doreadbatchcachedfh(), 'read batch w/ reused fd'),
1125 1132 (lambda: dochunk(), 'chunk'),
1126 1133 (lambda: dochunkbatch(), 'chunk batch'),
1127 1134 ]
1128 1135
1129 1136 for engine in sorted(engines):
1130 1137 compressor = util.compengines[engine].revlogcompressor()
1131 1138 benches.append((functools.partial(docompress, compressor),
1132 1139 'compress w/ %s' % engine))
1133 1140
1134 1141 for fn, title in benches:
1135 1142 timer, fm = gettimer(ui, opts)
1136 1143 timer(fn, title=title)
1137 1144 fm.end()
1138 1145
1139 1146 @command('perfrevlogrevision', revlogopts + formatteropts +
1140 1147 [('', 'cache', False, 'use caches instead of clearing')],
1141 1148 '-c|-m|FILE REV')
1142 1149 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
1143 1150 """Benchmark obtaining a revlog revision.
1144 1151
1145 1152 Obtaining a revlog revision consists of roughly the following steps:
1146 1153
1147 1154 1. Compute the delta chain
1148 1155 2. Obtain the raw chunks for that delta chain
1149 1156 3. Decompress each raw chunk
1150 1157 4. Apply binary patches to obtain fulltext
1151 1158 5. Verify hash of fulltext
1152 1159
1153 1160 This command measures the time spent in each of these phases.
1154 1161 """
1155 1162 if opts.get('changelog') or opts.get('manifest'):
1156 1163 file_, rev = None, file_
1157 1164 elif rev is None:
1158 1165 raise error.CommandError('perfrevlogrevision', 'invalid arguments')
1159 1166
1160 1167 r = cmdutil.openrevlog(repo, 'perfrevlogrevision', file_, opts)
1161 1168
1162 1169 # _chunkraw was renamed to _getsegmentforrevs.
1163 1170 try:
1164 1171 segmentforrevs = r._getsegmentforrevs
1165 1172 except AttributeError:
1166 1173 segmentforrevs = r._chunkraw
1167 1174
1168 1175 node = r.lookup(rev)
1169 1176 rev = r.rev(node)
1170 1177
1171 1178 def getrawchunks(data, chain):
1172 1179 start = r.start
1173 1180 length = r.length
1174 1181 inline = r._inline
1175 1182 iosize = r._io.size
1176 1183 buffer = util.buffer
1177 1184 offset = start(chain[0])
1178 1185
1179 1186 chunks = []
1180 1187 ladd = chunks.append
1181 1188
1182 1189 for rev in chain:
1183 1190 chunkstart = start(rev)
1184 1191 if inline:
1185 1192 chunkstart += (rev + 1) * iosize
1186 1193 chunklength = length(rev)
1187 1194 ladd(buffer(data, chunkstart - offset, chunklength))
1188 1195
1189 1196 return chunks
1190 1197
1191 1198 def dodeltachain(rev):
1192 1199 if not cache:
1193 1200 r.clearcaches()
1194 1201 r._deltachain(rev)
1195 1202
1196 1203 def doread(chain):
1197 1204 if not cache:
1198 1205 r.clearcaches()
1199 1206 segmentforrevs(chain[0], chain[-1])
1200 1207
1201 1208 def dorawchunks(data, chain):
1202 1209 if not cache:
1203 1210 r.clearcaches()
1204 1211 getrawchunks(data, chain)
1205 1212
1206 1213 def dodecompress(chunks):
1207 1214 decomp = r.decompress
1208 1215 for chunk in chunks:
1209 1216 decomp(chunk)
1210 1217
1211 1218 def dopatch(text, bins):
1212 1219 if not cache:
1213 1220 r.clearcaches()
1214 1221 mdiff.patches(text, bins)
1215 1222
1216 1223 def dohash(text):
1217 1224 if not cache:
1218 1225 r.clearcaches()
1219 1226 r.checkhash(text, node, rev=rev)
1220 1227
1221 1228 def dorevision():
1222 1229 if not cache:
1223 1230 r.clearcaches()
1224 1231 r.revision(node)
1225 1232
1226 1233 chain = r._deltachain(rev)[0]
1227 1234 data = segmentforrevs(chain[0], chain[-1])[1]
1228 1235 rawchunks = getrawchunks(data, chain)
1229 1236 bins = r._chunks(chain)
1230 1237 text = str(bins[0])
1231 1238 bins = bins[1:]
1232 1239 text = mdiff.patches(text, bins)
1233 1240
1234 1241 benches = [
1235 1242 (lambda: dorevision(), 'full'),
1236 1243 (lambda: dodeltachain(rev), 'deltachain'),
1237 1244 (lambda: doread(chain), 'read'),
1238 1245 (lambda: dorawchunks(data, chain), 'rawchunks'),
1239 1246 (lambda: dodecompress(rawchunks), 'decompress'),
1240 1247 (lambda: dopatch(text, bins), 'patch'),
1241 1248 (lambda: dohash(text), 'hash'),
1242 1249 ]
1243 1250
1244 1251 for fn, title in benches:
1245 1252 timer, fm = gettimer(ui, opts)
1246 1253 timer(fn, title=title)
1247 1254 fm.end()
1248 1255
1249 1256 @command('perfrevset',
1250 1257 [('C', 'clear', False, 'clear volatile cache between each call.'),
1251 1258 ('', 'contexts', False, 'obtain changectx for each revision')]
1252 1259 + formatteropts, "REVSET")
1253 1260 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
1254 1261 """benchmark the execution time of a revset
1255 1262
1256 1263 Use the --clean option if need to evaluate the impact of build volatile
1257 1264 revisions set cache on the revset execution. Volatile cache hold filtered
1258 1265 and obsolete related cache."""
1259 1266 timer, fm = gettimer(ui, opts)
1260 1267 def d():
1261 1268 if clear:
1262 1269 repo.invalidatevolatilesets()
1263 1270 if contexts:
1264 1271 for ctx in repo.set(expr): pass
1265 1272 else:
1266 1273 for r in repo.revs(expr): pass
1267 1274 timer(d)
1268 1275 fm.end()
1269 1276
1270 1277 @command('perfvolatilesets',
1271 1278 [('', 'clear-obsstore', False, 'drop obsstore between each call.'),
1272 1279 ] + formatteropts)
1273 1280 def perfvolatilesets(ui, repo, *names, **opts):
1274 1281 """benchmark the computation of various volatile set
1275 1282
1276 1283 Volatile set computes element related to filtering and obsolescence."""
1277 1284 timer, fm = gettimer(ui, opts)
1278 1285 repo = repo.unfiltered()
1279 1286
1280 1287 def getobs(name):
1281 1288 def d():
1282 1289 repo.invalidatevolatilesets()
1283 1290 if opts['clear_obsstore']:
1284 1291 clearfilecache(repo, 'obsstore')
1285 1292 obsolete.getrevs(repo, name)
1286 1293 return d
1287 1294
1288 1295 allobs = sorted(obsolete.cachefuncs)
1289 1296 if names:
1290 1297 allobs = [n for n in allobs if n in names]
1291 1298
1292 1299 for name in allobs:
1293 1300 timer(getobs(name), title=name)
1294 1301
1295 1302 def getfiltered(name):
1296 1303 def d():
1297 1304 repo.invalidatevolatilesets()
1298 1305 if opts['clear_obsstore']:
1299 1306 clearfilecache(repo, 'obsstore')
1300 1307 repoview.filterrevs(repo, name)
1301 1308 return d
1302 1309
1303 1310 allfilter = sorted(repoview.filtertable)
1304 1311 if names:
1305 1312 allfilter = [n for n in allfilter if n in names]
1306 1313
1307 1314 for name in allfilter:
1308 1315 timer(getfiltered(name), title=name)
1309 1316 fm.end()
1310 1317
1311 1318 @command('perfbranchmap',
1312 1319 [('f', 'full', False,
1313 1320 'Includes build time of subset'),
1314 1321 ('', 'clear-revbranch', False,
1315 1322 'purge the revbranch cache between computation'),
1316 1323 ] + formatteropts)
1317 1324 def perfbranchmap(ui, repo, full=False, clear_revbranch=False, **opts):
1318 1325 """benchmark the update of a branchmap
1319 1326
1320 1327 This benchmarks the full repo.branchmap() call with read and write disabled
1321 1328 """
1322 1329 timer, fm = gettimer(ui, opts)
1323 1330 def getbranchmap(filtername):
1324 1331 """generate a benchmark function for the filtername"""
1325 1332 if filtername is None:
1326 1333 view = repo
1327 1334 else:
1328 1335 view = repo.filtered(filtername)
1329 1336 def d():
1330 1337 if clear_revbranch:
1331 1338 repo.revbranchcache()._clear()
1332 1339 if full:
1333 1340 view._branchcaches.clear()
1334 1341 else:
1335 1342 view._branchcaches.pop(filtername, None)
1336 1343 view.branchmap()
1337 1344 return d
1338 1345 # add filter in smaller subset to bigger subset
1339 1346 possiblefilters = set(repoview.filtertable)
1340 1347 subsettable = getbranchmapsubsettable()
1341 1348 allfilters = []
1342 1349 while possiblefilters:
1343 1350 for name in possiblefilters:
1344 1351 subset = subsettable.get(name)
1345 1352 if subset not in possiblefilters:
1346 1353 break
1347 1354 else:
1348 1355 assert False, 'subset cycle %s!' % possiblefilters
1349 1356 allfilters.append(name)
1350 1357 possiblefilters.remove(name)
1351 1358
1352 1359 # warm the cache
1353 1360 if not full:
1354 1361 for name in allfilters:
1355 1362 repo.filtered(name).branchmap()
1356 1363 # add unfiltered
1357 1364 allfilters.append(None)
1358 1365
1359 1366 branchcacheread = safeattrsetter(branchmap, 'read')
1360 1367 branchcachewrite = safeattrsetter(branchmap.branchcache, 'write')
1361 1368 branchcacheread.set(lambda repo: None)
1362 1369 branchcachewrite.set(lambda bc, repo: None)
1363 1370 try:
1364 1371 for name in allfilters:
1365 1372 timer(getbranchmap(name), title=str(name))
1366 1373 finally:
1367 1374 branchcacheread.restore()
1368 1375 branchcachewrite.restore()
1369 1376 fm.end()
1370 1377
1371 1378 @command('perfloadmarkers')
1372 1379 def perfloadmarkers(ui, repo):
1373 1380 """benchmark the time to parse the on-disk markers for a repo
1374 1381
1375 1382 Result is the number of markers in the repo."""
1376 1383 timer, fm = gettimer(ui)
1377 1384 svfs = getsvfs(repo)
1378 1385 timer(lambda: len(obsolete.obsstore(svfs)))
1379 1386 fm.end()
1380 1387
1381 1388 @command('perflrucachedict', formatteropts +
1382 1389 [('', 'size', 4, 'size of cache'),
1383 1390 ('', 'gets', 10000, 'number of key lookups'),
1384 1391 ('', 'sets', 10000, 'number of key sets'),
1385 1392 ('', 'mixed', 10000, 'number of mixed mode operations'),
1386 1393 ('', 'mixedgetfreq', 50, 'frequency of get vs set ops in mixed mode')],
1387 1394 norepo=True)
1388 1395 def perflrucache(ui, size=4, gets=10000, sets=10000, mixed=10000,
1389 1396 mixedgetfreq=50, **opts):
1390 1397 def doinit():
1391 1398 for i in xrange(10000):
1392 1399 util.lrucachedict(size)
1393 1400
1394 1401 values = []
1395 1402 for i in xrange(size):
1396 1403 values.append(random.randint(0, sys.maxint))
1397 1404
1398 1405 # Get mode fills the cache and tests raw lookup performance with no
1399 1406 # eviction.
1400 1407 getseq = []
1401 1408 for i in xrange(gets):
1402 1409 getseq.append(random.choice(values))
1403 1410
1404 1411 def dogets():
1405 1412 d = util.lrucachedict(size)
1406 1413 for v in values:
1407 1414 d[v] = v
1408 1415 for key in getseq:
1409 1416 value = d[key]
1410 1417 value # silence pyflakes warning
1411 1418
1412 1419 # Set mode tests insertion speed with cache eviction.
1413 1420 setseq = []
1414 1421 for i in xrange(sets):
1415 1422 setseq.append(random.randint(0, sys.maxint))
1416 1423
1417 1424 def dosets():
1418 1425 d = util.lrucachedict(size)
1419 1426 for v in setseq:
1420 1427 d[v] = v
1421 1428
1422 1429 # Mixed mode randomly performs gets and sets with eviction.
1423 1430 mixedops = []
1424 1431 for i in xrange(mixed):
1425 1432 r = random.randint(0, 100)
1426 1433 if r < mixedgetfreq:
1427 1434 op = 0
1428 1435 else:
1429 1436 op = 1
1430 1437
1431 1438 mixedops.append((op, random.randint(0, size * 2)))
1432 1439
1433 1440 def domixed():
1434 1441 d = util.lrucachedict(size)
1435 1442
1436 1443 for op, v in mixedops:
1437 1444 if op == 0:
1438 1445 try:
1439 1446 d[v]
1440 1447 except KeyError:
1441 1448 pass
1442 1449 else:
1443 1450 d[v] = v
1444 1451
1445 1452 benches = [
1446 1453 (doinit, 'init'),
1447 1454 (dogets, 'gets'),
1448 1455 (dosets, 'sets'),
1449 1456 (domixed, 'mixed')
1450 1457 ]
1451 1458
1452 1459 for fn, title in benches:
1453 1460 timer, fm = gettimer(ui, opts)
1454 1461 timer(fn, title=title)
1455 1462 fm.end()
1456 1463
1457 1464 @command('perfwrite', formatteropts)
1458 1465 def perfwrite(ui, repo, **opts):
1459 1466 """microbenchmark ui.write
1460 1467 """
1461 1468 timer, fm = gettimer(ui, opts)
1462 1469 def write():
1463 1470 for i in range(100000):
1464 1471 ui.write(('Testing write performance\n'))
1465 1472 timer(write)
1466 1473 fm.end()
1467 1474
1468 1475 def uisetup(ui):
1469 1476 if (util.safehasattr(cmdutil, 'openrevlog') and
1470 1477 not util.safehasattr(commands, 'debugrevlogopts')):
1471 1478 # for "historical portability":
1472 1479 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
1473 1480 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
1474 1481 # openrevlog() should cause failure, because it has been
1475 1482 # available since 3.5 (or 49c583ca48c4).
1476 1483 def openrevlog(orig, repo, cmd, file_, opts):
1477 1484 if opts.get('dir') and not util.safehasattr(repo, 'dirlog'):
1478 1485 raise error.Abort("This version doesn't support --dir option",
1479 1486 hint="use 3.5 or later")
1480 1487 return orig(repo, cmd, file_, opts)
1481 1488 extensions.wrapfunction(cmdutil, 'openrevlog', openrevlog)
General Comments 0
You need to be logged in to leave comments. Login now