##// END OF EJS Templates
perf: pass limits as a function argument...
marmoute -
r42185:0e642294 default
parent child Browse files
Show More
@@ -1,2816 +1,2817 b''
1 1 # perf.py - performance test routines
2 2 '''helper extension to measure performance
3 3
4 4 Configurations
5 5 ==============
6 6
7 7 ``perf``
8 8 --------
9 9
10 10 ``all-timing``
11 11 When set, additional statistic will be reported for each benchmark: best,
12 12 worst, median average. If not set only the best timing is reported
13 13 (default: off).
14 14
15 15 ``presleep``
16 16 number of second to wait before any group of run (default: 1)
17 17
18 18 ``stub``
19 19 When set, benchmark will only be run once, useful for testing (default: off)
20 20 '''
21 21
22 22 # "historical portability" policy of perf.py:
23 23 #
24 24 # We have to do:
25 25 # - make perf.py "loadable" with as wide Mercurial version as possible
26 26 # This doesn't mean that perf commands work correctly with that Mercurial.
27 27 # BTW, perf.py itself has been available since 1.1 (or eb240755386d).
28 28 # - make historical perf command work correctly with as wide Mercurial
29 29 # version as possible
30 30 #
31 31 # We have to do, if possible with reasonable cost:
32 32 # - make recent perf command for historical feature work correctly
33 33 # with early Mercurial
34 34 #
35 35 # We don't have to do:
36 36 # - make perf command for recent feature work correctly with early
37 37 # Mercurial
38 38
39 39 from __future__ import absolute_import
40 40 import contextlib
41 41 import functools
42 42 import gc
43 43 import os
44 44 import random
45 45 import shutil
46 46 import struct
47 47 import sys
48 48 import tempfile
49 49 import threading
50 50 import time
51 51 from mercurial import (
52 52 changegroup,
53 53 cmdutil,
54 54 commands,
55 55 copies,
56 56 error,
57 57 extensions,
58 58 hg,
59 59 mdiff,
60 60 merge,
61 61 revlog,
62 62 util,
63 63 )
64 64
65 65 # for "historical portability":
66 66 # try to import modules separately (in dict order), and ignore
67 67 # failure, because these aren't available with early Mercurial
68 68 try:
69 69 from mercurial import branchmap # since 2.5 (or bcee63733aad)
70 70 except ImportError:
71 71 pass
72 72 try:
73 73 from mercurial import obsolete # since 2.3 (or ad0d6c2b3279)
74 74 except ImportError:
75 75 pass
76 76 try:
77 77 from mercurial import registrar # since 3.7 (or 37d50250b696)
78 78 dir(registrar) # forcibly load it
79 79 except ImportError:
80 80 registrar = None
81 81 try:
82 82 from mercurial import repoview # since 2.5 (or 3a6ddacb7198)
83 83 except ImportError:
84 84 pass
85 85 try:
86 86 from mercurial import scmutil # since 1.9 (or 8b252e826c68)
87 87 except ImportError:
88 88 pass
89 89 try:
90 90 from mercurial import setdiscovery # since 1.9 (or cb98fed52495)
91 91 except ImportError:
92 92 pass
93 93
94 94
95 95 def identity(a):
96 96 return a
97 97
98 98 try:
99 99 from mercurial import pycompat
100 100 getargspec = pycompat.getargspec # added to module after 4.5
101 101 _byteskwargs = pycompat.byteskwargs # since 4.1 (or fbc3f73dc802)
102 102 _sysstr = pycompat.sysstr # since 4.0 (or 2219f4f82ede)
103 103 _xrange = pycompat.xrange # since 4.8 (or 7eba8f83129b)
104 104 fsencode = pycompat.fsencode # since 3.9 (or f4a5e0e86a7e)
105 105 if pycompat.ispy3:
106 106 _maxint = sys.maxsize # per py3 docs for replacing maxint
107 107 else:
108 108 _maxint = sys.maxint
109 109 except (ImportError, AttributeError):
110 110 import inspect
111 111 getargspec = inspect.getargspec
112 112 _byteskwargs = identity
113 113 fsencode = identity # no py3 support
114 114 _maxint = sys.maxint # no py3 support
115 115 _sysstr = lambda x: x # no py3 support
116 116 _xrange = xrange
117 117
118 118 try:
119 119 # 4.7+
120 120 queue = pycompat.queue.Queue
121 121 except (AttributeError, ImportError):
122 122 # <4.7.
123 123 try:
124 124 queue = pycompat.queue
125 125 except (AttributeError, ImportError):
126 126 queue = util.queue
127 127
128 128 try:
129 129 from mercurial import logcmdutil
130 130 makelogtemplater = logcmdutil.maketemplater
131 131 except (AttributeError, ImportError):
132 132 try:
133 133 makelogtemplater = cmdutil.makelogtemplater
134 134 except (AttributeError, ImportError):
135 135 makelogtemplater = None
136 136
137 137 # for "historical portability":
138 138 # define util.safehasattr forcibly, because util.safehasattr has been
139 139 # available since 1.9.3 (or 94b200a11cf7)
140 140 _undefined = object()
141 141 def safehasattr(thing, attr):
142 142 return getattr(thing, _sysstr(attr), _undefined) is not _undefined
143 143 setattr(util, 'safehasattr', safehasattr)
144 144
145 145 # for "historical portability":
146 146 # define util.timer forcibly, because util.timer has been available
147 147 # since ae5d60bb70c9
148 148 if safehasattr(time, 'perf_counter'):
149 149 util.timer = time.perf_counter
150 150 elif os.name == b'nt':
151 151 util.timer = time.clock
152 152 else:
153 153 util.timer = time.time
154 154
155 155 # for "historical portability":
156 156 # use locally defined empty option list, if formatteropts isn't
157 157 # available, because commands.formatteropts has been available since
158 158 # 3.2 (or 7a7eed5176a4), even though formatting itself has been
159 159 # available since 2.2 (or ae5f92e154d3)
160 160 formatteropts = getattr(cmdutil, "formatteropts",
161 161 getattr(commands, "formatteropts", []))
162 162
163 163 # for "historical portability":
164 164 # use locally defined option list, if debugrevlogopts isn't available,
165 165 # because commands.debugrevlogopts has been available since 3.7 (or
166 166 # 5606f7d0d063), even though cmdutil.openrevlog() has been available
167 167 # since 1.9 (or a79fea6b3e77).
168 168 revlogopts = getattr(cmdutil, "debugrevlogopts",
169 169 getattr(commands, "debugrevlogopts", [
170 170 (b'c', b'changelog', False, (b'open changelog')),
171 171 (b'm', b'manifest', False, (b'open manifest')),
172 172 (b'', b'dir', False, (b'open directory manifest')),
173 173 ]))
174 174
175 175 cmdtable = {}
176 176
177 177 # for "historical portability":
178 178 # define parsealiases locally, because cmdutil.parsealiases has been
179 179 # available since 1.5 (or 6252852b4332)
180 180 def parsealiases(cmd):
181 181 return cmd.split(b"|")
182 182
183 183 if safehasattr(registrar, 'command'):
184 184 command = registrar.command(cmdtable)
185 185 elif safehasattr(cmdutil, 'command'):
186 186 command = cmdutil.command(cmdtable)
187 187 if b'norepo' not in getargspec(command).args:
188 188 # for "historical portability":
189 189 # wrap original cmdutil.command, because "norepo" option has
190 190 # been available since 3.1 (or 75a96326cecb)
191 191 _command = command
192 192 def command(name, options=(), synopsis=None, norepo=False):
193 193 if norepo:
194 194 commands.norepo += b' %s' % b' '.join(parsealiases(name))
195 195 return _command(name, list(options), synopsis)
196 196 else:
197 197 # for "historical portability":
198 198 # define "@command" annotation locally, because cmdutil.command
199 199 # has been available since 1.9 (or 2daa5179e73f)
200 200 def command(name, options=(), synopsis=None, norepo=False):
201 201 def decorator(func):
202 202 if synopsis:
203 203 cmdtable[name] = func, list(options), synopsis
204 204 else:
205 205 cmdtable[name] = func, list(options)
206 206 if norepo:
207 207 commands.norepo += b' %s' % b' '.join(parsealiases(name))
208 208 return func
209 209 return decorator
210 210
211 211 try:
212 212 import mercurial.registrar
213 213 import mercurial.configitems
214 214 configtable = {}
215 215 configitem = mercurial.registrar.configitem(configtable)
216 216 configitem(b'perf', b'presleep',
217 217 default=mercurial.configitems.dynamicdefault,
218 218 )
219 219 configitem(b'perf', b'stub',
220 220 default=mercurial.configitems.dynamicdefault,
221 221 )
222 222 configitem(b'perf', b'parentscount',
223 223 default=mercurial.configitems.dynamicdefault,
224 224 )
225 225 configitem(b'perf', b'all-timing',
226 226 default=mercurial.configitems.dynamicdefault,
227 227 )
228 228 except (ImportError, AttributeError):
229 229 pass
230 230
231 231 def getlen(ui):
232 232 if ui.configbool(b"perf", b"stub", False):
233 233 return lambda x: 1
234 234 return len
235 235
236 236 def gettimer(ui, opts=None):
237 237 """return a timer function and formatter: (timer, formatter)
238 238
239 239 This function exists to gather the creation of formatter in a single
240 240 place instead of duplicating it in all performance commands."""
241 241
242 242 # enforce an idle period before execution to counteract power management
243 243 # experimental config: perf.presleep
244 244 time.sleep(getint(ui, b"perf", b"presleep", 1))
245 245
246 246 if opts is None:
247 247 opts = {}
248 248 # redirect all to stderr unless buffer api is in use
249 249 if not ui._buffers:
250 250 ui = ui.copy()
251 251 uifout = safeattrsetter(ui, b'fout', ignoremissing=True)
252 252 if uifout:
253 253 # for "historical portability":
254 254 # ui.fout/ferr have been available since 1.9 (or 4e1ccd4c2b6d)
255 255 uifout.set(ui.ferr)
256 256
257 257 # get a formatter
258 258 uiformatter = getattr(ui, 'formatter', None)
259 259 if uiformatter:
260 260 fm = uiformatter(b'perf', opts)
261 261 else:
262 262 # for "historical portability":
263 263 # define formatter locally, because ui.formatter has been
264 264 # available since 2.2 (or ae5f92e154d3)
265 265 from mercurial import node
266 266 class defaultformatter(object):
267 267 """Minimized composition of baseformatter and plainformatter
268 268 """
269 269 def __init__(self, ui, topic, opts):
270 270 self._ui = ui
271 271 if ui.debugflag:
272 272 self.hexfunc = node.hex
273 273 else:
274 274 self.hexfunc = node.short
275 275 def __nonzero__(self):
276 276 return False
277 277 __bool__ = __nonzero__
278 278 def startitem(self):
279 279 pass
280 280 def data(self, **data):
281 281 pass
282 282 def write(self, fields, deftext, *fielddata, **opts):
283 283 self._ui.write(deftext % fielddata, **opts)
284 284 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
285 285 if cond:
286 286 self._ui.write(deftext % fielddata, **opts)
287 287 def plain(self, text, **opts):
288 288 self._ui.write(text, **opts)
289 289 def end(self):
290 290 pass
291 291 fm = defaultformatter(ui, b'perf', opts)
292 292
293 293 # stub function, runs code only once instead of in a loop
294 294 # experimental config: perf.stub
295 295 if ui.configbool(b"perf", b"stub", False):
296 296 return functools.partial(stub_timer, fm), fm
297 297
298 298 # experimental config: perf.all-timing
299 299 displayall = ui.configbool(b"perf", b"all-timing", False)
300 300 return functools.partial(_timer, fm, displayall=displayall), fm
301 301
302 302 def stub_timer(fm, func, setup=None, title=None):
303 303 if setup is not None:
304 304 setup()
305 305 func()
306 306
307 307 @contextlib.contextmanager
308 308 def timeone():
309 309 r = []
310 310 ostart = os.times()
311 311 cstart = util.timer()
312 312 yield r
313 313 cstop = util.timer()
314 314 ostop = os.times()
315 315 a, b = ostart, ostop
316 316 r.append((cstop - cstart, b[0] - a[0], b[1]-a[1]))
317 317
318 318
319 319 # list of stop condition (elapsed time, minimal run count)
320 320 DEFAULTLIMITS = (
321 321 (3.0, 100),
322 322 (10.0, 3),
323 323 )
324 324
325 def _timer(fm, func, setup=None, title=None, displayall=False):
325 def _timer(fm, func, setup=None, title=None, displayall=False,
326 limits=DEFAULTLIMITS):
326 327 gc.collect()
327 328 results = []
328 329 begin = util.timer()
329 330 count = 0
330 331 keepgoing = True
331 332 while keepgoing:
332 333 if setup is not None:
333 334 setup()
334 335 with timeone() as item:
335 336 r = func()
336 337 count += 1
337 338 results.append(item[0])
338 339 cstop = util.timer()
339 340 # Look for a stop condition.
340 341 elapsed = cstop - begin
341 for t, mincount in DEFAULTLIMITS:
342 for t, mincount in limits:
342 343 if elapsed >= t and count >= mincount:
343 344 keepgoing = False
344 345 break
345 346
346 347 formatone(fm, results, title=title, result=r,
347 348 displayall=displayall)
348 349
349 350 def formatone(fm, timings, title=None, result=None, displayall=False):
350 351
351 352 count = len(timings)
352 353
353 354 fm.startitem()
354 355
355 356 if title:
356 357 fm.write(b'title', b'! %s\n', title)
357 358 if result:
358 359 fm.write(b'result', b'! result: %s\n', result)
359 360 def display(role, entry):
360 361 prefix = b''
361 362 if role != b'best':
362 363 prefix = b'%s.' % role
363 364 fm.plain(b'!')
364 365 fm.write(prefix + b'wall', b' wall %f', entry[0])
365 366 fm.write(prefix + b'comb', b' comb %f', entry[1] + entry[2])
366 367 fm.write(prefix + b'user', b' user %f', entry[1])
367 368 fm.write(prefix + b'sys', b' sys %f', entry[2])
368 369 fm.write(prefix + b'count', b' (%s of %%d)' % role, count)
369 370 fm.plain(b'\n')
370 371 timings.sort()
371 372 min_val = timings[0]
372 373 display(b'best', min_val)
373 374 if displayall:
374 375 max_val = timings[-1]
375 376 display(b'max', max_val)
376 377 avg = tuple([sum(x) / count for x in zip(*timings)])
377 378 display(b'avg', avg)
378 379 median = timings[len(timings) // 2]
379 380 display(b'median', median)
380 381
381 382 # utilities for historical portability
382 383
383 384 def getint(ui, section, name, default):
384 385 # for "historical portability":
385 386 # ui.configint has been available since 1.9 (or fa2b596db182)
386 387 v = ui.config(section, name, None)
387 388 if v is None:
388 389 return default
389 390 try:
390 391 return int(v)
391 392 except ValueError:
392 393 raise error.ConfigError((b"%s.%s is not an integer ('%s')")
393 394 % (section, name, v))
394 395
395 396 def safeattrsetter(obj, name, ignoremissing=False):
396 397 """Ensure that 'obj' has 'name' attribute before subsequent setattr
397 398
398 399 This function is aborted, if 'obj' doesn't have 'name' attribute
399 400 at runtime. This avoids overlooking removal of an attribute, which
400 401 breaks assumption of performance measurement, in the future.
401 402
402 403 This function returns the object to (1) assign a new value, and
403 404 (2) restore an original value to the attribute.
404 405
405 406 If 'ignoremissing' is true, missing 'name' attribute doesn't cause
406 407 abortion, and this function returns None. This is useful to
407 408 examine an attribute, which isn't ensured in all Mercurial
408 409 versions.
409 410 """
410 411 if not util.safehasattr(obj, name):
411 412 if ignoremissing:
412 413 return None
413 414 raise error.Abort((b"missing attribute %s of %s might break assumption"
414 415 b" of performance measurement") % (name, obj))
415 416
416 417 origvalue = getattr(obj, _sysstr(name))
417 418 class attrutil(object):
418 419 def set(self, newvalue):
419 420 setattr(obj, _sysstr(name), newvalue)
420 421 def restore(self):
421 422 setattr(obj, _sysstr(name), origvalue)
422 423
423 424 return attrutil()
424 425
425 426 # utilities to examine each internal API changes
426 427
427 428 def getbranchmapsubsettable():
428 429 # for "historical portability":
429 430 # subsettable is defined in:
430 431 # - branchmap since 2.9 (or 175c6fd8cacc)
431 432 # - repoview since 2.5 (or 59a9f18d4587)
432 433 for mod in (branchmap, repoview):
433 434 subsettable = getattr(mod, 'subsettable', None)
434 435 if subsettable:
435 436 return subsettable
436 437
437 438 # bisecting in bcee63733aad::59a9f18d4587 can reach here (both
438 439 # branchmap and repoview modules exist, but subsettable attribute
439 440 # doesn't)
440 441 raise error.Abort((b"perfbranchmap not available with this Mercurial"),
441 442 hint=b"use 2.5 or later")
442 443
443 444 def getsvfs(repo):
444 445 """Return appropriate object to access files under .hg/store
445 446 """
446 447 # for "historical portability":
447 448 # repo.svfs has been available since 2.3 (or 7034365089bf)
448 449 svfs = getattr(repo, 'svfs', None)
449 450 if svfs:
450 451 return svfs
451 452 else:
452 453 return getattr(repo, 'sopener')
453 454
454 455 def getvfs(repo):
455 456 """Return appropriate object to access files under .hg
456 457 """
457 458 # for "historical portability":
458 459 # repo.vfs has been available since 2.3 (or 7034365089bf)
459 460 vfs = getattr(repo, 'vfs', None)
460 461 if vfs:
461 462 return vfs
462 463 else:
463 464 return getattr(repo, 'opener')
464 465
465 466 def repocleartagscachefunc(repo):
466 467 """Return the function to clear tags cache according to repo internal API
467 468 """
468 469 if util.safehasattr(repo, b'_tagscache'): # since 2.0 (or 9dca7653b525)
469 470 # in this case, setattr(repo, '_tagscache', None) or so isn't
470 471 # correct way to clear tags cache, because existing code paths
471 472 # expect _tagscache to be a structured object.
472 473 def clearcache():
473 474 # _tagscache has been filteredpropertycache since 2.5 (or
474 475 # 98c867ac1330), and delattr() can't work in such case
475 476 if b'_tagscache' in vars(repo):
476 477 del repo.__dict__[b'_tagscache']
477 478 return clearcache
478 479
479 480 repotags = safeattrsetter(repo, b'_tags', ignoremissing=True)
480 481 if repotags: # since 1.4 (or 5614a628d173)
481 482 return lambda : repotags.set(None)
482 483
483 484 repotagscache = safeattrsetter(repo, b'tagscache', ignoremissing=True)
484 485 if repotagscache: # since 0.6 (or d7df759d0e97)
485 486 return lambda : repotagscache.set(None)
486 487
487 488 # Mercurial earlier than 0.6 (or d7df759d0e97) logically reaches
488 489 # this point, but it isn't so problematic, because:
489 490 # - repo.tags of such Mercurial isn't "callable", and repo.tags()
490 491 # in perftags() causes failure soon
491 492 # - perf.py itself has been available since 1.1 (or eb240755386d)
492 493 raise error.Abort((b"tags API of this hg command is unknown"))
493 494
494 495 # utilities to clear cache
495 496
496 497 def clearfilecache(obj, attrname):
497 498 unfiltered = getattr(obj, 'unfiltered', None)
498 499 if unfiltered is not None:
499 500 obj = obj.unfiltered()
500 501 if attrname in vars(obj):
501 502 delattr(obj, attrname)
502 503 obj._filecache.pop(attrname, None)
503 504
504 505 def clearchangelog(repo):
505 506 if repo is not repo.unfiltered():
506 507 object.__setattr__(repo, r'_clcachekey', None)
507 508 object.__setattr__(repo, r'_clcache', None)
508 509 clearfilecache(repo.unfiltered(), 'changelog')
509 510
510 511 # perf commands
511 512
512 513 @command(b'perfwalk', formatteropts)
513 514 def perfwalk(ui, repo, *pats, **opts):
514 515 opts = _byteskwargs(opts)
515 516 timer, fm = gettimer(ui, opts)
516 517 m = scmutil.match(repo[None], pats, {})
517 518 timer(lambda: len(list(repo.dirstate.walk(m, subrepos=[], unknown=True,
518 519 ignored=False))))
519 520 fm.end()
520 521
521 522 @command(b'perfannotate', formatteropts)
522 523 def perfannotate(ui, repo, f, **opts):
523 524 opts = _byteskwargs(opts)
524 525 timer, fm = gettimer(ui, opts)
525 526 fc = repo[b'.'][f]
526 527 timer(lambda: len(fc.annotate(True)))
527 528 fm.end()
528 529
529 530 @command(b'perfstatus',
530 531 [(b'u', b'unknown', False,
531 532 b'ask status to look for unknown files')] + formatteropts)
532 533 def perfstatus(ui, repo, **opts):
533 534 opts = _byteskwargs(opts)
534 535 #m = match.always(repo.root, repo.getcwd())
535 536 #timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
536 537 # False))))
537 538 timer, fm = gettimer(ui, opts)
538 539 timer(lambda: sum(map(len, repo.status(unknown=opts[b'unknown']))))
539 540 fm.end()
540 541
541 542 @command(b'perfaddremove', formatteropts)
542 543 def perfaddremove(ui, repo, **opts):
543 544 opts = _byteskwargs(opts)
544 545 timer, fm = gettimer(ui, opts)
545 546 try:
546 547 oldquiet = repo.ui.quiet
547 548 repo.ui.quiet = True
548 549 matcher = scmutil.match(repo[None])
549 550 opts[b'dry_run'] = True
550 551 if b'uipathfn' in getargspec(scmutil.addremove).args:
551 552 uipathfn = scmutil.getuipathfn(repo)
552 553 timer(lambda: scmutil.addremove(repo, matcher, b"", uipathfn, opts))
553 554 else:
554 555 timer(lambda: scmutil.addremove(repo, matcher, b"", opts))
555 556 finally:
556 557 repo.ui.quiet = oldquiet
557 558 fm.end()
558 559
559 560 def clearcaches(cl):
560 561 # behave somewhat consistently across internal API changes
561 562 if util.safehasattr(cl, b'clearcaches'):
562 563 cl.clearcaches()
563 564 elif util.safehasattr(cl, b'_nodecache'):
564 565 from mercurial.node import nullid, nullrev
565 566 cl._nodecache = {nullid: nullrev}
566 567 cl._nodepos = None
567 568
568 569 @command(b'perfheads', formatteropts)
569 570 def perfheads(ui, repo, **opts):
570 571 """benchmark the computation of a changelog heads"""
571 572 opts = _byteskwargs(opts)
572 573 timer, fm = gettimer(ui, opts)
573 574 cl = repo.changelog
574 575 def s():
575 576 clearcaches(cl)
576 577 def d():
577 578 len(cl.headrevs())
578 579 timer(d, setup=s)
579 580 fm.end()
580 581
581 582 @command(b'perftags', formatteropts+
582 583 [
583 584 (b'', b'clear-revlogs', False, b'refresh changelog and manifest'),
584 585 ])
585 586 def perftags(ui, repo, **opts):
586 587 opts = _byteskwargs(opts)
587 588 timer, fm = gettimer(ui, opts)
588 589 repocleartagscache = repocleartagscachefunc(repo)
589 590 clearrevlogs = opts[b'clear_revlogs']
590 591 def s():
591 592 if clearrevlogs:
592 593 clearchangelog(repo)
593 594 clearfilecache(repo.unfiltered(), 'manifest')
594 595 repocleartagscache()
595 596 def t():
596 597 return len(repo.tags())
597 598 timer(t, setup=s)
598 599 fm.end()
599 600
600 601 @command(b'perfancestors', formatteropts)
601 602 def perfancestors(ui, repo, **opts):
602 603 opts = _byteskwargs(opts)
603 604 timer, fm = gettimer(ui, opts)
604 605 heads = repo.changelog.headrevs()
605 606 def d():
606 607 for a in repo.changelog.ancestors(heads):
607 608 pass
608 609 timer(d)
609 610 fm.end()
610 611
611 612 @command(b'perfancestorset', formatteropts)
612 613 def perfancestorset(ui, repo, revset, **opts):
613 614 opts = _byteskwargs(opts)
614 615 timer, fm = gettimer(ui, opts)
615 616 revs = repo.revs(revset)
616 617 heads = repo.changelog.headrevs()
617 618 def d():
618 619 s = repo.changelog.ancestors(heads)
619 620 for rev in revs:
620 621 rev in s
621 622 timer(d)
622 623 fm.end()
623 624
624 625 @command(b'perfdiscovery', formatteropts, b'PATH')
625 626 def perfdiscovery(ui, repo, path, **opts):
626 627 """benchmark discovery between local repo and the peer at given path
627 628 """
628 629 repos = [repo, None]
629 630 timer, fm = gettimer(ui, opts)
630 631 path = ui.expandpath(path)
631 632
632 633 def s():
633 634 repos[1] = hg.peer(ui, opts, path)
634 635 def d():
635 636 setdiscovery.findcommonheads(ui, *repos)
636 637 timer(d, setup=s)
637 638 fm.end()
638 639
639 640 @command(b'perfbookmarks', formatteropts +
640 641 [
641 642 (b'', b'clear-revlogs', False, b'refresh changelog and manifest'),
642 643 ])
643 644 def perfbookmarks(ui, repo, **opts):
644 645 """benchmark parsing bookmarks from disk to memory"""
645 646 opts = _byteskwargs(opts)
646 647 timer, fm = gettimer(ui, opts)
647 648
648 649 clearrevlogs = opts[b'clear_revlogs']
649 650 def s():
650 651 if clearrevlogs:
651 652 clearchangelog(repo)
652 653 clearfilecache(repo, b'_bookmarks')
653 654 def d():
654 655 repo._bookmarks
655 656 timer(d, setup=s)
656 657 fm.end()
657 658
658 659 @command(b'perfbundleread', formatteropts, b'BUNDLE')
659 660 def perfbundleread(ui, repo, bundlepath, **opts):
660 661 """Benchmark reading of bundle files.
661 662
662 663 This command is meant to isolate the I/O part of bundle reading as
663 664 much as possible.
664 665 """
665 666 from mercurial import (
666 667 bundle2,
667 668 exchange,
668 669 streamclone,
669 670 )
670 671
671 672 opts = _byteskwargs(opts)
672 673
673 674 def makebench(fn):
674 675 def run():
675 676 with open(bundlepath, b'rb') as fh:
676 677 bundle = exchange.readbundle(ui, fh, bundlepath)
677 678 fn(bundle)
678 679
679 680 return run
680 681
681 682 def makereadnbytes(size):
682 683 def run():
683 684 with open(bundlepath, b'rb') as fh:
684 685 bundle = exchange.readbundle(ui, fh, bundlepath)
685 686 while bundle.read(size):
686 687 pass
687 688
688 689 return run
689 690
690 691 def makestdioread(size):
691 692 def run():
692 693 with open(bundlepath, b'rb') as fh:
693 694 while fh.read(size):
694 695 pass
695 696
696 697 return run
697 698
698 699 # bundle1
699 700
700 701 def deltaiter(bundle):
701 702 for delta in bundle.deltaiter():
702 703 pass
703 704
704 705 def iterchunks(bundle):
705 706 for chunk in bundle.getchunks():
706 707 pass
707 708
708 709 # bundle2
709 710
710 711 def forwardchunks(bundle):
711 712 for chunk in bundle._forwardchunks():
712 713 pass
713 714
714 715 def iterparts(bundle):
715 716 for part in bundle.iterparts():
716 717 pass
717 718
718 719 def iterpartsseekable(bundle):
719 720 for part in bundle.iterparts(seekable=True):
720 721 pass
721 722
722 723 def seek(bundle):
723 724 for part in bundle.iterparts(seekable=True):
724 725 part.seek(0, os.SEEK_END)
725 726
726 727 def makepartreadnbytes(size):
727 728 def run():
728 729 with open(bundlepath, b'rb') as fh:
729 730 bundle = exchange.readbundle(ui, fh, bundlepath)
730 731 for part in bundle.iterparts():
731 732 while part.read(size):
732 733 pass
733 734
734 735 return run
735 736
736 737 benches = [
737 738 (makestdioread(8192), b'read(8k)'),
738 739 (makestdioread(16384), b'read(16k)'),
739 740 (makestdioread(32768), b'read(32k)'),
740 741 (makestdioread(131072), b'read(128k)'),
741 742 ]
742 743
743 744 with open(bundlepath, b'rb') as fh:
744 745 bundle = exchange.readbundle(ui, fh, bundlepath)
745 746
746 747 if isinstance(bundle, changegroup.cg1unpacker):
747 748 benches.extend([
748 749 (makebench(deltaiter), b'cg1 deltaiter()'),
749 750 (makebench(iterchunks), b'cg1 getchunks()'),
750 751 (makereadnbytes(8192), b'cg1 read(8k)'),
751 752 (makereadnbytes(16384), b'cg1 read(16k)'),
752 753 (makereadnbytes(32768), b'cg1 read(32k)'),
753 754 (makereadnbytes(131072), b'cg1 read(128k)'),
754 755 ])
755 756 elif isinstance(bundle, bundle2.unbundle20):
756 757 benches.extend([
757 758 (makebench(forwardchunks), b'bundle2 forwardchunks()'),
758 759 (makebench(iterparts), b'bundle2 iterparts()'),
759 760 (makebench(iterpartsseekable), b'bundle2 iterparts() seekable'),
760 761 (makebench(seek), b'bundle2 part seek()'),
761 762 (makepartreadnbytes(8192), b'bundle2 part read(8k)'),
762 763 (makepartreadnbytes(16384), b'bundle2 part read(16k)'),
763 764 (makepartreadnbytes(32768), b'bundle2 part read(32k)'),
764 765 (makepartreadnbytes(131072), b'bundle2 part read(128k)'),
765 766 ])
766 767 elif isinstance(bundle, streamclone.streamcloneapplier):
767 768 raise error.Abort(b'stream clone bundles not supported')
768 769 else:
769 770 raise error.Abort(b'unhandled bundle type: %s' % type(bundle))
770 771
771 772 for fn, title in benches:
772 773 timer, fm = gettimer(ui, opts)
773 774 timer(fn, title=title)
774 775 fm.end()
775 776
776 777 @command(b'perfchangegroupchangelog', formatteropts +
777 778 [(b'', b'cgversion', b'02', b'changegroup version'),
778 779 (b'r', b'rev', b'', b'revisions to add to changegroup')])
779 780 def perfchangegroupchangelog(ui, repo, cgversion=b'02', rev=None, **opts):
780 781 """Benchmark producing a changelog group for a changegroup.
781 782
782 783 This measures the time spent processing the changelog during a
783 784 bundle operation. This occurs during `hg bundle` and on a server
784 785 processing a `getbundle` wire protocol request (handles clones
785 786 and pull requests).
786 787
787 788 By default, all revisions are added to the changegroup.
788 789 """
789 790 opts = _byteskwargs(opts)
790 791 cl = repo.changelog
791 792 nodes = [cl.lookup(r) for r in repo.revs(rev or b'all()')]
792 793 bundler = changegroup.getbundler(cgversion, repo)
793 794
794 795 def d():
795 796 state, chunks = bundler._generatechangelog(cl, nodes)
796 797 for chunk in chunks:
797 798 pass
798 799
799 800 timer, fm = gettimer(ui, opts)
800 801
801 802 # Terminal printing can interfere with timing. So disable it.
802 803 with ui.configoverride({(b'progress', b'disable'): True}):
803 804 timer(d)
804 805
805 806 fm.end()
806 807
807 808 @command(b'perfdirs', formatteropts)
808 809 def perfdirs(ui, repo, **opts):
809 810 opts = _byteskwargs(opts)
810 811 timer, fm = gettimer(ui, opts)
811 812 dirstate = repo.dirstate
812 813 b'a' in dirstate
813 814 def d():
814 815 dirstate.hasdir(b'a')
815 816 del dirstate._map._dirs
816 817 timer(d)
817 818 fm.end()
818 819
819 820 @command(b'perfdirstate', formatteropts)
820 821 def perfdirstate(ui, repo, **opts):
821 822 opts = _byteskwargs(opts)
822 823 timer, fm = gettimer(ui, opts)
823 824 b"a" in repo.dirstate
824 825 def d():
825 826 repo.dirstate.invalidate()
826 827 b"a" in repo.dirstate
827 828 timer(d)
828 829 fm.end()
829 830
830 831 @command(b'perfdirstatedirs', formatteropts)
831 832 def perfdirstatedirs(ui, repo, **opts):
832 833 opts = _byteskwargs(opts)
833 834 timer, fm = gettimer(ui, opts)
834 835 b"a" in repo.dirstate
835 836 def d():
836 837 repo.dirstate.hasdir(b"a")
837 838 del repo.dirstate._map._dirs
838 839 timer(d)
839 840 fm.end()
840 841
841 842 @command(b'perfdirstatefoldmap', formatteropts)
842 843 def perfdirstatefoldmap(ui, repo, **opts):
843 844 opts = _byteskwargs(opts)
844 845 timer, fm = gettimer(ui, opts)
845 846 dirstate = repo.dirstate
846 847 b'a' in dirstate
847 848 def d():
848 849 dirstate._map.filefoldmap.get(b'a')
849 850 del dirstate._map.filefoldmap
850 851 timer(d)
851 852 fm.end()
852 853
853 854 @command(b'perfdirfoldmap', formatteropts)
854 855 def perfdirfoldmap(ui, repo, **opts):
855 856 opts = _byteskwargs(opts)
856 857 timer, fm = gettimer(ui, opts)
857 858 dirstate = repo.dirstate
858 859 b'a' in dirstate
859 860 def d():
860 861 dirstate._map.dirfoldmap.get(b'a')
861 862 del dirstate._map.dirfoldmap
862 863 del dirstate._map._dirs
863 864 timer(d)
864 865 fm.end()
865 866
866 867 @command(b'perfdirstatewrite', formatteropts)
867 868 def perfdirstatewrite(ui, repo, **opts):
868 869 opts = _byteskwargs(opts)
869 870 timer, fm = gettimer(ui, opts)
870 871 ds = repo.dirstate
871 872 b"a" in ds
872 873 def d():
873 874 ds._dirty = True
874 875 ds.write(repo.currenttransaction())
875 876 timer(d)
876 877 fm.end()
877 878
878 879 @command(b'perfmergecalculate',
879 880 [(b'r', b'rev', b'.', b'rev to merge against')] + formatteropts)
880 881 def perfmergecalculate(ui, repo, rev, **opts):
881 882 opts = _byteskwargs(opts)
882 883 timer, fm = gettimer(ui, opts)
883 884 wctx = repo[None]
884 885 rctx = scmutil.revsingle(repo, rev, rev)
885 886 ancestor = wctx.ancestor(rctx)
886 887 # we don't want working dir files to be stat'd in the benchmark, so prime
887 888 # that cache
888 889 wctx.dirty()
889 890 def d():
890 891 # acceptremote is True because we don't want prompts in the middle of
891 892 # our benchmark
892 893 merge.calculateupdates(repo, wctx, rctx, [ancestor], False, False,
893 894 acceptremote=True, followcopies=True)
894 895 timer(d)
895 896 fm.end()
896 897
897 898 @command(b'perfpathcopies', [], b"REV REV")
898 899 def perfpathcopies(ui, repo, rev1, rev2, **opts):
899 900 """benchmark the copy tracing logic"""
900 901 opts = _byteskwargs(opts)
901 902 timer, fm = gettimer(ui, opts)
902 903 ctx1 = scmutil.revsingle(repo, rev1, rev1)
903 904 ctx2 = scmutil.revsingle(repo, rev2, rev2)
904 905 def d():
905 906 copies.pathcopies(ctx1, ctx2)
906 907 timer(d)
907 908 fm.end()
908 909
909 910 @command(b'perfphases',
910 911 [(b'', b'full', False, b'include file reading time too'),
911 912 ], b"")
912 913 def perfphases(ui, repo, **opts):
913 914 """benchmark phasesets computation"""
914 915 opts = _byteskwargs(opts)
915 916 timer, fm = gettimer(ui, opts)
916 917 _phases = repo._phasecache
917 918 full = opts.get(b'full')
918 919 def d():
919 920 phases = _phases
920 921 if full:
921 922 clearfilecache(repo, b'_phasecache')
922 923 phases = repo._phasecache
923 924 phases.invalidate()
924 925 phases.loadphaserevs(repo)
925 926 timer(d)
926 927 fm.end()
927 928
928 929 @command(b'perfphasesremote',
929 930 [], b"[DEST]")
930 931 def perfphasesremote(ui, repo, dest=None, **opts):
931 932 """benchmark time needed to analyse phases of the remote server"""
932 933 from mercurial.node import (
933 934 bin,
934 935 )
935 936 from mercurial import (
936 937 exchange,
937 938 hg,
938 939 phases,
939 940 )
940 941 opts = _byteskwargs(opts)
941 942 timer, fm = gettimer(ui, opts)
942 943
943 944 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
944 945 if not path:
945 946 raise error.Abort((b'default repository not configured!'),
946 947 hint=(b"see 'hg help config.paths'"))
947 948 dest = path.pushloc or path.loc
948 949 ui.status((b'analysing phase of %s\n') % util.hidepassword(dest))
949 950 other = hg.peer(repo, opts, dest)
950 951
951 952 # easier to perform discovery through the operation
952 953 op = exchange.pushoperation(repo, other)
953 954 exchange._pushdiscoverychangeset(op)
954 955
955 956 remotesubset = op.fallbackheads
956 957
957 958 with other.commandexecutor() as e:
958 959 remotephases = e.callcommand(b'listkeys',
959 960 {b'namespace': b'phases'}).result()
960 961 del other
961 962 publishing = remotephases.get(b'publishing', False)
962 963 if publishing:
963 964 ui.status((b'publishing: yes\n'))
964 965 else:
965 966 ui.status((b'publishing: no\n'))
966 967
967 968 nodemap = repo.changelog.nodemap
968 969 nonpublishroots = 0
969 970 for nhex, phase in remotephases.iteritems():
970 971 if nhex == b'publishing': # ignore data related to publish option
971 972 continue
972 973 node = bin(nhex)
973 974 if node in nodemap and int(phase):
974 975 nonpublishroots += 1
975 976 ui.status((b'number of roots: %d\n') % len(remotephases))
976 977 ui.status((b'number of known non public roots: %d\n') % nonpublishroots)
977 978 def d():
978 979 phases.remotephasessummary(repo,
979 980 remotesubset,
980 981 remotephases)
981 982 timer(d)
982 983 fm.end()
983 984
984 985 @command(b'perfmanifest',[
985 986 (b'm', b'manifest-rev', False, b'Look up a manifest node revision'),
986 987 (b'', b'clear-disk', False, b'clear on-disk caches too'),
987 988 ] + formatteropts, b'REV|NODE')
988 989 def perfmanifest(ui, repo, rev, manifest_rev=False, clear_disk=False, **opts):
989 990 """benchmark the time to read a manifest from disk and return a usable
990 991 dict-like object
991 992
992 993 Manifest caches are cleared before retrieval."""
993 994 opts = _byteskwargs(opts)
994 995 timer, fm = gettimer(ui, opts)
995 996 if not manifest_rev:
996 997 ctx = scmutil.revsingle(repo, rev, rev)
997 998 t = ctx.manifestnode()
998 999 else:
999 1000 from mercurial.node import bin
1000 1001
1001 1002 if len(rev) == 40:
1002 1003 t = bin(rev)
1003 1004 else:
1004 1005 try:
1005 1006 rev = int(rev)
1006 1007
1007 1008 if util.safehasattr(repo.manifestlog, b'getstorage'):
1008 1009 t = repo.manifestlog.getstorage(b'').node(rev)
1009 1010 else:
1010 1011 t = repo.manifestlog._revlog.lookup(rev)
1011 1012 except ValueError:
1012 1013 raise error.Abort(b'manifest revision must be integer or full '
1013 1014 b'node')
1014 1015 def d():
1015 1016 repo.manifestlog.clearcaches(clear_persisted_data=clear_disk)
1016 1017 repo.manifestlog[t].read()
1017 1018 timer(d)
1018 1019 fm.end()
1019 1020
1020 1021 @command(b'perfchangeset', formatteropts)
1021 1022 def perfchangeset(ui, repo, rev, **opts):
1022 1023 opts = _byteskwargs(opts)
1023 1024 timer, fm = gettimer(ui, opts)
1024 1025 n = scmutil.revsingle(repo, rev).node()
1025 1026 def d():
1026 1027 repo.changelog.read(n)
1027 1028 #repo.changelog._cache = None
1028 1029 timer(d)
1029 1030 fm.end()
1030 1031
1031 1032 @command(b'perfignore', formatteropts)
1032 1033 def perfignore(ui, repo, **opts):
1033 1034 """benchmark operation related to computing ignore"""
1034 1035 opts = _byteskwargs(opts)
1035 1036 timer, fm = gettimer(ui, opts)
1036 1037 dirstate = repo.dirstate
1037 1038
1038 1039 def setupone():
1039 1040 dirstate.invalidate()
1040 1041 clearfilecache(dirstate, b'_ignore')
1041 1042
1042 1043 def runone():
1043 1044 dirstate._ignore
1044 1045
1045 1046 timer(runone, setup=setupone, title=b"load")
1046 1047 fm.end()
1047 1048
1048 1049 @command(b'perfindex', [
1049 1050 (b'', b'rev', [], b'revision to be looked up (default tip)'),
1050 1051 (b'', b'no-lookup', None, b'do not revision lookup post creation'),
1051 1052 ] + formatteropts)
1052 1053 def perfindex(ui, repo, **opts):
1053 1054 """benchmark index creation time followed by a lookup
1054 1055
1055 1056 The default is to look `tip` up. Depending on the index implementation,
1056 1057 the revision looked up can matters. For example, an implementation
1057 1058 scanning the index will have a faster lookup time for `--rev tip` than for
1058 1059 `--rev 0`. The number of looked up revisions and their order can also
1059 1060 matters.
1060 1061
1061 1062 Example of useful set to test:
1062 1063 * tip
1063 1064 * 0
1064 1065 * -10:
1065 1066 * :10
1066 1067 * -10: + :10
1067 1068 * :10: + -10:
1068 1069 * -10000:
1069 1070 * -10000: + 0
1070 1071
1071 1072 It is not currently possible to check for lookup of a missing node. For
1072 1073 deeper lookup benchmarking, checkout the `perfnodemap` command."""
1073 1074 import mercurial.revlog
1074 1075 opts = _byteskwargs(opts)
1075 1076 timer, fm = gettimer(ui, opts)
1076 1077 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
1077 1078 if opts[b'no_lookup']:
1078 1079 if opts['rev']:
1079 1080 raise error.Abort('--no-lookup and --rev are mutually exclusive')
1080 1081 nodes = []
1081 1082 elif not opts[b'rev']:
1082 1083 nodes = [repo[b"tip"].node()]
1083 1084 else:
1084 1085 revs = scmutil.revrange(repo, opts[b'rev'])
1085 1086 cl = repo.changelog
1086 1087 nodes = [cl.node(r) for r in revs]
1087 1088
1088 1089 unfi = repo.unfiltered()
1089 1090 # find the filecache func directly
1090 1091 # This avoid polluting the benchmark with the filecache logic
1091 1092 makecl = unfi.__class__.changelog.func
1092 1093 def setup():
1093 1094 # probably not necessary, but for good measure
1094 1095 clearchangelog(unfi)
1095 1096 def d():
1096 1097 cl = makecl(unfi)
1097 1098 for n in nodes:
1098 1099 cl.rev(n)
1099 1100 timer(d, setup=setup)
1100 1101 fm.end()
1101 1102
1102 1103 @command(b'perfnodemap', [
1103 1104 (b'', b'rev', [], b'revision to be looked up (default tip)'),
1104 1105 (b'', b'clear-caches', True, b'clear revlog cache between calls'),
1105 1106 ] + formatteropts)
1106 1107 def perfnodemap(ui, repo, **opts):
1107 1108 """benchmark the time necessary to look up revision from a cold nodemap
1108 1109
1109 1110 Depending on the implementation, the amount and order of revision we look
1110 1111 up can varies. Example of useful set to test:
1111 1112 * tip
1112 1113 * 0
1113 1114 * -10:
1114 1115 * :10
1115 1116 * -10: + :10
1116 1117 * :10: + -10:
1117 1118 * -10000:
1118 1119 * -10000: + 0
1119 1120
1120 1121 The command currently focus on valid binary lookup. Benchmarking for
1121 1122 hexlookup, prefix lookup and missing lookup would also be valuable.
1122 1123 """
1123 1124 import mercurial.revlog
1124 1125 opts = _byteskwargs(opts)
1125 1126 timer, fm = gettimer(ui, opts)
1126 1127 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
1127 1128
1128 1129 unfi = repo.unfiltered()
1129 1130 clearcaches = opts['clear_caches']
1130 1131 # find the filecache func directly
1131 1132 # This avoid polluting the benchmark with the filecache logic
1132 1133 makecl = unfi.__class__.changelog.func
1133 1134 if not opts[b'rev']:
1134 1135 raise error.Abort('use --rev to specify revisions to look up')
1135 1136 revs = scmutil.revrange(repo, opts[b'rev'])
1136 1137 cl = repo.changelog
1137 1138 nodes = [cl.node(r) for r in revs]
1138 1139
1139 1140 # use a list to pass reference to a nodemap from one closure to the next
1140 1141 nodeget = [None]
1141 1142 def setnodeget():
1142 1143 # probably not necessary, but for good measure
1143 1144 clearchangelog(unfi)
1144 1145 nodeget[0] = makecl(unfi).nodemap.get
1145 1146
1146 1147 def d():
1147 1148 get = nodeget[0]
1148 1149 for n in nodes:
1149 1150 get(n)
1150 1151
1151 1152 setup = None
1152 1153 if clearcaches:
1153 1154 def setup():
1154 1155 setnodeget()
1155 1156 else:
1156 1157 setnodeget()
1157 1158 d() # prewarm the data structure
1158 1159 timer(d, setup=setup)
1159 1160 fm.end()
1160 1161
1161 1162 @command(b'perfstartup', formatteropts)
1162 1163 def perfstartup(ui, repo, **opts):
1163 1164 opts = _byteskwargs(opts)
1164 1165 timer, fm = gettimer(ui, opts)
1165 1166 def d():
1166 1167 if os.name != r'nt':
1167 1168 os.system(b"HGRCPATH= %s version -q > /dev/null" %
1168 1169 fsencode(sys.argv[0]))
1169 1170 else:
1170 1171 os.environ[r'HGRCPATH'] = r' '
1171 1172 os.system(r"%s version -q > NUL" % sys.argv[0])
1172 1173 timer(d)
1173 1174 fm.end()
1174 1175
1175 1176 @command(b'perfparents', formatteropts)
1176 1177 def perfparents(ui, repo, **opts):
1177 1178 """benchmark the time necessary to fetch one changeset's parents.
1178 1179
1179 1180 The fetch is done using the `node identifier`, traversing all object layer
1180 1181 from the repository object. The N first revision will be used for this
1181 1182 benchmark. N is controlled by the ``perf.parentscount`` config option
1182 1183 (default: 1000).
1183 1184 """
1184 1185 opts = _byteskwargs(opts)
1185 1186 timer, fm = gettimer(ui, opts)
1186 1187 # control the number of commits perfparents iterates over
1187 1188 # experimental config: perf.parentscount
1188 1189 count = getint(ui, b"perf", b"parentscount", 1000)
1189 1190 if len(repo.changelog) < count:
1190 1191 raise error.Abort(b"repo needs %d commits for this test" % count)
1191 1192 repo = repo.unfiltered()
1192 1193 nl = [repo.changelog.node(i) for i in _xrange(count)]
1193 1194 def d():
1194 1195 for n in nl:
1195 1196 repo.changelog.parents(n)
1196 1197 timer(d)
1197 1198 fm.end()
1198 1199
1199 1200 @command(b'perfctxfiles', formatteropts)
1200 1201 def perfctxfiles(ui, repo, x, **opts):
1201 1202 opts = _byteskwargs(opts)
1202 1203 x = int(x)
1203 1204 timer, fm = gettimer(ui, opts)
1204 1205 def d():
1205 1206 len(repo[x].files())
1206 1207 timer(d)
1207 1208 fm.end()
1208 1209
1209 1210 @command(b'perfrawfiles', formatteropts)
1210 1211 def perfrawfiles(ui, repo, x, **opts):
1211 1212 opts = _byteskwargs(opts)
1212 1213 x = int(x)
1213 1214 timer, fm = gettimer(ui, opts)
1214 1215 cl = repo.changelog
1215 1216 def d():
1216 1217 len(cl.read(x)[3])
1217 1218 timer(d)
1218 1219 fm.end()
1219 1220
1220 1221 @command(b'perflookup', formatteropts)
1221 1222 def perflookup(ui, repo, rev, **opts):
1222 1223 opts = _byteskwargs(opts)
1223 1224 timer, fm = gettimer(ui, opts)
1224 1225 timer(lambda: len(repo.lookup(rev)))
1225 1226 fm.end()
1226 1227
1227 1228 @command(b'perflinelogedits',
1228 1229 [(b'n', b'edits', 10000, b'number of edits'),
1229 1230 (b'', b'max-hunk-lines', 10, b'max lines in a hunk'),
1230 1231 ], norepo=True)
1231 1232 def perflinelogedits(ui, **opts):
1232 1233 from mercurial import linelog
1233 1234
1234 1235 opts = _byteskwargs(opts)
1235 1236
1236 1237 edits = opts[b'edits']
1237 1238 maxhunklines = opts[b'max_hunk_lines']
1238 1239
1239 1240 maxb1 = 100000
1240 1241 random.seed(0)
1241 1242 randint = random.randint
1242 1243 currentlines = 0
1243 1244 arglist = []
1244 1245 for rev in _xrange(edits):
1245 1246 a1 = randint(0, currentlines)
1246 1247 a2 = randint(a1, min(currentlines, a1 + maxhunklines))
1247 1248 b1 = randint(0, maxb1)
1248 1249 b2 = randint(b1, b1 + maxhunklines)
1249 1250 currentlines += (b2 - b1) - (a2 - a1)
1250 1251 arglist.append((rev, a1, a2, b1, b2))
1251 1252
1252 1253 def d():
1253 1254 ll = linelog.linelog()
1254 1255 for args in arglist:
1255 1256 ll.replacelines(*args)
1256 1257
1257 1258 timer, fm = gettimer(ui, opts)
1258 1259 timer(d)
1259 1260 fm.end()
1260 1261
1261 1262 @command(b'perfrevrange', formatteropts)
1262 1263 def perfrevrange(ui, repo, *specs, **opts):
1263 1264 opts = _byteskwargs(opts)
1264 1265 timer, fm = gettimer(ui, opts)
1265 1266 revrange = scmutil.revrange
1266 1267 timer(lambda: len(revrange(repo, specs)))
1267 1268 fm.end()
1268 1269
1269 1270 @command(b'perfnodelookup', formatteropts)
1270 1271 def perfnodelookup(ui, repo, rev, **opts):
1271 1272 opts = _byteskwargs(opts)
1272 1273 timer, fm = gettimer(ui, opts)
1273 1274 import mercurial.revlog
1274 1275 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
1275 1276 n = scmutil.revsingle(repo, rev).node()
1276 1277 cl = mercurial.revlog.revlog(getsvfs(repo), b"00changelog.i")
1277 1278 def d():
1278 1279 cl.rev(n)
1279 1280 clearcaches(cl)
1280 1281 timer(d)
1281 1282 fm.end()
1282 1283
1283 1284 @command(b'perflog',
1284 1285 [(b'', b'rename', False, b'ask log to follow renames')
1285 1286 ] + formatteropts)
1286 1287 def perflog(ui, repo, rev=None, **opts):
1287 1288 opts = _byteskwargs(opts)
1288 1289 if rev is None:
1289 1290 rev=[]
1290 1291 timer, fm = gettimer(ui, opts)
1291 1292 ui.pushbuffer()
1292 1293 timer(lambda: commands.log(ui, repo, rev=rev, date=b'', user=b'',
1293 1294 copies=opts.get(b'rename')))
1294 1295 ui.popbuffer()
1295 1296 fm.end()
1296 1297
1297 1298 @command(b'perfmoonwalk', formatteropts)
1298 1299 def perfmoonwalk(ui, repo, **opts):
1299 1300 """benchmark walking the changelog backwards
1300 1301
1301 1302 This also loads the changelog data for each revision in the changelog.
1302 1303 """
1303 1304 opts = _byteskwargs(opts)
1304 1305 timer, fm = gettimer(ui, opts)
1305 1306 def moonwalk():
1306 1307 for i in repo.changelog.revs(start=(len(repo) - 1), stop=-1):
1307 1308 ctx = repo[i]
1308 1309 ctx.branch() # read changelog data (in addition to the index)
1309 1310 timer(moonwalk)
1310 1311 fm.end()
1311 1312
1312 1313 @command(b'perftemplating',
1313 1314 [(b'r', b'rev', [], b'revisions to run the template on'),
1314 1315 ] + formatteropts)
1315 1316 def perftemplating(ui, repo, testedtemplate=None, **opts):
1316 1317 """test the rendering time of a given template"""
1317 1318 if makelogtemplater is None:
1318 1319 raise error.Abort((b"perftemplating not available with this Mercurial"),
1319 1320 hint=b"use 4.3 or later")
1320 1321
1321 1322 opts = _byteskwargs(opts)
1322 1323
1323 1324 nullui = ui.copy()
1324 1325 nullui.fout = open(os.devnull, r'wb')
1325 1326 nullui.disablepager()
1326 1327 revs = opts.get(b'rev')
1327 1328 if not revs:
1328 1329 revs = [b'all()']
1329 1330 revs = list(scmutil.revrange(repo, revs))
1330 1331
1331 1332 defaulttemplate = (b'{date|shortdate} [{rev}:{node|short}]'
1332 1333 b' {author|person}: {desc|firstline}\n')
1333 1334 if testedtemplate is None:
1334 1335 testedtemplate = defaulttemplate
1335 1336 displayer = makelogtemplater(nullui, repo, testedtemplate)
1336 1337 def format():
1337 1338 for r in revs:
1338 1339 ctx = repo[r]
1339 1340 displayer.show(ctx)
1340 1341 displayer.flush(ctx)
1341 1342
1342 1343 timer, fm = gettimer(ui, opts)
1343 1344 timer(format)
1344 1345 fm.end()
1345 1346
1346 1347 @command(b'perfhelper-pathcopies', formatteropts +
1347 1348 [
1348 1349 (b'r', b'revs', [], b'restrict search to these revisions'),
1349 1350 (b'', b'timing', False, b'provides extra data (costly)'),
1350 1351 ])
1351 1352 def perfhelperpathcopies(ui, repo, revs=[], **opts):
1352 1353 """find statistic about potential parameters for the `perftracecopies`
1353 1354
1354 1355 This command find source-destination pair relevant for copytracing testing.
1355 1356 It report value for some of the parameters that impact copy tracing time.
1356 1357
1357 1358 If `--timing` is set, rename detection is run and the associated timing
1358 1359 will be reported. The extra details comes at the cost of a slower command
1359 1360 execution.
1360 1361
1361 1362 Since the rename detection is only run once, other factors might easily
1362 1363 affect the precision of the timing. However it should give a good
1363 1364 approximation of which revision pairs are very costly.
1364 1365 """
1365 1366 opts = _byteskwargs(opts)
1366 1367 fm = ui.formatter(b'perf', opts)
1367 1368 dotiming = opts[b'timing']
1368 1369
1369 1370 if dotiming:
1370 1371 header = '%12s %12s %12s %12s %12s %12s\n'
1371 1372 output = ("%(source)12s %(destination)12s "
1372 1373 "%(nbrevs)12d %(nbmissingfiles)12d "
1373 1374 "%(nbrenamedfiles)12d %(time)18.5f\n")
1374 1375 header_names = ("source", "destination", "nb-revs", "nb-files",
1375 1376 "nb-renames", "time")
1376 1377 fm.plain(header % header_names)
1377 1378 else:
1378 1379 header = '%12s %12s %12s %12s\n'
1379 1380 output = ("%(source)12s %(destination)12s "
1380 1381 "%(nbrevs)12d %(nbmissingfiles)12d\n")
1381 1382 fm.plain(header % ("source", "destination", "nb-revs", "nb-files"))
1382 1383
1383 1384 if not revs:
1384 1385 revs = ['all()']
1385 1386 revs = scmutil.revrange(repo, revs)
1386 1387
1387 1388 roi = repo.revs('merge() and %ld', revs)
1388 1389 for r in roi:
1389 1390 ctx = repo[r]
1390 1391 p1 = ctx.p1().rev()
1391 1392 p2 = ctx.p2().rev()
1392 1393 bases = repo.changelog._commonancestorsheads(p1, p2)
1393 1394 for p in (p1, p2):
1394 1395 for b in bases:
1395 1396 base = repo[b]
1396 1397 parent = repo[p]
1397 1398 missing = copies._computeforwardmissing(base, parent)
1398 1399 if not missing:
1399 1400 continue
1400 1401 data = {
1401 1402 b'source': base.hex(),
1402 1403 b'destination': parent.hex(),
1403 1404 b'nbrevs': len(repo.revs('%d::%d', b, p)),
1404 1405 b'nbmissingfiles': len(missing),
1405 1406 }
1406 1407 if dotiming:
1407 1408 begin = util.timer()
1408 1409 renames = copies.pathcopies(base, parent)
1409 1410 end = util.timer()
1410 1411 # not very stable timing since we did only one run
1411 1412 data['time'] = end - begin
1412 1413 data['nbrenamedfiles'] = len(renames)
1413 1414 fm.startitem()
1414 1415 fm.data(**data)
1415 1416 out = data.copy()
1416 1417 out['source'] = fm.hexfunc(base.node())
1417 1418 out['destination'] = fm.hexfunc(parent.node())
1418 1419 fm.plain(output % out)
1419 1420
1420 1421 fm.end()
1421 1422
1422 1423 @command(b'perfcca', formatteropts)
1423 1424 def perfcca(ui, repo, **opts):
1424 1425 opts = _byteskwargs(opts)
1425 1426 timer, fm = gettimer(ui, opts)
1426 1427 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
1427 1428 fm.end()
1428 1429
1429 1430 @command(b'perffncacheload', formatteropts)
1430 1431 def perffncacheload(ui, repo, **opts):
1431 1432 opts = _byteskwargs(opts)
1432 1433 timer, fm = gettimer(ui, opts)
1433 1434 s = repo.store
1434 1435 def d():
1435 1436 s.fncache._load()
1436 1437 timer(d)
1437 1438 fm.end()
1438 1439
1439 1440 @command(b'perffncachewrite', formatteropts)
1440 1441 def perffncachewrite(ui, repo, **opts):
1441 1442 opts = _byteskwargs(opts)
1442 1443 timer, fm = gettimer(ui, opts)
1443 1444 s = repo.store
1444 1445 lock = repo.lock()
1445 1446 s.fncache._load()
1446 1447 tr = repo.transaction(b'perffncachewrite')
1447 1448 tr.addbackup(b'fncache')
1448 1449 def d():
1449 1450 s.fncache._dirty = True
1450 1451 s.fncache.write(tr)
1451 1452 timer(d)
1452 1453 tr.close()
1453 1454 lock.release()
1454 1455 fm.end()
1455 1456
1456 1457 @command(b'perffncacheencode', formatteropts)
1457 1458 def perffncacheencode(ui, repo, **opts):
1458 1459 opts = _byteskwargs(opts)
1459 1460 timer, fm = gettimer(ui, opts)
1460 1461 s = repo.store
1461 1462 s.fncache._load()
1462 1463 def d():
1463 1464 for p in s.fncache.entries:
1464 1465 s.encode(p)
1465 1466 timer(d)
1466 1467 fm.end()
1467 1468
1468 1469 def _bdiffworker(q, blocks, xdiff, ready, done):
1469 1470 while not done.is_set():
1470 1471 pair = q.get()
1471 1472 while pair is not None:
1472 1473 if xdiff:
1473 1474 mdiff.bdiff.xdiffblocks(*pair)
1474 1475 elif blocks:
1475 1476 mdiff.bdiff.blocks(*pair)
1476 1477 else:
1477 1478 mdiff.textdiff(*pair)
1478 1479 q.task_done()
1479 1480 pair = q.get()
1480 1481 q.task_done() # for the None one
1481 1482 with ready:
1482 1483 ready.wait()
1483 1484
1484 1485 def _manifestrevision(repo, mnode):
1485 1486 ml = repo.manifestlog
1486 1487
1487 1488 if util.safehasattr(ml, b'getstorage'):
1488 1489 store = ml.getstorage(b'')
1489 1490 else:
1490 1491 store = ml._revlog
1491 1492
1492 1493 return store.revision(mnode)
1493 1494
1494 1495 @command(b'perfbdiff', revlogopts + formatteropts + [
1495 1496 (b'', b'count', 1, b'number of revisions to test (when using --startrev)'),
1496 1497 (b'', b'alldata', False, b'test bdiffs for all associated revisions'),
1497 1498 (b'', b'threads', 0, b'number of thread to use (disable with 0)'),
1498 1499 (b'', b'blocks', False, b'test computing diffs into blocks'),
1499 1500 (b'', b'xdiff', False, b'use xdiff algorithm'),
1500 1501 ],
1501 1502
1502 1503 b'-c|-m|FILE REV')
1503 1504 def perfbdiff(ui, repo, file_, rev=None, count=None, threads=0, **opts):
1504 1505 """benchmark a bdiff between revisions
1505 1506
1506 1507 By default, benchmark a bdiff between its delta parent and itself.
1507 1508
1508 1509 With ``--count``, benchmark bdiffs between delta parents and self for N
1509 1510 revisions starting at the specified revision.
1510 1511
1511 1512 With ``--alldata``, assume the requested revision is a changeset and
1512 1513 measure bdiffs for all changes related to that changeset (manifest
1513 1514 and filelogs).
1514 1515 """
1515 1516 opts = _byteskwargs(opts)
1516 1517
1517 1518 if opts[b'xdiff'] and not opts[b'blocks']:
1518 1519 raise error.CommandError(b'perfbdiff', b'--xdiff requires --blocks')
1519 1520
1520 1521 if opts[b'alldata']:
1521 1522 opts[b'changelog'] = True
1522 1523
1523 1524 if opts.get(b'changelog') or opts.get(b'manifest'):
1524 1525 file_, rev = None, file_
1525 1526 elif rev is None:
1526 1527 raise error.CommandError(b'perfbdiff', b'invalid arguments')
1527 1528
1528 1529 blocks = opts[b'blocks']
1529 1530 xdiff = opts[b'xdiff']
1530 1531 textpairs = []
1531 1532
1532 1533 r = cmdutil.openrevlog(repo, b'perfbdiff', file_, opts)
1533 1534
1534 1535 startrev = r.rev(r.lookup(rev))
1535 1536 for rev in range(startrev, min(startrev + count, len(r) - 1)):
1536 1537 if opts[b'alldata']:
1537 1538 # Load revisions associated with changeset.
1538 1539 ctx = repo[rev]
1539 1540 mtext = _manifestrevision(repo, ctx.manifestnode())
1540 1541 for pctx in ctx.parents():
1541 1542 pman = _manifestrevision(repo, pctx.manifestnode())
1542 1543 textpairs.append((pman, mtext))
1543 1544
1544 1545 # Load filelog revisions by iterating manifest delta.
1545 1546 man = ctx.manifest()
1546 1547 pman = ctx.p1().manifest()
1547 1548 for filename, change in pman.diff(man).items():
1548 1549 fctx = repo.file(filename)
1549 1550 f1 = fctx.revision(change[0][0] or -1)
1550 1551 f2 = fctx.revision(change[1][0] or -1)
1551 1552 textpairs.append((f1, f2))
1552 1553 else:
1553 1554 dp = r.deltaparent(rev)
1554 1555 textpairs.append((r.revision(dp), r.revision(rev)))
1555 1556
1556 1557 withthreads = threads > 0
1557 1558 if not withthreads:
1558 1559 def d():
1559 1560 for pair in textpairs:
1560 1561 if xdiff:
1561 1562 mdiff.bdiff.xdiffblocks(*pair)
1562 1563 elif blocks:
1563 1564 mdiff.bdiff.blocks(*pair)
1564 1565 else:
1565 1566 mdiff.textdiff(*pair)
1566 1567 else:
1567 1568 q = queue()
1568 1569 for i in _xrange(threads):
1569 1570 q.put(None)
1570 1571 ready = threading.Condition()
1571 1572 done = threading.Event()
1572 1573 for i in _xrange(threads):
1573 1574 threading.Thread(target=_bdiffworker,
1574 1575 args=(q, blocks, xdiff, ready, done)).start()
1575 1576 q.join()
1576 1577 def d():
1577 1578 for pair in textpairs:
1578 1579 q.put(pair)
1579 1580 for i in _xrange(threads):
1580 1581 q.put(None)
1581 1582 with ready:
1582 1583 ready.notify_all()
1583 1584 q.join()
1584 1585 timer, fm = gettimer(ui, opts)
1585 1586 timer(d)
1586 1587 fm.end()
1587 1588
1588 1589 if withthreads:
1589 1590 done.set()
1590 1591 for i in _xrange(threads):
1591 1592 q.put(None)
1592 1593 with ready:
1593 1594 ready.notify_all()
1594 1595
1595 1596 @command(b'perfunidiff', revlogopts + formatteropts + [
1596 1597 (b'', b'count', 1, b'number of revisions to test (when using --startrev)'),
1597 1598 (b'', b'alldata', False, b'test unidiffs for all associated revisions'),
1598 1599 ], b'-c|-m|FILE REV')
1599 1600 def perfunidiff(ui, repo, file_, rev=None, count=None, **opts):
1600 1601 """benchmark a unified diff between revisions
1601 1602
1602 1603 This doesn't include any copy tracing - it's just a unified diff
1603 1604 of the texts.
1604 1605
1605 1606 By default, benchmark a diff between its delta parent and itself.
1606 1607
1607 1608 With ``--count``, benchmark diffs between delta parents and self for N
1608 1609 revisions starting at the specified revision.
1609 1610
1610 1611 With ``--alldata``, assume the requested revision is a changeset and
1611 1612 measure diffs for all changes related to that changeset (manifest
1612 1613 and filelogs).
1613 1614 """
1614 1615 opts = _byteskwargs(opts)
1615 1616 if opts[b'alldata']:
1616 1617 opts[b'changelog'] = True
1617 1618
1618 1619 if opts.get(b'changelog') or opts.get(b'manifest'):
1619 1620 file_, rev = None, file_
1620 1621 elif rev is None:
1621 1622 raise error.CommandError(b'perfunidiff', b'invalid arguments')
1622 1623
1623 1624 textpairs = []
1624 1625
1625 1626 r = cmdutil.openrevlog(repo, b'perfunidiff', file_, opts)
1626 1627
1627 1628 startrev = r.rev(r.lookup(rev))
1628 1629 for rev in range(startrev, min(startrev + count, len(r) - 1)):
1629 1630 if opts[b'alldata']:
1630 1631 # Load revisions associated with changeset.
1631 1632 ctx = repo[rev]
1632 1633 mtext = _manifestrevision(repo, ctx.manifestnode())
1633 1634 for pctx in ctx.parents():
1634 1635 pman = _manifestrevision(repo, pctx.manifestnode())
1635 1636 textpairs.append((pman, mtext))
1636 1637
1637 1638 # Load filelog revisions by iterating manifest delta.
1638 1639 man = ctx.manifest()
1639 1640 pman = ctx.p1().manifest()
1640 1641 for filename, change in pman.diff(man).items():
1641 1642 fctx = repo.file(filename)
1642 1643 f1 = fctx.revision(change[0][0] or -1)
1643 1644 f2 = fctx.revision(change[1][0] or -1)
1644 1645 textpairs.append((f1, f2))
1645 1646 else:
1646 1647 dp = r.deltaparent(rev)
1647 1648 textpairs.append((r.revision(dp), r.revision(rev)))
1648 1649
1649 1650 def d():
1650 1651 for left, right in textpairs:
1651 1652 # The date strings don't matter, so we pass empty strings.
1652 1653 headerlines, hunks = mdiff.unidiff(
1653 1654 left, b'', right, b'', b'left', b'right', binary=False)
1654 1655 # consume iterators in roughly the way patch.py does
1655 1656 b'\n'.join(headerlines)
1656 1657 b''.join(sum((list(hlines) for hrange, hlines in hunks), []))
1657 1658 timer, fm = gettimer(ui, opts)
1658 1659 timer(d)
1659 1660 fm.end()
1660 1661
1661 1662 @command(b'perfdiffwd', formatteropts)
1662 1663 def perfdiffwd(ui, repo, **opts):
1663 1664 """Profile diff of working directory changes"""
1664 1665 opts = _byteskwargs(opts)
1665 1666 timer, fm = gettimer(ui, opts)
1666 1667 options = {
1667 1668 'w': 'ignore_all_space',
1668 1669 'b': 'ignore_space_change',
1669 1670 'B': 'ignore_blank_lines',
1670 1671 }
1671 1672
1672 1673 for diffopt in ('', 'w', 'b', 'B', 'wB'):
1673 1674 opts = dict((options[c], b'1') for c in diffopt)
1674 1675 def d():
1675 1676 ui.pushbuffer()
1676 1677 commands.diff(ui, repo, **opts)
1677 1678 ui.popbuffer()
1678 1679 diffopt = diffopt.encode('ascii')
1679 1680 title = b'diffopts: %s' % (diffopt and (b'-' + diffopt) or b'none')
1680 1681 timer(d, title=title)
1681 1682 fm.end()
1682 1683
1683 1684 @command(b'perfrevlogindex', revlogopts + formatteropts,
1684 1685 b'-c|-m|FILE')
1685 1686 def perfrevlogindex(ui, repo, file_=None, **opts):
1686 1687 """Benchmark operations against a revlog index.
1687 1688
1688 1689 This tests constructing a revlog instance, reading index data,
1689 1690 parsing index data, and performing various operations related to
1690 1691 index data.
1691 1692 """
1692 1693
1693 1694 opts = _byteskwargs(opts)
1694 1695
1695 1696 rl = cmdutil.openrevlog(repo, b'perfrevlogindex', file_, opts)
1696 1697
1697 1698 opener = getattr(rl, 'opener') # trick linter
1698 1699 indexfile = rl.indexfile
1699 1700 data = opener.read(indexfile)
1700 1701
1701 1702 header = struct.unpack(b'>I', data[0:4])[0]
1702 1703 version = header & 0xFFFF
1703 1704 if version == 1:
1704 1705 revlogio = revlog.revlogio()
1705 1706 inline = header & (1 << 16)
1706 1707 else:
1707 1708 raise error.Abort((b'unsupported revlog version: %d') % version)
1708 1709
1709 1710 rllen = len(rl)
1710 1711
1711 1712 node0 = rl.node(0)
1712 1713 node25 = rl.node(rllen // 4)
1713 1714 node50 = rl.node(rllen // 2)
1714 1715 node75 = rl.node(rllen // 4 * 3)
1715 1716 node100 = rl.node(rllen - 1)
1716 1717
1717 1718 allrevs = range(rllen)
1718 1719 allrevsrev = list(reversed(allrevs))
1719 1720 allnodes = [rl.node(rev) for rev in range(rllen)]
1720 1721 allnodesrev = list(reversed(allnodes))
1721 1722
1722 1723 def constructor():
1723 1724 revlog.revlog(opener, indexfile)
1724 1725
1725 1726 def read():
1726 1727 with opener(indexfile) as fh:
1727 1728 fh.read()
1728 1729
1729 1730 def parseindex():
1730 1731 revlogio.parseindex(data, inline)
1731 1732
1732 1733 def getentry(revornode):
1733 1734 index = revlogio.parseindex(data, inline)[0]
1734 1735 index[revornode]
1735 1736
1736 1737 def getentries(revs, count=1):
1737 1738 index = revlogio.parseindex(data, inline)[0]
1738 1739
1739 1740 for i in range(count):
1740 1741 for rev in revs:
1741 1742 index[rev]
1742 1743
1743 1744 def resolvenode(node):
1744 1745 nodemap = revlogio.parseindex(data, inline)[1]
1745 1746 # This only works for the C code.
1746 1747 if nodemap is None:
1747 1748 return
1748 1749
1749 1750 try:
1750 1751 nodemap[node]
1751 1752 except error.RevlogError:
1752 1753 pass
1753 1754
1754 1755 def resolvenodes(nodes, count=1):
1755 1756 nodemap = revlogio.parseindex(data, inline)[1]
1756 1757 if nodemap is None:
1757 1758 return
1758 1759
1759 1760 for i in range(count):
1760 1761 for node in nodes:
1761 1762 try:
1762 1763 nodemap[node]
1763 1764 except error.RevlogError:
1764 1765 pass
1765 1766
1766 1767 benches = [
1767 1768 (constructor, b'revlog constructor'),
1768 1769 (read, b'read'),
1769 1770 (parseindex, b'create index object'),
1770 1771 (lambda: getentry(0), b'retrieve index entry for rev 0'),
1771 1772 (lambda: resolvenode(b'a' * 20), b'look up missing node'),
1772 1773 (lambda: resolvenode(node0), b'look up node at rev 0'),
1773 1774 (lambda: resolvenode(node25), b'look up node at 1/4 len'),
1774 1775 (lambda: resolvenode(node50), b'look up node at 1/2 len'),
1775 1776 (lambda: resolvenode(node75), b'look up node at 3/4 len'),
1776 1777 (lambda: resolvenode(node100), b'look up node at tip'),
1777 1778 # 2x variation is to measure caching impact.
1778 1779 (lambda: resolvenodes(allnodes),
1779 1780 b'look up all nodes (forward)'),
1780 1781 (lambda: resolvenodes(allnodes, 2),
1781 1782 b'look up all nodes 2x (forward)'),
1782 1783 (lambda: resolvenodes(allnodesrev),
1783 1784 b'look up all nodes (reverse)'),
1784 1785 (lambda: resolvenodes(allnodesrev, 2),
1785 1786 b'look up all nodes 2x (reverse)'),
1786 1787 (lambda: getentries(allrevs),
1787 1788 b'retrieve all index entries (forward)'),
1788 1789 (lambda: getentries(allrevs, 2),
1789 1790 b'retrieve all index entries 2x (forward)'),
1790 1791 (lambda: getentries(allrevsrev),
1791 1792 b'retrieve all index entries (reverse)'),
1792 1793 (lambda: getentries(allrevsrev, 2),
1793 1794 b'retrieve all index entries 2x (reverse)'),
1794 1795 ]
1795 1796
1796 1797 for fn, title in benches:
1797 1798 timer, fm = gettimer(ui, opts)
1798 1799 timer(fn, title=title)
1799 1800 fm.end()
1800 1801
1801 1802 @command(b'perfrevlogrevisions', revlogopts + formatteropts +
1802 1803 [(b'd', b'dist', 100, b'distance between the revisions'),
1803 1804 (b's', b'startrev', 0, b'revision to start reading at'),
1804 1805 (b'', b'reverse', False, b'read in reverse')],
1805 1806 b'-c|-m|FILE')
1806 1807 def perfrevlogrevisions(ui, repo, file_=None, startrev=0, reverse=False,
1807 1808 **opts):
1808 1809 """Benchmark reading a series of revisions from a revlog.
1809 1810
1810 1811 By default, we read every ``-d/--dist`` revision from 0 to tip of
1811 1812 the specified revlog.
1812 1813
1813 1814 The start revision can be defined via ``-s/--startrev``.
1814 1815 """
1815 1816 opts = _byteskwargs(opts)
1816 1817
1817 1818 rl = cmdutil.openrevlog(repo, b'perfrevlogrevisions', file_, opts)
1818 1819 rllen = getlen(ui)(rl)
1819 1820
1820 1821 if startrev < 0:
1821 1822 startrev = rllen + startrev
1822 1823
1823 1824 def d():
1824 1825 rl.clearcaches()
1825 1826
1826 1827 beginrev = startrev
1827 1828 endrev = rllen
1828 1829 dist = opts[b'dist']
1829 1830
1830 1831 if reverse:
1831 1832 beginrev, endrev = endrev - 1, beginrev - 1
1832 1833 dist = -1 * dist
1833 1834
1834 1835 for x in _xrange(beginrev, endrev, dist):
1835 1836 # Old revisions don't support passing int.
1836 1837 n = rl.node(x)
1837 1838 rl.revision(n)
1838 1839
1839 1840 timer, fm = gettimer(ui, opts)
1840 1841 timer(d)
1841 1842 fm.end()
1842 1843
1843 1844 @command(b'perfrevlogwrite', revlogopts + formatteropts +
1844 1845 [(b's', b'startrev', 1000, b'revision to start writing at'),
1845 1846 (b'', b'stoprev', -1, b'last revision to write'),
1846 1847 (b'', b'count', 3, b'last revision to write'),
1847 1848 (b'', b'details', False, b'print timing for every revisions tested'),
1848 1849 (b'', b'source', b'full', b'the kind of data feed in the revlog'),
1849 1850 (b'', b'lazydeltabase', True, b'try the provided delta first'),
1850 1851 (b'', b'clear-caches', True, b'clear revlog cache between calls'),
1851 1852 ],
1852 1853 b'-c|-m|FILE')
1853 1854 def perfrevlogwrite(ui, repo, file_=None, startrev=1000, stoprev=-1, **opts):
1854 1855 """Benchmark writing a series of revisions to a revlog.
1855 1856
1856 1857 Possible source values are:
1857 1858 * `full`: add from a full text (default).
1858 1859 * `parent-1`: add from a delta to the first parent
1859 1860 * `parent-2`: add from a delta to the second parent if it exists
1860 1861 (use a delta from the first parent otherwise)
1861 1862 * `parent-smallest`: add from the smallest delta (either p1 or p2)
1862 1863 * `storage`: add from the existing precomputed deltas
1863 1864 """
1864 1865 opts = _byteskwargs(opts)
1865 1866
1866 1867 rl = cmdutil.openrevlog(repo, b'perfrevlogwrite', file_, opts)
1867 1868 rllen = getlen(ui)(rl)
1868 1869 if startrev < 0:
1869 1870 startrev = rllen + startrev
1870 1871 if stoprev < 0:
1871 1872 stoprev = rllen + stoprev
1872 1873
1873 1874 lazydeltabase = opts['lazydeltabase']
1874 1875 source = opts['source']
1875 1876 clearcaches = opts['clear_caches']
1876 1877 validsource = (b'full', b'parent-1', b'parent-2', b'parent-smallest',
1877 1878 b'storage')
1878 1879 if source not in validsource:
1879 1880 raise error.Abort('invalid source type: %s' % source)
1880 1881
1881 1882 ### actually gather results
1882 1883 count = opts['count']
1883 1884 if count <= 0:
1884 1885 raise error.Abort('invalide run count: %d' % count)
1885 1886 allresults = []
1886 1887 for c in range(count):
1887 1888 timing = _timeonewrite(ui, rl, source, startrev, stoprev, c + 1,
1888 1889 lazydeltabase=lazydeltabase,
1889 1890 clearcaches=clearcaches)
1890 1891 allresults.append(timing)
1891 1892
1892 1893 ### consolidate the results in a single list
1893 1894 results = []
1894 1895 for idx, (rev, t) in enumerate(allresults[0]):
1895 1896 ts = [t]
1896 1897 for other in allresults[1:]:
1897 1898 orev, ot = other[idx]
1898 1899 assert orev == rev
1899 1900 ts.append(ot)
1900 1901 results.append((rev, ts))
1901 1902 resultcount = len(results)
1902 1903
1903 1904 ### Compute and display relevant statistics
1904 1905
1905 1906 # get a formatter
1906 1907 fm = ui.formatter(b'perf', opts)
1907 1908 displayall = ui.configbool(b"perf", b"all-timing", False)
1908 1909
1909 1910 # print individual details if requested
1910 1911 if opts['details']:
1911 1912 for idx, item in enumerate(results, 1):
1912 1913 rev, data = item
1913 1914 title = 'revisions #%d of %d, rev %d' % (idx, resultcount, rev)
1914 1915 formatone(fm, data, title=title, displayall=displayall)
1915 1916
1916 1917 # sorts results by median time
1917 1918 results.sort(key=lambda x: sorted(x[1])[len(x[1]) // 2])
1918 1919 # list of (name, index) to display)
1919 1920 relevants = [
1920 1921 ("min", 0),
1921 1922 ("10%", resultcount * 10 // 100),
1922 1923 ("25%", resultcount * 25 // 100),
1923 1924 ("50%", resultcount * 70 // 100),
1924 1925 ("75%", resultcount * 75 // 100),
1925 1926 ("90%", resultcount * 90 // 100),
1926 1927 ("95%", resultcount * 95 // 100),
1927 1928 ("99%", resultcount * 99 // 100),
1928 1929 ("99.9%", resultcount * 999 // 1000),
1929 1930 ("99.99%", resultcount * 9999 // 10000),
1930 1931 ("99.999%", resultcount * 99999 // 100000),
1931 1932 ("max", -1),
1932 1933 ]
1933 1934 if not ui.quiet:
1934 1935 for name, idx in relevants:
1935 1936 data = results[idx]
1936 1937 title = '%s of %d, rev %d' % (name, resultcount, data[0])
1937 1938 formatone(fm, data[1], title=title, displayall=displayall)
1938 1939
1939 1940 # XXX summing that many float will not be very precise, we ignore this fact
1940 1941 # for now
1941 1942 totaltime = []
1942 1943 for item in allresults:
1943 1944 totaltime.append((sum(x[1][0] for x in item),
1944 1945 sum(x[1][1] for x in item),
1945 1946 sum(x[1][2] for x in item),)
1946 1947 )
1947 1948 formatone(fm, totaltime, title="total time (%d revs)" % resultcount,
1948 1949 displayall=displayall)
1949 1950 fm.end()
1950 1951
1951 1952 class _faketr(object):
1952 1953 def add(s, x, y, z=None):
1953 1954 return None
1954 1955
1955 1956 def _timeonewrite(ui, orig, source, startrev, stoprev, runidx=None,
1956 1957 lazydeltabase=True, clearcaches=True):
1957 1958 timings = []
1958 1959 tr = _faketr()
1959 1960 with _temprevlog(ui, orig, startrev) as dest:
1960 1961 dest._lazydeltabase = lazydeltabase
1961 1962 revs = list(orig.revs(startrev, stoprev))
1962 1963 total = len(revs)
1963 1964 topic = 'adding'
1964 1965 if runidx is not None:
1965 1966 topic += ' (run #%d)' % runidx
1966 1967 # Support both old and new progress API
1967 1968 if util.safehasattr(ui, 'makeprogress'):
1968 1969 progress = ui.makeprogress(topic, unit='revs', total=total)
1969 1970 def updateprogress(pos):
1970 1971 progress.update(pos)
1971 1972 def completeprogress():
1972 1973 progress.complete()
1973 1974 else:
1974 1975 def updateprogress(pos):
1975 1976 ui.progress(topic, pos, unit='revs', total=total)
1976 1977 def completeprogress():
1977 1978 ui.progress(topic, None, unit='revs', total=total)
1978 1979
1979 1980 for idx, rev in enumerate(revs):
1980 1981 updateprogress(idx)
1981 1982 addargs, addkwargs = _getrevisionseed(orig, rev, tr, source)
1982 1983 if clearcaches:
1983 1984 dest.index.clearcaches()
1984 1985 dest.clearcaches()
1985 1986 with timeone() as r:
1986 1987 dest.addrawrevision(*addargs, **addkwargs)
1987 1988 timings.append((rev, r[0]))
1988 1989 updateprogress(total)
1989 1990 completeprogress()
1990 1991 return timings
1991 1992
1992 1993 def _getrevisionseed(orig, rev, tr, source):
1993 1994 from mercurial.node import nullid
1994 1995
1995 1996 linkrev = orig.linkrev(rev)
1996 1997 node = orig.node(rev)
1997 1998 p1, p2 = orig.parents(node)
1998 1999 flags = orig.flags(rev)
1999 2000 cachedelta = None
2000 2001 text = None
2001 2002
2002 2003 if source == b'full':
2003 2004 text = orig.revision(rev)
2004 2005 elif source == b'parent-1':
2005 2006 baserev = orig.rev(p1)
2006 2007 cachedelta = (baserev, orig.revdiff(p1, rev))
2007 2008 elif source == b'parent-2':
2008 2009 parent = p2
2009 2010 if p2 == nullid:
2010 2011 parent = p1
2011 2012 baserev = orig.rev(parent)
2012 2013 cachedelta = (baserev, orig.revdiff(parent, rev))
2013 2014 elif source == b'parent-smallest':
2014 2015 p1diff = orig.revdiff(p1, rev)
2015 2016 parent = p1
2016 2017 diff = p1diff
2017 2018 if p2 != nullid:
2018 2019 p2diff = orig.revdiff(p2, rev)
2019 2020 if len(p1diff) > len(p2diff):
2020 2021 parent = p2
2021 2022 diff = p2diff
2022 2023 baserev = orig.rev(parent)
2023 2024 cachedelta = (baserev, diff)
2024 2025 elif source == b'storage':
2025 2026 baserev = orig.deltaparent(rev)
2026 2027 cachedelta = (baserev, orig.revdiff(orig.node(baserev), rev))
2027 2028
2028 2029 return ((text, tr, linkrev, p1, p2),
2029 2030 {'node': node, 'flags': flags, 'cachedelta': cachedelta})
2030 2031
2031 2032 @contextlib.contextmanager
2032 2033 def _temprevlog(ui, orig, truncaterev):
2033 2034 from mercurial import vfs as vfsmod
2034 2035
2035 2036 if orig._inline:
2036 2037 raise error.Abort('not supporting inline revlog (yet)')
2037 2038
2038 2039 origindexpath = orig.opener.join(orig.indexfile)
2039 2040 origdatapath = orig.opener.join(orig.datafile)
2040 2041 indexname = 'revlog.i'
2041 2042 dataname = 'revlog.d'
2042 2043
2043 2044 tmpdir = tempfile.mkdtemp(prefix='tmp-hgperf-')
2044 2045 try:
2045 2046 # copy the data file in a temporary directory
2046 2047 ui.debug('copying data in %s\n' % tmpdir)
2047 2048 destindexpath = os.path.join(tmpdir, 'revlog.i')
2048 2049 destdatapath = os.path.join(tmpdir, 'revlog.d')
2049 2050 shutil.copyfile(origindexpath, destindexpath)
2050 2051 shutil.copyfile(origdatapath, destdatapath)
2051 2052
2052 2053 # remove the data we want to add again
2053 2054 ui.debug('truncating data to be rewritten\n')
2054 2055 with open(destindexpath, 'ab') as index:
2055 2056 index.seek(0)
2056 2057 index.truncate(truncaterev * orig._io.size)
2057 2058 with open(destdatapath, 'ab') as data:
2058 2059 data.seek(0)
2059 2060 data.truncate(orig.start(truncaterev))
2060 2061
2061 2062 # instantiate a new revlog from the temporary copy
2062 2063 ui.debug('truncating adding to be rewritten\n')
2063 2064 vfs = vfsmod.vfs(tmpdir)
2064 2065 vfs.options = getattr(orig.opener, 'options', None)
2065 2066
2066 2067 dest = revlog.revlog(vfs,
2067 2068 indexfile=indexname,
2068 2069 datafile=dataname)
2069 2070 if dest._inline:
2070 2071 raise error.Abort('not supporting inline revlog (yet)')
2071 2072 # make sure internals are initialized
2072 2073 dest.revision(len(dest) - 1)
2073 2074 yield dest
2074 2075 del dest, vfs
2075 2076 finally:
2076 2077 shutil.rmtree(tmpdir, True)
2077 2078
2078 2079 @command(b'perfrevlogchunks', revlogopts + formatteropts +
2079 2080 [(b'e', b'engines', b'', b'compression engines to use'),
2080 2081 (b's', b'startrev', 0, b'revision to start at')],
2081 2082 b'-c|-m|FILE')
2082 2083 def perfrevlogchunks(ui, repo, file_=None, engines=None, startrev=0, **opts):
2083 2084 """Benchmark operations on revlog chunks.
2084 2085
2085 2086 Logically, each revlog is a collection of fulltext revisions. However,
2086 2087 stored within each revlog are "chunks" of possibly compressed data. This
2087 2088 data needs to be read and decompressed or compressed and written.
2088 2089
2089 2090 This command measures the time it takes to read+decompress and recompress
2090 2091 chunks in a revlog. It effectively isolates I/O and compression performance.
2091 2092 For measurements of higher-level operations like resolving revisions,
2092 2093 see ``perfrevlogrevisions`` and ``perfrevlogrevision``.
2093 2094 """
2094 2095 opts = _byteskwargs(opts)
2095 2096
2096 2097 rl = cmdutil.openrevlog(repo, b'perfrevlogchunks', file_, opts)
2097 2098
2098 2099 # _chunkraw was renamed to _getsegmentforrevs.
2099 2100 try:
2100 2101 segmentforrevs = rl._getsegmentforrevs
2101 2102 except AttributeError:
2102 2103 segmentforrevs = rl._chunkraw
2103 2104
2104 2105 # Verify engines argument.
2105 2106 if engines:
2106 2107 engines = set(e.strip() for e in engines.split(b','))
2107 2108 for engine in engines:
2108 2109 try:
2109 2110 util.compressionengines[engine]
2110 2111 except KeyError:
2111 2112 raise error.Abort(b'unknown compression engine: %s' % engine)
2112 2113 else:
2113 2114 engines = []
2114 2115 for e in util.compengines:
2115 2116 engine = util.compengines[e]
2116 2117 try:
2117 2118 if engine.available():
2118 2119 engine.revlogcompressor().compress(b'dummy')
2119 2120 engines.append(e)
2120 2121 except NotImplementedError:
2121 2122 pass
2122 2123
2123 2124 revs = list(rl.revs(startrev, len(rl) - 1))
2124 2125
2125 2126 def rlfh(rl):
2126 2127 if rl._inline:
2127 2128 return getsvfs(repo)(rl.indexfile)
2128 2129 else:
2129 2130 return getsvfs(repo)(rl.datafile)
2130 2131
2131 2132 def doread():
2132 2133 rl.clearcaches()
2133 2134 for rev in revs:
2134 2135 segmentforrevs(rev, rev)
2135 2136
2136 2137 def doreadcachedfh():
2137 2138 rl.clearcaches()
2138 2139 fh = rlfh(rl)
2139 2140 for rev in revs:
2140 2141 segmentforrevs(rev, rev, df=fh)
2141 2142
2142 2143 def doreadbatch():
2143 2144 rl.clearcaches()
2144 2145 segmentforrevs(revs[0], revs[-1])
2145 2146
2146 2147 def doreadbatchcachedfh():
2147 2148 rl.clearcaches()
2148 2149 fh = rlfh(rl)
2149 2150 segmentforrevs(revs[0], revs[-1], df=fh)
2150 2151
2151 2152 def dochunk():
2152 2153 rl.clearcaches()
2153 2154 fh = rlfh(rl)
2154 2155 for rev in revs:
2155 2156 rl._chunk(rev, df=fh)
2156 2157
2157 2158 chunks = [None]
2158 2159
2159 2160 def dochunkbatch():
2160 2161 rl.clearcaches()
2161 2162 fh = rlfh(rl)
2162 2163 # Save chunks as a side-effect.
2163 2164 chunks[0] = rl._chunks(revs, df=fh)
2164 2165
2165 2166 def docompress(compressor):
2166 2167 rl.clearcaches()
2167 2168
2168 2169 try:
2169 2170 # Swap in the requested compression engine.
2170 2171 oldcompressor = rl._compressor
2171 2172 rl._compressor = compressor
2172 2173 for chunk in chunks[0]:
2173 2174 rl.compress(chunk)
2174 2175 finally:
2175 2176 rl._compressor = oldcompressor
2176 2177
2177 2178 benches = [
2178 2179 (lambda: doread(), b'read'),
2179 2180 (lambda: doreadcachedfh(), b'read w/ reused fd'),
2180 2181 (lambda: doreadbatch(), b'read batch'),
2181 2182 (lambda: doreadbatchcachedfh(), b'read batch w/ reused fd'),
2182 2183 (lambda: dochunk(), b'chunk'),
2183 2184 (lambda: dochunkbatch(), b'chunk batch'),
2184 2185 ]
2185 2186
2186 2187 for engine in sorted(engines):
2187 2188 compressor = util.compengines[engine].revlogcompressor()
2188 2189 benches.append((functools.partial(docompress, compressor),
2189 2190 b'compress w/ %s' % engine))
2190 2191
2191 2192 for fn, title in benches:
2192 2193 timer, fm = gettimer(ui, opts)
2193 2194 timer(fn, title=title)
2194 2195 fm.end()
2195 2196
2196 2197 @command(b'perfrevlogrevision', revlogopts + formatteropts +
2197 2198 [(b'', b'cache', False, b'use caches instead of clearing')],
2198 2199 b'-c|-m|FILE REV')
2199 2200 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
2200 2201 """Benchmark obtaining a revlog revision.
2201 2202
2202 2203 Obtaining a revlog revision consists of roughly the following steps:
2203 2204
2204 2205 1. Compute the delta chain
2205 2206 2. Slice the delta chain if applicable
2206 2207 3. Obtain the raw chunks for that delta chain
2207 2208 4. Decompress each raw chunk
2208 2209 5. Apply binary patches to obtain fulltext
2209 2210 6. Verify hash of fulltext
2210 2211
2211 2212 This command measures the time spent in each of these phases.
2212 2213 """
2213 2214 opts = _byteskwargs(opts)
2214 2215
2215 2216 if opts.get(b'changelog') or opts.get(b'manifest'):
2216 2217 file_, rev = None, file_
2217 2218 elif rev is None:
2218 2219 raise error.CommandError(b'perfrevlogrevision', b'invalid arguments')
2219 2220
2220 2221 r = cmdutil.openrevlog(repo, b'perfrevlogrevision', file_, opts)
2221 2222
2222 2223 # _chunkraw was renamed to _getsegmentforrevs.
2223 2224 try:
2224 2225 segmentforrevs = r._getsegmentforrevs
2225 2226 except AttributeError:
2226 2227 segmentforrevs = r._chunkraw
2227 2228
2228 2229 node = r.lookup(rev)
2229 2230 rev = r.rev(node)
2230 2231
2231 2232 def getrawchunks(data, chain):
2232 2233 start = r.start
2233 2234 length = r.length
2234 2235 inline = r._inline
2235 2236 iosize = r._io.size
2236 2237 buffer = util.buffer
2237 2238
2238 2239 chunks = []
2239 2240 ladd = chunks.append
2240 2241 for idx, item in enumerate(chain):
2241 2242 offset = start(item[0])
2242 2243 bits = data[idx]
2243 2244 for rev in item:
2244 2245 chunkstart = start(rev)
2245 2246 if inline:
2246 2247 chunkstart += (rev + 1) * iosize
2247 2248 chunklength = length(rev)
2248 2249 ladd(buffer(bits, chunkstart - offset, chunklength))
2249 2250
2250 2251 return chunks
2251 2252
2252 2253 def dodeltachain(rev):
2253 2254 if not cache:
2254 2255 r.clearcaches()
2255 2256 r._deltachain(rev)
2256 2257
2257 2258 def doread(chain):
2258 2259 if not cache:
2259 2260 r.clearcaches()
2260 2261 for item in slicedchain:
2261 2262 segmentforrevs(item[0], item[-1])
2262 2263
2263 2264 def doslice(r, chain, size):
2264 2265 for s in slicechunk(r, chain, targetsize=size):
2265 2266 pass
2266 2267
2267 2268 def dorawchunks(data, chain):
2268 2269 if not cache:
2269 2270 r.clearcaches()
2270 2271 getrawchunks(data, chain)
2271 2272
2272 2273 def dodecompress(chunks):
2273 2274 decomp = r.decompress
2274 2275 for chunk in chunks:
2275 2276 decomp(chunk)
2276 2277
2277 2278 def dopatch(text, bins):
2278 2279 if not cache:
2279 2280 r.clearcaches()
2280 2281 mdiff.patches(text, bins)
2281 2282
2282 2283 def dohash(text):
2283 2284 if not cache:
2284 2285 r.clearcaches()
2285 2286 r.checkhash(text, node, rev=rev)
2286 2287
2287 2288 def dorevision():
2288 2289 if not cache:
2289 2290 r.clearcaches()
2290 2291 r.revision(node)
2291 2292
2292 2293 try:
2293 2294 from mercurial.revlogutils.deltas import slicechunk
2294 2295 except ImportError:
2295 2296 slicechunk = getattr(revlog, '_slicechunk', None)
2296 2297
2297 2298 size = r.length(rev)
2298 2299 chain = r._deltachain(rev)[0]
2299 2300 if not getattr(r, '_withsparseread', False):
2300 2301 slicedchain = (chain,)
2301 2302 else:
2302 2303 slicedchain = tuple(slicechunk(r, chain, targetsize=size))
2303 2304 data = [segmentforrevs(seg[0], seg[-1])[1] for seg in slicedchain]
2304 2305 rawchunks = getrawchunks(data, slicedchain)
2305 2306 bins = r._chunks(chain)
2306 2307 text = bytes(bins[0])
2307 2308 bins = bins[1:]
2308 2309 text = mdiff.patches(text, bins)
2309 2310
2310 2311 benches = [
2311 2312 (lambda: dorevision(), b'full'),
2312 2313 (lambda: dodeltachain(rev), b'deltachain'),
2313 2314 (lambda: doread(chain), b'read'),
2314 2315 ]
2315 2316
2316 2317 if getattr(r, '_withsparseread', False):
2317 2318 slicing = (lambda: doslice(r, chain, size), b'slice-sparse-chain')
2318 2319 benches.append(slicing)
2319 2320
2320 2321 benches.extend([
2321 2322 (lambda: dorawchunks(data, slicedchain), b'rawchunks'),
2322 2323 (lambda: dodecompress(rawchunks), b'decompress'),
2323 2324 (lambda: dopatch(text, bins), b'patch'),
2324 2325 (lambda: dohash(text), b'hash'),
2325 2326 ])
2326 2327
2327 2328 timer, fm = gettimer(ui, opts)
2328 2329 for fn, title in benches:
2329 2330 timer(fn, title=title)
2330 2331 fm.end()
2331 2332
2332 2333 @command(b'perfrevset',
2333 2334 [(b'C', b'clear', False, b'clear volatile cache between each call.'),
2334 2335 (b'', b'contexts', False, b'obtain changectx for each revision')]
2335 2336 + formatteropts, b"REVSET")
2336 2337 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
2337 2338 """benchmark the execution time of a revset
2338 2339
2339 2340 Use the --clean option if need to evaluate the impact of build volatile
2340 2341 revisions set cache on the revset execution. Volatile cache hold filtered
2341 2342 and obsolete related cache."""
2342 2343 opts = _byteskwargs(opts)
2343 2344
2344 2345 timer, fm = gettimer(ui, opts)
2345 2346 def d():
2346 2347 if clear:
2347 2348 repo.invalidatevolatilesets()
2348 2349 if contexts:
2349 2350 for ctx in repo.set(expr): pass
2350 2351 else:
2351 2352 for r in repo.revs(expr): pass
2352 2353 timer(d)
2353 2354 fm.end()
2354 2355
2355 2356 @command(b'perfvolatilesets',
2356 2357 [(b'', b'clear-obsstore', False, b'drop obsstore between each call.'),
2357 2358 ] + formatteropts)
2358 2359 def perfvolatilesets(ui, repo, *names, **opts):
2359 2360 """benchmark the computation of various volatile set
2360 2361
2361 2362 Volatile set computes element related to filtering and obsolescence."""
2362 2363 opts = _byteskwargs(opts)
2363 2364 timer, fm = gettimer(ui, opts)
2364 2365 repo = repo.unfiltered()
2365 2366
2366 2367 def getobs(name):
2367 2368 def d():
2368 2369 repo.invalidatevolatilesets()
2369 2370 if opts[b'clear_obsstore']:
2370 2371 clearfilecache(repo, b'obsstore')
2371 2372 obsolete.getrevs(repo, name)
2372 2373 return d
2373 2374
2374 2375 allobs = sorted(obsolete.cachefuncs)
2375 2376 if names:
2376 2377 allobs = [n for n in allobs if n in names]
2377 2378
2378 2379 for name in allobs:
2379 2380 timer(getobs(name), title=name)
2380 2381
2381 2382 def getfiltered(name):
2382 2383 def d():
2383 2384 repo.invalidatevolatilesets()
2384 2385 if opts[b'clear_obsstore']:
2385 2386 clearfilecache(repo, b'obsstore')
2386 2387 repoview.filterrevs(repo, name)
2387 2388 return d
2388 2389
2389 2390 allfilter = sorted(repoview.filtertable)
2390 2391 if names:
2391 2392 allfilter = [n for n in allfilter if n in names]
2392 2393
2393 2394 for name in allfilter:
2394 2395 timer(getfiltered(name), title=name)
2395 2396 fm.end()
2396 2397
2397 2398 @command(b'perfbranchmap',
2398 2399 [(b'f', b'full', False,
2399 2400 b'Includes build time of subset'),
2400 2401 (b'', b'clear-revbranch', False,
2401 2402 b'purge the revbranch cache between computation'),
2402 2403 ] + formatteropts)
2403 2404 def perfbranchmap(ui, repo, *filternames, **opts):
2404 2405 """benchmark the update of a branchmap
2405 2406
2406 2407 This benchmarks the full repo.branchmap() call with read and write disabled
2407 2408 """
2408 2409 opts = _byteskwargs(opts)
2409 2410 full = opts.get(b"full", False)
2410 2411 clear_revbranch = opts.get(b"clear_revbranch", False)
2411 2412 timer, fm = gettimer(ui, opts)
2412 2413 def getbranchmap(filtername):
2413 2414 """generate a benchmark function for the filtername"""
2414 2415 if filtername is None:
2415 2416 view = repo
2416 2417 else:
2417 2418 view = repo.filtered(filtername)
2418 2419 if util.safehasattr(view._branchcaches, '_per_filter'):
2419 2420 filtered = view._branchcaches._per_filter
2420 2421 else:
2421 2422 # older versions
2422 2423 filtered = view._branchcaches
2423 2424 def d():
2424 2425 if clear_revbranch:
2425 2426 repo.revbranchcache()._clear()
2426 2427 if full:
2427 2428 view._branchcaches.clear()
2428 2429 else:
2429 2430 filtered.pop(filtername, None)
2430 2431 view.branchmap()
2431 2432 return d
2432 2433 # add filter in smaller subset to bigger subset
2433 2434 possiblefilters = set(repoview.filtertable)
2434 2435 if filternames:
2435 2436 possiblefilters &= set(filternames)
2436 2437 subsettable = getbranchmapsubsettable()
2437 2438 allfilters = []
2438 2439 while possiblefilters:
2439 2440 for name in possiblefilters:
2440 2441 subset = subsettable.get(name)
2441 2442 if subset not in possiblefilters:
2442 2443 break
2443 2444 else:
2444 2445 assert False, b'subset cycle %s!' % possiblefilters
2445 2446 allfilters.append(name)
2446 2447 possiblefilters.remove(name)
2447 2448
2448 2449 # warm the cache
2449 2450 if not full:
2450 2451 for name in allfilters:
2451 2452 repo.filtered(name).branchmap()
2452 2453 if not filternames or b'unfiltered' in filternames:
2453 2454 # add unfiltered
2454 2455 allfilters.append(None)
2455 2456
2456 2457 if util.safehasattr(branchmap.branchcache, 'fromfile'):
2457 2458 branchcacheread = safeattrsetter(branchmap.branchcache, b'fromfile')
2458 2459 branchcacheread.set(classmethod(lambda *args: None))
2459 2460 else:
2460 2461 # older versions
2461 2462 branchcacheread = safeattrsetter(branchmap, b'read')
2462 2463 branchcacheread.set(lambda *args: None)
2463 2464 branchcachewrite = safeattrsetter(branchmap.branchcache, b'write')
2464 2465 branchcachewrite.set(lambda *args: None)
2465 2466 try:
2466 2467 for name in allfilters:
2467 2468 printname = name
2468 2469 if name is None:
2469 2470 printname = b'unfiltered'
2470 2471 timer(getbranchmap(name), title=str(printname))
2471 2472 finally:
2472 2473 branchcacheread.restore()
2473 2474 branchcachewrite.restore()
2474 2475 fm.end()
2475 2476
2476 2477 @command(b'perfbranchmapupdate', [
2477 2478 (b'', b'base', [], b'subset of revision to start from'),
2478 2479 (b'', b'target', [], b'subset of revision to end with'),
2479 2480 (b'', b'clear-caches', False, b'clear cache between each runs')
2480 2481 ] + formatteropts)
2481 2482 def perfbranchmapupdate(ui, repo, base=(), target=(), **opts):
2482 2483 """benchmark branchmap update from for <base> revs to <target> revs
2483 2484
2484 2485 If `--clear-caches` is passed, the following items will be reset before
2485 2486 each update:
2486 2487 * the changelog instance and associated indexes
2487 2488 * the rev-branch-cache instance
2488 2489
2489 2490 Examples:
2490 2491
2491 2492 # update for the one last revision
2492 2493 $ hg perfbranchmapupdate --base 'not tip' --target 'tip'
2493 2494
2494 2495 $ update for change coming with a new branch
2495 2496 $ hg perfbranchmapupdate --base 'stable' --target 'default'
2496 2497 """
2497 2498 from mercurial import branchmap
2498 2499 from mercurial import repoview
2499 2500 opts = _byteskwargs(opts)
2500 2501 timer, fm = gettimer(ui, opts)
2501 2502 clearcaches = opts[b'clear_caches']
2502 2503 unfi = repo.unfiltered()
2503 2504 x = [None] # used to pass data between closure
2504 2505
2505 2506 # we use a `list` here to avoid possible side effect from smartset
2506 2507 baserevs = list(scmutil.revrange(repo, base))
2507 2508 targetrevs = list(scmutil.revrange(repo, target))
2508 2509 if not baserevs:
2509 2510 raise error.Abort(b'no revisions selected for --base')
2510 2511 if not targetrevs:
2511 2512 raise error.Abort(b'no revisions selected for --target')
2512 2513
2513 2514 # make sure the target branchmap also contains the one in the base
2514 2515 targetrevs = list(set(baserevs) | set(targetrevs))
2515 2516 targetrevs.sort()
2516 2517
2517 2518 cl = repo.changelog
2518 2519 allbaserevs = list(cl.ancestors(baserevs, inclusive=True))
2519 2520 allbaserevs.sort()
2520 2521 alltargetrevs = frozenset(cl.ancestors(targetrevs, inclusive=True))
2521 2522
2522 2523 newrevs = list(alltargetrevs.difference(allbaserevs))
2523 2524 newrevs.sort()
2524 2525
2525 2526 allrevs = frozenset(unfi.changelog.revs())
2526 2527 basefilterrevs = frozenset(allrevs.difference(allbaserevs))
2527 2528 targetfilterrevs = frozenset(allrevs.difference(alltargetrevs))
2528 2529
2529 2530 def basefilter(repo, visibilityexceptions=None):
2530 2531 return basefilterrevs
2531 2532
2532 2533 def targetfilter(repo, visibilityexceptions=None):
2533 2534 return targetfilterrevs
2534 2535
2535 2536 msg = b'benchmark of branchmap with %d revisions with %d new ones\n'
2536 2537 ui.status(msg % (len(allbaserevs), len(newrevs)))
2537 2538 if targetfilterrevs:
2538 2539 msg = b'(%d revisions still filtered)\n'
2539 2540 ui.status(msg % len(targetfilterrevs))
2540 2541
2541 2542 try:
2542 2543 repoview.filtertable[b'__perf_branchmap_update_base'] = basefilter
2543 2544 repoview.filtertable[b'__perf_branchmap_update_target'] = targetfilter
2544 2545
2545 2546 baserepo = repo.filtered(b'__perf_branchmap_update_base')
2546 2547 targetrepo = repo.filtered(b'__perf_branchmap_update_target')
2547 2548
2548 2549 # try to find an existing branchmap to reuse
2549 2550 subsettable = getbranchmapsubsettable()
2550 2551 candidatefilter = subsettable.get(None)
2551 2552 while candidatefilter is not None:
2552 2553 candidatebm = repo.filtered(candidatefilter).branchmap()
2553 2554 if candidatebm.validfor(baserepo):
2554 2555 filtered = repoview.filterrevs(repo, candidatefilter)
2555 2556 missing = [r for r in allbaserevs if r in filtered]
2556 2557 base = candidatebm.copy()
2557 2558 base.update(baserepo, missing)
2558 2559 break
2559 2560 candidatefilter = subsettable.get(candidatefilter)
2560 2561 else:
2561 2562 # no suitable subset where found
2562 2563 base = branchmap.branchcache()
2563 2564 base.update(baserepo, allbaserevs)
2564 2565
2565 2566 def setup():
2566 2567 x[0] = base.copy()
2567 2568 if clearcaches:
2568 2569 unfi._revbranchcache = None
2569 2570 clearchangelog(repo)
2570 2571
2571 2572 def bench():
2572 2573 x[0].update(targetrepo, newrevs)
2573 2574
2574 2575 timer(bench, setup=setup)
2575 2576 fm.end()
2576 2577 finally:
2577 2578 repoview.filtertable.pop(b'__perf_branchmap_update_base', None)
2578 2579 repoview.filtertable.pop(b'__perf_branchmap_update_target', None)
2579 2580
2580 2581 @command(b'perfbranchmapload', [
2581 2582 (b'f', b'filter', b'', b'Specify repoview filter'),
2582 2583 (b'', b'list', False, b'List brachmap filter caches'),
2583 2584 (b'', b'clear-revlogs', False, b'refresh changelog and manifest'),
2584 2585
2585 2586 ] + formatteropts)
2586 2587 def perfbranchmapload(ui, repo, filter=b'', list=False, **opts):
2587 2588 """benchmark reading the branchmap"""
2588 2589 opts = _byteskwargs(opts)
2589 2590 clearrevlogs = opts[b'clear_revlogs']
2590 2591
2591 2592 if list:
2592 2593 for name, kind, st in repo.cachevfs.readdir(stat=True):
2593 2594 if name.startswith(b'branch2'):
2594 2595 filtername = name.partition(b'-')[2] or b'unfiltered'
2595 2596 ui.status(b'%s - %s\n'
2596 2597 % (filtername, util.bytecount(st.st_size)))
2597 2598 return
2598 2599 if not filter:
2599 2600 filter = None
2600 2601 subsettable = getbranchmapsubsettable()
2601 2602 if filter is None:
2602 2603 repo = repo.unfiltered()
2603 2604 else:
2604 2605 repo = repoview.repoview(repo, filter)
2605 2606
2606 2607 repo.branchmap() # make sure we have a relevant, up to date branchmap
2607 2608
2608 2609 try:
2609 2610 fromfile = branchmap.branchcache.fromfile
2610 2611 except AttributeError:
2611 2612 # older versions
2612 2613 fromfile = branchmap.read
2613 2614
2614 2615 currentfilter = filter
2615 2616 # try once without timer, the filter may not be cached
2616 2617 while fromfile(repo) is None:
2617 2618 currentfilter = subsettable.get(currentfilter)
2618 2619 if currentfilter is None:
2619 2620 raise error.Abort(b'No branchmap cached for %s repo'
2620 2621 % (filter or b'unfiltered'))
2621 2622 repo = repo.filtered(currentfilter)
2622 2623 timer, fm = gettimer(ui, opts)
2623 2624 def setup():
2624 2625 if clearrevlogs:
2625 2626 clearchangelog(repo)
2626 2627 def bench():
2627 2628 fromfile(repo)
2628 2629 timer(bench, setup=setup)
2629 2630 fm.end()
2630 2631
2631 2632 @command(b'perfloadmarkers')
2632 2633 def perfloadmarkers(ui, repo):
2633 2634 """benchmark the time to parse the on-disk markers for a repo
2634 2635
2635 2636 Result is the number of markers in the repo."""
2636 2637 timer, fm = gettimer(ui)
2637 2638 svfs = getsvfs(repo)
2638 2639 timer(lambda: len(obsolete.obsstore(svfs)))
2639 2640 fm.end()
2640 2641
2641 2642 @command(b'perflrucachedict', formatteropts +
2642 2643 [(b'', b'costlimit', 0, b'maximum total cost of items in cache'),
2643 2644 (b'', b'mincost', 0, b'smallest cost of items in cache'),
2644 2645 (b'', b'maxcost', 100, b'maximum cost of items in cache'),
2645 2646 (b'', b'size', 4, b'size of cache'),
2646 2647 (b'', b'gets', 10000, b'number of key lookups'),
2647 2648 (b'', b'sets', 10000, b'number of key sets'),
2648 2649 (b'', b'mixed', 10000, b'number of mixed mode operations'),
2649 2650 (b'', b'mixedgetfreq', 50, b'frequency of get vs set ops in mixed mode')],
2650 2651 norepo=True)
2651 2652 def perflrucache(ui, mincost=0, maxcost=100, costlimit=0, size=4,
2652 2653 gets=10000, sets=10000, mixed=10000, mixedgetfreq=50, **opts):
2653 2654 opts = _byteskwargs(opts)
2654 2655
2655 2656 def doinit():
2656 2657 for i in _xrange(10000):
2657 2658 util.lrucachedict(size)
2658 2659
2659 2660 costrange = list(range(mincost, maxcost + 1))
2660 2661
2661 2662 values = []
2662 2663 for i in _xrange(size):
2663 2664 values.append(random.randint(0, _maxint))
2664 2665
2665 2666 # Get mode fills the cache and tests raw lookup performance with no
2666 2667 # eviction.
2667 2668 getseq = []
2668 2669 for i in _xrange(gets):
2669 2670 getseq.append(random.choice(values))
2670 2671
2671 2672 def dogets():
2672 2673 d = util.lrucachedict(size)
2673 2674 for v in values:
2674 2675 d[v] = v
2675 2676 for key in getseq:
2676 2677 value = d[key]
2677 2678 value # silence pyflakes warning
2678 2679
2679 2680 def dogetscost():
2680 2681 d = util.lrucachedict(size, maxcost=costlimit)
2681 2682 for i, v in enumerate(values):
2682 2683 d.insert(v, v, cost=costs[i])
2683 2684 for key in getseq:
2684 2685 try:
2685 2686 value = d[key]
2686 2687 value # silence pyflakes warning
2687 2688 except KeyError:
2688 2689 pass
2689 2690
2690 2691 # Set mode tests insertion speed with cache eviction.
2691 2692 setseq = []
2692 2693 costs = []
2693 2694 for i in _xrange(sets):
2694 2695 setseq.append(random.randint(0, _maxint))
2695 2696 costs.append(random.choice(costrange))
2696 2697
2697 2698 def doinserts():
2698 2699 d = util.lrucachedict(size)
2699 2700 for v in setseq:
2700 2701 d.insert(v, v)
2701 2702
2702 2703 def doinsertscost():
2703 2704 d = util.lrucachedict(size, maxcost=costlimit)
2704 2705 for i, v in enumerate(setseq):
2705 2706 d.insert(v, v, cost=costs[i])
2706 2707
2707 2708 def dosets():
2708 2709 d = util.lrucachedict(size)
2709 2710 for v in setseq:
2710 2711 d[v] = v
2711 2712
2712 2713 # Mixed mode randomly performs gets and sets with eviction.
2713 2714 mixedops = []
2714 2715 for i in _xrange(mixed):
2715 2716 r = random.randint(0, 100)
2716 2717 if r < mixedgetfreq:
2717 2718 op = 0
2718 2719 else:
2719 2720 op = 1
2720 2721
2721 2722 mixedops.append((op,
2722 2723 random.randint(0, size * 2),
2723 2724 random.choice(costrange)))
2724 2725
2725 2726 def domixed():
2726 2727 d = util.lrucachedict(size)
2727 2728
2728 2729 for op, v, cost in mixedops:
2729 2730 if op == 0:
2730 2731 try:
2731 2732 d[v]
2732 2733 except KeyError:
2733 2734 pass
2734 2735 else:
2735 2736 d[v] = v
2736 2737
2737 2738 def domixedcost():
2738 2739 d = util.lrucachedict(size, maxcost=costlimit)
2739 2740
2740 2741 for op, v, cost in mixedops:
2741 2742 if op == 0:
2742 2743 try:
2743 2744 d[v]
2744 2745 except KeyError:
2745 2746 pass
2746 2747 else:
2747 2748 d.insert(v, v, cost=cost)
2748 2749
2749 2750 benches = [
2750 2751 (doinit, b'init'),
2751 2752 ]
2752 2753
2753 2754 if costlimit:
2754 2755 benches.extend([
2755 2756 (dogetscost, b'gets w/ cost limit'),
2756 2757 (doinsertscost, b'inserts w/ cost limit'),
2757 2758 (domixedcost, b'mixed w/ cost limit'),
2758 2759 ])
2759 2760 else:
2760 2761 benches.extend([
2761 2762 (dogets, b'gets'),
2762 2763 (doinserts, b'inserts'),
2763 2764 (dosets, b'sets'),
2764 2765 (domixed, b'mixed')
2765 2766 ])
2766 2767
2767 2768 for fn, title in benches:
2768 2769 timer, fm = gettimer(ui, opts)
2769 2770 timer(fn, title=title)
2770 2771 fm.end()
2771 2772
2772 2773 @command(b'perfwrite', formatteropts)
2773 2774 def perfwrite(ui, repo, **opts):
2774 2775 """microbenchmark ui.write
2775 2776 """
2776 2777 opts = _byteskwargs(opts)
2777 2778
2778 2779 timer, fm = gettimer(ui, opts)
2779 2780 def write():
2780 2781 for i in range(100000):
2781 2782 ui.write((b'Testing write performance\n'))
2782 2783 timer(write)
2783 2784 fm.end()
2784 2785
2785 2786 def uisetup(ui):
2786 2787 if (util.safehasattr(cmdutil, b'openrevlog') and
2787 2788 not util.safehasattr(commands, b'debugrevlogopts')):
2788 2789 # for "historical portability":
2789 2790 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
2790 2791 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
2791 2792 # openrevlog() should cause failure, because it has been
2792 2793 # available since 3.5 (or 49c583ca48c4).
2793 2794 def openrevlog(orig, repo, cmd, file_, opts):
2794 2795 if opts.get(b'dir') and not util.safehasattr(repo, b'dirlog'):
2795 2796 raise error.Abort(b"This version doesn't support --dir option",
2796 2797 hint=b"use 3.5 or later")
2797 2798 return orig(repo, cmd, file_, opts)
2798 2799 extensions.wrapfunction(cmdutil, b'openrevlog', openrevlog)
2799 2800
2800 2801 @command(b'perfprogress', formatteropts + [
2801 2802 (b'', b'topic', b'topic', b'topic for progress messages'),
2802 2803 (b'c', b'total', 1000000, b'total value we are progressing to'),
2803 2804 ], norepo=True)
2804 2805 def perfprogress(ui, topic=None, total=None, **opts):
2805 2806 """printing of progress bars"""
2806 2807 opts = _byteskwargs(opts)
2807 2808
2808 2809 timer, fm = gettimer(ui, opts)
2809 2810
2810 2811 def doprogress():
2811 2812 with ui.makeprogress(topic, total=total) as progress:
2812 2813 for i in pycompat.xrange(total):
2813 2814 progress.increment()
2814 2815
2815 2816 timer(doprogress)
2816 2817 fm.end()
General Comments 0
You need to be logged in to leave comments. Login now