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