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