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