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