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