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