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