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