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