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