##// END OF EJS Templates
perf: add a perfbookmarks command...
marmoute -
r32733:2b0a8b0f default
parent child Browse files
Show More
@@ -1,1488 +1,1498 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 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 @command('perfbookmarks', formatteropts)
471 def perfbookmarks(ui, repo, **opts):
472 """benchmark parsing bookmarks from disk to memory"""
473 timer, fm = gettimer(ui, opts)
474 def d():
475 clearfilecache(repo, '_bookmarks')
476 repo._bookmarks
477 timer(d)
478 fm.end()
479
470 480 @command('perfchangegroupchangelog', formatteropts +
471 481 [('', 'version', '02', 'changegroup version'),
472 482 ('r', 'rev', '', 'revisions to add to changegroup')])
473 483 def perfchangegroupchangelog(ui, repo, version='02', rev=None, **opts):
474 484 """Benchmark producing a changelog group for a changegroup.
475 485
476 486 This measures the time spent processing the changelog during a
477 487 bundle operation. This occurs during `hg bundle` and on a server
478 488 processing a `getbundle` wire protocol request (handles clones
479 489 and pull requests).
480 490
481 491 By default, all revisions are added to the changegroup.
482 492 """
483 493 cl = repo.changelog
484 494 revs = [cl.lookup(r) for r in repo.revs(rev or 'all()')]
485 495 bundler = changegroup.getbundler(version, repo)
486 496
487 497 def lookup(node):
488 498 # The real bundler reads the revision in order to access the
489 499 # manifest node and files list. Do that here.
490 500 cl.read(node)
491 501 return node
492 502
493 503 def d():
494 504 for chunk in bundler.group(revs, cl, lookup):
495 505 pass
496 506
497 507 timer, fm = gettimer(ui, opts)
498 508 timer(d)
499 509 fm.end()
500 510
501 511 @command('perfdirs', formatteropts)
502 512 def perfdirs(ui, repo, **opts):
503 513 timer, fm = gettimer(ui, opts)
504 514 dirstate = repo.dirstate
505 515 'a' in dirstate
506 516 def d():
507 517 dirstate.dirs()
508 518 del dirstate._dirs
509 519 timer(d)
510 520 fm.end()
511 521
512 522 @command('perfdirstate', formatteropts)
513 523 def perfdirstate(ui, repo, **opts):
514 524 timer, fm = gettimer(ui, opts)
515 525 "a" in repo.dirstate
516 526 def d():
517 527 repo.dirstate.invalidate()
518 528 "a" in repo.dirstate
519 529 timer(d)
520 530 fm.end()
521 531
522 532 @command('perfdirstatedirs', formatteropts)
523 533 def perfdirstatedirs(ui, repo, **opts):
524 534 timer, fm = gettimer(ui, opts)
525 535 "a" in repo.dirstate
526 536 def d():
527 537 "a" in repo.dirstate._dirs
528 538 del repo.dirstate._dirs
529 539 timer(d)
530 540 fm.end()
531 541
532 542 @command('perfdirstatefoldmap', formatteropts)
533 543 def perfdirstatefoldmap(ui, repo, **opts):
534 544 timer, fm = gettimer(ui, opts)
535 545 dirstate = repo.dirstate
536 546 'a' in dirstate
537 547 def d():
538 548 dirstate._filefoldmap.get('a')
539 549 del dirstate._filefoldmap
540 550 timer(d)
541 551 fm.end()
542 552
543 553 @command('perfdirfoldmap', formatteropts)
544 554 def perfdirfoldmap(ui, repo, **opts):
545 555 timer, fm = gettimer(ui, opts)
546 556 dirstate = repo.dirstate
547 557 'a' in dirstate
548 558 def d():
549 559 dirstate._dirfoldmap.get('a')
550 560 del dirstate._dirfoldmap
551 561 del dirstate._dirs
552 562 timer(d)
553 563 fm.end()
554 564
555 565 @command('perfdirstatewrite', formatteropts)
556 566 def perfdirstatewrite(ui, repo, **opts):
557 567 timer, fm = gettimer(ui, opts)
558 568 ds = repo.dirstate
559 569 "a" in ds
560 570 def d():
561 571 ds._dirty = True
562 572 ds.write(repo.currenttransaction())
563 573 timer(d)
564 574 fm.end()
565 575
566 576 @command('perfmergecalculate',
567 577 [('r', 'rev', '.', 'rev to merge against')] + formatteropts)
568 578 def perfmergecalculate(ui, repo, rev, **opts):
569 579 timer, fm = gettimer(ui, opts)
570 580 wctx = repo[None]
571 581 rctx = scmutil.revsingle(repo, rev, rev)
572 582 ancestor = wctx.ancestor(rctx)
573 583 # we don't want working dir files to be stat'd in the benchmark, so prime
574 584 # that cache
575 585 wctx.dirty()
576 586 def d():
577 587 # acceptremote is True because we don't want prompts in the middle of
578 588 # our benchmark
579 589 merge.calculateupdates(repo, wctx, rctx, [ancestor], False, False,
580 590 acceptremote=True, followcopies=True)
581 591 timer(d)
582 592 fm.end()
583 593
584 594 @command('perfpathcopies', [], "REV REV")
585 595 def perfpathcopies(ui, repo, rev1, rev2, **opts):
586 596 timer, fm = gettimer(ui, opts)
587 597 ctx1 = scmutil.revsingle(repo, rev1, rev1)
588 598 ctx2 = scmutil.revsingle(repo, rev2, rev2)
589 599 def d():
590 600 copies.pathcopies(ctx1, ctx2)
591 601 timer(d)
592 602 fm.end()
593 603
594 604 @command('perfphases',
595 605 [('', 'full', False, 'include file reading time too'),
596 606 ], "")
597 607 def perfphases(ui, repo, **opts):
598 608 """benchmark phasesets computation"""
599 609 timer, fm = gettimer(ui, opts)
600 610 _phases = repo._phasecache
601 611 full = opts.get('full')
602 612 def d():
603 613 phases = _phases
604 614 if full:
605 615 clearfilecache(repo, '_phasecache')
606 616 phases = repo._phasecache
607 617 phases.invalidate()
608 618 phases.loadphaserevs(repo)
609 619 timer(d)
610 620 fm.end()
611 621
612 622 @command('perfmanifest', [], 'REV')
613 623 def perfmanifest(ui, repo, rev, **opts):
614 624 timer, fm = gettimer(ui, opts)
615 625 ctx = scmutil.revsingle(repo, rev, rev)
616 626 t = ctx.manifestnode()
617 627 def d():
618 628 repo.manifestlog.clearcaches()
619 629 repo.manifestlog[t].read()
620 630 timer(d)
621 631 fm.end()
622 632
623 633 @command('perfchangeset', formatteropts)
624 634 def perfchangeset(ui, repo, rev, **opts):
625 635 timer, fm = gettimer(ui, opts)
626 636 n = repo[rev].node()
627 637 def d():
628 638 repo.changelog.read(n)
629 639 #repo.changelog._cache = None
630 640 timer(d)
631 641 fm.end()
632 642
633 643 @command('perfindex', formatteropts)
634 644 def perfindex(ui, repo, **opts):
635 645 import mercurial.revlog
636 646 timer, fm = gettimer(ui, opts)
637 647 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
638 648 n = repo["tip"].node()
639 649 svfs = getsvfs(repo)
640 650 def d():
641 651 cl = mercurial.revlog.revlog(svfs, "00changelog.i")
642 652 cl.rev(n)
643 653 timer(d)
644 654 fm.end()
645 655
646 656 @command('perfstartup', formatteropts)
647 657 def perfstartup(ui, repo, **opts):
648 658 timer, fm = gettimer(ui, opts)
649 659 cmd = sys.argv[0]
650 660 def d():
651 661 if os.name != 'nt':
652 662 os.system("HGRCPATH= %s version -q > /dev/null" % cmd)
653 663 else:
654 664 os.environ['HGRCPATH'] = ''
655 665 os.system("%s version -q > NUL" % cmd)
656 666 timer(d)
657 667 fm.end()
658 668
659 669 @command('perfparents', formatteropts)
660 670 def perfparents(ui, repo, **opts):
661 671 timer, fm = gettimer(ui, opts)
662 672 # control the number of commits perfparents iterates over
663 673 # experimental config: perf.parentscount
664 674 count = getint(ui, "perf", "parentscount", 1000)
665 675 if len(repo.changelog) < count:
666 676 raise error.Abort("repo needs %d commits for this test" % count)
667 677 repo = repo.unfiltered()
668 678 nl = [repo.changelog.node(i) for i in xrange(count)]
669 679 def d():
670 680 for n in nl:
671 681 repo.changelog.parents(n)
672 682 timer(d)
673 683 fm.end()
674 684
675 685 @command('perfctxfiles', formatteropts)
676 686 def perfctxfiles(ui, repo, x, **opts):
677 687 x = int(x)
678 688 timer, fm = gettimer(ui, opts)
679 689 def d():
680 690 len(repo[x].files())
681 691 timer(d)
682 692 fm.end()
683 693
684 694 @command('perfrawfiles', formatteropts)
685 695 def perfrawfiles(ui, repo, x, **opts):
686 696 x = int(x)
687 697 timer, fm = gettimer(ui, opts)
688 698 cl = repo.changelog
689 699 def d():
690 700 len(cl.read(x)[3])
691 701 timer(d)
692 702 fm.end()
693 703
694 704 @command('perflookup', formatteropts)
695 705 def perflookup(ui, repo, rev, **opts):
696 706 timer, fm = gettimer(ui, opts)
697 707 timer(lambda: len(repo.lookup(rev)))
698 708 fm.end()
699 709
700 710 @command('perfrevrange', formatteropts)
701 711 def perfrevrange(ui, repo, *specs, **opts):
702 712 timer, fm = gettimer(ui, opts)
703 713 revrange = scmutil.revrange
704 714 timer(lambda: len(revrange(repo, specs)))
705 715 fm.end()
706 716
707 717 @command('perfnodelookup', formatteropts)
708 718 def perfnodelookup(ui, repo, rev, **opts):
709 719 timer, fm = gettimer(ui, opts)
710 720 import mercurial.revlog
711 721 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
712 722 n = repo[rev].node()
713 723 cl = mercurial.revlog.revlog(getsvfs(repo), "00changelog.i")
714 724 def d():
715 725 cl.rev(n)
716 726 clearcaches(cl)
717 727 timer(d)
718 728 fm.end()
719 729
720 730 @command('perflog',
721 731 [('', 'rename', False, 'ask log to follow renames')] + formatteropts)
722 732 def perflog(ui, repo, rev=None, **opts):
723 733 if rev is None:
724 734 rev=[]
725 735 timer, fm = gettimer(ui, opts)
726 736 ui.pushbuffer()
727 737 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
728 738 copies=opts.get('rename')))
729 739 ui.popbuffer()
730 740 fm.end()
731 741
732 742 @command('perfmoonwalk', formatteropts)
733 743 def perfmoonwalk(ui, repo, **opts):
734 744 """benchmark walking the changelog backwards
735 745
736 746 This also loads the changelog data for each revision in the changelog.
737 747 """
738 748 timer, fm = gettimer(ui, opts)
739 749 def moonwalk():
740 750 for i in xrange(len(repo), -1, -1):
741 751 ctx = repo[i]
742 752 ctx.branch() # read changelog data (in addition to the index)
743 753 timer(moonwalk)
744 754 fm.end()
745 755
746 756 @command('perftemplating', formatteropts)
747 757 def perftemplating(ui, repo, rev=None, **opts):
748 758 if rev is None:
749 759 rev=[]
750 760 timer, fm = gettimer(ui, opts)
751 761 ui.pushbuffer()
752 762 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
753 763 template='{date|shortdate} [{rev}:{node|short}]'
754 764 ' {author|person}: {desc|firstline}\n'))
755 765 ui.popbuffer()
756 766 fm.end()
757 767
758 768 @command('perfcca', formatteropts)
759 769 def perfcca(ui, repo, **opts):
760 770 timer, fm = gettimer(ui, opts)
761 771 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
762 772 fm.end()
763 773
764 774 @command('perffncacheload', formatteropts)
765 775 def perffncacheload(ui, repo, **opts):
766 776 timer, fm = gettimer(ui, opts)
767 777 s = repo.store
768 778 def d():
769 779 s.fncache._load()
770 780 timer(d)
771 781 fm.end()
772 782
773 783 @command('perffncachewrite', formatteropts)
774 784 def perffncachewrite(ui, repo, **opts):
775 785 timer, fm = gettimer(ui, opts)
776 786 s = repo.store
777 787 s.fncache._load()
778 788 lock = repo.lock()
779 789 tr = repo.transaction('perffncachewrite')
780 790 def d():
781 791 s.fncache._dirty = True
782 792 s.fncache.write(tr)
783 793 timer(d)
784 794 tr.close()
785 795 lock.release()
786 796 fm.end()
787 797
788 798 @command('perffncacheencode', formatteropts)
789 799 def perffncacheencode(ui, repo, **opts):
790 800 timer, fm = gettimer(ui, opts)
791 801 s = repo.store
792 802 s.fncache._load()
793 803 def d():
794 804 for p in s.fncache.entries:
795 805 s.encode(p)
796 806 timer(d)
797 807 fm.end()
798 808
799 809 @command('perfbdiff', revlogopts + formatteropts + [
800 810 ('', 'count', 1, 'number of revisions to test (when using --startrev)'),
801 811 ('', 'alldata', False, 'test bdiffs for all associated revisions')],
802 812 '-c|-m|FILE REV')
803 813 def perfbdiff(ui, repo, file_, rev=None, count=None, **opts):
804 814 """benchmark a bdiff between revisions
805 815
806 816 By default, benchmark a bdiff between its delta parent and itself.
807 817
808 818 With ``--count``, benchmark bdiffs between delta parents and self for N
809 819 revisions starting at the specified revision.
810 820
811 821 With ``--alldata``, assume the requested revision is a changeset and
812 822 measure bdiffs for all changes related to that changeset (manifest
813 823 and filelogs).
814 824 """
815 825 if opts['alldata']:
816 826 opts['changelog'] = True
817 827
818 828 if opts.get('changelog') or opts.get('manifest'):
819 829 file_, rev = None, file_
820 830 elif rev is None:
821 831 raise error.CommandError('perfbdiff', 'invalid arguments')
822 832
823 833 textpairs = []
824 834
825 835 r = cmdutil.openrevlog(repo, 'perfbdiff', file_, opts)
826 836
827 837 startrev = r.rev(r.lookup(rev))
828 838 for rev in range(startrev, min(startrev + count, len(r) - 1)):
829 839 if opts['alldata']:
830 840 # Load revisions associated with changeset.
831 841 ctx = repo[rev]
832 842 mtext = repo.manifestlog._revlog.revision(ctx.manifestnode())
833 843 for pctx in ctx.parents():
834 844 pman = repo.manifestlog._revlog.revision(pctx.manifestnode())
835 845 textpairs.append((pman, mtext))
836 846
837 847 # Load filelog revisions by iterating manifest delta.
838 848 man = ctx.manifest()
839 849 pman = ctx.p1().manifest()
840 850 for filename, change in pman.diff(man).items():
841 851 fctx = repo.file(filename)
842 852 f1 = fctx.revision(change[0][0] or -1)
843 853 f2 = fctx.revision(change[1][0] or -1)
844 854 textpairs.append((f1, f2))
845 855 else:
846 856 dp = r.deltaparent(rev)
847 857 textpairs.append((r.revision(dp), r.revision(rev)))
848 858
849 859 def d():
850 860 for pair in textpairs:
851 861 mdiff.textdiff(*pair)
852 862
853 863 timer, fm = gettimer(ui, opts)
854 864 timer(d)
855 865 fm.end()
856 866
857 867 @command('perfdiffwd', formatteropts)
858 868 def perfdiffwd(ui, repo, **opts):
859 869 """Profile diff of working directory changes"""
860 870 timer, fm = gettimer(ui, opts)
861 871 options = {
862 872 'w': 'ignore_all_space',
863 873 'b': 'ignore_space_change',
864 874 'B': 'ignore_blank_lines',
865 875 }
866 876
867 877 for diffopt in ('', 'w', 'b', 'B', 'wB'):
868 878 opts = dict((options[c], '1') for c in diffopt)
869 879 def d():
870 880 ui.pushbuffer()
871 881 commands.diff(ui, repo, **opts)
872 882 ui.popbuffer()
873 883 title = 'diffopts: %s' % (diffopt and ('-' + diffopt) or 'none')
874 884 timer(d, title)
875 885 fm.end()
876 886
877 887 @command('perfrevlogindex', revlogopts + formatteropts,
878 888 '-c|-m|FILE')
879 889 def perfrevlogindex(ui, repo, file_=None, **opts):
880 890 """Benchmark operations against a revlog index.
881 891
882 892 This tests constructing a revlog instance, reading index data,
883 893 parsing index data, and performing various operations related to
884 894 index data.
885 895 """
886 896
887 897 rl = cmdutil.openrevlog(repo, 'perfrevlogindex', file_, opts)
888 898
889 899 opener = getattr(rl, 'opener') # trick linter
890 900 indexfile = rl.indexfile
891 901 data = opener.read(indexfile)
892 902
893 903 header = struct.unpack('>I', data[0:4])[0]
894 904 version = header & 0xFFFF
895 905 if version == 1:
896 906 revlogio = revlog.revlogio()
897 907 inline = header & (1 << 16)
898 908 else:
899 909 raise error.Abort(('unsupported revlog version: %d') % version)
900 910
901 911 rllen = len(rl)
902 912
903 913 node0 = rl.node(0)
904 914 node25 = rl.node(rllen // 4)
905 915 node50 = rl.node(rllen // 2)
906 916 node75 = rl.node(rllen // 4 * 3)
907 917 node100 = rl.node(rllen - 1)
908 918
909 919 allrevs = range(rllen)
910 920 allrevsrev = list(reversed(allrevs))
911 921 allnodes = [rl.node(rev) for rev in range(rllen)]
912 922 allnodesrev = list(reversed(allnodes))
913 923
914 924 def constructor():
915 925 revlog.revlog(opener, indexfile)
916 926
917 927 def read():
918 928 with opener(indexfile) as fh:
919 929 fh.read()
920 930
921 931 def parseindex():
922 932 revlogio.parseindex(data, inline)
923 933
924 934 def getentry(revornode):
925 935 index = revlogio.parseindex(data, inline)[0]
926 936 index[revornode]
927 937
928 938 def getentries(revs, count=1):
929 939 index = revlogio.parseindex(data, inline)[0]
930 940
931 941 for i in range(count):
932 942 for rev in revs:
933 943 index[rev]
934 944
935 945 def resolvenode(node):
936 946 nodemap = revlogio.parseindex(data, inline)[1]
937 947 # This only works for the C code.
938 948 if nodemap is None:
939 949 return
940 950
941 951 try:
942 952 nodemap[node]
943 953 except error.RevlogError:
944 954 pass
945 955
946 956 def resolvenodes(nodes, count=1):
947 957 nodemap = revlogio.parseindex(data, inline)[1]
948 958 if nodemap is None:
949 959 return
950 960
951 961 for i in range(count):
952 962 for node in nodes:
953 963 try:
954 964 nodemap[node]
955 965 except error.RevlogError:
956 966 pass
957 967
958 968 benches = [
959 969 (constructor, 'revlog constructor'),
960 970 (read, 'read'),
961 971 (parseindex, 'create index object'),
962 972 (lambda: getentry(0), 'retrieve index entry for rev 0'),
963 973 (lambda: resolvenode('a' * 20), 'look up missing node'),
964 974 (lambda: resolvenode(node0), 'look up node at rev 0'),
965 975 (lambda: resolvenode(node25), 'look up node at 1/4 len'),
966 976 (lambda: resolvenode(node50), 'look up node at 1/2 len'),
967 977 (lambda: resolvenode(node75), 'look up node at 3/4 len'),
968 978 (lambda: resolvenode(node100), 'look up node at tip'),
969 979 # 2x variation is to measure caching impact.
970 980 (lambda: resolvenodes(allnodes),
971 981 'look up all nodes (forward)'),
972 982 (lambda: resolvenodes(allnodes, 2),
973 983 'look up all nodes 2x (forward)'),
974 984 (lambda: resolvenodes(allnodesrev),
975 985 'look up all nodes (reverse)'),
976 986 (lambda: resolvenodes(allnodesrev, 2),
977 987 'look up all nodes 2x (reverse)'),
978 988 (lambda: getentries(allrevs),
979 989 'retrieve all index entries (forward)'),
980 990 (lambda: getentries(allrevs, 2),
981 991 'retrieve all index entries 2x (forward)'),
982 992 (lambda: getentries(allrevsrev),
983 993 'retrieve all index entries (reverse)'),
984 994 (lambda: getentries(allrevsrev, 2),
985 995 'retrieve all index entries 2x (reverse)'),
986 996 ]
987 997
988 998 for fn, title in benches:
989 999 timer, fm = gettimer(ui, opts)
990 1000 timer(fn, title=title)
991 1001 fm.end()
992 1002
993 1003 @command('perfrevlogrevisions', revlogopts + formatteropts +
994 1004 [('d', 'dist', 100, 'distance between the revisions'),
995 1005 ('s', 'startrev', 0, 'revision to start reading at'),
996 1006 ('', 'reverse', False, 'read in reverse')],
997 1007 '-c|-m|FILE')
998 1008 def perfrevlogrevisions(ui, repo, file_=None, startrev=0, reverse=False,
999 1009 **opts):
1000 1010 """Benchmark reading a series of revisions from a revlog.
1001 1011
1002 1012 By default, we read every ``-d/--dist`` revision from 0 to tip of
1003 1013 the specified revlog.
1004 1014
1005 1015 The start revision can be defined via ``-s/--startrev``.
1006 1016 """
1007 1017 rl = cmdutil.openrevlog(repo, 'perfrevlogrevisions', file_, opts)
1008 1018 rllen = getlen(ui)(rl)
1009 1019
1010 1020 def d():
1011 1021 rl.clearcaches()
1012 1022
1013 1023 beginrev = startrev
1014 1024 endrev = rllen
1015 1025 dist = opts['dist']
1016 1026
1017 1027 if reverse:
1018 1028 beginrev, endrev = endrev, beginrev
1019 1029 dist = -1 * dist
1020 1030
1021 1031 for x in xrange(beginrev, endrev, dist):
1022 1032 # Old revisions don't support passing int.
1023 1033 n = rl.node(x)
1024 1034 rl.revision(n)
1025 1035
1026 1036 timer, fm = gettimer(ui, opts)
1027 1037 timer(d)
1028 1038 fm.end()
1029 1039
1030 1040 @command('perfrevlogchunks', revlogopts + formatteropts +
1031 1041 [('e', 'engines', '', 'compression engines to use'),
1032 1042 ('s', 'startrev', 0, 'revision to start at')],
1033 1043 '-c|-m|FILE')
1034 1044 def perfrevlogchunks(ui, repo, file_=None, engines=None, startrev=0, **opts):
1035 1045 """Benchmark operations on revlog chunks.
1036 1046
1037 1047 Logically, each revlog is a collection of fulltext revisions. However,
1038 1048 stored within each revlog are "chunks" of possibly compressed data. This
1039 1049 data needs to be read and decompressed or compressed and written.
1040 1050
1041 1051 This command measures the time it takes to read+decompress and recompress
1042 1052 chunks in a revlog. It effectively isolates I/O and compression performance.
1043 1053 For measurements of higher-level operations like resolving revisions,
1044 1054 see ``perfrevlogrevisions`` and ``perfrevlogrevision``.
1045 1055 """
1046 1056 rl = cmdutil.openrevlog(repo, 'perfrevlogchunks', file_, opts)
1047 1057
1048 1058 # _chunkraw was renamed to _getsegmentforrevs.
1049 1059 try:
1050 1060 segmentforrevs = rl._getsegmentforrevs
1051 1061 except AttributeError:
1052 1062 segmentforrevs = rl._chunkraw
1053 1063
1054 1064 # Verify engines argument.
1055 1065 if engines:
1056 1066 engines = set(e.strip() for e in engines.split(','))
1057 1067 for engine in engines:
1058 1068 try:
1059 1069 util.compressionengines[engine]
1060 1070 except KeyError:
1061 1071 raise error.Abort('unknown compression engine: %s' % engine)
1062 1072 else:
1063 1073 engines = []
1064 1074 for e in util.compengines:
1065 1075 engine = util.compengines[e]
1066 1076 try:
1067 1077 if engine.available():
1068 1078 engine.revlogcompressor().compress('dummy')
1069 1079 engines.append(e)
1070 1080 except NotImplementedError:
1071 1081 pass
1072 1082
1073 1083 revs = list(rl.revs(startrev, len(rl) - 1))
1074 1084
1075 1085 def rlfh(rl):
1076 1086 if rl._inline:
1077 1087 return getsvfs(repo)(rl.indexfile)
1078 1088 else:
1079 1089 return getsvfs(repo)(rl.datafile)
1080 1090
1081 1091 def doread():
1082 1092 rl.clearcaches()
1083 1093 for rev in revs:
1084 1094 segmentforrevs(rev, rev)
1085 1095
1086 1096 def doreadcachedfh():
1087 1097 rl.clearcaches()
1088 1098 fh = rlfh(rl)
1089 1099 for rev in revs:
1090 1100 segmentforrevs(rev, rev, df=fh)
1091 1101
1092 1102 def doreadbatch():
1093 1103 rl.clearcaches()
1094 1104 segmentforrevs(revs[0], revs[-1])
1095 1105
1096 1106 def doreadbatchcachedfh():
1097 1107 rl.clearcaches()
1098 1108 fh = rlfh(rl)
1099 1109 segmentforrevs(revs[0], revs[-1], df=fh)
1100 1110
1101 1111 def dochunk():
1102 1112 rl.clearcaches()
1103 1113 fh = rlfh(rl)
1104 1114 for rev in revs:
1105 1115 rl._chunk(rev, df=fh)
1106 1116
1107 1117 chunks = [None]
1108 1118
1109 1119 def dochunkbatch():
1110 1120 rl.clearcaches()
1111 1121 fh = rlfh(rl)
1112 1122 # Save chunks as a side-effect.
1113 1123 chunks[0] = rl._chunks(revs, df=fh)
1114 1124
1115 1125 def docompress(compressor):
1116 1126 rl.clearcaches()
1117 1127
1118 1128 try:
1119 1129 # Swap in the requested compression engine.
1120 1130 oldcompressor = rl._compressor
1121 1131 rl._compressor = compressor
1122 1132 for chunk in chunks[0]:
1123 1133 rl.compress(chunk)
1124 1134 finally:
1125 1135 rl._compressor = oldcompressor
1126 1136
1127 1137 benches = [
1128 1138 (lambda: doread(), 'read'),
1129 1139 (lambda: doreadcachedfh(), 'read w/ reused fd'),
1130 1140 (lambda: doreadbatch(), 'read batch'),
1131 1141 (lambda: doreadbatchcachedfh(), 'read batch w/ reused fd'),
1132 1142 (lambda: dochunk(), 'chunk'),
1133 1143 (lambda: dochunkbatch(), 'chunk batch'),
1134 1144 ]
1135 1145
1136 1146 for engine in sorted(engines):
1137 1147 compressor = util.compengines[engine].revlogcompressor()
1138 1148 benches.append((functools.partial(docompress, compressor),
1139 1149 'compress w/ %s' % engine))
1140 1150
1141 1151 for fn, title in benches:
1142 1152 timer, fm = gettimer(ui, opts)
1143 1153 timer(fn, title=title)
1144 1154 fm.end()
1145 1155
1146 1156 @command('perfrevlogrevision', revlogopts + formatteropts +
1147 1157 [('', 'cache', False, 'use caches instead of clearing')],
1148 1158 '-c|-m|FILE REV')
1149 1159 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
1150 1160 """Benchmark obtaining a revlog revision.
1151 1161
1152 1162 Obtaining a revlog revision consists of roughly the following steps:
1153 1163
1154 1164 1. Compute the delta chain
1155 1165 2. Obtain the raw chunks for that delta chain
1156 1166 3. Decompress each raw chunk
1157 1167 4. Apply binary patches to obtain fulltext
1158 1168 5. Verify hash of fulltext
1159 1169
1160 1170 This command measures the time spent in each of these phases.
1161 1171 """
1162 1172 if opts.get('changelog') or opts.get('manifest'):
1163 1173 file_, rev = None, file_
1164 1174 elif rev is None:
1165 1175 raise error.CommandError('perfrevlogrevision', 'invalid arguments')
1166 1176
1167 1177 r = cmdutil.openrevlog(repo, 'perfrevlogrevision', file_, opts)
1168 1178
1169 1179 # _chunkraw was renamed to _getsegmentforrevs.
1170 1180 try:
1171 1181 segmentforrevs = r._getsegmentforrevs
1172 1182 except AttributeError:
1173 1183 segmentforrevs = r._chunkraw
1174 1184
1175 1185 node = r.lookup(rev)
1176 1186 rev = r.rev(node)
1177 1187
1178 1188 def getrawchunks(data, chain):
1179 1189 start = r.start
1180 1190 length = r.length
1181 1191 inline = r._inline
1182 1192 iosize = r._io.size
1183 1193 buffer = util.buffer
1184 1194 offset = start(chain[0])
1185 1195
1186 1196 chunks = []
1187 1197 ladd = chunks.append
1188 1198
1189 1199 for rev in chain:
1190 1200 chunkstart = start(rev)
1191 1201 if inline:
1192 1202 chunkstart += (rev + 1) * iosize
1193 1203 chunklength = length(rev)
1194 1204 ladd(buffer(data, chunkstart - offset, chunklength))
1195 1205
1196 1206 return chunks
1197 1207
1198 1208 def dodeltachain(rev):
1199 1209 if not cache:
1200 1210 r.clearcaches()
1201 1211 r._deltachain(rev)
1202 1212
1203 1213 def doread(chain):
1204 1214 if not cache:
1205 1215 r.clearcaches()
1206 1216 segmentforrevs(chain[0], chain[-1])
1207 1217
1208 1218 def dorawchunks(data, chain):
1209 1219 if not cache:
1210 1220 r.clearcaches()
1211 1221 getrawchunks(data, chain)
1212 1222
1213 1223 def dodecompress(chunks):
1214 1224 decomp = r.decompress
1215 1225 for chunk in chunks:
1216 1226 decomp(chunk)
1217 1227
1218 1228 def dopatch(text, bins):
1219 1229 if not cache:
1220 1230 r.clearcaches()
1221 1231 mdiff.patches(text, bins)
1222 1232
1223 1233 def dohash(text):
1224 1234 if not cache:
1225 1235 r.clearcaches()
1226 1236 r.checkhash(text, node, rev=rev)
1227 1237
1228 1238 def dorevision():
1229 1239 if not cache:
1230 1240 r.clearcaches()
1231 1241 r.revision(node)
1232 1242
1233 1243 chain = r._deltachain(rev)[0]
1234 1244 data = segmentforrevs(chain[0], chain[-1])[1]
1235 1245 rawchunks = getrawchunks(data, chain)
1236 1246 bins = r._chunks(chain)
1237 1247 text = str(bins[0])
1238 1248 bins = bins[1:]
1239 1249 text = mdiff.patches(text, bins)
1240 1250
1241 1251 benches = [
1242 1252 (lambda: dorevision(), 'full'),
1243 1253 (lambda: dodeltachain(rev), 'deltachain'),
1244 1254 (lambda: doread(chain), 'read'),
1245 1255 (lambda: dorawchunks(data, chain), 'rawchunks'),
1246 1256 (lambda: dodecompress(rawchunks), 'decompress'),
1247 1257 (lambda: dopatch(text, bins), 'patch'),
1248 1258 (lambda: dohash(text), 'hash'),
1249 1259 ]
1250 1260
1251 1261 for fn, title in benches:
1252 1262 timer, fm = gettimer(ui, opts)
1253 1263 timer(fn, title=title)
1254 1264 fm.end()
1255 1265
1256 1266 @command('perfrevset',
1257 1267 [('C', 'clear', False, 'clear volatile cache between each call.'),
1258 1268 ('', 'contexts', False, 'obtain changectx for each revision')]
1259 1269 + formatteropts, "REVSET")
1260 1270 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
1261 1271 """benchmark the execution time of a revset
1262 1272
1263 1273 Use the --clean option if need to evaluate the impact of build volatile
1264 1274 revisions set cache on the revset execution. Volatile cache hold filtered
1265 1275 and obsolete related cache."""
1266 1276 timer, fm = gettimer(ui, opts)
1267 1277 def d():
1268 1278 if clear:
1269 1279 repo.invalidatevolatilesets()
1270 1280 if contexts:
1271 1281 for ctx in repo.set(expr): pass
1272 1282 else:
1273 1283 for r in repo.revs(expr): pass
1274 1284 timer(d)
1275 1285 fm.end()
1276 1286
1277 1287 @command('perfvolatilesets',
1278 1288 [('', 'clear-obsstore', False, 'drop obsstore between each call.'),
1279 1289 ] + formatteropts)
1280 1290 def perfvolatilesets(ui, repo, *names, **opts):
1281 1291 """benchmark the computation of various volatile set
1282 1292
1283 1293 Volatile set computes element related to filtering and obsolescence."""
1284 1294 timer, fm = gettimer(ui, opts)
1285 1295 repo = repo.unfiltered()
1286 1296
1287 1297 def getobs(name):
1288 1298 def d():
1289 1299 repo.invalidatevolatilesets()
1290 1300 if opts['clear_obsstore']:
1291 1301 clearfilecache(repo, 'obsstore')
1292 1302 obsolete.getrevs(repo, name)
1293 1303 return d
1294 1304
1295 1305 allobs = sorted(obsolete.cachefuncs)
1296 1306 if names:
1297 1307 allobs = [n for n in allobs if n in names]
1298 1308
1299 1309 for name in allobs:
1300 1310 timer(getobs(name), title=name)
1301 1311
1302 1312 def getfiltered(name):
1303 1313 def d():
1304 1314 repo.invalidatevolatilesets()
1305 1315 if opts['clear_obsstore']:
1306 1316 clearfilecache(repo, 'obsstore')
1307 1317 repoview.filterrevs(repo, name)
1308 1318 return d
1309 1319
1310 1320 allfilter = sorted(repoview.filtertable)
1311 1321 if names:
1312 1322 allfilter = [n for n in allfilter if n in names]
1313 1323
1314 1324 for name in allfilter:
1315 1325 timer(getfiltered(name), title=name)
1316 1326 fm.end()
1317 1327
1318 1328 @command('perfbranchmap',
1319 1329 [('f', 'full', False,
1320 1330 'Includes build time of subset'),
1321 1331 ('', 'clear-revbranch', False,
1322 1332 'purge the revbranch cache between computation'),
1323 1333 ] + formatteropts)
1324 1334 def perfbranchmap(ui, repo, full=False, clear_revbranch=False, **opts):
1325 1335 """benchmark the update of a branchmap
1326 1336
1327 1337 This benchmarks the full repo.branchmap() call with read and write disabled
1328 1338 """
1329 1339 timer, fm = gettimer(ui, opts)
1330 1340 def getbranchmap(filtername):
1331 1341 """generate a benchmark function for the filtername"""
1332 1342 if filtername is None:
1333 1343 view = repo
1334 1344 else:
1335 1345 view = repo.filtered(filtername)
1336 1346 def d():
1337 1347 if clear_revbranch:
1338 1348 repo.revbranchcache()._clear()
1339 1349 if full:
1340 1350 view._branchcaches.clear()
1341 1351 else:
1342 1352 view._branchcaches.pop(filtername, None)
1343 1353 view.branchmap()
1344 1354 return d
1345 1355 # add filter in smaller subset to bigger subset
1346 1356 possiblefilters = set(repoview.filtertable)
1347 1357 subsettable = getbranchmapsubsettable()
1348 1358 allfilters = []
1349 1359 while possiblefilters:
1350 1360 for name in possiblefilters:
1351 1361 subset = subsettable.get(name)
1352 1362 if subset not in possiblefilters:
1353 1363 break
1354 1364 else:
1355 1365 assert False, 'subset cycle %s!' % possiblefilters
1356 1366 allfilters.append(name)
1357 1367 possiblefilters.remove(name)
1358 1368
1359 1369 # warm the cache
1360 1370 if not full:
1361 1371 for name in allfilters:
1362 1372 repo.filtered(name).branchmap()
1363 1373 # add unfiltered
1364 1374 allfilters.append(None)
1365 1375
1366 1376 branchcacheread = safeattrsetter(branchmap, 'read')
1367 1377 branchcachewrite = safeattrsetter(branchmap.branchcache, 'write')
1368 1378 branchcacheread.set(lambda repo: None)
1369 1379 branchcachewrite.set(lambda bc, repo: None)
1370 1380 try:
1371 1381 for name in allfilters:
1372 1382 timer(getbranchmap(name), title=str(name))
1373 1383 finally:
1374 1384 branchcacheread.restore()
1375 1385 branchcachewrite.restore()
1376 1386 fm.end()
1377 1387
1378 1388 @command('perfloadmarkers')
1379 1389 def perfloadmarkers(ui, repo):
1380 1390 """benchmark the time to parse the on-disk markers for a repo
1381 1391
1382 1392 Result is the number of markers in the repo."""
1383 1393 timer, fm = gettimer(ui)
1384 1394 svfs = getsvfs(repo)
1385 1395 timer(lambda: len(obsolete.obsstore(svfs)))
1386 1396 fm.end()
1387 1397
1388 1398 @command('perflrucachedict', formatteropts +
1389 1399 [('', 'size', 4, 'size of cache'),
1390 1400 ('', 'gets', 10000, 'number of key lookups'),
1391 1401 ('', 'sets', 10000, 'number of key sets'),
1392 1402 ('', 'mixed', 10000, 'number of mixed mode operations'),
1393 1403 ('', 'mixedgetfreq', 50, 'frequency of get vs set ops in mixed mode')],
1394 1404 norepo=True)
1395 1405 def perflrucache(ui, size=4, gets=10000, sets=10000, mixed=10000,
1396 1406 mixedgetfreq=50, **opts):
1397 1407 def doinit():
1398 1408 for i in xrange(10000):
1399 1409 util.lrucachedict(size)
1400 1410
1401 1411 values = []
1402 1412 for i in xrange(size):
1403 1413 values.append(random.randint(0, sys.maxint))
1404 1414
1405 1415 # Get mode fills the cache and tests raw lookup performance with no
1406 1416 # eviction.
1407 1417 getseq = []
1408 1418 for i in xrange(gets):
1409 1419 getseq.append(random.choice(values))
1410 1420
1411 1421 def dogets():
1412 1422 d = util.lrucachedict(size)
1413 1423 for v in values:
1414 1424 d[v] = v
1415 1425 for key in getseq:
1416 1426 value = d[key]
1417 1427 value # silence pyflakes warning
1418 1428
1419 1429 # Set mode tests insertion speed with cache eviction.
1420 1430 setseq = []
1421 1431 for i in xrange(sets):
1422 1432 setseq.append(random.randint(0, sys.maxint))
1423 1433
1424 1434 def dosets():
1425 1435 d = util.lrucachedict(size)
1426 1436 for v in setseq:
1427 1437 d[v] = v
1428 1438
1429 1439 # Mixed mode randomly performs gets and sets with eviction.
1430 1440 mixedops = []
1431 1441 for i in xrange(mixed):
1432 1442 r = random.randint(0, 100)
1433 1443 if r < mixedgetfreq:
1434 1444 op = 0
1435 1445 else:
1436 1446 op = 1
1437 1447
1438 1448 mixedops.append((op, random.randint(0, size * 2)))
1439 1449
1440 1450 def domixed():
1441 1451 d = util.lrucachedict(size)
1442 1452
1443 1453 for op, v in mixedops:
1444 1454 if op == 0:
1445 1455 try:
1446 1456 d[v]
1447 1457 except KeyError:
1448 1458 pass
1449 1459 else:
1450 1460 d[v] = v
1451 1461
1452 1462 benches = [
1453 1463 (doinit, 'init'),
1454 1464 (dogets, 'gets'),
1455 1465 (dosets, 'sets'),
1456 1466 (domixed, 'mixed')
1457 1467 ]
1458 1468
1459 1469 for fn, title in benches:
1460 1470 timer, fm = gettimer(ui, opts)
1461 1471 timer(fn, title=title)
1462 1472 fm.end()
1463 1473
1464 1474 @command('perfwrite', formatteropts)
1465 1475 def perfwrite(ui, repo, **opts):
1466 1476 """microbenchmark ui.write
1467 1477 """
1468 1478 timer, fm = gettimer(ui, opts)
1469 1479 def write():
1470 1480 for i in range(100000):
1471 1481 ui.write(('Testing write performance\n'))
1472 1482 timer(write)
1473 1483 fm.end()
1474 1484
1475 1485 def uisetup(ui):
1476 1486 if (util.safehasattr(cmdutil, 'openrevlog') and
1477 1487 not util.safehasattr(commands, 'debugrevlogopts')):
1478 1488 # for "historical portability":
1479 1489 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
1480 1490 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
1481 1491 # openrevlog() should cause failure, because it has been
1482 1492 # available since 3.5 (or 49c583ca48c4).
1483 1493 def openrevlog(orig, repo, cmd, file_, opts):
1484 1494 if opts.get('dir') and not util.safehasattr(repo, 'dirlog'):
1485 1495 raise error.Abort("This version doesn't support --dir option",
1486 1496 hint="use 3.5 or later")
1487 1497 return orig(repo, cmd, file_, opts)
1488 1498 extensions.wrapfunction(cmdutil, 'openrevlog', openrevlog)
@@ -1,172 +1,175 b''
1 1 #require test-repo
2 2
3 3 Set vars:
4 4
5 5 $ . "$TESTDIR/helpers-testrepo.sh"
6 6 $ CONTRIBDIR="$TESTDIR/../contrib"
7 7
8 8 Prepare repo:
9 9
10 10 $ hg init
11 11
12 12 $ echo this is file a > a
13 13 $ hg add a
14 14 $ hg commit -m first
15 15
16 16 $ echo adding to file a >> a
17 17 $ hg commit -m second
18 18
19 19 $ echo adding more to file a >> a
20 20 $ hg commit -m third
21 21
22 22 $ hg up -r 0
23 23 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
24 24 $ echo merge-this >> a
25 25 $ hg commit -m merge-able
26 26 created new head
27 27
28 28 $ hg up -r 2
29 29 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
30 30
31 31 perfstatus
32 32
33 33 $ cat >> $HGRCPATH << EOF
34 34 > [extensions]
35 35 > perfstatusext=$CONTRIBDIR/perf.py
36 36 > [perf]
37 37 > presleep=0
38 38 > stub=on
39 39 > parentscount=1
40 40 > EOF
41 41 $ hg help perfstatusext
42 42 perfstatusext extension - helper extension to measure performance
43 43
44 44 list of commands:
45 45
46 46 perfaddremove
47 47 (no help text available)
48 48 perfancestors
49 49 (no help text available)
50 50 perfancestorset
51 51 (no help text available)
52 52 perfannotate (no help text available)
53 53 perfbdiff benchmark a bdiff between revisions
54 perfbookmarks
55 benchmark parsing bookmarks from disk to memory
54 56 perfbranchmap
55 57 benchmark the update of a branchmap
56 58 perfcca (no help text available)
57 59 perfchangegroupchangelog
58 60 Benchmark producing a changelog group for a changegroup.
59 61 perfchangeset
60 62 (no help text available)
61 63 perfctxfiles (no help text available)
62 64 perfdiffwd Profile diff of working directory changes
63 65 perfdirfoldmap
64 66 (no help text available)
65 67 perfdirs (no help text available)
66 68 perfdirstate (no help text available)
67 69 perfdirstatedirs
68 70 (no help text available)
69 71 perfdirstatefoldmap
70 72 (no help text available)
71 73 perfdirstatewrite
72 74 (no help text available)
73 75 perffncacheencode
74 76 (no help text available)
75 77 perffncacheload
76 78 (no help text available)
77 79 perffncachewrite
78 80 (no help text available)
79 81 perfheads (no help text available)
80 82 perfindex (no help text available)
81 83 perfloadmarkers
82 84 benchmark the time to parse the on-disk markers for a repo
83 85 perflog (no help text available)
84 86 perflookup (no help text available)
85 87 perflrucachedict
86 88 (no help text available)
87 89 perfmanifest (no help text available)
88 90 perfmergecalculate
89 91 (no help text available)
90 92 perfmoonwalk benchmark walking the changelog backwards
91 93 perfnodelookup
92 94 (no help text available)
93 95 perfparents (no help text available)
94 96 perfpathcopies
95 97 (no help text available)
96 98 perfphases benchmark phasesets computation
97 99 perfrawfiles (no help text available)
98 100 perfrevlogchunks
99 101 Benchmark operations on revlog chunks.
100 102 perfrevlogindex
101 103 Benchmark operations against a revlog index.
102 104 perfrevlogrevision
103 105 Benchmark obtaining a revlog revision.
104 106 perfrevlogrevisions
105 107 Benchmark reading a series of revisions from a revlog.
106 108 perfrevrange (no help text available)
107 109 perfrevset benchmark the execution time of a revset
108 110 perfstartup (no help text available)
109 111 perfstatus (no help text available)
110 112 perftags (no help text available)
111 113 perftemplating
112 114 (no help text available)
113 115 perfvolatilesets
114 116 benchmark the computation of various volatile set
115 117 perfwalk (no help text available)
116 118 perfwrite microbenchmark ui.write
117 119
118 120 (use 'hg help -v perfstatusext' to show built-in aliases and global options)
119 121 $ hg perfaddremove
120 122 $ hg perfancestors
121 123 $ hg perfancestorset 2
122 124 $ hg perfannotate a
123 125 $ hg perfbdiff -c 1
124 126 $ hg perfbdiff --alldata 1
127 $ hg perfbookmarks
125 128 $ hg perfbranchmap
126 129 $ hg perfcca
127 130 $ hg perfchangegroupchangelog
128 131 $ hg perfchangeset 2
129 132 $ hg perfctxfiles 2
130 133 $ hg perfdiffwd
131 134 $ hg perfdirfoldmap
132 135 $ hg perfdirs
133 136 $ hg perfdirstate
134 137 $ hg perfdirstatedirs
135 138 $ hg perfdirstatefoldmap
136 139 $ hg perfdirstatewrite
137 140 $ hg perffncacheencode
138 141 $ hg perffncacheload
139 142 $ hg perffncachewrite
140 143 $ hg perfheads
141 144 $ hg perfindex
142 145 $ hg perfloadmarkers
143 146 $ hg perflog
144 147 $ hg perflookup 2
145 148 $ hg perflrucache
146 149 $ hg perfmanifest 2
147 150 $ hg perfmergecalculate -r 3
148 151 $ hg perfmoonwalk
149 152 $ hg perfnodelookup 2
150 153 $ hg perfpathcopies 1 2
151 154 $ hg perfrawfiles 2
152 155 $ hg perfrevlogindex -c
153 156 $ hg perfrevlogrevisions .hg/store/data/a.i
154 157 $ hg perfrevlogrevision -m 0
155 158 $ hg perfrevlogchunks -c
156 159 $ hg perfrevrange
157 160 $ hg perfrevset 'all()'
158 161 $ hg perfstartup
159 162 $ hg perfstatus
160 163 $ hg perftags
161 164 $ hg perftemplating
162 165 $ hg perfvolatilesets
163 166 $ hg perfwalk
164 167 $ hg perfparents
165 168
166 169 Check perf.py for historical portability
167 170
168 171 $ cd "$TESTDIR/.."
169 172
170 173 $ (hg files -r 1.2 glob:mercurial/*.c glob:mercurial/*.py;
171 174 > hg files -r tip glob:mercurial/*.c glob:mercurial/*.py) |
172 175 > "$TESTDIR"/check-perf-code.py contrib/perf.py
General Comments 0
You need to be logged in to leave comments. Login now