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