##// END OF EJS Templates
perf: omit copying ui and redirect to ferr if buffer API is in use...
Philippe Pepiot -
r30405:e77e8876 default
parent child Browse files
Show More
@@ -1,1162 +1,1163 b''
1 1 # perf.py - performance test routines
2 2 '''helper extension to measure performance'''
3 3
4 4 # "historical portability" policy of perf.py:
5 5 #
6 6 # We have to do:
7 7 # - make perf.py "loadable" with as wide Mercurial version as possible
8 8 # This doesn't mean that perf commands work correctly with that Mercurial.
9 9 # BTW, perf.py itself has been available since 1.1 (or eb240755386d).
10 10 # - make historical perf command work correctly with as wide Mercurial
11 11 # version as possible
12 12 #
13 13 # We have to do, if possible with reasonable cost:
14 14 # - make recent perf command for historical feature work correctly
15 15 # with early Mercurial
16 16 #
17 17 # We don't have to do:
18 18 # - make perf command for recent feature work correctly with early
19 19 # Mercurial
20 20
21 21 from __future__ import absolute_import
22 22 import functools
23 23 import os
24 24 import random
25 25 import sys
26 26 import time
27 27 from mercurial import (
28 28 bdiff,
29 29 changegroup,
30 30 cmdutil,
31 31 commands,
32 32 copies,
33 33 error,
34 34 extensions,
35 35 mdiff,
36 36 merge,
37 37 revlog,
38 38 util,
39 39 )
40 40
41 41 # for "historical portability":
42 42 # try to import modules separately (in dict order), and ignore
43 43 # failure, because these aren't available with early Mercurial
44 44 try:
45 45 from mercurial import branchmap # since 2.5 (or bcee63733aad)
46 46 except ImportError:
47 47 pass
48 48 try:
49 49 from mercurial import obsolete # since 2.3 (or ad0d6c2b3279)
50 50 except ImportError:
51 51 pass
52 52 try:
53 53 from mercurial import repoview # since 2.5 (or 3a6ddacb7198)
54 54 except ImportError:
55 55 pass
56 56 try:
57 57 from mercurial import scmutil # since 1.9 (or 8b252e826c68)
58 58 except ImportError:
59 59 pass
60 60
61 61 # for "historical portability":
62 62 # define util.safehasattr forcibly, because util.safehasattr has been
63 63 # available since 1.9.3 (or 94b200a11cf7)
64 64 _undefined = object()
65 65 def safehasattr(thing, attr):
66 66 return getattr(thing, attr, _undefined) is not _undefined
67 67 setattr(util, 'safehasattr', safehasattr)
68 68
69 69 # for "historical portability":
70 70 # use locally defined empty option list, if formatteropts isn't
71 71 # available, because commands.formatteropts has been available since
72 72 # 3.2 (or 7a7eed5176a4), even though formatting itself has been
73 73 # available since 2.2 (or ae5f92e154d3)
74 74 formatteropts = getattr(commands, "formatteropts", [])
75 75
76 76 # for "historical portability":
77 77 # use locally defined option list, if debugrevlogopts isn't available,
78 78 # because commands.debugrevlogopts has been available since 3.7 (or
79 79 # 5606f7d0d063), even though cmdutil.openrevlog() has been available
80 80 # since 1.9 (or a79fea6b3e77).
81 81 revlogopts = getattr(commands, "debugrevlogopts", [
82 82 ('c', 'changelog', False, ('open changelog')),
83 83 ('m', 'manifest', False, ('open manifest')),
84 84 ('', 'dir', False, ('open directory manifest')),
85 85 ])
86 86
87 87 cmdtable = {}
88 88
89 89 # for "historical portability":
90 90 # define parsealiases locally, because cmdutil.parsealiases has been
91 91 # available since 1.5 (or 6252852b4332)
92 92 def parsealiases(cmd):
93 93 return cmd.lstrip("^").split("|")
94 94
95 95 if safehasattr(cmdutil, 'command'):
96 96 import inspect
97 97 command = cmdutil.command(cmdtable)
98 98 if 'norepo' not in inspect.getargspec(command)[0]:
99 99 # for "historical portability":
100 100 # wrap original cmdutil.command, because "norepo" option has
101 101 # been available since 3.1 (or 75a96326cecb)
102 102 _command = command
103 103 def command(name, options=(), synopsis=None, norepo=False):
104 104 if norepo:
105 105 commands.norepo += ' %s' % ' '.join(parsealiases(name))
106 106 return _command(name, list(options), synopsis)
107 107 else:
108 108 # for "historical portability":
109 109 # define "@command" annotation locally, because cmdutil.command
110 110 # has been available since 1.9 (or 2daa5179e73f)
111 111 def command(name, options=(), synopsis=None, norepo=False):
112 112 def decorator(func):
113 113 if synopsis:
114 114 cmdtable[name] = func, list(options), synopsis
115 115 else:
116 116 cmdtable[name] = func, list(options)
117 117 if norepo:
118 118 commands.norepo += ' %s' % ' '.join(parsealiases(name))
119 119 return func
120 120 return decorator
121 121
122 122 def getlen(ui):
123 123 if ui.configbool("perf", "stub"):
124 124 return lambda x: 1
125 125 return len
126 126
127 127 def gettimer(ui, opts=None):
128 128 """return a timer function and formatter: (timer, formatter)
129 129
130 130 This function exists to gather the creation of formatter in a single
131 131 place instead of duplicating it in all performance commands."""
132 132
133 133 # enforce an idle period before execution to counteract power management
134 134 # experimental config: perf.presleep
135 135 time.sleep(getint(ui, "perf", "presleep", 1))
136 136
137 137 if opts is None:
138 138 opts = {}
139 # redirect all to stderr
140 ui = ui.copy()
141 uifout = safeattrsetter(ui, 'fout', ignoremissing=True)
142 if uifout:
143 # for "historical portability":
144 # ui.fout/ferr have been available since 1.9 (or 4e1ccd4c2b6d)
145 uifout.set(ui.ferr)
139 # redirect all to stderr unless buffer api is in use
140 if not ui._buffers:
141 ui = ui.copy()
142 uifout = safeattrsetter(ui, 'fout', ignoremissing=True)
143 if uifout:
144 # for "historical portability":
145 # ui.fout/ferr have been available since 1.9 (or 4e1ccd4c2b6d)
146 uifout.set(ui.ferr)
146 147
147 148 # get a formatter
148 149 uiformatter = getattr(ui, 'formatter', None)
149 150 if uiformatter:
150 151 fm = uiformatter('perf', opts)
151 152 else:
152 153 # for "historical portability":
153 154 # define formatter locally, because ui.formatter has been
154 155 # available since 2.2 (or ae5f92e154d3)
155 156 from mercurial import node
156 157 class defaultformatter(object):
157 158 """Minimized composition of baseformatter and plainformatter
158 159 """
159 160 def __init__(self, ui, topic, opts):
160 161 self._ui = ui
161 162 if ui.debugflag:
162 163 self.hexfunc = node.hex
163 164 else:
164 165 self.hexfunc = node.short
165 166 def __nonzero__(self):
166 167 return False
167 168 def startitem(self):
168 169 pass
169 170 def data(self, **data):
170 171 pass
171 172 def write(self, fields, deftext, *fielddata, **opts):
172 173 self._ui.write(deftext % fielddata, **opts)
173 174 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
174 175 if cond:
175 176 self._ui.write(deftext % fielddata, **opts)
176 177 def plain(self, text, **opts):
177 178 self._ui.write(text, **opts)
178 179 def end(self):
179 180 pass
180 181 fm = defaultformatter(ui, 'perf', opts)
181 182
182 183 # stub function, runs code only once instead of in a loop
183 184 # experimental config: perf.stub
184 185 if ui.configbool("perf", "stub"):
185 186 return functools.partial(stub_timer, fm), fm
186 187 return functools.partial(_timer, fm), fm
187 188
188 189 def stub_timer(fm, func, title=None):
189 190 func()
190 191
191 192 def _timer(fm, func, title=None):
192 193 results = []
193 194 begin = time.time()
194 195 count = 0
195 196 while True:
196 197 ostart = os.times()
197 198 cstart = time.time()
198 199 r = func()
199 200 cstop = time.time()
200 201 ostop = os.times()
201 202 count += 1
202 203 a, b = ostart, ostop
203 204 results.append((cstop - cstart, b[0] - a[0], b[1]-a[1]))
204 205 if cstop - begin > 3 and count >= 100:
205 206 break
206 207 if cstop - begin > 10 and count >= 3:
207 208 break
208 209
209 210 fm.startitem()
210 211
211 212 if title:
212 213 fm.write('title', '! %s\n', title)
213 214 if r:
214 215 fm.write('result', '! result: %s\n', r)
215 216 m = min(results)
216 217 fm.plain('!')
217 218 fm.write('wall', ' wall %f', m[0])
218 219 fm.write('comb', ' comb %f', m[1] + m[2])
219 220 fm.write('user', ' user %f', m[1])
220 221 fm.write('sys', ' sys %f', m[2])
221 222 fm.write('count', ' (best of %d)', count)
222 223 fm.plain('\n')
223 224
224 225 # utilities for historical portability
225 226
226 227 def getint(ui, section, name, default):
227 228 # for "historical portability":
228 229 # ui.configint has been available since 1.9 (or fa2b596db182)
229 230 v = ui.config(section, name, None)
230 231 if v is None:
231 232 return default
232 233 try:
233 234 return int(v)
234 235 except ValueError:
235 236 raise error.ConfigError(("%s.%s is not an integer ('%s')")
236 237 % (section, name, v))
237 238
238 239 def safeattrsetter(obj, name, ignoremissing=False):
239 240 """Ensure that 'obj' has 'name' attribute before subsequent setattr
240 241
241 242 This function is aborted, if 'obj' doesn't have 'name' attribute
242 243 at runtime. This avoids overlooking removal of an attribute, which
243 244 breaks assumption of performance measurement, in the future.
244 245
245 246 This function returns the object to (1) assign a new value, and
246 247 (2) restore an original value to the attribute.
247 248
248 249 If 'ignoremissing' is true, missing 'name' attribute doesn't cause
249 250 abortion, and this function returns None. This is useful to
250 251 examine an attribute, which isn't ensured in all Mercurial
251 252 versions.
252 253 """
253 254 if not util.safehasattr(obj, name):
254 255 if ignoremissing:
255 256 return None
256 257 raise error.Abort(("missing attribute %s of %s might break assumption"
257 258 " of performance measurement") % (name, obj))
258 259
259 260 origvalue = getattr(obj, name)
260 261 class attrutil(object):
261 262 def set(self, newvalue):
262 263 setattr(obj, name, newvalue)
263 264 def restore(self):
264 265 setattr(obj, name, origvalue)
265 266
266 267 return attrutil()
267 268
268 269 # utilities to examine each internal API changes
269 270
270 271 def getbranchmapsubsettable():
271 272 # for "historical portability":
272 273 # subsettable is defined in:
273 274 # - branchmap since 2.9 (or 175c6fd8cacc)
274 275 # - repoview since 2.5 (or 59a9f18d4587)
275 276 for mod in (branchmap, repoview):
276 277 subsettable = getattr(mod, 'subsettable', None)
277 278 if subsettable:
278 279 return subsettable
279 280
280 281 # bisecting in bcee63733aad::59a9f18d4587 can reach here (both
281 282 # branchmap and repoview modules exist, but subsettable attribute
282 283 # doesn't)
283 284 raise error.Abort(("perfbranchmap not available with this Mercurial"),
284 285 hint="use 2.5 or later")
285 286
286 287 def getsvfs(repo):
287 288 """Return appropriate object to access files under .hg/store
288 289 """
289 290 # for "historical portability":
290 291 # repo.svfs has been available since 2.3 (or 7034365089bf)
291 292 svfs = getattr(repo, 'svfs', None)
292 293 if svfs:
293 294 return svfs
294 295 else:
295 296 return getattr(repo, 'sopener')
296 297
297 298 def getvfs(repo):
298 299 """Return appropriate object to access files under .hg
299 300 """
300 301 # for "historical portability":
301 302 # repo.vfs has been available since 2.3 (or 7034365089bf)
302 303 vfs = getattr(repo, 'vfs', None)
303 304 if vfs:
304 305 return vfs
305 306 else:
306 307 return getattr(repo, 'opener')
307 308
308 309 def repocleartagscachefunc(repo):
309 310 """Return the function to clear tags cache according to repo internal API
310 311 """
311 312 if util.safehasattr(repo, '_tagscache'): # since 2.0 (or 9dca7653b525)
312 313 # in this case, setattr(repo, '_tagscache', None) or so isn't
313 314 # correct way to clear tags cache, because existing code paths
314 315 # expect _tagscache to be a structured object.
315 316 def clearcache():
316 317 # _tagscache has been filteredpropertycache since 2.5 (or
317 318 # 98c867ac1330), and delattr() can't work in such case
318 319 if '_tagscache' in vars(repo):
319 320 del repo.__dict__['_tagscache']
320 321 return clearcache
321 322
322 323 repotags = safeattrsetter(repo, '_tags', ignoremissing=True)
323 324 if repotags: # since 1.4 (or 5614a628d173)
324 325 return lambda : repotags.set(None)
325 326
326 327 repotagscache = safeattrsetter(repo, 'tagscache', ignoremissing=True)
327 328 if repotagscache: # since 0.6 (or d7df759d0e97)
328 329 return lambda : repotagscache.set(None)
329 330
330 331 # Mercurial earlier than 0.6 (or d7df759d0e97) logically reaches
331 332 # this point, but it isn't so problematic, because:
332 333 # - repo.tags of such Mercurial isn't "callable", and repo.tags()
333 334 # in perftags() causes failure soon
334 335 # - perf.py itself has been available since 1.1 (or eb240755386d)
335 336 raise error.Abort(("tags API of this hg command is unknown"))
336 337
337 338 # perf commands
338 339
339 340 @command('perfwalk', formatteropts)
340 341 def perfwalk(ui, repo, *pats, **opts):
341 342 timer, fm = gettimer(ui, opts)
342 343 try:
343 344 m = scmutil.match(repo[None], pats, {})
344 345 timer(lambda: len(list(repo.dirstate.walk(m, [], True, False))))
345 346 except Exception:
346 347 try:
347 348 m = scmutil.match(repo[None], pats, {})
348 349 timer(lambda: len([b for a, b, c in repo.dirstate.statwalk([], m)]))
349 350 except Exception:
350 351 timer(lambda: len(list(cmdutil.walk(repo, pats, {}))))
351 352 fm.end()
352 353
353 354 @command('perfannotate', formatteropts)
354 355 def perfannotate(ui, repo, f, **opts):
355 356 timer, fm = gettimer(ui, opts)
356 357 fc = repo['.'][f]
357 358 timer(lambda: len(fc.annotate(True)))
358 359 fm.end()
359 360
360 361 @command('perfstatus',
361 362 [('u', 'unknown', False,
362 363 'ask status to look for unknown files')] + formatteropts)
363 364 def perfstatus(ui, repo, **opts):
364 365 #m = match.always(repo.root, repo.getcwd())
365 366 #timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
366 367 # False))))
367 368 timer, fm = gettimer(ui, opts)
368 369 timer(lambda: sum(map(len, repo.status(unknown=opts['unknown']))))
369 370 fm.end()
370 371
371 372 @command('perfaddremove', formatteropts)
372 373 def perfaddremove(ui, repo, **opts):
373 374 timer, fm = gettimer(ui, opts)
374 375 try:
375 376 oldquiet = repo.ui.quiet
376 377 repo.ui.quiet = True
377 378 matcher = scmutil.match(repo[None])
378 379 timer(lambda: scmutil.addremove(repo, matcher, "", dry_run=True))
379 380 finally:
380 381 repo.ui.quiet = oldquiet
381 382 fm.end()
382 383
383 384 def clearcaches(cl):
384 385 # behave somewhat consistently across internal API changes
385 386 if util.safehasattr(cl, 'clearcaches'):
386 387 cl.clearcaches()
387 388 elif util.safehasattr(cl, '_nodecache'):
388 389 from mercurial.node import nullid, nullrev
389 390 cl._nodecache = {nullid: nullrev}
390 391 cl._nodepos = None
391 392
392 393 @command('perfheads', formatteropts)
393 394 def perfheads(ui, repo, **opts):
394 395 timer, fm = gettimer(ui, opts)
395 396 cl = repo.changelog
396 397 def d():
397 398 len(cl.headrevs())
398 399 clearcaches(cl)
399 400 timer(d)
400 401 fm.end()
401 402
402 403 @command('perftags', formatteropts)
403 404 def perftags(ui, repo, **opts):
404 405 import mercurial.changelog
405 406 import mercurial.manifest
406 407 timer, fm = gettimer(ui, opts)
407 408 svfs = getsvfs(repo)
408 409 repocleartagscache = repocleartagscachefunc(repo)
409 410 def t():
410 411 repo.changelog = mercurial.changelog.changelog(svfs)
411 412 repo.manifestlog = mercurial.manifest.manifestlog(svfs, repo)
412 413 repocleartagscache()
413 414 return len(repo.tags())
414 415 timer(t)
415 416 fm.end()
416 417
417 418 @command('perfancestors', formatteropts)
418 419 def perfancestors(ui, repo, **opts):
419 420 timer, fm = gettimer(ui, opts)
420 421 heads = repo.changelog.headrevs()
421 422 def d():
422 423 for a in repo.changelog.ancestors(heads):
423 424 pass
424 425 timer(d)
425 426 fm.end()
426 427
427 428 @command('perfancestorset', formatteropts)
428 429 def perfancestorset(ui, repo, revset, **opts):
429 430 timer, fm = gettimer(ui, opts)
430 431 revs = repo.revs(revset)
431 432 heads = repo.changelog.headrevs()
432 433 def d():
433 434 s = repo.changelog.ancestors(heads)
434 435 for rev in revs:
435 436 rev in s
436 437 timer(d)
437 438 fm.end()
438 439
439 440 @command('perfchangegroupchangelog', formatteropts +
440 441 [('', 'version', '02', 'changegroup version'),
441 442 ('r', 'rev', '', 'revisions to add to changegroup')])
442 443 def perfchangegroupchangelog(ui, repo, version='02', rev=None, **opts):
443 444 """Benchmark producing a changelog group for a changegroup.
444 445
445 446 This measures the time spent processing the changelog during a
446 447 bundle operation. This occurs during `hg bundle` and on a server
447 448 processing a `getbundle` wire protocol request (handles clones
448 449 and pull requests).
449 450
450 451 By default, all revisions are added to the changegroup.
451 452 """
452 453 cl = repo.changelog
453 454 revs = [cl.lookup(r) for r in repo.revs(rev or 'all()')]
454 455 bundler = changegroup.getbundler(version, repo)
455 456
456 457 def lookup(node):
457 458 # The real bundler reads the revision in order to access the
458 459 # manifest node and files list. Do that here.
459 460 cl.read(node)
460 461 return node
461 462
462 463 def d():
463 464 for chunk in bundler.group(revs, cl, lookup):
464 465 pass
465 466
466 467 timer, fm = gettimer(ui, opts)
467 468 timer(d)
468 469 fm.end()
469 470
470 471 @command('perfdirs', formatteropts)
471 472 def perfdirs(ui, repo, **opts):
472 473 timer, fm = gettimer(ui, opts)
473 474 dirstate = repo.dirstate
474 475 'a' in dirstate
475 476 def d():
476 477 dirstate.dirs()
477 478 del dirstate._dirs
478 479 timer(d)
479 480 fm.end()
480 481
481 482 @command('perfdirstate', formatteropts)
482 483 def perfdirstate(ui, repo, **opts):
483 484 timer, fm = gettimer(ui, opts)
484 485 "a" in repo.dirstate
485 486 def d():
486 487 repo.dirstate.invalidate()
487 488 "a" in repo.dirstate
488 489 timer(d)
489 490 fm.end()
490 491
491 492 @command('perfdirstatedirs', formatteropts)
492 493 def perfdirstatedirs(ui, repo, **opts):
493 494 timer, fm = gettimer(ui, opts)
494 495 "a" in repo.dirstate
495 496 def d():
496 497 "a" in repo.dirstate._dirs
497 498 del repo.dirstate._dirs
498 499 timer(d)
499 500 fm.end()
500 501
501 502 @command('perfdirstatefoldmap', formatteropts)
502 503 def perfdirstatefoldmap(ui, repo, **opts):
503 504 timer, fm = gettimer(ui, opts)
504 505 dirstate = repo.dirstate
505 506 'a' in dirstate
506 507 def d():
507 508 dirstate._filefoldmap.get('a')
508 509 del dirstate._filefoldmap
509 510 timer(d)
510 511 fm.end()
511 512
512 513 @command('perfdirfoldmap', formatteropts)
513 514 def perfdirfoldmap(ui, repo, **opts):
514 515 timer, fm = gettimer(ui, opts)
515 516 dirstate = repo.dirstate
516 517 'a' in dirstate
517 518 def d():
518 519 dirstate._dirfoldmap.get('a')
519 520 del dirstate._dirfoldmap
520 521 del dirstate._dirs
521 522 timer(d)
522 523 fm.end()
523 524
524 525 @command('perfdirstatewrite', formatteropts)
525 526 def perfdirstatewrite(ui, repo, **opts):
526 527 timer, fm = gettimer(ui, opts)
527 528 ds = repo.dirstate
528 529 "a" in ds
529 530 def d():
530 531 ds._dirty = True
531 532 ds.write(repo.currenttransaction())
532 533 timer(d)
533 534 fm.end()
534 535
535 536 @command('perfmergecalculate',
536 537 [('r', 'rev', '.', 'rev to merge against')] + formatteropts)
537 538 def perfmergecalculate(ui, repo, rev, **opts):
538 539 timer, fm = gettimer(ui, opts)
539 540 wctx = repo[None]
540 541 rctx = scmutil.revsingle(repo, rev, rev)
541 542 ancestor = wctx.ancestor(rctx)
542 543 # we don't want working dir files to be stat'd in the benchmark, so prime
543 544 # that cache
544 545 wctx.dirty()
545 546 def d():
546 547 # acceptremote is True because we don't want prompts in the middle of
547 548 # our benchmark
548 549 merge.calculateupdates(repo, wctx, rctx, [ancestor], False, False,
549 550 acceptremote=True, followcopies=True)
550 551 timer(d)
551 552 fm.end()
552 553
553 554 @command('perfpathcopies', [], "REV REV")
554 555 def perfpathcopies(ui, repo, rev1, rev2, **opts):
555 556 timer, fm = gettimer(ui, opts)
556 557 ctx1 = scmutil.revsingle(repo, rev1, rev1)
557 558 ctx2 = scmutil.revsingle(repo, rev2, rev2)
558 559 def d():
559 560 copies.pathcopies(ctx1, ctx2)
560 561 timer(d)
561 562 fm.end()
562 563
563 564 @command('perfmanifest', [], 'REV')
564 565 def perfmanifest(ui, repo, rev, **opts):
565 566 timer, fm = gettimer(ui, opts)
566 567 ctx = scmutil.revsingle(repo, rev, rev)
567 568 t = ctx.manifestnode()
568 569 def d():
569 570 repo.manifestlog.clearcaches()
570 571 repo.manifestlog[t].read()
571 572 timer(d)
572 573 fm.end()
573 574
574 575 @command('perfchangeset', formatteropts)
575 576 def perfchangeset(ui, repo, rev, **opts):
576 577 timer, fm = gettimer(ui, opts)
577 578 n = repo[rev].node()
578 579 def d():
579 580 repo.changelog.read(n)
580 581 #repo.changelog._cache = None
581 582 timer(d)
582 583 fm.end()
583 584
584 585 @command('perfindex', formatteropts)
585 586 def perfindex(ui, repo, **opts):
586 587 import mercurial.revlog
587 588 timer, fm = gettimer(ui, opts)
588 589 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
589 590 n = repo["tip"].node()
590 591 svfs = getsvfs(repo)
591 592 def d():
592 593 cl = mercurial.revlog.revlog(svfs, "00changelog.i")
593 594 cl.rev(n)
594 595 timer(d)
595 596 fm.end()
596 597
597 598 @command('perfstartup', formatteropts)
598 599 def perfstartup(ui, repo, **opts):
599 600 timer, fm = gettimer(ui, opts)
600 601 cmd = sys.argv[0]
601 602 def d():
602 603 if os.name != 'nt':
603 604 os.system("HGRCPATH= %s version -q > /dev/null" % cmd)
604 605 else:
605 606 os.environ['HGRCPATH'] = ''
606 607 os.system("%s version -q > NUL" % cmd)
607 608 timer(d)
608 609 fm.end()
609 610
610 611 @command('perfparents', formatteropts)
611 612 def perfparents(ui, repo, **opts):
612 613 timer, fm = gettimer(ui, opts)
613 614 # control the number of commits perfparents iterates over
614 615 # experimental config: perf.parentscount
615 616 count = getint(ui, "perf", "parentscount", 1000)
616 617 if len(repo.changelog) < count:
617 618 raise error.Abort("repo needs %d commits for this test" % count)
618 619 repo = repo.unfiltered()
619 620 nl = [repo.changelog.node(i) for i in xrange(count)]
620 621 def d():
621 622 for n in nl:
622 623 repo.changelog.parents(n)
623 624 timer(d)
624 625 fm.end()
625 626
626 627 @command('perfctxfiles', formatteropts)
627 628 def perfctxfiles(ui, repo, x, **opts):
628 629 x = int(x)
629 630 timer, fm = gettimer(ui, opts)
630 631 def d():
631 632 len(repo[x].files())
632 633 timer(d)
633 634 fm.end()
634 635
635 636 @command('perfrawfiles', formatteropts)
636 637 def perfrawfiles(ui, repo, x, **opts):
637 638 x = int(x)
638 639 timer, fm = gettimer(ui, opts)
639 640 cl = repo.changelog
640 641 def d():
641 642 len(cl.read(x)[3])
642 643 timer(d)
643 644 fm.end()
644 645
645 646 @command('perflookup', formatteropts)
646 647 def perflookup(ui, repo, rev, **opts):
647 648 timer, fm = gettimer(ui, opts)
648 649 timer(lambda: len(repo.lookup(rev)))
649 650 fm.end()
650 651
651 652 @command('perfrevrange', formatteropts)
652 653 def perfrevrange(ui, repo, *specs, **opts):
653 654 timer, fm = gettimer(ui, opts)
654 655 revrange = scmutil.revrange
655 656 timer(lambda: len(revrange(repo, specs)))
656 657 fm.end()
657 658
658 659 @command('perfnodelookup', formatteropts)
659 660 def perfnodelookup(ui, repo, rev, **opts):
660 661 timer, fm = gettimer(ui, opts)
661 662 import mercurial.revlog
662 663 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
663 664 n = repo[rev].node()
664 665 cl = mercurial.revlog.revlog(getsvfs(repo), "00changelog.i")
665 666 def d():
666 667 cl.rev(n)
667 668 clearcaches(cl)
668 669 timer(d)
669 670 fm.end()
670 671
671 672 @command('perflog',
672 673 [('', 'rename', False, 'ask log to follow renames')] + formatteropts)
673 674 def perflog(ui, repo, rev=None, **opts):
674 675 if rev is None:
675 676 rev=[]
676 677 timer, fm = gettimer(ui, opts)
677 678 ui.pushbuffer()
678 679 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
679 680 copies=opts.get('rename')))
680 681 ui.popbuffer()
681 682 fm.end()
682 683
683 684 @command('perfmoonwalk', formatteropts)
684 685 def perfmoonwalk(ui, repo, **opts):
685 686 """benchmark walking the changelog backwards
686 687
687 688 This also loads the changelog data for each revision in the changelog.
688 689 """
689 690 timer, fm = gettimer(ui, opts)
690 691 def moonwalk():
691 692 for i in xrange(len(repo), -1, -1):
692 693 ctx = repo[i]
693 694 ctx.branch() # read changelog data (in addition to the index)
694 695 timer(moonwalk)
695 696 fm.end()
696 697
697 698 @command('perftemplating', formatteropts)
698 699 def perftemplating(ui, repo, rev=None, **opts):
699 700 if rev is None:
700 701 rev=[]
701 702 timer, fm = gettimer(ui, opts)
702 703 ui.pushbuffer()
703 704 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
704 705 template='{date|shortdate} [{rev}:{node|short}]'
705 706 ' {author|person}: {desc|firstline}\n'))
706 707 ui.popbuffer()
707 708 fm.end()
708 709
709 710 @command('perfcca', formatteropts)
710 711 def perfcca(ui, repo, **opts):
711 712 timer, fm = gettimer(ui, opts)
712 713 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
713 714 fm.end()
714 715
715 716 @command('perffncacheload', formatteropts)
716 717 def perffncacheload(ui, repo, **opts):
717 718 timer, fm = gettimer(ui, opts)
718 719 s = repo.store
719 720 def d():
720 721 s.fncache._load()
721 722 timer(d)
722 723 fm.end()
723 724
724 725 @command('perffncachewrite', formatteropts)
725 726 def perffncachewrite(ui, repo, **opts):
726 727 timer, fm = gettimer(ui, opts)
727 728 s = repo.store
728 729 s.fncache._load()
729 730 lock = repo.lock()
730 731 tr = repo.transaction('perffncachewrite')
731 732 def d():
732 733 s.fncache._dirty = True
733 734 s.fncache.write(tr)
734 735 timer(d)
735 736 tr.close()
736 737 lock.release()
737 738 fm.end()
738 739
739 740 @command('perffncacheencode', formatteropts)
740 741 def perffncacheencode(ui, repo, **opts):
741 742 timer, fm = gettimer(ui, opts)
742 743 s = repo.store
743 744 s.fncache._load()
744 745 def d():
745 746 for p in s.fncache.entries:
746 747 s.encode(p)
747 748 timer(d)
748 749 fm.end()
749 750
750 751 @command('perfbdiff', revlogopts + formatteropts + [
751 752 ('', 'count', 1, 'number of revisions to test (when using --startrev)'),
752 753 ('', 'alldata', False, 'test bdiffs for all associated revisions')],
753 754 '-c|-m|FILE REV')
754 755 def perfbdiff(ui, repo, file_, rev=None, count=None, **opts):
755 756 """benchmark a bdiff between revisions
756 757
757 758 By default, benchmark a bdiff between its delta parent and itself.
758 759
759 760 With ``--count``, benchmark bdiffs between delta parents and self for N
760 761 revisions starting at the specified revision.
761 762
762 763 With ``--alldata``, assume the requested revision is a changeset and
763 764 measure bdiffs for all changes related to that changeset (manifest
764 765 and filelogs).
765 766 """
766 767 if opts['alldata']:
767 768 opts['changelog'] = True
768 769
769 770 if opts.get('changelog') or opts.get('manifest'):
770 771 file_, rev = None, file_
771 772 elif rev is None:
772 773 raise error.CommandError('perfbdiff', 'invalid arguments')
773 774
774 775 textpairs = []
775 776
776 777 r = cmdutil.openrevlog(repo, 'perfbdiff', file_, opts)
777 778
778 779 startrev = r.rev(r.lookup(rev))
779 780 for rev in range(startrev, min(startrev + count, len(r) - 1)):
780 781 if opts['alldata']:
781 782 # Load revisions associated with changeset.
782 783 ctx = repo[rev]
783 784 mtext = repo.manifest.revision(ctx.manifestnode())
784 785 for pctx in ctx.parents():
785 786 pman = repo.manifest.revision(pctx.manifestnode())
786 787 textpairs.append((pman, mtext))
787 788
788 789 # Load filelog revisions by iterating manifest delta.
789 790 man = ctx.manifest()
790 791 pman = ctx.p1().manifest()
791 792 for filename, change in pman.diff(man).items():
792 793 fctx = repo.file(filename)
793 794 f1 = fctx.revision(change[0][0] or -1)
794 795 f2 = fctx.revision(change[1][0] or -1)
795 796 textpairs.append((f1, f2))
796 797 else:
797 798 dp = r.deltaparent(rev)
798 799 textpairs.append((r.revision(dp), r.revision(rev)))
799 800
800 801 def d():
801 802 for pair in textpairs:
802 803 bdiff.bdiff(*pair)
803 804
804 805 timer, fm = gettimer(ui, opts)
805 806 timer(d)
806 807 fm.end()
807 808
808 809 @command('perfdiffwd', formatteropts)
809 810 def perfdiffwd(ui, repo, **opts):
810 811 """Profile diff of working directory changes"""
811 812 timer, fm = gettimer(ui, opts)
812 813 options = {
813 814 'w': 'ignore_all_space',
814 815 'b': 'ignore_space_change',
815 816 'B': 'ignore_blank_lines',
816 817 }
817 818
818 819 for diffopt in ('', 'w', 'b', 'B', 'wB'):
819 820 opts = dict((options[c], '1') for c in diffopt)
820 821 def d():
821 822 ui.pushbuffer()
822 823 commands.diff(ui, repo, **opts)
823 824 ui.popbuffer()
824 825 title = 'diffopts: %s' % (diffopt and ('-' + diffopt) or 'none')
825 826 timer(d, title)
826 827 fm.end()
827 828
828 829 @command('perfrevlog', revlogopts + formatteropts +
829 830 [('d', 'dist', 100, 'distance between the revisions'),
830 831 ('s', 'startrev', 0, 'revision to start reading at'),
831 832 ('', 'reverse', False, 'read in reverse')],
832 833 '-c|-m|FILE')
833 834 def perfrevlog(ui, repo, file_=None, startrev=0, reverse=False, **opts):
834 835 """Benchmark reading a series of revisions from a revlog.
835 836
836 837 By default, we read every ``-d/--dist`` revision from 0 to tip of
837 838 the specified revlog.
838 839
839 840 The start revision can be defined via ``-s/--startrev``.
840 841 """
841 842 timer, fm = gettimer(ui, opts)
842 843 _len = getlen(ui)
843 844
844 845 def d():
845 846 r = cmdutil.openrevlog(repo, 'perfrevlog', file_, opts)
846 847
847 848 startrev = 0
848 849 endrev = _len(r)
849 850 dist = opts['dist']
850 851
851 852 if reverse:
852 853 startrev, endrev = endrev, startrev
853 854 dist = -1 * dist
854 855
855 856 for x in xrange(startrev, endrev, dist):
856 857 r.revision(r.node(x))
857 858
858 859 timer(d)
859 860 fm.end()
860 861
861 862 @command('perfrevlogrevision', revlogopts + formatteropts +
862 863 [('', 'cache', False, 'use caches instead of clearing')],
863 864 '-c|-m|FILE REV')
864 865 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
865 866 """Benchmark obtaining a revlog revision.
866 867
867 868 Obtaining a revlog revision consists of roughly the following steps:
868 869
869 870 1. Compute the delta chain
870 871 2. Obtain the raw chunks for that delta chain
871 872 3. Decompress each raw chunk
872 873 4. Apply binary patches to obtain fulltext
873 874 5. Verify hash of fulltext
874 875
875 876 This command measures the time spent in each of these phases.
876 877 """
877 878 if opts.get('changelog') or opts.get('manifest'):
878 879 file_, rev = None, file_
879 880 elif rev is None:
880 881 raise error.CommandError('perfrevlogrevision', 'invalid arguments')
881 882
882 883 r = cmdutil.openrevlog(repo, 'perfrevlogrevision', file_, opts)
883 884 node = r.lookup(rev)
884 885 rev = r.rev(node)
885 886
886 887 def dodeltachain(rev):
887 888 if not cache:
888 889 r.clearcaches()
889 890 r._deltachain(rev)
890 891
891 892 def doread(chain):
892 893 if not cache:
893 894 r.clearcaches()
894 895 r._chunkraw(chain[0], chain[-1])
895 896
896 897 def dodecompress(data, chain):
897 898 if not cache:
898 899 r.clearcaches()
899 900
900 901 start = r.start
901 902 length = r.length
902 903 inline = r._inline
903 904 iosize = r._io.size
904 905 buffer = util.buffer
905 906 offset = start(chain[0])
906 907
907 908 for rev in chain:
908 909 chunkstart = start(rev)
909 910 if inline:
910 911 chunkstart += (rev + 1) * iosize
911 912 chunklength = length(rev)
912 913 b = buffer(data, chunkstart - offset, chunklength)
913 914 revlog.decompress(b)
914 915
915 916 def dopatch(text, bins):
916 917 if not cache:
917 918 r.clearcaches()
918 919 mdiff.patches(text, bins)
919 920
920 921 def dohash(text):
921 922 if not cache:
922 923 r.clearcaches()
923 924 r._checkhash(text, node, rev)
924 925
925 926 def dorevision():
926 927 if not cache:
927 928 r.clearcaches()
928 929 r.revision(node)
929 930
930 931 chain = r._deltachain(rev)[0]
931 932 data = r._chunkraw(chain[0], chain[-1])[1]
932 933 bins = r._chunks(chain)
933 934 text = str(bins[0])
934 935 bins = bins[1:]
935 936 text = mdiff.patches(text, bins)
936 937
937 938 benches = [
938 939 (lambda: dorevision(), 'full'),
939 940 (lambda: dodeltachain(rev), 'deltachain'),
940 941 (lambda: doread(chain), 'read'),
941 942 (lambda: dodecompress(data, chain), 'decompress'),
942 943 (lambda: dopatch(text, bins), 'patch'),
943 944 (lambda: dohash(text), 'hash'),
944 945 ]
945 946
946 947 for fn, title in benches:
947 948 timer, fm = gettimer(ui, opts)
948 949 timer(fn, title=title)
949 950 fm.end()
950 951
951 952 @command('perfrevset',
952 953 [('C', 'clear', False, 'clear volatile cache between each call.'),
953 954 ('', 'contexts', False, 'obtain changectx for each revision')]
954 955 + formatteropts, "REVSET")
955 956 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
956 957 """benchmark the execution time of a revset
957 958
958 959 Use the --clean option if need to evaluate the impact of build volatile
959 960 revisions set cache on the revset execution. Volatile cache hold filtered
960 961 and obsolete related cache."""
961 962 timer, fm = gettimer(ui, opts)
962 963 def d():
963 964 if clear:
964 965 repo.invalidatevolatilesets()
965 966 if contexts:
966 967 for ctx in repo.set(expr): pass
967 968 else:
968 969 for r in repo.revs(expr): pass
969 970 timer(d)
970 971 fm.end()
971 972
972 973 @command('perfvolatilesets', formatteropts)
973 974 def perfvolatilesets(ui, repo, *names, **opts):
974 975 """benchmark the computation of various volatile set
975 976
976 977 Volatile set computes element related to filtering and obsolescence."""
977 978 timer, fm = gettimer(ui, opts)
978 979 repo = repo.unfiltered()
979 980
980 981 def getobs(name):
981 982 def d():
982 983 repo.invalidatevolatilesets()
983 984 obsolete.getrevs(repo, name)
984 985 return d
985 986
986 987 allobs = sorted(obsolete.cachefuncs)
987 988 if names:
988 989 allobs = [n for n in allobs if n in names]
989 990
990 991 for name in allobs:
991 992 timer(getobs(name), title=name)
992 993
993 994 def getfiltered(name):
994 995 def d():
995 996 repo.invalidatevolatilesets()
996 997 repoview.filterrevs(repo, name)
997 998 return d
998 999
999 1000 allfilter = sorted(repoview.filtertable)
1000 1001 if names:
1001 1002 allfilter = [n for n in allfilter if n in names]
1002 1003
1003 1004 for name in allfilter:
1004 1005 timer(getfiltered(name), title=name)
1005 1006 fm.end()
1006 1007
1007 1008 @command('perfbranchmap',
1008 1009 [('f', 'full', False,
1009 1010 'Includes build time of subset'),
1010 1011 ] + formatteropts)
1011 1012 def perfbranchmap(ui, repo, full=False, **opts):
1012 1013 """benchmark the update of a branchmap
1013 1014
1014 1015 This benchmarks the full repo.branchmap() call with read and write disabled
1015 1016 """
1016 1017 timer, fm = gettimer(ui, opts)
1017 1018 def getbranchmap(filtername):
1018 1019 """generate a benchmark function for the filtername"""
1019 1020 if filtername is None:
1020 1021 view = repo
1021 1022 else:
1022 1023 view = repo.filtered(filtername)
1023 1024 def d():
1024 1025 if full:
1025 1026 view._branchcaches.clear()
1026 1027 else:
1027 1028 view._branchcaches.pop(filtername, None)
1028 1029 view.branchmap()
1029 1030 return d
1030 1031 # add filter in smaller subset to bigger subset
1031 1032 possiblefilters = set(repoview.filtertable)
1032 1033 subsettable = getbranchmapsubsettable()
1033 1034 allfilters = []
1034 1035 while possiblefilters:
1035 1036 for name in possiblefilters:
1036 1037 subset = subsettable.get(name)
1037 1038 if subset not in possiblefilters:
1038 1039 break
1039 1040 else:
1040 1041 assert False, 'subset cycle %s!' % possiblefilters
1041 1042 allfilters.append(name)
1042 1043 possiblefilters.remove(name)
1043 1044
1044 1045 # warm the cache
1045 1046 if not full:
1046 1047 for name in allfilters:
1047 1048 repo.filtered(name).branchmap()
1048 1049 # add unfiltered
1049 1050 allfilters.append(None)
1050 1051
1051 1052 branchcacheread = safeattrsetter(branchmap, 'read')
1052 1053 branchcachewrite = safeattrsetter(branchmap.branchcache, 'write')
1053 1054 branchcacheread.set(lambda repo: None)
1054 1055 branchcachewrite.set(lambda bc, repo: None)
1055 1056 try:
1056 1057 for name in allfilters:
1057 1058 timer(getbranchmap(name), title=str(name))
1058 1059 finally:
1059 1060 branchcacheread.restore()
1060 1061 branchcachewrite.restore()
1061 1062 fm.end()
1062 1063
1063 1064 @command('perfloadmarkers')
1064 1065 def perfloadmarkers(ui, repo):
1065 1066 """benchmark the time to parse the on-disk markers for a repo
1066 1067
1067 1068 Result is the number of markers in the repo."""
1068 1069 timer, fm = gettimer(ui)
1069 1070 svfs = getsvfs(repo)
1070 1071 timer(lambda: len(obsolete.obsstore(svfs)))
1071 1072 fm.end()
1072 1073
1073 1074 @command('perflrucachedict', formatteropts +
1074 1075 [('', 'size', 4, 'size of cache'),
1075 1076 ('', 'gets', 10000, 'number of key lookups'),
1076 1077 ('', 'sets', 10000, 'number of key sets'),
1077 1078 ('', 'mixed', 10000, 'number of mixed mode operations'),
1078 1079 ('', 'mixedgetfreq', 50, 'frequency of get vs set ops in mixed mode')],
1079 1080 norepo=True)
1080 1081 def perflrucache(ui, size=4, gets=10000, sets=10000, mixed=10000,
1081 1082 mixedgetfreq=50, **opts):
1082 1083 def doinit():
1083 1084 for i in xrange(10000):
1084 1085 util.lrucachedict(size)
1085 1086
1086 1087 values = []
1087 1088 for i in xrange(size):
1088 1089 values.append(random.randint(0, sys.maxint))
1089 1090
1090 1091 # Get mode fills the cache and tests raw lookup performance with no
1091 1092 # eviction.
1092 1093 getseq = []
1093 1094 for i in xrange(gets):
1094 1095 getseq.append(random.choice(values))
1095 1096
1096 1097 def dogets():
1097 1098 d = util.lrucachedict(size)
1098 1099 for v in values:
1099 1100 d[v] = v
1100 1101 for key in getseq:
1101 1102 value = d[key]
1102 1103 value # silence pyflakes warning
1103 1104
1104 1105 # Set mode tests insertion speed with cache eviction.
1105 1106 setseq = []
1106 1107 for i in xrange(sets):
1107 1108 setseq.append(random.randint(0, sys.maxint))
1108 1109
1109 1110 def dosets():
1110 1111 d = util.lrucachedict(size)
1111 1112 for v in setseq:
1112 1113 d[v] = v
1113 1114
1114 1115 # Mixed mode randomly performs gets and sets with eviction.
1115 1116 mixedops = []
1116 1117 for i in xrange(mixed):
1117 1118 r = random.randint(0, 100)
1118 1119 if r < mixedgetfreq:
1119 1120 op = 0
1120 1121 else:
1121 1122 op = 1
1122 1123
1123 1124 mixedops.append((op, random.randint(0, size * 2)))
1124 1125
1125 1126 def domixed():
1126 1127 d = util.lrucachedict(size)
1127 1128
1128 1129 for op, v in mixedops:
1129 1130 if op == 0:
1130 1131 try:
1131 1132 d[v]
1132 1133 except KeyError:
1133 1134 pass
1134 1135 else:
1135 1136 d[v] = v
1136 1137
1137 1138 benches = [
1138 1139 (doinit, 'init'),
1139 1140 (dogets, 'gets'),
1140 1141 (dosets, 'sets'),
1141 1142 (domixed, 'mixed')
1142 1143 ]
1143 1144
1144 1145 for fn, title in benches:
1145 1146 timer, fm = gettimer(ui, opts)
1146 1147 timer(fn, title=title)
1147 1148 fm.end()
1148 1149
1149 1150 def uisetup(ui):
1150 1151 if (util.safehasattr(cmdutil, 'openrevlog') and
1151 1152 not util.safehasattr(commands, 'debugrevlogopts')):
1152 1153 # for "historical portability":
1153 1154 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
1154 1155 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
1155 1156 # openrevlog() should cause failure, because it has been
1156 1157 # available since 3.5 (or 49c583ca48c4).
1157 1158 def openrevlog(orig, repo, cmd, file_, opts):
1158 1159 if opts.get('dir') and not util.safehasattr(repo, 'dirlog'):
1159 1160 raise error.Abort("This version doesn't support --dir option",
1160 1161 hint="use 3.5 or later")
1161 1162 return orig(repo, cmd, file_, opts)
1162 1163 extensions.wrapfunction(cmdutil, 'openrevlog', openrevlog)
General Comments 0
You need to be logged in to leave comments. Login now