##// END OF EJS Templates
perf: teach perfbdiff to call blocks() and to use xdiff...
Gregory Szorc -
r36784:d382344c default
parent child Browse files
Show More
@@ -1,1743 +1,1763 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 def _bdiffworker(q, ready, done):
942 def _bdiffworker(q, blocks, xdiff, ready, done):
943 943 while not done.is_set():
944 944 pair = q.get()
945 945 while pair is not None:
946 mdiff.textdiff(*pair)
946 if xdiff:
947 mdiff.bdiff.xdiffblocks(*pair)
948 elif blocks:
949 mdiff.bdiff.blocks(*pair)
950 else:
951 mdiff.textdiff(*pair)
947 952 q.task_done()
948 953 pair = q.get()
949 954 q.task_done() # for the None one
950 955 with ready:
951 956 ready.wait()
952 957
953 958 @command('perfbdiff', revlogopts + formatteropts + [
954 959 ('', 'count', 1, 'number of revisions to test (when using --startrev)'),
955 960 ('', 'alldata', False, 'test bdiffs for all associated revisions'),
956 961 ('', 'threads', 0, 'number of thread to use (disable with 0)'),
962 ('', 'blocks', False, 'test computing diffs into blocks'),
963 ('', 'xdiff', False, 'use xdiff algorithm'),
957 964 ],
958 965
959 966 '-c|-m|FILE REV')
960 967 def perfbdiff(ui, repo, file_, rev=None, count=None, threads=0, **opts):
961 968 """benchmark a bdiff between revisions
962 969
963 970 By default, benchmark a bdiff between its delta parent and itself.
964 971
965 972 With ``--count``, benchmark bdiffs between delta parents and self for N
966 973 revisions starting at the specified revision.
967 974
968 975 With ``--alldata``, assume the requested revision is a changeset and
969 976 measure bdiffs for all changes related to that changeset (manifest
970 977 and filelogs).
971 978 """
979 opts = pycompat.byteskwargs(opts)
980
981 if opts['xdiff'] and not opts['blocks']:
982 raise error.CommandError('perfbdiff', '--xdiff requires --blocks')
983
972 984 if opts['alldata']:
973 985 opts['changelog'] = True
974 986
975 987 if opts.get('changelog') or opts.get('manifest'):
976 988 file_, rev = None, file_
977 989 elif rev is None:
978 990 raise error.CommandError('perfbdiff', 'invalid arguments')
979 991
992 blocks = opts['blocks']
993 xdiff = opts['xdiff']
980 994 textpairs = []
981 995
982 996 r = cmdutil.openrevlog(repo, 'perfbdiff', file_, opts)
983 997
984 998 startrev = r.rev(r.lookup(rev))
985 999 for rev in range(startrev, min(startrev + count, len(r) - 1)):
986 1000 if opts['alldata']:
987 1001 # Load revisions associated with changeset.
988 1002 ctx = repo[rev]
989 1003 mtext = repo.manifestlog._revlog.revision(ctx.manifestnode())
990 1004 for pctx in ctx.parents():
991 1005 pman = repo.manifestlog._revlog.revision(pctx.manifestnode())
992 1006 textpairs.append((pman, mtext))
993 1007
994 1008 # Load filelog revisions by iterating manifest delta.
995 1009 man = ctx.manifest()
996 1010 pman = ctx.p1().manifest()
997 1011 for filename, change in pman.diff(man).items():
998 1012 fctx = repo.file(filename)
999 1013 f1 = fctx.revision(change[0][0] or -1)
1000 1014 f2 = fctx.revision(change[1][0] or -1)
1001 1015 textpairs.append((f1, f2))
1002 1016 else:
1003 1017 dp = r.deltaparent(rev)
1004 1018 textpairs.append((r.revision(dp), r.revision(rev)))
1005 1019
1006 1020 withthreads = threads > 0
1007 1021 if not withthreads:
1008 1022 def d():
1009 1023 for pair in textpairs:
1010 mdiff.textdiff(*pair)
1024 if xdiff:
1025 mdiff.bdiff.xdiffblocks(*pair)
1026 elif blocks:
1027 mdiff.bdiff.blocks(*pair)
1028 else:
1029 mdiff.textdiff(*pair)
1011 1030 else:
1012 1031 q = util.queue()
1013 1032 for i in xrange(threads):
1014 1033 q.put(None)
1015 1034 ready = threading.Condition()
1016 1035 done = threading.Event()
1017 1036 for i in xrange(threads):
1018 threading.Thread(target=_bdiffworker, args=(q, ready, done)).start()
1037 threading.Thread(target=_bdiffworker,
1038 args=(q, blocks, xdiff, ready, done)).start()
1019 1039 q.join()
1020 1040 def d():
1021 1041 for pair in textpairs:
1022 1042 q.put(pair)
1023 1043 for i in xrange(threads):
1024 1044 q.put(None)
1025 1045 with ready:
1026 1046 ready.notify_all()
1027 1047 q.join()
1028 1048 timer, fm = gettimer(ui, opts)
1029 1049 timer(d)
1030 1050 fm.end()
1031 1051
1032 1052 if withthreads:
1033 1053 done.set()
1034 1054 for i in xrange(threads):
1035 1055 q.put(None)
1036 1056 with ready:
1037 1057 ready.notify_all()
1038 1058
1039 1059 @command('perfunidiff', revlogopts + formatteropts + [
1040 1060 ('', 'count', 1, 'number of revisions to test (when using --startrev)'),
1041 1061 ('', 'alldata', False, 'test unidiffs for all associated revisions'),
1042 1062 ], '-c|-m|FILE REV')
1043 1063 def perfunidiff(ui, repo, file_, rev=None, count=None, **opts):
1044 1064 """benchmark a unified diff between revisions
1045 1065
1046 1066 This doesn't include any copy tracing - it's just a unified diff
1047 1067 of the texts.
1048 1068
1049 1069 By default, benchmark a diff between its delta parent and itself.
1050 1070
1051 1071 With ``--count``, benchmark diffs between delta parents and self for N
1052 1072 revisions starting at the specified revision.
1053 1073
1054 1074 With ``--alldata``, assume the requested revision is a changeset and
1055 1075 measure diffs for all changes related to that changeset (manifest
1056 1076 and filelogs).
1057 1077 """
1058 1078 if opts['alldata']:
1059 1079 opts['changelog'] = True
1060 1080
1061 1081 if opts.get('changelog') or opts.get('manifest'):
1062 1082 file_, rev = None, file_
1063 1083 elif rev is None:
1064 1084 raise error.CommandError('perfunidiff', 'invalid arguments')
1065 1085
1066 1086 textpairs = []
1067 1087
1068 1088 r = cmdutil.openrevlog(repo, 'perfunidiff', file_, opts)
1069 1089
1070 1090 startrev = r.rev(r.lookup(rev))
1071 1091 for rev in range(startrev, min(startrev + count, len(r) - 1)):
1072 1092 if opts['alldata']:
1073 1093 # Load revisions associated with changeset.
1074 1094 ctx = repo[rev]
1075 1095 mtext = repo.manifestlog._revlog.revision(ctx.manifestnode())
1076 1096 for pctx in ctx.parents():
1077 1097 pman = repo.manifestlog._revlog.revision(pctx.manifestnode())
1078 1098 textpairs.append((pman, mtext))
1079 1099
1080 1100 # Load filelog revisions by iterating manifest delta.
1081 1101 man = ctx.manifest()
1082 1102 pman = ctx.p1().manifest()
1083 1103 for filename, change in pman.diff(man).items():
1084 1104 fctx = repo.file(filename)
1085 1105 f1 = fctx.revision(change[0][0] or -1)
1086 1106 f2 = fctx.revision(change[1][0] or -1)
1087 1107 textpairs.append((f1, f2))
1088 1108 else:
1089 1109 dp = r.deltaparent(rev)
1090 1110 textpairs.append((r.revision(dp), r.revision(rev)))
1091 1111
1092 1112 def d():
1093 1113 for left, right in textpairs:
1094 1114 # The date strings don't matter, so we pass empty strings.
1095 1115 headerlines, hunks = mdiff.unidiff(
1096 1116 left, '', right, '', 'left', 'right', binary=False)
1097 1117 # consume iterators in roughly the way patch.py does
1098 1118 b'\n'.join(headerlines)
1099 1119 b''.join(sum((list(hlines) for hrange, hlines in hunks), []))
1100 1120 timer, fm = gettimer(ui, opts)
1101 1121 timer(d)
1102 1122 fm.end()
1103 1123
1104 1124 @command('perfdiffwd', formatteropts)
1105 1125 def perfdiffwd(ui, repo, **opts):
1106 1126 """Profile diff of working directory changes"""
1107 1127 timer, fm = gettimer(ui, opts)
1108 1128 options = {
1109 1129 'w': 'ignore_all_space',
1110 1130 'b': 'ignore_space_change',
1111 1131 'B': 'ignore_blank_lines',
1112 1132 }
1113 1133
1114 1134 for diffopt in ('', 'w', 'b', 'B', 'wB'):
1115 1135 opts = dict((options[c], '1') for c in diffopt)
1116 1136 def d():
1117 1137 ui.pushbuffer()
1118 1138 commands.diff(ui, repo, **opts)
1119 1139 ui.popbuffer()
1120 1140 title = 'diffopts: %s' % (diffopt and ('-' + diffopt) or 'none')
1121 1141 timer(d, title)
1122 1142 fm.end()
1123 1143
1124 1144 @command('perfrevlogindex', revlogopts + formatteropts,
1125 1145 '-c|-m|FILE')
1126 1146 def perfrevlogindex(ui, repo, file_=None, **opts):
1127 1147 """Benchmark operations against a revlog index.
1128 1148
1129 1149 This tests constructing a revlog instance, reading index data,
1130 1150 parsing index data, and performing various operations related to
1131 1151 index data.
1132 1152 """
1133 1153
1134 1154 rl = cmdutil.openrevlog(repo, 'perfrevlogindex', file_, opts)
1135 1155
1136 1156 opener = getattr(rl, 'opener') # trick linter
1137 1157 indexfile = rl.indexfile
1138 1158 data = opener.read(indexfile)
1139 1159
1140 1160 header = struct.unpack('>I', data[0:4])[0]
1141 1161 version = header & 0xFFFF
1142 1162 if version == 1:
1143 1163 revlogio = revlog.revlogio()
1144 1164 inline = header & (1 << 16)
1145 1165 else:
1146 1166 raise error.Abort(('unsupported revlog version: %d') % version)
1147 1167
1148 1168 rllen = len(rl)
1149 1169
1150 1170 node0 = rl.node(0)
1151 1171 node25 = rl.node(rllen // 4)
1152 1172 node50 = rl.node(rllen // 2)
1153 1173 node75 = rl.node(rllen // 4 * 3)
1154 1174 node100 = rl.node(rllen - 1)
1155 1175
1156 1176 allrevs = range(rllen)
1157 1177 allrevsrev = list(reversed(allrevs))
1158 1178 allnodes = [rl.node(rev) for rev in range(rllen)]
1159 1179 allnodesrev = list(reversed(allnodes))
1160 1180
1161 1181 def constructor():
1162 1182 revlog.revlog(opener, indexfile)
1163 1183
1164 1184 def read():
1165 1185 with opener(indexfile) as fh:
1166 1186 fh.read()
1167 1187
1168 1188 def parseindex():
1169 1189 revlogio.parseindex(data, inline)
1170 1190
1171 1191 def getentry(revornode):
1172 1192 index = revlogio.parseindex(data, inline)[0]
1173 1193 index[revornode]
1174 1194
1175 1195 def getentries(revs, count=1):
1176 1196 index = revlogio.parseindex(data, inline)[0]
1177 1197
1178 1198 for i in range(count):
1179 1199 for rev in revs:
1180 1200 index[rev]
1181 1201
1182 1202 def resolvenode(node):
1183 1203 nodemap = revlogio.parseindex(data, inline)[1]
1184 1204 # This only works for the C code.
1185 1205 if nodemap is None:
1186 1206 return
1187 1207
1188 1208 try:
1189 1209 nodemap[node]
1190 1210 except error.RevlogError:
1191 1211 pass
1192 1212
1193 1213 def resolvenodes(nodes, count=1):
1194 1214 nodemap = revlogio.parseindex(data, inline)[1]
1195 1215 if nodemap is None:
1196 1216 return
1197 1217
1198 1218 for i in range(count):
1199 1219 for node in nodes:
1200 1220 try:
1201 1221 nodemap[node]
1202 1222 except error.RevlogError:
1203 1223 pass
1204 1224
1205 1225 benches = [
1206 1226 (constructor, 'revlog constructor'),
1207 1227 (read, 'read'),
1208 1228 (parseindex, 'create index object'),
1209 1229 (lambda: getentry(0), 'retrieve index entry for rev 0'),
1210 1230 (lambda: resolvenode('a' * 20), 'look up missing node'),
1211 1231 (lambda: resolvenode(node0), 'look up node at rev 0'),
1212 1232 (lambda: resolvenode(node25), 'look up node at 1/4 len'),
1213 1233 (lambda: resolvenode(node50), 'look up node at 1/2 len'),
1214 1234 (lambda: resolvenode(node75), 'look up node at 3/4 len'),
1215 1235 (lambda: resolvenode(node100), 'look up node at tip'),
1216 1236 # 2x variation is to measure caching impact.
1217 1237 (lambda: resolvenodes(allnodes),
1218 1238 'look up all nodes (forward)'),
1219 1239 (lambda: resolvenodes(allnodes, 2),
1220 1240 'look up all nodes 2x (forward)'),
1221 1241 (lambda: resolvenodes(allnodesrev),
1222 1242 'look up all nodes (reverse)'),
1223 1243 (lambda: resolvenodes(allnodesrev, 2),
1224 1244 'look up all nodes 2x (reverse)'),
1225 1245 (lambda: getentries(allrevs),
1226 1246 'retrieve all index entries (forward)'),
1227 1247 (lambda: getentries(allrevs, 2),
1228 1248 'retrieve all index entries 2x (forward)'),
1229 1249 (lambda: getentries(allrevsrev),
1230 1250 'retrieve all index entries (reverse)'),
1231 1251 (lambda: getentries(allrevsrev, 2),
1232 1252 'retrieve all index entries 2x (reverse)'),
1233 1253 ]
1234 1254
1235 1255 for fn, title in benches:
1236 1256 timer, fm = gettimer(ui, opts)
1237 1257 timer(fn, title=title)
1238 1258 fm.end()
1239 1259
1240 1260 @command('perfrevlogrevisions', revlogopts + formatteropts +
1241 1261 [('d', 'dist', 100, 'distance between the revisions'),
1242 1262 ('s', 'startrev', 0, 'revision to start reading at'),
1243 1263 ('', 'reverse', False, 'read in reverse')],
1244 1264 '-c|-m|FILE')
1245 1265 def perfrevlogrevisions(ui, repo, file_=None, startrev=0, reverse=False,
1246 1266 **opts):
1247 1267 """Benchmark reading a series of revisions from a revlog.
1248 1268
1249 1269 By default, we read every ``-d/--dist`` revision from 0 to tip of
1250 1270 the specified revlog.
1251 1271
1252 1272 The start revision can be defined via ``-s/--startrev``.
1253 1273 """
1254 1274 rl = cmdutil.openrevlog(repo, 'perfrevlogrevisions', file_, opts)
1255 1275 rllen = getlen(ui)(rl)
1256 1276
1257 1277 def d():
1258 1278 rl.clearcaches()
1259 1279
1260 1280 beginrev = startrev
1261 1281 endrev = rllen
1262 1282 dist = opts['dist']
1263 1283
1264 1284 if reverse:
1265 1285 beginrev, endrev = endrev, beginrev
1266 1286 dist = -1 * dist
1267 1287
1268 1288 for x in xrange(beginrev, endrev, dist):
1269 1289 # Old revisions don't support passing int.
1270 1290 n = rl.node(x)
1271 1291 rl.revision(n)
1272 1292
1273 1293 timer, fm = gettimer(ui, opts)
1274 1294 timer(d)
1275 1295 fm.end()
1276 1296
1277 1297 @command('perfrevlogchunks', revlogopts + formatteropts +
1278 1298 [('e', 'engines', '', 'compression engines to use'),
1279 1299 ('s', 'startrev', 0, 'revision to start at')],
1280 1300 '-c|-m|FILE')
1281 1301 def perfrevlogchunks(ui, repo, file_=None, engines=None, startrev=0, **opts):
1282 1302 """Benchmark operations on revlog chunks.
1283 1303
1284 1304 Logically, each revlog is a collection of fulltext revisions. However,
1285 1305 stored within each revlog are "chunks" of possibly compressed data. This
1286 1306 data needs to be read and decompressed or compressed and written.
1287 1307
1288 1308 This command measures the time it takes to read+decompress and recompress
1289 1309 chunks in a revlog. It effectively isolates I/O and compression performance.
1290 1310 For measurements of higher-level operations like resolving revisions,
1291 1311 see ``perfrevlogrevisions`` and ``perfrevlogrevision``.
1292 1312 """
1293 1313 rl = cmdutil.openrevlog(repo, 'perfrevlogchunks', file_, opts)
1294 1314
1295 1315 # _chunkraw was renamed to _getsegmentforrevs.
1296 1316 try:
1297 1317 segmentforrevs = rl._getsegmentforrevs
1298 1318 except AttributeError:
1299 1319 segmentforrevs = rl._chunkraw
1300 1320
1301 1321 # Verify engines argument.
1302 1322 if engines:
1303 1323 engines = set(e.strip() for e in engines.split(','))
1304 1324 for engine in engines:
1305 1325 try:
1306 1326 util.compressionengines[engine]
1307 1327 except KeyError:
1308 1328 raise error.Abort('unknown compression engine: %s' % engine)
1309 1329 else:
1310 1330 engines = []
1311 1331 for e in util.compengines:
1312 1332 engine = util.compengines[e]
1313 1333 try:
1314 1334 if engine.available():
1315 1335 engine.revlogcompressor().compress('dummy')
1316 1336 engines.append(e)
1317 1337 except NotImplementedError:
1318 1338 pass
1319 1339
1320 1340 revs = list(rl.revs(startrev, len(rl) - 1))
1321 1341
1322 1342 def rlfh(rl):
1323 1343 if rl._inline:
1324 1344 return getsvfs(repo)(rl.indexfile)
1325 1345 else:
1326 1346 return getsvfs(repo)(rl.datafile)
1327 1347
1328 1348 def doread():
1329 1349 rl.clearcaches()
1330 1350 for rev in revs:
1331 1351 segmentforrevs(rev, rev)
1332 1352
1333 1353 def doreadcachedfh():
1334 1354 rl.clearcaches()
1335 1355 fh = rlfh(rl)
1336 1356 for rev in revs:
1337 1357 segmentforrevs(rev, rev, df=fh)
1338 1358
1339 1359 def doreadbatch():
1340 1360 rl.clearcaches()
1341 1361 segmentforrevs(revs[0], revs[-1])
1342 1362
1343 1363 def doreadbatchcachedfh():
1344 1364 rl.clearcaches()
1345 1365 fh = rlfh(rl)
1346 1366 segmentforrevs(revs[0], revs[-1], df=fh)
1347 1367
1348 1368 def dochunk():
1349 1369 rl.clearcaches()
1350 1370 fh = rlfh(rl)
1351 1371 for rev in revs:
1352 1372 rl._chunk(rev, df=fh)
1353 1373
1354 1374 chunks = [None]
1355 1375
1356 1376 def dochunkbatch():
1357 1377 rl.clearcaches()
1358 1378 fh = rlfh(rl)
1359 1379 # Save chunks as a side-effect.
1360 1380 chunks[0] = rl._chunks(revs, df=fh)
1361 1381
1362 1382 def docompress(compressor):
1363 1383 rl.clearcaches()
1364 1384
1365 1385 try:
1366 1386 # Swap in the requested compression engine.
1367 1387 oldcompressor = rl._compressor
1368 1388 rl._compressor = compressor
1369 1389 for chunk in chunks[0]:
1370 1390 rl.compress(chunk)
1371 1391 finally:
1372 1392 rl._compressor = oldcompressor
1373 1393
1374 1394 benches = [
1375 1395 (lambda: doread(), 'read'),
1376 1396 (lambda: doreadcachedfh(), 'read w/ reused fd'),
1377 1397 (lambda: doreadbatch(), 'read batch'),
1378 1398 (lambda: doreadbatchcachedfh(), 'read batch w/ reused fd'),
1379 1399 (lambda: dochunk(), 'chunk'),
1380 1400 (lambda: dochunkbatch(), 'chunk batch'),
1381 1401 ]
1382 1402
1383 1403 for engine in sorted(engines):
1384 1404 compressor = util.compengines[engine].revlogcompressor()
1385 1405 benches.append((functools.partial(docompress, compressor),
1386 1406 'compress w/ %s' % engine))
1387 1407
1388 1408 for fn, title in benches:
1389 1409 timer, fm = gettimer(ui, opts)
1390 1410 timer(fn, title=title)
1391 1411 fm.end()
1392 1412
1393 1413 @command('perfrevlogrevision', revlogopts + formatteropts +
1394 1414 [('', 'cache', False, 'use caches instead of clearing')],
1395 1415 '-c|-m|FILE REV')
1396 1416 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
1397 1417 """Benchmark obtaining a revlog revision.
1398 1418
1399 1419 Obtaining a revlog revision consists of roughly the following steps:
1400 1420
1401 1421 1. Compute the delta chain
1402 1422 2. Obtain the raw chunks for that delta chain
1403 1423 3. Decompress each raw chunk
1404 1424 4. Apply binary patches to obtain fulltext
1405 1425 5. Verify hash of fulltext
1406 1426
1407 1427 This command measures the time spent in each of these phases.
1408 1428 """
1409 1429 if opts.get('changelog') or opts.get('manifest'):
1410 1430 file_, rev = None, file_
1411 1431 elif rev is None:
1412 1432 raise error.CommandError('perfrevlogrevision', 'invalid arguments')
1413 1433
1414 1434 r = cmdutil.openrevlog(repo, 'perfrevlogrevision', file_, opts)
1415 1435
1416 1436 # _chunkraw was renamed to _getsegmentforrevs.
1417 1437 try:
1418 1438 segmentforrevs = r._getsegmentforrevs
1419 1439 except AttributeError:
1420 1440 segmentforrevs = r._chunkraw
1421 1441
1422 1442 node = r.lookup(rev)
1423 1443 rev = r.rev(node)
1424 1444
1425 1445 def getrawchunks(data, chain):
1426 1446 start = r.start
1427 1447 length = r.length
1428 1448 inline = r._inline
1429 1449 iosize = r._io.size
1430 1450 buffer = util.buffer
1431 1451 offset = start(chain[0])
1432 1452
1433 1453 chunks = []
1434 1454 ladd = chunks.append
1435 1455
1436 1456 for rev in chain:
1437 1457 chunkstart = start(rev)
1438 1458 if inline:
1439 1459 chunkstart += (rev + 1) * iosize
1440 1460 chunklength = length(rev)
1441 1461 ladd(buffer(data, chunkstart - offset, chunklength))
1442 1462
1443 1463 return chunks
1444 1464
1445 1465 def dodeltachain(rev):
1446 1466 if not cache:
1447 1467 r.clearcaches()
1448 1468 r._deltachain(rev)
1449 1469
1450 1470 def doread(chain):
1451 1471 if not cache:
1452 1472 r.clearcaches()
1453 1473 segmentforrevs(chain[0], chain[-1])
1454 1474
1455 1475 def dorawchunks(data, chain):
1456 1476 if not cache:
1457 1477 r.clearcaches()
1458 1478 getrawchunks(data, chain)
1459 1479
1460 1480 def dodecompress(chunks):
1461 1481 decomp = r.decompress
1462 1482 for chunk in chunks:
1463 1483 decomp(chunk)
1464 1484
1465 1485 def dopatch(text, bins):
1466 1486 if not cache:
1467 1487 r.clearcaches()
1468 1488 mdiff.patches(text, bins)
1469 1489
1470 1490 def dohash(text):
1471 1491 if not cache:
1472 1492 r.clearcaches()
1473 1493 r.checkhash(text, node, rev=rev)
1474 1494
1475 1495 def dorevision():
1476 1496 if not cache:
1477 1497 r.clearcaches()
1478 1498 r.revision(node)
1479 1499
1480 1500 chain = r._deltachain(rev)[0]
1481 1501 data = segmentforrevs(chain[0], chain[-1])[1]
1482 1502 rawchunks = getrawchunks(data, chain)
1483 1503 bins = r._chunks(chain)
1484 1504 text = str(bins[0])
1485 1505 bins = bins[1:]
1486 1506 text = mdiff.patches(text, bins)
1487 1507
1488 1508 benches = [
1489 1509 (lambda: dorevision(), 'full'),
1490 1510 (lambda: dodeltachain(rev), 'deltachain'),
1491 1511 (lambda: doread(chain), 'read'),
1492 1512 (lambda: dorawchunks(data, chain), 'rawchunks'),
1493 1513 (lambda: dodecompress(rawchunks), 'decompress'),
1494 1514 (lambda: dopatch(text, bins), 'patch'),
1495 1515 (lambda: dohash(text), 'hash'),
1496 1516 ]
1497 1517
1498 1518 for fn, title in benches:
1499 1519 timer, fm = gettimer(ui, opts)
1500 1520 timer(fn, title=title)
1501 1521 fm.end()
1502 1522
1503 1523 @command('perfrevset',
1504 1524 [('C', 'clear', False, 'clear volatile cache between each call.'),
1505 1525 ('', 'contexts', False, 'obtain changectx for each revision')]
1506 1526 + formatteropts, "REVSET")
1507 1527 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
1508 1528 """benchmark the execution time of a revset
1509 1529
1510 1530 Use the --clean option if need to evaluate the impact of build volatile
1511 1531 revisions set cache on the revset execution. Volatile cache hold filtered
1512 1532 and obsolete related cache."""
1513 1533 timer, fm = gettimer(ui, opts)
1514 1534 def d():
1515 1535 if clear:
1516 1536 repo.invalidatevolatilesets()
1517 1537 if contexts:
1518 1538 for ctx in repo.set(expr): pass
1519 1539 else:
1520 1540 for r in repo.revs(expr): pass
1521 1541 timer(d)
1522 1542 fm.end()
1523 1543
1524 1544 @command('perfvolatilesets',
1525 1545 [('', 'clear-obsstore', False, 'drop obsstore between each call.'),
1526 1546 ] + formatteropts)
1527 1547 def perfvolatilesets(ui, repo, *names, **opts):
1528 1548 """benchmark the computation of various volatile set
1529 1549
1530 1550 Volatile set computes element related to filtering and obsolescence."""
1531 1551 timer, fm = gettimer(ui, opts)
1532 1552 repo = repo.unfiltered()
1533 1553
1534 1554 def getobs(name):
1535 1555 def d():
1536 1556 repo.invalidatevolatilesets()
1537 1557 if opts['clear_obsstore']:
1538 1558 clearfilecache(repo, 'obsstore')
1539 1559 obsolete.getrevs(repo, name)
1540 1560 return d
1541 1561
1542 1562 allobs = sorted(obsolete.cachefuncs)
1543 1563 if names:
1544 1564 allobs = [n for n in allobs if n in names]
1545 1565
1546 1566 for name in allobs:
1547 1567 timer(getobs(name), title=name)
1548 1568
1549 1569 def getfiltered(name):
1550 1570 def d():
1551 1571 repo.invalidatevolatilesets()
1552 1572 if opts['clear_obsstore']:
1553 1573 clearfilecache(repo, 'obsstore')
1554 1574 repoview.filterrevs(repo, name)
1555 1575 return d
1556 1576
1557 1577 allfilter = sorted(repoview.filtertable)
1558 1578 if names:
1559 1579 allfilter = [n for n in allfilter if n in names]
1560 1580
1561 1581 for name in allfilter:
1562 1582 timer(getfiltered(name), title=name)
1563 1583 fm.end()
1564 1584
1565 1585 @command('perfbranchmap',
1566 1586 [('f', 'full', False,
1567 1587 'Includes build time of subset'),
1568 1588 ('', 'clear-revbranch', False,
1569 1589 'purge the revbranch cache between computation'),
1570 1590 ] + formatteropts)
1571 1591 def perfbranchmap(ui, repo, *filternames, **opts):
1572 1592 """benchmark the update of a branchmap
1573 1593
1574 1594 This benchmarks the full repo.branchmap() call with read and write disabled
1575 1595 """
1576 1596 full = opts.get("full", False)
1577 1597 clear_revbranch = opts.get("clear_revbranch", False)
1578 1598 timer, fm = gettimer(ui, opts)
1579 1599 def getbranchmap(filtername):
1580 1600 """generate a benchmark function for the filtername"""
1581 1601 if filtername is None:
1582 1602 view = repo
1583 1603 else:
1584 1604 view = repo.filtered(filtername)
1585 1605 def d():
1586 1606 if clear_revbranch:
1587 1607 repo.revbranchcache()._clear()
1588 1608 if full:
1589 1609 view._branchcaches.clear()
1590 1610 else:
1591 1611 view._branchcaches.pop(filtername, None)
1592 1612 view.branchmap()
1593 1613 return d
1594 1614 # add filter in smaller subset to bigger subset
1595 1615 possiblefilters = set(repoview.filtertable)
1596 1616 if filternames:
1597 1617 possiblefilters &= set(filternames)
1598 1618 subsettable = getbranchmapsubsettable()
1599 1619 allfilters = []
1600 1620 while possiblefilters:
1601 1621 for name in possiblefilters:
1602 1622 subset = subsettable.get(name)
1603 1623 if subset not in possiblefilters:
1604 1624 break
1605 1625 else:
1606 1626 assert False, 'subset cycle %s!' % possiblefilters
1607 1627 allfilters.append(name)
1608 1628 possiblefilters.remove(name)
1609 1629
1610 1630 # warm the cache
1611 1631 if not full:
1612 1632 for name in allfilters:
1613 1633 repo.filtered(name).branchmap()
1614 1634 if not filternames or 'unfiltered' in filternames:
1615 1635 # add unfiltered
1616 1636 allfilters.append(None)
1617 1637
1618 1638 branchcacheread = safeattrsetter(branchmap, 'read')
1619 1639 branchcachewrite = safeattrsetter(branchmap.branchcache, 'write')
1620 1640 branchcacheread.set(lambda repo: None)
1621 1641 branchcachewrite.set(lambda bc, repo: None)
1622 1642 try:
1623 1643 for name in allfilters:
1624 1644 printname = name
1625 1645 if name is None:
1626 1646 printname = 'unfiltered'
1627 1647 timer(getbranchmap(name), title=str(printname))
1628 1648 finally:
1629 1649 branchcacheread.restore()
1630 1650 branchcachewrite.restore()
1631 1651 fm.end()
1632 1652
1633 1653 @command('perfloadmarkers')
1634 1654 def perfloadmarkers(ui, repo):
1635 1655 """benchmark the time to parse the on-disk markers for a repo
1636 1656
1637 1657 Result is the number of markers in the repo."""
1638 1658 timer, fm = gettimer(ui)
1639 1659 svfs = getsvfs(repo)
1640 1660 timer(lambda: len(obsolete.obsstore(svfs)))
1641 1661 fm.end()
1642 1662
1643 1663 @command('perflrucachedict', formatteropts +
1644 1664 [('', 'size', 4, 'size of cache'),
1645 1665 ('', 'gets', 10000, 'number of key lookups'),
1646 1666 ('', 'sets', 10000, 'number of key sets'),
1647 1667 ('', 'mixed', 10000, 'number of mixed mode operations'),
1648 1668 ('', 'mixedgetfreq', 50, 'frequency of get vs set ops in mixed mode')],
1649 1669 norepo=True)
1650 1670 def perflrucache(ui, size=4, gets=10000, sets=10000, mixed=10000,
1651 1671 mixedgetfreq=50, **opts):
1652 1672 def doinit():
1653 1673 for i in xrange(10000):
1654 1674 util.lrucachedict(size)
1655 1675
1656 1676 values = []
1657 1677 for i in xrange(size):
1658 1678 values.append(random.randint(0, sys.maxint))
1659 1679
1660 1680 # Get mode fills the cache and tests raw lookup performance with no
1661 1681 # eviction.
1662 1682 getseq = []
1663 1683 for i in xrange(gets):
1664 1684 getseq.append(random.choice(values))
1665 1685
1666 1686 def dogets():
1667 1687 d = util.lrucachedict(size)
1668 1688 for v in values:
1669 1689 d[v] = v
1670 1690 for key in getseq:
1671 1691 value = d[key]
1672 1692 value # silence pyflakes warning
1673 1693
1674 1694 # Set mode tests insertion speed with cache eviction.
1675 1695 setseq = []
1676 1696 for i in xrange(sets):
1677 1697 setseq.append(random.randint(0, sys.maxint))
1678 1698
1679 1699 def dosets():
1680 1700 d = util.lrucachedict(size)
1681 1701 for v in setseq:
1682 1702 d[v] = v
1683 1703
1684 1704 # Mixed mode randomly performs gets and sets with eviction.
1685 1705 mixedops = []
1686 1706 for i in xrange(mixed):
1687 1707 r = random.randint(0, 100)
1688 1708 if r < mixedgetfreq:
1689 1709 op = 0
1690 1710 else:
1691 1711 op = 1
1692 1712
1693 1713 mixedops.append((op, random.randint(0, size * 2)))
1694 1714
1695 1715 def domixed():
1696 1716 d = util.lrucachedict(size)
1697 1717
1698 1718 for op, v in mixedops:
1699 1719 if op == 0:
1700 1720 try:
1701 1721 d[v]
1702 1722 except KeyError:
1703 1723 pass
1704 1724 else:
1705 1725 d[v] = v
1706 1726
1707 1727 benches = [
1708 1728 (doinit, 'init'),
1709 1729 (dogets, 'gets'),
1710 1730 (dosets, 'sets'),
1711 1731 (domixed, 'mixed')
1712 1732 ]
1713 1733
1714 1734 for fn, title in benches:
1715 1735 timer, fm = gettimer(ui, opts)
1716 1736 timer(fn, title=title)
1717 1737 fm.end()
1718 1738
1719 1739 @command('perfwrite', formatteropts)
1720 1740 def perfwrite(ui, repo, **opts):
1721 1741 """microbenchmark ui.write
1722 1742 """
1723 1743 timer, fm = gettimer(ui, opts)
1724 1744 def write():
1725 1745 for i in range(100000):
1726 1746 ui.write(('Testing write performance\n'))
1727 1747 timer(write)
1728 1748 fm.end()
1729 1749
1730 1750 def uisetup(ui):
1731 1751 if (util.safehasattr(cmdutil, 'openrevlog') and
1732 1752 not util.safehasattr(commands, 'debugrevlogopts')):
1733 1753 # for "historical portability":
1734 1754 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
1735 1755 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
1736 1756 # openrevlog() should cause failure, because it has been
1737 1757 # available since 3.5 (or 49c583ca48c4).
1738 1758 def openrevlog(orig, repo, cmd, file_, opts):
1739 1759 if opts.get('dir') and not util.safehasattr(repo, 'dirlog'):
1740 1760 raise error.Abort("This version doesn't support --dir option",
1741 1761 hint="use 3.5 or later")
1742 1762 return orig(repo, cmd, file_, opts)
1743 1763 extensions.wrapfunction(cmdutil, 'openrevlog', openrevlog)
General Comments 0
You need to be logged in to leave comments. Login now