##// END OF EJS Templates
perf: teach perfrevlogrevision about sparse reading...
Boris Feld -
r40566:914079ee default
parent child Browse files
Show More
@@ -1,2126 +1,2138
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 contextlib
23 23 import functools
24 24 import gc
25 25 import os
26 26 import random
27 27 import struct
28 28 import sys
29 29 import threading
30 30 import time
31 31 from mercurial import (
32 32 changegroup,
33 33 cmdutil,
34 34 commands,
35 35 copies,
36 36 error,
37 37 extensions,
38 38 mdiff,
39 39 merge,
40 40 revlog,
41 41 util,
42 42 )
43 43
44 44 # for "historical portability":
45 45 # try to import modules separately (in dict order), and ignore
46 46 # failure, because these aren't available with early Mercurial
47 47 try:
48 48 from mercurial import branchmap # since 2.5 (or bcee63733aad)
49 49 except ImportError:
50 50 pass
51 51 try:
52 52 from mercurial import obsolete # since 2.3 (or ad0d6c2b3279)
53 53 except ImportError:
54 54 pass
55 55 try:
56 56 from mercurial import registrar # since 3.7 (or 37d50250b696)
57 57 dir(registrar) # forcibly load it
58 58 except ImportError:
59 59 registrar = None
60 60 try:
61 61 from mercurial import repoview # since 2.5 (or 3a6ddacb7198)
62 62 except ImportError:
63 63 pass
64 64 try:
65 65 from mercurial import scmutil # since 1.9 (or 8b252e826c68)
66 66 except ImportError:
67 67 pass
68 68
69 69 def identity(a):
70 70 return a
71 71
72 72 try:
73 73 from mercurial import pycompat
74 74 getargspec = pycompat.getargspec # added to module after 4.5
75 75 _byteskwargs = pycompat.byteskwargs # since 4.1 (or fbc3f73dc802)
76 76 _sysstr = pycompat.sysstr # since 4.0 (or 2219f4f82ede)
77 77 _xrange = pycompat.xrange # since 4.8 (or 7eba8f83129b)
78 78 fsencode = pycompat.fsencode # since 3.9 (or f4a5e0e86a7e)
79 79 if pycompat.ispy3:
80 80 _maxint = sys.maxsize # per py3 docs for replacing maxint
81 81 else:
82 82 _maxint = sys.maxint
83 83 except (ImportError, AttributeError):
84 84 import inspect
85 85 getargspec = inspect.getargspec
86 86 _byteskwargs = identity
87 87 fsencode = identity # no py3 support
88 88 _maxint = sys.maxint # no py3 support
89 89 _sysstr = lambda x: x # no py3 support
90 90 _xrange = xrange
91 91
92 92 try:
93 93 # 4.7+
94 94 queue = pycompat.queue.Queue
95 95 except (AttributeError, ImportError):
96 96 # <4.7.
97 97 try:
98 98 queue = pycompat.queue
99 99 except (AttributeError, ImportError):
100 100 queue = util.queue
101 101
102 102 try:
103 103 from mercurial import logcmdutil
104 104 makelogtemplater = logcmdutil.maketemplater
105 105 except (AttributeError, ImportError):
106 106 try:
107 107 makelogtemplater = cmdutil.makelogtemplater
108 108 except (AttributeError, ImportError):
109 109 makelogtemplater = None
110 110
111 111 # for "historical portability":
112 112 # define util.safehasattr forcibly, because util.safehasattr has been
113 113 # available since 1.9.3 (or 94b200a11cf7)
114 114 _undefined = object()
115 115 def safehasattr(thing, attr):
116 116 return getattr(thing, _sysstr(attr), _undefined) is not _undefined
117 117 setattr(util, 'safehasattr', safehasattr)
118 118
119 119 # for "historical portability":
120 120 # define util.timer forcibly, because util.timer has been available
121 121 # since ae5d60bb70c9
122 122 if safehasattr(time, 'perf_counter'):
123 123 util.timer = time.perf_counter
124 124 elif os.name == b'nt':
125 125 util.timer = time.clock
126 126 else:
127 127 util.timer = time.time
128 128
129 129 # for "historical portability":
130 130 # use locally defined empty option list, if formatteropts isn't
131 131 # available, because commands.formatteropts has been available since
132 132 # 3.2 (or 7a7eed5176a4), even though formatting itself has been
133 133 # available since 2.2 (or ae5f92e154d3)
134 134 formatteropts = getattr(cmdutil, "formatteropts",
135 135 getattr(commands, "formatteropts", []))
136 136
137 137 # for "historical portability":
138 138 # use locally defined option list, if debugrevlogopts isn't available,
139 139 # because commands.debugrevlogopts has been available since 3.7 (or
140 140 # 5606f7d0d063), even though cmdutil.openrevlog() has been available
141 141 # since 1.9 (or a79fea6b3e77).
142 142 revlogopts = getattr(cmdutil, "debugrevlogopts",
143 143 getattr(commands, "debugrevlogopts", [
144 144 (b'c', b'changelog', False, (b'open changelog')),
145 145 (b'm', b'manifest', False, (b'open manifest')),
146 146 (b'', b'dir', False, (b'open directory manifest')),
147 147 ]))
148 148
149 149 cmdtable = {}
150 150
151 151 # for "historical portability":
152 152 # define parsealiases locally, because cmdutil.parsealiases has been
153 153 # available since 1.5 (or 6252852b4332)
154 154 def parsealiases(cmd):
155 155 return cmd.split(b"|")
156 156
157 157 if safehasattr(registrar, 'command'):
158 158 command = registrar.command(cmdtable)
159 159 elif safehasattr(cmdutil, 'command'):
160 160 command = cmdutil.command(cmdtable)
161 161 if b'norepo' not in getargspec(command).args:
162 162 # for "historical portability":
163 163 # wrap original cmdutil.command, because "norepo" option has
164 164 # been available since 3.1 (or 75a96326cecb)
165 165 _command = command
166 166 def command(name, options=(), synopsis=None, norepo=False):
167 167 if norepo:
168 168 commands.norepo += b' %s' % b' '.join(parsealiases(name))
169 169 return _command(name, list(options), synopsis)
170 170 else:
171 171 # for "historical portability":
172 172 # define "@command" annotation locally, because cmdutil.command
173 173 # has been available since 1.9 (or 2daa5179e73f)
174 174 def command(name, options=(), synopsis=None, norepo=False):
175 175 def decorator(func):
176 176 if synopsis:
177 177 cmdtable[name] = func, list(options), synopsis
178 178 else:
179 179 cmdtable[name] = func, list(options)
180 180 if norepo:
181 181 commands.norepo += b' %s' % b' '.join(parsealiases(name))
182 182 return func
183 183 return decorator
184 184
185 185 try:
186 186 import mercurial.registrar
187 187 import mercurial.configitems
188 188 configtable = {}
189 189 configitem = mercurial.registrar.configitem(configtable)
190 190 configitem(b'perf', b'presleep',
191 191 default=mercurial.configitems.dynamicdefault,
192 192 )
193 193 configitem(b'perf', b'stub',
194 194 default=mercurial.configitems.dynamicdefault,
195 195 )
196 196 configitem(b'perf', b'parentscount',
197 197 default=mercurial.configitems.dynamicdefault,
198 198 )
199 199 configitem(b'perf', b'all-timing',
200 200 default=mercurial.configitems.dynamicdefault,
201 201 )
202 202 except (ImportError, AttributeError):
203 203 pass
204 204
205 205 def getlen(ui):
206 206 if ui.configbool(b"perf", b"stub", False):
207 207 return lambda x: 1
208 208 return len
209 209
210 210 def gettimer(ui, opts=None):
211 211 """return a timer function and formatter: (timer, formatter)
212 212
213 213 This function exists to gather the creation of formatter in a single
214 214 place instead of duplicating it in all performance commands."""
215 215
216 216 # enforce an idle period before execution to counteract power management
217 217 # experimental config: perf.presleep
218 218 time.sleep(getint(ui, b"perf", b"presleep", 1))
219 219
220 220 if opts is None:
221 221 opts = {}
222 222 # redirect all to stderr unless buffer api is in use
223 223 if not ui._buffers:
224 224 ui = ui.copy()
225 225 uifout = safeattrsetter(ui, b'fout', ignoremissing=True)
226 226 if uifout:
227 227 # for "historical portability":
228 228 # ui.fout/ferr have been available since 1.9 (or 4e1ccd4c2b6d)
229 229 uifout.set(ui.ferr)
230 230
231 231 # get a formatter
232 232 uiformatter = getattr(ui, 'formatter', None)
233 233 if uiformatter:
234 234 fm = uiformatter(b'perf', opts)
235 235 else:
236 236 # for "historical portability":
237 237 # define formatter locally, because ui.formatter has been
238 238 # available since 2.2 (or ae5f92e154d3)
239 239 from mercurial import node
240 240 class defaultformatter(object):
241 241 """Minimized composition of baseformatter and plainformatter
242 242 """
243 243 def __init__(self, ui, topic, opts):
244 244 self._ui = ui
245 245 if ui.debugflag:
246 246 self.hexfunc = node.hex
247 247 else:
248 248 self.hexfunc = node.short
249 249 def __nonzero__(self):
250 250 return False
251 251 __bool__ = __nonzero__
252 252 def startitem(self):
253 253 pass
254 254 def data(self, **data):
255 255 pass
256 256 def write(self, fields, deftext, *fielddata, **opts):
257 257 self._ui.write(deftext % fielddata, **opts)
258 258 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
259 259 if cond:
260 260 self._ui.write(deftext % fielddata, **opts)
261 261 def plain(self, text, **opts):
262 262 self._ui.write(text, **opts)
263 263 def end(self):
264 264 pass
265 265 fm = defaultformatter(ui, b'perf', opts)
266 266
267 267 # stub function, runs code only once instead of in a loop
268 268 # experimental config: perf.stub
269 269 if ui.configbool(b"perf", b"stub", False):
270 270 return functools.partial(stub_timer, fm), fm
271 271
272 272 # experimental config: perf.all-timing
273 273 displayall = ui.configbool(b"perf", b"all-timing", False)
274 274 return functools.partial(_timer, fm, displayall=displayall), fm
275 275
276 276 def stub_timer(fm, func, title=None):
277 277 func()
278 278
279 279 @contextlib.contextmanager
280 280 def timeone():
281 281 r = []
282 282 ostart = os.times()
283 283 cstart = util.timer()
284 284 yield r
285 285 cstop = util.timer()
286 286 ostop = os.times()
287 287 a, b = ostart, ostop
288 288 r.append((cstop - cstart, b[0] - a[0], b[1]-a[1]))
289 289
290 290 def _timer(fm, func, title=None, displayall=False):
291 291 gc.collect()
292 292 results = []
293 293 begin = util.timer()
294 294 count = 0
295 295 while True:
296 296 with timeone() as item:
297 297 r = func()
298 298 count += 1
299 299 results.append(item[0])
300 300 cstop = util.timer()
301 301 if cstop - begin > 3 and count >= 100:
302 302 break
303 303 if cstop - begin > 10 and count >= 3:
304 304 break
305 305
306 306 formatone(fm, results, title=title, result=r,
307 307 displayall=displayall)
308 308
309 309 def formatone(fm, timings, title=None, result=None, displayall=False):
310 310
311 311 count = len(timings)
312 312
313 313 fm.startitem()
314 314
315 315 if title:
316 316 fm.write(b'title', b'! %s\n', title)
317 317 if result:
318 318 fm.write(b'result', b'! result: %s\n', result)
319 319 def display(role, entry):
320 320 prefix = b''
321 321 if role != b'best':
322 322 prefix = b'%s.' % role
323 323 fm.plain(b'!')
324 324 fm.write(prefix + b'wall', b' wall %f', entry[0])
325 325 fm.write(prefix + b'comb', b' comb %f', entry[1] + entry[2])
326 326 fm.write(prefix + b'user', b' user %f', entry[1])
327 327 fm.write(prefix + b'sys', b' sys %f', entry[2])
328 328 fm.write(prefix + b'count', b' (%s of %%d)' % role, count)
329 329 fm.plain(b'\n')
330 330 timings.sort()
331 331 min_val = timings[0]
332 332 display(b'best', min_val)
333 333 if displayall:
334 334 max_val = timings[-1]
335 335 display(b'max', max_val)
336 336 avg = tuple([sum(x) / count for x in zip(*timings)])
337 337 display(b'avg', avg)
338 338 median = timings[len(timings) // 2]
339 339 display(b'median', median)
340 340
341 341 # utilities for historical portability
342 342
343 343 def getint(ui, section, name, default):
344 344 # for "historical portability":
345 345 # ui.configint has been available since 1.9 (or fa2b596db182)
346 346 v = ui.config(section, name, None)
347 347 if v is None:
348 348 return default
349 349 try:
350 350 return int(v)
351 351 except ValueError:
352 352 raise error.ConfigError((b"%s.%s is not an integer ('%s')")
353 353 % (section, name, v))
354 354
355 355 def safeattrsetter(obj, name, ignoremissing=False):
356 356 """Ensure that 'obj' has 'name' attribute before subsequent setattr
357 357
358 358 This function is aborted, if 'obj' doesn't have 'name' attribute
359 359 at runtime. This avoids overlooking removal of an attribute, which
360 360 breaks assumption of performance measurement, in the future.
361 361
362 362 This function returns the object to (1) assign a new value, and
363 363 (2) restore an original value to the attribute.
364 364
365 365 If 'ignoremissing' is true, missing 'name' attribute doesn't cause
366 366 abortion, and this function returns None. This is useful to
367 367 examine an attribute, which isn't ensured in all Mercurial
368 368 versions.
369 369 """
370 370 if not util.safehasattr(obj, name):
371 371 if ignoremissing:
372 372 return None
373 373 raise error.Abort((b"missing attribute %s of %s might break assumption"
374 374 b" of performance measurement") % (name, obj))
375 375
376 376 origvalue = getattr(obj, _sysstr(name))
377 377 class attrutil(object):
378 378 def set(self, newvalue):
379 379 setattr(obj, _sysstr(name), newvalue)
380 380 def restore(self):
381 381 setattr(obj, _sysstr(name), origvalue)
382 382
383 383 return attrutil()
384 384
385 385 # utilities to examine each internal API changes
386 386
387 387 def getbranchmapsubsettable():
388 388 # for "historical portability":
389 389 # subsettable is defined in:
390 390 # - branchmap since 2.9 (or 175c6fd8cacc)
391 391 # - repoview since 2.5 (or 59a9f18d4587)
392 392 for mod in (branchmap, repoview):
393 393 subsettable = getattr(mod, 'subsettable', None)
394 394 if subsettable:
395 395 return subsettable
396 396
397 397 # bisecting in bcee63733aad::59a9f18d4587 can reach here (both
398 398 # branchmap and repoview modules exist, but subsettable attribute
399 399 # doesn't)
400 400 raise error.Abort((b"perfbranchmap not available with this Mercurial"),
401 401 hint=b"use 2.5 or later")
402 402
403 403 def getsvfs(repo):
404 404 """Return appropriate object to access files under .hg/store
405 405 """
406 406 # for "historical portability":
407 407 # repo.svfs has been available since 2.3 (or 7034365089bf)
408 408 svfs = getattr(repo, 'svfs', None)
409 409 if svfs:
410 410 return svfs
411 411 else:
412 412 return getattr(repo, 'sopener')
413 413
414 414 def getvfs(repo):
415 415 """Return appropriate object to access files under .hg
416 416 """
417 417 # for "historical portability":
418 418 # repo.vfs has been available since 2.3 (or 7034365089bf)
419 419 vfs = getattr(repo, 'vfs', None)
420 420 if vfs:
421 421 return vfs
422 422 else:
423 423 return getattr(repo, 'opener')
424 424
425 425 def repocleartagscachefunc(repo):
426 426 """Return the function to clear tags cache according to repo internal API
427 427 """
428 428 if util.safehasattr(repo, b'_tagscache'): # since 2.0 (or 9dca7653b525)
429 429 # in this case, setattr(repo, '_tagscache', None) or so isn't
430 430 # correct way to clear tags cache, because existing code paths
431 431 # expect _tagscache to be a structured object.
432 432 def clearcache():
433 433 # _tagscache has been filteredpropertycache since 2.5 (or
434 434 # 98c867ac1330), and delattr() can't work in such case
435 435 if b'_tagscache' in vars(repo):
436 436 del repo.__dict__[b'_tagscache']
437 437 return clearcache
438 438
439 439 repotags = safeattrsetter(repo, b'_tags', ignoremissing=True)
440 440 if repotags: # since 1.4 (or 5614a628d173)
441 441 return lambda : repotags.set(None)
442 442
443 443 repotagscache = safeattrsetter(repo, b'tagscache', ignoremissing=True)
444 444 if repotagscache: # since 0.6 (or d7df759d0e97)
445 445 return lambda : repotagscache.set(None)
446 446
447 447 # Mercurial earlier than 0.6 (or d7df759d0e97) logically reaches
448 448 # this point, but it isn't so problematic, because:
449 449 # - repo.tags of such Mercurial isn't "callable", and repo.tags()
450 450 # in perftags() causes failure soon
451 451 # - perf.py itself has been available since 1.1 (or eb240755386d)
452 452 raise error.Abort((b"tags API of this hg command is unknown"))
453 453
454 454 # utilities to clear cache
455 455
456 456 def clearfilecache(repo, attrname):
457 457 unfi = repo.unfiltered()
458 458 if attrname in vars(unfi):
459 459 delattr(unfi, attrname)
460 460 unfi._filecache.pop(attrname, None)
461 461
462 462 # perf commands
463 463
464 464 @command(b'perfwalk', formatteropts)
465 465 def perfwalk(ui, repo, *pats, **opts):
466 466 opts = _byteskwargs(opts)
467 467 timer, fm = gettimer(ui, opts)
468 468 m = scmutil.match(repo[None], pats, {})
469 469 timer(lambda: len(list(repo.dirstate.walk(m, subrepos=[], unknown=True,
470 470 ignored=False))))
471 471 fm.end()
472 472
473 473 @command(b'perfannotate', formatteropts)
474 474 def perfannotate(ui, repo, f, **opts):
475 475 opts = _byteskwargs(opts)
476 476 timer, fm = gettimer(ui, opts)
477 477 fc = repo[b'.'][f]
478 478 timer(lambda: len(fc.annotate(True)))
479 479 fm.end()
480 480
481 481 @command(b'perfstatus',
482 482 [(b'u', b'unknown', False,
483 483 b'ask status to look for unknown files')] + formatteropts)
484 484 def perfstatus(ui, repo, **opts):
485 485 opts = _byteskwargs(opts)
486 486 #m = match.always(repo.root, repo.getcwd())
487 487 #timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
488 488 # False))))
489 489 timer, fm = gettimer(ui, opts)
490 490 timer(lambda: sum(map(len, repo.status(unknown=opts[b'unknown']))))
491 491 fm.end()
492 492
493 493 @command(b'perfaddremove', formatteropts)
494 494 def perfaddremove(ui, repo, **opts):
495 495 opts = _byteskwargs(opts)
496 496 timer, fm = gettimer(ui, opts)
497 497 try:
498 498 oldquiet = repo.ui.quiet
499 499 repo.ui.quiet = True
500 500 matcher = scmutil.match(repo[None])
501 501 opts[b'dry_run'] = True
502 502 timer(lambda: scmutil.addremove(repo, matcher, b"", opts))
503 503 finally:
504 504 repo.ui.quiet = oldquiet
505 505 fm.end()
506 506
507 507 def clearcaches(cl):
508 508 # behave somewhat consistently across internal API changes
509 509 if util.safehasattr(cl, b'clearcaches'):
510 510 cl.clearcaches()
511 511 elif util.safehasattr(cl, b'_nodecache'):
512 512 from mercurial.node import nullid, nullrev
513 513 cl._nodecache = {nullid: nullrev}
514 514 cl._nodepos = None
515 515
516 516 @command(b'perfheads', formatteropts)
517 517 def perfheads(ui, repo, **opts):
518 518 opts = _byteskwargs(opts)
519 519 timer, fm = gettimer(ui, opts)
520 520 cl = repo.changelog
521 521 def d():
522 522 len(cl.headrevs())
523 523 clearcaches(cl)
524 524 timer(d)
525 525 fm.end()
526 526
527 527 @command(b'perftags', formatteropts)
528 528 def perftags(ui, repo, **opts):
529 529 import mercurial.changelog
530 530 import mercurial.manifest
531 531
532 532 opts = _byteskwargs(opts)
533 533 timer, fm = gettimer(ui, opts)
534 534 svfs = getsvfs(repo)
535 535 repocleartagscache = repocleartagscachefunc(repo)
536 536 def t():
537 537 repo.changelog = mercurial.changelog.changelog(svfs)
538 538 rootmanifest = mercurial.manifest.manifestrevlog(svfs)
539 539 repo.manifestlog = mercurial.manifest.manifestlog(svfs, repo,
540 540 rootmanifest)
541 541 repocleartagscache()
542 542 return len(repo.tags())
543 543 timer(t)
544 544 fm.end()
545 545
546 546 @command(b'perfancestors', formatteropts)
547 547 def perfancestors(ui, repo, **opts):
548 548 opts = _byteskwargs(opts)
549 549 timer, fm = gettimer(ui, opts)
550 550 heads = repo.changelog.headrevs()
551 551 def d():
552 552 for a in repo.changelog.ancestors(heads):
553 553 pass
554 554 timer(d)
555 555 fm.end()
556 556
557 557 @command(b'perfancestorset', formatteropts)
558 558 def perfancestorset(ui, repo, revset, **opts):
559 559 opts = _byteskwargs(opts)
560 560 timer, fm = gettimer(ui, opts)
561 561 revs = repo.revs(revset)
562 562 heads = repo.changelog.headrevs()
563 563 def d():
564 564 s = repo.changelog.ancestors(heads)
565 565 for rev in revs:
566 566 rev in s
567 567 timer(d)
568 568 fm.end()
569 569
570 570 @command(b'perfbookmarks', formatteropts)
571 571 def perfbookmarks(ui, repo, **opts):
572 572 """benchmark parsing bookmarks from disk to memory"""
573 573 opts = _byteskwargs(opts)
574 574 timer, fm = gettimer(ui, opts)
575 575 def d():
576 576 clearfilecache(repo, b'_bookmarks')
577 577 repo._bookmarks
578 578 timer(d)
579 579 fm.end()
580 580
581 581 @command(b'perfbundleread', formatteropts, b'BUNDLE')
582 582 def perfbundleread(ui, repo, bundlepath, **opts):
583 583 """Benchmark reading of bundle files.
584 584
585 585 This command is meant to isolate the I/O part of bundle reading as
586 586 much as possible.
587 587 """
588 588 from mercurial import (
589 589 bundle2,
590 590 exchange,
591 591 streamclone,
592 592 )
593 593
594 594 opts = _byteskwargs(opts)
595 595
596 596 def makebench(fn):
597 597 def run():
598 598 with open(bundlepath, b'rb') as fh:
599 599 bundle = exchange.readbundle(ui, fh, bundlepath)
600 600 fn(bundle)
601 601
602 602 return run
603 603
604 604 def makereadnbytes(size):
605 605 def run():
606 606 with open(bundlepath, b'rb') as fh:
607 607 bundle = exchange.readbundle(ui, fh, bundlepath)
608 608 while bundle.read(size):
609 609 pass
610 610
611 611 return run
612 612
613 613 def makestdioread(size):
614 614 def run():
615 615 with open(bundlepath, b'rb') as fh:
616 616 while fh.read(size):
617 617 pass
618 618
619 619 return run
620 620
621 621 # bundle1
622 622
623 623 def deltaiter(bundle):
624 624 for delta in bundle.deltaiter():
625 625 pass
626 626
627 627 def iterchunks(bundle):
628 628 for chunk in bundle.getchunks():
629 629 pass
630 630
631 631 # bundle2
632 632
633 633 def forwardchunks(bundle):
634 634 for chunk in bundle._forwardchunks():
635 635 pass
636 636
637 637 def iterparts(bundle):
638 638 for part in bundle.iterparts():
639 639 pass
640 640
641 641 def iterpartsseekable(bundle):
642 642 for part in bundle.iterparts(seekable=True):
643 643 pass
644 644
645 645 def seek(bundle):
646 646 for part in bundle.iterparts(seekable=True):
647 647 part.seek(0, os.SEEK_END)
648 648
649 649 def makepartreadnbytes(size):
650 650 def run():
651 651 with open(bundlepath, b'rb') as fh:
652 652 bundle = exchange.readbundle(ui, fh, bundlepath)
653 653 for part in bundle.iterparts():
654 654 while part.read(size):
655 655 pass
656 656
657 657 return run
658 658
659 659 benches = [
660 660 (makestdioread(8192), b'read(8k)'),
661 661 (makestdioread(16384), b'read(16k)'),
662 662 (makestdioread(32768), b'read(32k)'),
663 663 (makestdioread(131072), b'read(128k)'),
664 664 ]
665 665
666 666 with open(bundlepath, b'rb') as fh:
667 667 bundle = exchange.readbundle(ui, fh, bundlepath)
668 668
669 669 if isinstance(bundle, changegroup.cg1unpacker):
670 670 benches.extend([
671 671 (makebench(deltaiter), b'cg1 deltaiter()'),
672 672 (makebench(iterchunks), b'cg1 getchunks()'),
673 673 (makereadnbytes(8192), b'cg1 read(8k)'),
674 674 (makereadnbytes(16384), b'cg1 read(16k)'),
675 675 (makereadnbytes(32768), b'cg1 read(32k)'),
676 676 (makereadnbytes(131072), b'cg1 read(128k)'),
677 677 ])
678 678 elif isinstance(bundle, bundle2.unbundle20):
679 679 benches.extend([
680 680 (makebench(forwardchunks), b'bundle2 forwardchunks()'),
681 681 (makebench(iterparts), b'bundle2 iterparts()'),
682 682 (makebench(iterpartsseekable), b'bundle2 iterparts() seekable'),
683 683 (makebench(seek), b'bundle2 part seek()'),
684 684 (makepartreadnbytes(8192), b'bundle2 part read(8k)'),
685 685 (makepartreadnbytes(16384), b'bundle2 part read(16k)'),
686 686 (makepartreadnbytes(32768), b'bundle2 part read(32k)'),
687 687 (makepartreadnbytes(131072), b'bundle2 part read(128k)'),
688 688 ])
689 689 elif isinstance(bundle, streamclone.streamcloneapplier):
690 690 raise error.Abort(b'stream clone bundles not supported')
691 691 else:
692 692 raise error.Abort(b'unhandled bundle type: %s' % type(bundle))
693 693
694 694 for fn, title in benches:
695 695 timer, fm = gettimer(ui, opts)
696 696 timer(fn, title=title)
697 697 fm.end()
698 698
699 699 @command(b'perfchangegroupchangelog', formatteropts +
700 700 [(b'', b'version', b'02', b'changegroup version'),
701 701 (b'r', b'rev', b'', b'revisions to add to changegroup')])
702 702 def perfchangegroupchangelog(ui, repo, version=b'02', rev=None, **opts):
703 703 """Benchmark producing a changelog group for a changegroup.
704 704
705 705 This measures the time spent processing the changelog during a
706 706 bundle operation. This occurs during `hg bundle` and on a server
707 707 processing a `getbundle` wire protocol request (handles clones
708 708 and pull requests).
709 709
710 710 By default, all revisions are added to the changegroup.
711 711 """
712 712 opts = _byteskwargs(opts)
713 713 cl = repo.changelog
714 714 nodes = [cl.lookup(r) for r in repo.revs(rev or b'all()')]
715 715 bundler = changegroup.getbundler(version, repo)
716 716
717 717 def d():
718 718 state, chunks = bundler._generatechangelog(cl, nodes)
719 719 for chunk in chunks:
720 720 pass
721 721
722 722 timer, fm = gettimer(ui, opts)
723 723
724 724 # Terminal printing can interfere with timing. So disable it.
725 725 with ui.configoverride({(b'progress', b'disable'): True}):
726 726 timer(d)
727 727
728 728 fm.end()
729 729
730 730 @command(b'perfdirs', formatteropts)
731 731 def perfdirs(ui, repo, **opts):
732 732 opts = _byteskwargs(opts)
733 733 timer, fm = gettimer(ui, opts)
734 734 dirstate = repo.dirstate
735 735 b'a' in dirstate
736 736 def d():
737 737 dirstate.hasdir(b'a')
738 738 del dirstate._map._dirs
739 739 timer(d)
740 740 fm.end()
741 741
742 742 @command(b'perfdirstate', formatteropts)
743 743 def perfdirstate(ui, repo, **opts):
744 744 opts = _byteskwargs(opts)
745 745 timer, fm = gettimer(ui, opts)
746 746 b"a" in repo.dirstate
747 747 def d():
748 748 repo.dirstate.invalidate()
749 749 b"a" in repo.dirstate
750 750 timer(d)
751 751 fm.end()
752 752
753 753 @command(b'perfdirstatedirs', formatteropts)
754 754 def perfdirstatedirs(ui, repo, **opts):
755 755 opts = _byteskwargs(opts)
756 756 timer, fm = gettimer(ui, opts)
757 757 b"a" in repo.dirstate
758 758 def d():
759 759 repo.dirstate.hasdir(b"a")
760 760 del repo.dirstate._map._dirs
761 761 timer(d)
762 762 fm.end()
763 763
764 764 @command(b'perfdirstatefoldmap', formatteropts)
765 765 def perfdirstatefoldmap(ui, repo, **opts):
766 766 opts = _byteskwargs(opts)
767 767 timer, fm = gettimer(ui, opts)
768 768 dirstate = repo.dirstate
769 769 b'a' in dirstate
770 770 def d():
771 771 dirstate._map.filefoldmap.get(b'a')
772 772 del dirstate._map.filefoldmap
773 773 timer(d)
774 774 fm.end()
775 775
776 776 @command(b'perfdirfoldmap', formatteropts)
777 777 def perfdirfoldmap(ui, repo, **opts):
778 778 opts = _byteskwargs(opts)
779 779 timer, fm = gettimer(ui, opts)
780 780 dirstate = repo.dirstate
781 781 b'a' in dirstate
782 782 def d():
783 783 dirstate._map.dirfoldmap.get(b'a')
784 784 del dirstate._map.dirfoldmap
785 785 del dirstate._map._dirs
786 786 timer(d)
787 787 fm.end()
788 788
789 789 @command(b'perfdirstatewrite', formatteropts)
790 790 def perfdirstatewrite(ui, repo, **opts):
791 791 opts = _byteskwargs(opts)
792 792 timer, fm = gettimer(ui, opts)
793 793 ds = repo.dirstate
794 794 b"a" in ds
795 795 def d():
796 796 ds._dirty = True
797 797 ds.write(repo.currenttransaction())
798 798 timer(d)
799 799 fm.end()
800 800
801 801 @command(b'perfmergecalculate',
802 802 [(b'r', b'rev', b'.', b'rev to merge against')] + formatteropts)
803 803 def perfmergecalculate(ui, repo, rev, **opts):
804 804 opts = _byteskwargs(opts)
805 805 timer, fm = gettimer(ui, opts)
806 806 wctx = repo[None]
807 807 rctx = scmutil.revsingle(repo, rev, rev)
808 808 ancestor = wctx.ancestor(rctx)
809 809 # we don't want working dir files to be stat'd in the benchmark, so prime
810 810 # that cache
811 811 wctx.dirty()
812 812 def d():
813 813 # acceptremote is True because we don't want prompts in the middle of
814 814 # our benchmark
815 815 merge.calculateupdates(repo, wctx, rctx, [ancestor], False, False,
816 816 acceptremote=True, followcopies=True)
817 817 timer(d)
818 818 fm.end()
819 819
820 820 @command(b'perfpathcopies', [], b"REV REV")
821 821 def perfpathcopies(ui, repo, rev1, rev2, **opts):
822 822 opts = _byteskwargs(opts)
823 823 timer, fm = gettimer(ui, opts)
824 824 ctx1 = scmutil.revsingle(repo, rev1, rev1)
825 825 ctx2 = scmutil.revsingle(repo, rev2, rev2)
826 826 def d():
827 827 copies.pathcopies(ctx1, ctx2)
828 828 timer(d)
829 829 fm.end()
830 830
831 831 @command(b'perfphases',
832 832 [(b'', b'full', False, b'include file reading time too'),
833 833 ], b"")
834 834 def perfphases(ui, repo, **opts):
835 835 """benchmark phasesets computation"""
836 836 opts = _byteskwargs(opts)
837 837 timer, fm = gettimer(ui, opts)
838 838 _phases = repo._phasecache
839 839 full = opts.get(b'full')
840 840 def d():
841 841 phases = _phases
842 842 if full:
843 843 clearfilecache(repo, b'_phasecache')
844 844 phases = repo._phasecache
845 845 phases.invalidate()
846 846 phases.loadphaserevs(repo)
847 847 timer(d)
848 848 fm.end()
849 849
850 850 @command(b'perfphasesremote',
851 851 [], b"[DEST]")
852 852 def perfphasesremote(ui, repo, dest=None, **opts):
853 853 """benchmark time needed to analyse phases of the remote server"""
854 854 from mercurial.node import (
855 855 bin,
856 856 )
857 857 from mercurial import (
858 858 exchange,
859 859 hg,
860 860 phases,
861 861 )
862 862 opts = _byteskwargs(opts)
863 863 timer, fm = gettimer(ui, opts)
864 864
865 865 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
866 866 if not path:
867 867 raise error.Abort((b'default repository not configured!'),
868 868 hint=(b"see 'hg help config.paths'"))
869 869 dest = path.pushloc or path.loc
870 870 branches = (path.branch, opts.get(b'branch') or [])
871 871 ui.status((b'analysing phase of %s\n') % util.hidepassword(dest))
872 872 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get(b'rev'))
873 873 other = hg.peer(repo, opts, dest)
874 874
875 875 # easier to perform discovery through the operation
876 876 op = exchange.pushoperation(repo, other)
877 877 exchange._pushdiscoverychangeset(op)
878 878
879 879 remotesubset = op.fallbackheads
880 880
881 881 with other.commandexecutor() as e:
882 882 remotephases = e.callcommand(b'listkeys',
883 883 {b'namespace': b'phases'}).result()
884 884 del other
885 885 publishing = remotephases.get(b'publishing', False)
886 886 if publishing:
887 887 ui.status((b'publishing: yes\n'))
888 888 else:
889 889 ui.status((b'publishing: no\n'))
890 890
891 891 nodemap = repo.changelog.nodemap
892 892 nonpublishroots = 0
893 893 for nhex, phase in remotephases.iteritems():
894 894 if nhex == b'publishing': # ignore data related to publish option
895 895 continue
896 896 node = bin(nhex)
897 897 if node in nodemap and int(phase):
898 898 nonpublishroots += 1
899 899 ui.status((b'number of roots: %d\n') % len(remotephases))
900 900 ui.status((b'number of known non public roots: %d\n') % nonpublishroots)
901 901 def d():
902 902 phases.remotephasessummary(repo,
903 903 remotesubset,
904 904 remotephases)
905 905 timer(d)
906 906 fm.end()
907 907
908 908 @command(b'perfmanifest',[
909 909 (b'm', b'manifest-rev', False, b'Look up a manifest node revision'),
910 910 (b'', b'clear-disk', False, b'clear on-disk caches too'),
911 911 ] + formatteropts, b'REV|NODE')
912 912 def perfmanifest(ui, repo, rev, manifest_rev=False, clear_disk=False, **opts):
913 913 """benchmark the time to read a manifest from disk and return a usable
914 914 dict-like object
915 915
916 916 Manifest caches are cleared before retrieval."""
917 917 opts = _byteskwargs(opts)
918 918 timer, fm = gettimer(ui, opts)
919 919 if not manifest_rev:
920 920 ctx = scmutil.revsingle(repo, rev, rev)
921 921 t = ctx.manifestnode()
922 922 else:
923 923 from mercurial.node import bin
924 924
925 925 if len(rev) == 40:
926 926 t = bin(rev)
927 927 else:
928 928 try:
929 929 rev = int(rev)
930 930
931 931 if util.safehasattr(repo.manifestlog, b'getstorage'):
932 932 t = repo.manifestlog.getstorage(b'').node(rev)
933 933 else:
934 934 t = repo.manifestlog._revlog.lookup(rev)
935 935 except ValueError:
936 936 raise error.Abort(b'manifest revision must be integer or full '
937 937 b'node')
938 938 def d():
939 939 repo.manifestlog.clearcaches(clear_persisted_data=clear_disk)
940 940 repo.manifestlog[t].read()
941 941 timer(d)
942 942 fm.end()
943 943
944 944 @command(b'perfchangeset', formatteropts)
945 945 def perfchangeset(ui, repo, rev, **opts):
946 946 opts = _byteskwargs(opts)
947 947 timer, fm = gettimer(ui, opts)
948 948 n = scmutil.revsingle(repo, rev).node()
949 949 def d():
950 950 repo.changelog.read(n)
951 951 #repo.changelog._cache = None
952 952 timer(d)
953 953 fm.end()
954 954
955 955 @command(b'perfindex', formatteropts)
956 956 def perfindex(ui, repo, **opts):
957 957 import mercurial.revlog
958 958 opts = _byteskwargs(opts)
959 959 timer, fm = gettimer(ui, opts)
960 960 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
961 961 n = repo[b"tip"].node()
962 962 svfs = getsvfs(repo)
963 963 def d():
964 964 cl = mercurial.revlog.revlog(svfs, b"00changelog.i")
965 965 cl.rev(n)
966 966 timer(d)
967 967 fm.end()
968 968
969 969 @command(b'perfstartup', formatteropts)
970 970 def perfstartup(ui, repo, **opts):
971 971 opts = _byteskwargs(opts)
972 972 timer, fm = gettimer(ui, opts)
973 973 def d():
974 974 if os.name != r'nt':
975 975 os.system(b"HGRCPATH= %s version -q > /dev/null" %
976 976 fsencode(sys.argv[0]))
977 977 else:
978 978 os.environ[r'HGRCPATH'] = r' '
979 979 os.system(r"%s version -q > NUL" % sys.argv[0])
980 980 timer(d)
981 981 fm.end()
982 982
983 983 @command(b'perfparents', formatteropts)
984 984 def perfparents(ui, repo, **opts):
985 985 opts = _byteskwargs(opts)
986 986 timer, fm = gettimer(ui, opts)
987 987 # control the number of commits perfparents iterates over
988 988 # experimental config: perf.parentscount
989 989 count = getint(ui, b"perf", b"parentscount", 1000)
990 990 if len(repo.changelog) < count:
991 991 raise error.Abort(b"repo needs %d commits for this test" % count)
992 992 repo = repo.unfiltered()
993 993 nl = [repo.changelog.node(i) for i in _xrange(count)]
994 994 def d():
995 995 for n in nl:
996 996 repo.changelog.parents(n)
997 997 timer(d)
998 998 fm.end()
999 999
1000 1000 @command(b'perfctxfiles', formatteropts)
1001 1001 def perfctxfiles(ui, repo, x, **opts):
1002 1002 opts = _byteskwargs(opts)
1003 1003 x = int(x)
1004 1004 timer, fm = gettimer(ui, opts)
1005 1005 def d():
1006 1006 len(repo[x].files())
1007 1007 timer(d)
1008 1008 fm.end()
1009 1009
1010 1010 @command(b'perfrawfiles', formatteropts)
1011 1011 def perfrawfiles(ui, repo, x, **opts):
1012 1012 opts = _byteskwargs(opts)
1013 1013 x = int(x)
1014 1014 timer, fm = gettimer(ui, opts)
1015 1015 cl = repo.changelog
1016 1016 def d():
1017 1017 len(cl.read(x)[3])
1018 1018 timer(d)
1019 1019 fm.end()
1020 1020
1021 1021 @command(b'perflookup', formatteropts)
1022 1022 def perflookup(ui, repo, rev, **opts):
1023 1023 opts = _byteskwargs(opts)
1024 1024 timer, fm = gettimer(ui, opts)
1025 1025 timer(lambda: len(repo.lookup(rev)))
1026 1026 fm.end()
1027 1027
1028 1028 @command(b'perflinelogedits',
1029 1029 [(b'n', b'edits', 10000, b'number of edits'),
1030 1030 (b'', b'max-hunk-lines', 10, b'max lines in a hunk'),
1031 1031 ], norepo=True)
1032 1032 def perflinelogedits(ui, **opts):
1033 1033 from mercurial import linelog
1034 1034
1035 1035 opts = _byteskwargs(opts)
1036 1036
1037 1037 edits = opts[b'edits']
1038 1038 maxhunklines = opts[b'max_hunk_lines']
1039 1039
1040 1040 maxb1 = 100000
1041 1041 random.seed(0)
1042 1042 randint = random.randint
1043 1043 currentlines = 0
1044 1044 arglist = []
1045 1045 for rev in _xrange(edits):
1046 1046 a1 = randint(0, currentlines)
1047 1047 a2 = randint(a1, min(currentlines, a1 + maxhunklines))
1048 1048 b1 = randint(0, maxb1)
1049 1049 b2 = randint(b1, b1 + maxhunklines)
1050 1050 currentlines += (b2 - b1) - (a2 - a1)
1051 1051 arglist.append((rev, a1, a2, b1, b2))
1052 1052
1053 1053 def d():
1054 1054 ll = linelog.linelog()
1055 1055 for args in arglist:
1056 1056 ll.replacelines(*args)
1057 1057
1058 1058 timer, fm = gettimer(ui, opts)
1059 1059 timer(d)
1060 1060 fm.end()
1061 1061
1062 1062 @command(b'perfrevrange', formatteropts)
1063 1063 def perfrevrange(ui, repo, *specs, **opts):
1064 1064 opts = _byteskwargs(opts)
1065 1065 timer, fm = gettimer(ui, opts)
1066 1066 revrange = scmutil.revrange
1067 1067 timer(lambda: len(revrange(repo, specs)))
1068 1068 fm.end()
1069 1069
1070 1070 @command(b'perfnodelookup', formatteropts)
1071 1071 def perfnodelookup(ui, repo, rev, **opts):
1072 1072 opts = _byteskwargs(opts)
1073 1073 timer, fm = gettimer(ui, opts)
1074 1074 import mercurial.revlog
1075 1075 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
1076 1076 n = scmutil.revsingle(repo, rev).node()
1077 1077 cl = mercurial.revlog.revlog(getsvfs(repo), b"00changelog.i")
1078 1078 def d():
1079 1079 cl.rev(n)
1080 1080 clearcaches(cl)
1081 1081 timer(d)
1082 1082 fm.end()
1083 1083
1084 1084 @command(b'perflog',
1085 1085 [(b'', b'rename', False, b'ask log to follow renames')
1086 1086 ] + formatteropts)
1087 1087 def perflog(ui, repo, rev=None, **opts):
1088 1088 opts = _byteskwargs(opts)
1089 1089 if rev is None:
1090 1090 rev=[]
1091 1091 timer, fm = gettimer(ui, opts)
1092 1092 ui.pushbuffer()
1093 1093 timer(lambda: commands.log(ui, repo, rev=rev, date=b'', user=b'',
1094 1094 copies=opts.get(b'rename')))
1095 1095 ui.popbuffer()
1096 1096 fm.end()
1097 1097
1098 1098 @command(b'perfmoonwalk', formatteropts)
1099 1099 def perfmoonwalk(ui, repo, **opts):
1100 1100 """benchmark walking the changelog backwards
1101 1101
1102 1102 This also loads the changelog data for each revision in the changelog.
1103 1103 """
1104 1104 opts = _byteskwargs(opts)
1105 1105 timer, fm = gettimer(ui, opts)
1106 1106 def moonwalk():
1107 1107 for i in repo.changelog.revs(start=(len(repo) - 1), stop=-1):
1108 1108 ctx = repo[i]
1109 1109 ctx.branch() # read changelog data (in addition to the index)
1110 1110 timer(moonwalk)
1111 1111 fm.end()
1112 1112
1113 1113 @command(b'perftemplating',
1114 1114 [(b'r', b'rev', [], b'revisions to run the template on'),
1115 1115 ] + formatteropts)
1116 1116 def perftemplating(ui, repo, testedtemplate=None, **opts):
1117 1117 """test the rendering time of a given template"""
1118 1118 if makelogtemplater is None:
1119 1119 raise error.Abort((b"perftemplating not available with this Mercurial"),
1120 1120 hint=b"use 4.3 or later")
1121 1121
1122 1122 opts = _byteskwargs(opts)
1123 1123
1124 1124 nullui = ui.copy()
1125 1125 nullui.fout = open(os.devnull, r'wb')
1126 1126 nullui.disablepager()
1127 1127 revs = opts.get(b'rev')
1128 1128 if not revs:
1129 1129 revs = [b'all()']
1130 1130 revs = list(scmutil.revrange(repo, revs))
1131 1131
1132 1132 defaulttemplate = (b'{date|shortdate} [{rev}:{node|short}]'
1133 1133 b' {author|person}: {desc|firstline}\n')
1134 1134 if testedtemplate is None:
1135 1135 testedtemplate = defaulttemplate
1136 1136 displayer = makelogtemplater(nullui, repo, testedtemplate)
1137 1137 def format():
1138 1138 for r in revs:
1139 1139 ctx = repo[r]
1140 1140 displayer.show(ctx)
1141 1141 displayer.flush(ctx)
1142 1142
1143 1143 timer, fm = gettimer(ui, opts)
1144 1144 timer(format)
1145 1145 fm.end()
1146 1146
1147 1147 @command(b'perfcca', formatteropts)
1148 1148 def perfcca(ui, repo, **opts):
1149 1149 opts = _byteskwargs(opts)
1150 1150 timer, fm = gettimer(ui, opts)
1151 1151 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
1152 1152 fm.end()
1153 1153
1154 1154 @command(b'perffncacheload', formatteropts)
1155 1155 def perffncacheload(ui, repo, **opts):
1156 1156 opts = _byteskwargs(opts)
1157 1157 timer, fm = gettimer(ui, opts)
1158 1158 s = repo.store
1159 1159 def d():
1160 1160 s.fncache._load()
1161 1161 timer(d)
1162 1162 fm.end()
1163 1163
1164 1164 @command(b'perffncachewrite', formatteropts)
1165 1165 def perffncachewrite(ui, repo, **opts):
1166 1166 opts = _byteskwargs(opts)
1167 1167 timer, fm = gettimer(ui, opts)
1168 1168 s = repo.store
1169 1169 lock = repo.lock()
1170 1170 s.fncache._load()
1171 1171 tr = repo.transaction(b'perffncachewrite')
1172 1172 tr.addbackup(b'fncache')
1173 1173 def d():
1174 1174 s.fncache._dirty = True
1175 1175 s.fncache.write(tr)
1176 1176 timer(d)
1177 1177 tr.close()
1178 1178 lock.release()
1179 1179 fm.end()
1180 1180
1181 1181 @command(b'perffncacheencode', formatteropts)
1182 1182 def perffncacheencode(ui, repo, **opts):
1183 1183 opts = _byteskwargs(opts)
1184 1184 timer, fm = gettimer(ui, opts)
1185 1185 s = repo.store
1186 1186 s.fncache._load()
1187 1187 def d():
1188 1188 for p in s.fncache.entries:
1189 1189 s.encode(p)
1190 1190 timer(d)
1191 1191 fm.end()
1192 1192
1193 1193 def _bdiffworker(q, blocks, xdiff, ready, done):
1194 1194 while not done.is_set():
1195 1195 pair = q.get()
1196 1196 while pair is not None:
1197 1197 if xdiff:
1198 1198 mdiff.bdiff.xdiffblocks(*pair)
1199 1199 elif blocks:
1200 1200 mdiff.bdiff.blocks(*pair)
1201 1201 else:
1202 1202 mdiff.textdiff(*pair)
1203 1203 q.task_done()
1204 1204 pair = q.get()
1205 1205 q.task_done() # for the None one
1206 1206 with ready:
1207 1207 ready.wait()
1208 1208
1209 1209 def _manifestrevision(repo, mnode):
1210 1210 ml = repo.manifestlog
1211 1211
1212 1212 if util.safehasattr(ml, b'getstorage'):
1213 1213 store = ml.getstorage(b'')
1214 1214 else:
1215 1215 store = ml._revlog
1216 1216
1217 1217 return store.revision(mnode)
1218 1218
1219 1219 @command(b'perfbdiff', revlogopts + formatteropts + [
1220 1220 (b'', b'count', 1, b'number of revisions to test (when using --startrev)'),
1221 1221 (b'', b'alldata', False, b'test bdiffs for all associated revisions'),
1222 1222 (b'', b'threads', 0, b'number of thread to use (disable with 0)'),
1223 1223 (b'', b'blocks', False, b'test computing diffs into blocks'),
1224 1224 (b'', b'xdiff', False, b'use xdiff algorithm'),
1225 1225 ],
1226 1226
1227 1227 b'-c|-m|FILE REV')
1228 1228 def perfbdiff(ui, repo, file_, rev=None, count=None, threads=0, **opts):
1229 1229 """benchmark a bdiff between revisions
1230 1230
1231 1231 By default, benchmark a bdiff between its delta parent and itself.
1232 1232
1233 1233 With ``--count``, benchmark bdiffs between delta parents and self for N
1234 1234 revisions starting at the specified revision.
1235 1235
1236 1236 With ``--alldata``, assume the requested revision is a changeset and
1237 1237 measure bdiffs for all changes related to that changeset (manifest
1238 1238 and filelogs).
1239 1239 """
1240 1240 opts = _byteskwargs(opts)
1241 1241
1242 1242 if opts[b'xdiff'] and not opts[b'blocks']:
1243 1243 raise error.CommandError(b'perfbdiff', b'--xdiff requires --blocks')
1244 1244
1245 1245 if opts[b'alldata']:
1246 1246 opts[b'changelog'] = True
1247 1247
1248 1248 if opts.get(b'changelog') or opts.get(b'manifest'):
1249 1249 file_, rev = None, file_
1250 1250 elif rev is None:
1251 1251 raise error.CommandError(b'perfbdiff', b'invalid arguments')
1252 1252
1253 1253 blocks = opts[b'blocks']
1254 1254 xdiff = opts[b'xdiff']
1255 1255 textpairs = []
1256 1256
1257 1257 r = cmdutil.openrevlog(repo, b'perfbdiff', file_, opts)
1258 1258
1259 1259 startrev = r.rev(r.lookup(rev))
1260 1260 for rev in range(startrev, min(startrev + count, len(r) - 1)):
1261 1261 if opts[b'alldata']:
1262 1262 # Load revisions associated with changeset.
1263 1263 ctx = repo[rev]
1264 1264 mtext = _manifestrevision(repo, ctx.manifestnode())
1265 1265 for pctx in ctx.parents():
1266 1266 pman = _manifestrevision(repo, pctx.manifestnode())
1267 1267 textpairs.append((pman, mtext))
1268 1268
1269 1269 # Load filelog revisions by iterating manifest delta.
1270 1270 man = ctx.manifest()
1271 1271 pman = ctx.p1().manifest()
1272 1272 for filename, change in pman.diff(man).items():
1273 1273 fctx = repo.file(filename)
1274 1274 f1 = fctx.revision(change[0][0] or -1)
1275 1275 f2 = fctx.revision(change[1][0] or -1)
1276 1276 textpairs.append((f1, f2))
1277 1277 else:
1278 1278 dp = r.deltaparent(rev)
1279 1279 textpairs.append((r.revision(dp), r.revision(rev)))
1280 1280
1281 1281 withthreads = threads > 0
1282 1282 if not withthreads:
1283 1283 def d():
1284 1284 for pair in textpairs:
1285 1285 if xdiff:
1286 1286 mdiff.bdiff.xdiffblocks(*pair)
1287 1287 elif blocks:
1288 1288 mdiff.bdiff.blocks(*pair)
1289 1289 else:
1290 1290 mdiff.textdiff(*pair)
1291 1291 else:
1292 1292 q = queue()
1293 1293 for i in _xrange(threads):
1294 1294 q.put(None)
1295 1295 ready = threading.Condition()
1296 1296 done = threading.Event()
1297 1297 for i in _xrange(threads):
1298 1298 threading.Thread(target=_bdiffworker,
1299 1299 args=(q, blocks, xdiff, ready, done)).start()
1300 1300 q.join()
1301 1301 def d():
1302 1302 for pair in textpairs:
1303 1303 q.put(pair)
1304 1304 for i in _xrange(threads):
1305 1305 q.put(None)
1306 1306 with ready:
1307 1307 ready.notify_all()
1308 1308 q.join()
1309 1309 timer, fm = gettimer(ui, opts)
1310 1310 timer(d)
1311 1311 fm.end()
1312 1312
1313 1313 if withthreads:
1314 1314 done.set()
1315 1315 for i in _xrange(threads):
1316 1316 q.put(None)
1317 1317 with ready:
1318 1318 ready.notify_all()
1319 1319
1320 1320 @command(b'perfunidiff', revlogopts + formatteropts + [
1321 1321 (b'', b'count', 1, b'number of revisions to test (when using --startrev)'),
1322 1322 (b'', b'alldata', False, b'test unidiffs for all associated revisions'),
1323 1323 ], b'-c|-m|FILE REV')
1324 1324 def perfunidiff(ui, repo, file_, rev=None, count=None, **opts):
1325 1325 """benchmark a unified diff between revisions
1326 1326
1327 1327 This doesn't include any copy tracing - it's just a unified diff
1328 1328 of the texts.
1329 1329
1330 1330 By default, benchmark a diff between its delta parent and itself.
1331 1331
1332 1332 With ``--count``, benchmark diffs between delta parents and self for N
1333 1333 revisions starting at the specified revision.
1334 1334
1335 1335 With ``--alldata``, assume the requested revision is a changeset and
1336 1336 measure diffs for all changes related to that changeset (manifest
1337 1337 and filelogs).
1338 1338 """
1339 1339 opts = _byteskwargs(opts)
1340 1340 if opts[b'alldata']:
1341 1341 opts[b'changelog'] = True
1342 1342
1343 1343 if opts.get(b'changelog') or opts.get(b'manifest'):
1344 1344 file_, rev = None, file_
1345 1345 elif rev is None:
1346 1346 raise error.CommandError(b'perfunidiff', b'invalid arguments')
1347 1347
1348 1348 textpairs = []
1349 1349
1350 1350 r = cmdutil.openrevlog(repo, b'perfunidiff', file_, opts)
1351 1351
1352 1352 startrev = r.rev(r.lookup(rev))
1353 1353 for rev in range(startrev, min(startrev + count, len(r) - 1)):
1354 1354 if opts[b'alldata']:
1355 1355 # Load revisions associated with changeset.
1356 1356 ctx = repo[rev]
1357 1357 mtext = _manifestrevision(repo, ctx.manifestnode())
1358 1358 for pctx in ctx.parents():
1359 1359 pman = _manifestrevision(repo, pctx.manifestnode())
1360 1360 textpairs.append((pman, mtext))
1361 1361
1362 1362 # Load filelog revisions by iterating manifest delta.
1363 1363 man = ctx.manifest()
1364 1364 pman = ctx.p1().manifest()
1365 1365 for filename, change in pman.diff(man).items():
1366 1366 fctx = repo.file(filename)
1367 1367 f1 = fctx.revision(change[0][0] or -1)
1368 1368 f2 = fctx.revision(change[1][0] or -1)
1369 1369 textpairs.append((f1, f2))
1370 1370 else:
1371 1371 dp = r.deltaparent(rev)
1372 1372 textpairs.append((r.revision(dp), r.revision(rev)))
1373 1373
1374 1374 def d():
1375 1375 for left, right in textpairs:
1376 1376 # The date strings don't matter, so we pass empty strings.
1377 1377 headerlines, hunks = mdiff.unidiff(
1378 1378 left, b'', right, b'', b'left', b'right', binary=False)
1379 1379 # consume iterators in roughly the way patch.py does
1380 1380 b'\n'.join(headerlines)
1381 1381 b''.join(sum((list(hlines) for hrange, hlines in hunks), []))
1382 1382 timer, fm = gettimer(ui, opts)
1383 1383 timer(d)
1384 1384 fm.end()
1385 1385
1386 1386 @command(b'perfdiffwd', formatteropts)
1387 1387 def perfdiffwd(ui, repo, **opts):
1388 1388 """Profile diff of working directory changes"""
1389 1389 opts = _byteskwargs(opts)
1390 1390 timer, fm = gettimer(ui, opts)
1391 1391 options = {
1392 1392 'w': 'ignore_all_space',
1393 1393 'b': 'ignore_space_change',
1394 1394 'B': 'ignore_blank_lines',
1395 1395 }
1396 1396
1397 1397 for diffopt in ('', 'w', 'b', 'B', 'wB'):
1398 1398 opts = dict((options[c], b'1') for c in diffopt)
1399 1399 def d():
1400 1400 ui.pushbuffer()
1401 1401 commands.diff(ui, repo, **opts)
1402 1402 ui.popbuffer()
1403 1403 diffopt = diffopt.encode('ascii')
1404 1404 title = b'diffopts: %s' % (diffopt and (b'-' + diffopt) or b'none')
1405 1405 timer(d, title)
1406 1406 fm.end()
1407 1407
1408 1408 @command(b'perfrevlogindex', revlogopts + formatteropts,
1409 1409 b'-c|-m|FILE')
1410 1410 def perfrevlogindex(ui, repo, file_=None, **opts):
1411 1411 """Benchmark operations against a revlog index.
1412 1412
1413 1413 This tests constructing a revlog instance, reading index data,
1414 1414 parsing index data, and performing various operations related to
1415 1415 index data.
1416 1416 """
1417 1417
1418 1418 opts = _byteskwargs(opts)
1419 1419
1420 1420 rl = cmdutil.openrevlog(repo, b'perfrevlogindex', file_, opts)
1421 1421
1422 1422 opener = getattr(rl, 'opener') # trick linter
1423 1423 indexfile = rl.indexfile
1424 1424 data = opener.read(indexfile)
1425 1425
1426 1426 header = struct.unpack(b'>I', data[0:4])[0]
1427 1427 version = header & 0xFFFF
1428 1428 if version == 1:
1429 1429 revlogio = revlog.revlogio()
1430 1430 inline = header & (1 << 16)
1431 1431 else:
1432 1432 raise error.Abort((b'unsupported revlog version: %d') % version)
1433 1433
1434 1434 rllen = len(rl)
1435 1435
1436 1436 node0 = rl.node(0)
1437 1437 node25 = rl.node(rllen // 4)
1438 1438 node50 = rl.node(rllen // 2)
1439 1439 node75 = rl.node(rllen // 4 * 3)
1440 1440 node100 = rl.node(rllen - 1)
1441 1441
1442 1442 allrevs = range(rllen)
1443 1443 allrevsrev = list(reversed(allrevs))
1444 1444 allnodes = [rl.node(rev) for rev in range(rllen)]
1445 1445 allnodesrev = list(reversed(allnodes))
1446 1446
1447 1447 def constructor():
1448 1448 revlog.revlog(opener, indexfile)
1449 1449
1450 1450 def read():
1451 1451 with opener(indexfile) as fh:
1452 1452 fh.read()
1453 1453
1454 1454 def parseindex():
1455 1455 revlogio.parseindex(data, inline)
1456 1456
1457 1457 def getentry(revornode):
1458 1458 index = revlogio.parseindex(data, inline)[0]
1459 1459 index[revornode]
1460 1460
1461 1461 def getentries(revs, count=1):
1462 1462 index = revlogio.parseindex(data, inline)[0]
1463 1463
1464 1464 for i in range(count):
1465 1465 for rev in revs:
1466 1466 index[rev]
1467 1467
1468 1468 def resolvenode(node):
1469 1469 nodemap = revlogio.parseindex(data, inline)[1]
1470 1470 # This only works for the C code.
1471 1471 if nodemap is None:
1472 1472 return
1473 1473
1474 1474 try:
1475 1475 nodemap[node]
1476 1476 except error.RevlogError:
1477 1477 pass
1478 1478
1479 1479 def resolvenodes(nodes, count=1):
1480 1480 nodemap = revlogio.parseindex(data, inline)[1]
1481 1481 if nodemap is None:
1482 1482 return
1483 1483
1484 1484 for i in range(count):
1485 1485 for node in nodes:
1486 1486 try:
1487 1487 nodemap[node]
1488 1488 except error.RevlogError:
1489 1489 pass
1490 1490
1491 1491 benches = [
1492 1492 (constructor, b'revlog constructor'),
1493 1493 (read, b'read'),
1494 1494 (parseindex, b'create index object'),
1495 1495 (lambda: getentry(0), b'retrieve index entry for rev 0'),
1496 1496 (lambda: resolvenode(b'a' * 20), b'look up missing node'),
1497 1497 (lambda: resolvenode(node0), b'look up node at rev 0'),
1498 1498 (lambda: resolvenode(node25), b'look up node at 1/4 len'),
1499 1499 (lambda: resolvenode(node50), b'look up node at 1/2 len'),
1500 1500 (lambda: resolvenode(node75), b'look up node at 3/4 len'),
1501 1501 (lambda: resolvenode(node100), b'look up node at tip'),
1502 1502 # 2x variation is to measure caching impact.
1503 1503 (lambda: resolvenodes(allnodes),
1504 1504 b'look up all nodes (forward)'),
1505 1505 (lambda: resolvenodes(allnodes, 2),
1506 1506 b'look up all nodes 2x (forward)'),
1507 1507 (lambda: resolvenodes(allnodesrev),
1508 1508 b'look up all nodes (reverse)'),
1509 1509 (lambda: resolvenodes(allnodesrev, 2),
1510 1510 b'look up all nodes 2x (reverse)'),
1511 1511 (lambda: getentries(allrevs),
1512 1512 b'retrieve all index entries (forward)'),
1513 1513 (lambda: getentries(allrevs, 2),
1514 1514 b'retrieve all index entries 2x (forward)'),
1515 1515 (lambda: getentries(allrevsrev),
1516 1516 b'retrieve all index entries (reverse)'),
1517 1517 (lambda: getentries(allrevsrev, 2),
1518 1518 b'retrieve all index entries 2x (reverse)'),
1519 1519 ]
1520 1520
1521 1521 for fn, title in benches:
1522 1522 timer, fm = gettimer(ui, opts)
1523 1523 timer(fn, title=title)
1524 1524 fm.end()
1525 1525
1526 1526 @command(b'perfrevlogrevisions', revlogopts + formatteropts +
1527 1527 [(b'd', b'dist', 100, b'distance between the revisions'),
1528 1528 (b's', b'startrev', 0, b'revision to start reading at'),
1529 1529 (b'', b'reverse', False, b'read in reverse')],
1530 1530 b'-c|-m|FILE')
1531 1531 def perfrevlogrevisions(ui, repo, file_=None, startrev=0, reverse=False,
1532 1532 **opts):
1533 1533 """Benchmark reading a series of revisions from a revlog.
1534 1534
1535 1535 By default, we read every ``-d/--dist`` revision from 0 to tip of
1536 1536 the specified revlog.
1537 1537
1538 1538 The start revision can be defined via ``-s/--startrev``.
1539 1539 """
1540 1540 opts = _byteskwargs(opts)
1541 1541
1542 1542 rl = cmdutil.openrevlog(repo, b'perfrevlogrevisions', file_, opts)
1543 1543 rllen = getlen(ui)(rl)
1544 1544
1545 1545 if startrev < 0:
1546 1546 startrev = rllen + startrev
1547 1547
1548 1548 def d():
1549 1549 rl.clearcaches()
1550 1550
1551 1551 beginrev = startrev
1552 1552 endrev = rllen
1553 1553 dist = opts[b'dist']
1554 1554
1555 1555 if reverse:
1556 1556 beginrev, endrev = endrev, beginrev
1557 1557 dist = -1 * dist
1558 1558
1559 1559 for x in _xrange(beginrev, endrev, dist):
1560 1560 # Old revisions don't support passing int.
1561 1561 n = rl.node(x)
1562 1562 rl.revision(n)
1563 1563
1564 1564 timer, fm = gettimer(ui, opts)
1565 1565 timer(d)
1566 1566 fm.end()
1567 1567
1568 1568 @command(b'perfrevlogchunks', revlogopts + formatteropts +
1569 1569 [(b'e', b'engines', b'', b'compression engines to use'),
1570 1570 (b's', b'startrev', 0, b'revision to start at')],
1571 1571 b'-c|-m|FILE')
1572 1572 def perfrevlogchunks(ui, repo, file_=None, engines=None, startrev=0, **opts):
1573 1573 """Benchmark operations on revlog chunks.
1574 1574
1575 1575 Logically, each revlog is a collection of fulltext revisions. However,
1576 1576 stored within each revlog are "chunks" of possibly compressed data. This
1577 1577 data needs to be read and decompressed or compressed and written.
1578 1578
1579 1579 This command measures the time it takes to read+decompress and recompress
1580 1580 chunks in a revlog. It effectively isolates I/O and compression performance.
1581 1581 For measurements of higher-level operations like resolving revisions,
1582 1582 see ``perfrevlogrevisions`` and ``perfrevlogrevision``.
1583 1583 """
1584 1584 opts = _byteskwargs(opts)
1585 1585
1586 1586 rl = cmdutil.openrevlog(repo, b'perfrevlogchunks', file_, opts)
1587 1587
1588 1588 # _chunkraw was renamed to _getsegmentforrevs.
1589 1589 try:
1590 1590 segmentforrevs = rl._getsegmentforrevs
1591 1591 except AttributeError:
1592 1592 segmentforrevs = rl._chunkraw
1593 1593
1594 1594 # Verify engines argument.
1595 1595 if engines:
1596 1596 engines = set(e.strip() for e in engines.split(b','))
1597 1597 for engine in engines:
1598 1598 try:
1599 1599 util.compressionengines[engine]
1600 1600 except KeyError:
1601 1601 raise error.Abort(b'unknown compression engine: %s' % engine)
1602 1602 else:
1603 1603 engines = []
1604 1604 for e in util.compengines:
1605 1605 engine = util.compengines[e]
1606 1606 try:
1607 1607 if engine.available():
1608 1608 engine.revlogcompressor().compress(b'dummy')
1609 1609 engines.append(e)
1610 1610 except NotImplementedError:
1611 1611 pass
1612 1612
1613 1613 revs = list(rl.revs(startrev, len(rl) - 1))
1614 1614
1615 1615 def rlfh(rl):
1616 1616 if rl._inline:
1617 1617 return getsvfs(repo)(rl.indexfile)
1618 1618 else:
1619 1619 return getsvfs(repo)(rl.datafile)
1620 1620
1621 1621 def doread():
1622 1622 rl.clearcaches()
1623 1623 for rev in revs:
1624 1624 segmentforrevs(rev, rev)
1625 1625
1626 1626 def doreadcachedfh():
1627 1627 rl.clearcaches()
1628 1628 fh = rlfh(rl)
1629 1629 for rev in revs:
1630 1630 segmentforrevs(rev, rev, df=fh)
1631 1631
1632 1632 def doreadbatch():
1633 1633 rl.clearcaches()
1634 1634 segmentforrevs(revs[0], revs[-1])
1635 1635
1636 1636 def doreadbatchcachedfh():
1637 1637 rl.clearcaches()
1638 1638 fh = rlfh(rl)
1639 1639 segmentforrevs(revs[0], revs[-1], df=fh)
1640 1640
1641 1641 def dochunk():
1642 1642 rl.clearcaches()
1643 1643 fh = rlfh(rl)
1644 1644 for rev in revs:
1645 1645 rl._chunk(rev, df=fh)
1646 1646
1647 1647 chunks = [None]
1648 1648
1649 1649 def dochunkbatch():
1650 1650 rl.clearcaches()
1651 1651 fh = rlfh(rl)
1652 1652 # Save chunks as a side-effect.
1653 1653 chunks[0] = rl._chunks(revs, df=fh)
1654 1654
1655 1655 def docompress(compressor):
1656 1656 rl.clearcaches()
1657 1657
1658 1658 try:
1659 1659 # Swap in the requested compression engine.
1660 1660 oldcompressor = rl._compressor
1661 1661 rl._compressor = compressor
1662 1662 for chunk in chunks[0]:
1663 1663 rl.compress(chunk)
1664 1664 finally:
1665 1665 rl._compressor = oldcompressor
1666 1666
1667 1667 benches = [
1668 1668 (lambda: doread(), b'read'),
1669 1669 (lambda: doreadcachedfh(), b'read w/ reused fd'),
1670 1670 (lambda: doreadbatch(), b'read batch'),
1671 1671 (lambda: doreadbatchcachedfh(), b'read batch w/ reused fd'),
1672 1672 (lambda: dochunk(), b'chunk'),
1673 1673 (lambda: dochunkbatch(), b'chunk batch'),
1674 1674 ]
1675 1675
1676 1676 for engine in sorted(engines):
1677 1677 compressor = util.compengines[engine].revlogcompressor()
1678 1678 benches.append((functools.partial(docompress, compressor),
1679 1679 b'compress w/ %s' % engine))
1680 1680
1681 1681 for fn, title in benches:
1682 1682 timer, fm = gettimer(ui, opts)
1683 1683 timer(fn, title=title)
1684 1684 fm.end()
1685 1685
1686 1686 @command(b'perfrevlogrevision', revlogopts + formatteropts +
1687 1687 [(b'', b'cache', False, b'use caches instead of clearing')],
1688 1688 b'-c|-m|FILE REV')
1689 1689 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
1690 1690 """Benchmark obtaining a revlog revision.
1691 1691
1692 1692 Obtaining a revlog revision consists of roughly the following steps:
1693 1693
1694 1694 1. Compute the delta chain
1695 1695 2. Obtain the raw chunks for that delta chain
1696 1696 3. Decompress each raw chunk
1697 1697 4. Apply binary patches to obtain fulltext
1698 1698 5. Verify hash of fulltext
1699 1699
1700 1700 This command measures the time spent in each of these phases.
1701 1701 """
1702 1702 opts = _byteskwargs(opts)
1703 1703
1704 1704 if opts.get(b'changelog') or opts.get(b'manifest'):
1705 1705 file_, rev = None, file_
1706 1706 elif rev is None:
1707 1707 raise error.CommandError(b'perfrevlogrevision', b'invalid arguments')
1708 1708
1709 1709 r = cmdutil.openrevlog(repo, b'perfrevlogrevision', file_, opts)
1710 1710
1711 1711 # _chunkraw was renamed to _getsegmentforrevs.
1712 1712 try:
1713 1713 segmentforrevs = r._getsegmentforrevs
1714 1714 except AttributeError:
1715 1715 segmentforrevs = r._chunkraw
1716 1716
1717 1717 node = r.lookup(rev)
1718 1718 rev = r.rev(node)
1719 1719
1720 1720 def getrawchunks(data, chain):
1721 1721 start = r.start
1722 1722 length = r.length
1723 1723 inline = r._inline
1724 1724 iosize = r._io.size
1725 1725 buffer = util.buffer
1726 offset = start(chain[0])
1727 1726
1728 1727 chunks = []
1729 1728 ladd = chunks.append
1730
1731 for rev in chain:
1732 chunkstart = start(rev)
1733 if inline:
1734 chunkstart += (rev + 1) * iosize
1735 chunklength = length(rev)
1736 ladd(buffer(data, chunkstart - offset, chunklength))
1729 for idx, item in enumerate(chain):
1730 offset = start(item[0])
1731 bits = data[idx]
1732 for rev in item:
1733 chunkstart = start(rev)
1734 if inline:
1735 chunkstart += (rev + 1) * iosize
1736 chunklength = length(rev)
1737 ladd(buffer(bits, chunkstart - offset, chunklength))
1737 1738
1738 1739 return chunks
1739 1740
1740 1741 def dodeltachain(rev):
1741 1742 if not cache:
1742 1743 r.clearcaches()
1743 1744 r._deltachain(rev)
1744 1745
1745 1746 def doread(chain):
1746 1747 if not cache:
1747 1748 r.clearcaches()
1748 segmentforrevs(chain[0], chain[-1])
1749 for item in slicedchain:
1750 segmentforrevs(item[0], item[-1])
1749 1751
1750 1752 def dorawchunks(data, chain):
1751 1753 if not cache:
1752 1754 r.clearcaches()
1753 1755 getrawchunks(data, chain)
1754 1756
1755 1757 def dodecompress(chunks):
1756 1758 decomp = r.decompress
1757 1759 for chunk in chunks:
1758 1760 decomp(chunk)
1759 1761
1760 1762 def dopatch(text, bins):
1761 1763 if not cache:
1762 1764 r.clearcaches()
1763 1765 mdiff.patches(text, bins)
1764 1766
1765 1767 def dohash(text):
1766 1768 if not cache:
1767 1769 r.clearcaches()
1768 1770 r.checkhash(text, node, rev=rev)
1769 1771
1770 1772 def dorevision():
1771 1773 if not cache:
1772 1774 r.clearcaches()
1773 1775 r.revision(node)
1774 1776
1777 try:
1778 from mercurial.revlogutils.deltas import slicechunk
1779 except ImportError:
1780 slicechunk = getattr(revlog, '_slicechunk', None)
1781
1782 size = r.length(rev)
1775 1783 chain = r._deltachain(rev)[0]
1776 data = segmentforrevs(chain[0], chain[-1])[1]
1777 rawchunks = getrawchunks(data, chain)
1784 if not getattr(r, '_withsparseread', False):
1785 slicedchain = (chain,)
1786 else:
1787 slicedchain = tuple(slicechunk(r, chain, targetsize=size))
1788 data = [segmentforrevs(seg[0], seg[-1])[1] for seg in slicedchain]
1789 rawchunks = getrawchunks(data, slicedchain)
1778 1790 bins = r._chunks(chain)
1779 1791 text = bytes(bins[0])
1780 1792 bins = bins[1:]
1781 1793 text = mdiff.patches(text, bins)
1782 1794
1783 1795 benches = [
1784 1796 (lambda: dorevision(), b'full'),
1785 1797 (lambda: dodeltachain(rev), b'deltachain'),
1786 1798 (lambda: doread(chain), b'read'),
1787 (lambda: dorawchunks(data, chain), b'rawchunks'),
1799 (lambda: dorawchunks(data, slicedchain), b'rawchunks'),
1788 1800 (lambda: dodecompress(rawchunks), b'decompress'),
1789 1801 (lambda: dopatch(text, bins), b'patch'),
1790 1802 (lambda: dohash(text), b'hash'),
1791 1803 ]
1792 1804
1793 1805 timer, fm = gettimer(ui, opts)
1794 1806 for fn, title in benches:
1795 1807 timer(fn, title=title)
1796 1808 fm.end()
1797 1809
1798 1810 @command(b'perfrevset',
1799 1811 [(b'C', b'clear', False, b'clear volatile cache between each call.'),
1800 1812 (b'', b'contexts', False, b'obtain changectx for each revision')]
1801 1813 + formatteropts, b"REVSET")
1802 1814 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
1803 1815 """benchmark the execution time of a revset
1804 1816
1805 1817 Use the --clean option if need to evaluate the impact of build volatile
1806 1818 revisions set cache on the revset execution. Volatile cache hold filtered
1807 1819 and obsolete related cache."""
1808 1820 opts = _byteskwargs(opts)
1809 1821
1810 1822 timer, fm = gettimer(ui, opts)
1811 1823 def d():
1812 1824 if clear:
1813 1825 repo.invalidatevolatilesets()
1814 1826 if contexts:
1815 1827 for ctx in repo.set(expr): pass
1816 1828 else:
1817 1829 for r in repo.revs(expr): pass
1818 1830 timer(d)
1819 1831 fm.end()
1820 1832
1821 1833 @command(b'perfvolatilesets',
1822 1834 [(b'', b'clear-obsstore', False, b'drop obsstore between each call.'),
1823 1835 ] + formatteropts)
1824 1836 def perfvolatilesets(ui, repo, *names, **opts):
1825 1837 """benchmark the computation of various volatile set
1826 1838
1827 1839 Volatile set computes element related to filtering and obsolescence."""
1828 1840 opts = _byteskwargs(opts)
1829 1841 timer, fm = gettimer(ui, opts)
1830 1842 repo = repo.unfiltered()
1831 1843
1832 1844 def getobs(name):
1833 1845 def d():
1834 1846 repo.invalidatevolatilesets()
1835 1847 if opts[b'clear_obsstore']:
1836 1848 clearfilecache(repo, b'obsstore')
1837 1849 obsolete.getrevs(repo, name)
1838 1850 return d
1839 1851
1840 1852 allobs = sorted(obsolete.cachefuncs)
1841 1853 if names:
1842 1854 allobs = [n for n in allobs if n in names]
1843 1855
1844 1856 for name in allobs:
1845 1857 timer(getobs(name), title=name)
1846 1858
1847 1859 def getfiltered(name):
1848 1860 def d():
1849 1861 repo.invalidatevolatilesets()
1850 1862 if opts[b'clear_obsstore']:
1851 1863 clearfilecache(repo, b'obsstore')
1852 1864 repoview.filterrevs(repo, name)
1853 1865 return d
1854 1866
1855 1867 allfilter = sorted(repoview.filtertable)
1856 1868 if names:
1857 1869 allfilter = [n for n in allfilter if n in names]
1858 1870
1859 1871 for name in allfilter:
1860 1872 timer(getfiltered(name), title=name)
1861 1873 fm.end()
1862 1874
1863 1875 @command(b'perfbranchmap',
1864 1876 [(b'f', b'full', False,
1865 1877 b'Includes build time of subset'),
1866 1878 (b'', b'clear-revbranch', False,
1867 1879 b'purge the revbranch cache between computation'),
1868 1880 ] + formatteropts)
1869 1881 def perfbranchmap(ui, repo, *filternames, **opts):
1870 1882 """benchmark the update of a branchmap
1871 1883
1872 1884 This benchmarks the full repo.branchmap() call with read and write disabled
1873 1885 """
1874 1886 opts = _byteskwargs(opts)
1875 1887 full = opts.get(b"full", False)
1876 1888 clear_revbranch = opts.get(b"clear_revbranch", False)
1877 1889 timer, fm = gettimer(ui, opts)
1878 1890 def getbranchmap(filtername):
1879 1891 """generate a benchmark function for the filtername"""
1880 1892 if filtername is None:
1881 1893 view = repo
1882 1894 else:
1883 1895 view = repo.filtered(filtername)
1884 1896 def d():
1885 1897 if clear_revbranch:
1886 1898 repo.revbranchcache()._clear()
1887 1899 if full:
1888 1900 view._branchcaches.clear()
1889 1901 else:
1890 1902 view._branchcaches.pop(filtername, None)
1891 1903 view.branchmap()
1892 1904 return d
1893 1905 # add filter in smaller subset to bigger subset
1894 1906 possiblefilters = set(repoview.filtertable)
1895 1907 if filternames:
1896 1908 possiblefilters &= set(filternames)
1897 1909 subsettable = getbranchmapsubsettable()
1898 1910 allfilters = []
1899 1911 while possiblefilters:
1900 1912 for name in possiblefilters:
1901 1913 subset = subsettable.get(name)
1902 1914 if subset not in possiblefilters:
1903 1915 break
1904 1916 else:
1905 1917 assert False, b'subset cycle %s!' % possiblefilters
1906 1918 allfilters.append(name)
1907 1919 possiblefilters.remove(name)
1908 1920
1909 1921 # warm the cache
1910 1922 if not full:
1911 1923 for name in allfilters:
1912 1924 repo.filtered(name).branchmap()
1913 1925 if not filternames or b'unfiltered' in filternames:
1914 1926 # add unfiltered
1915 1927 allfilters.append(None)
1916 1928
1917 1929 branchcacheread = safeattrsetter(branchmap, b'read')
1918 1930 branchcachewrite = safeattrsetter(branchmap.branchcache, b'write')
1919 1931 branchcacheread.set(lambda repo: None)
1920 1932 branchcachewrite.set(lambda bc, repo: None)
1921 1933 try:
1922 1934 for name in allfilters:
1923 1935 printname = name
1924 1936 if name is None:
1925 1937 printname = b'unfiltered'
1926 1938 timer(getbranchmap(name), title=str(printname))
1927 1939 finally:
1928 1940 branchcacheread.restore()
1929 1941 branchcachewrite.restore()
1930 1942 fm.end()
1931 1943
1932 1944 @command(b'perfbranchmapload', [
1933 1945 (b'f', b'filter', b'', b'Specify repoview filter'),
1934 1946 (b'', b'list', False, b'List brachmap filter caches'),
1935 1947 ] + formatteropts)
1936 1948 def perfbranchmapread(ui, repo, filter=b'', list=False, **opts):
1937 1949 """benchmark reading the branchmap"""
1938 1950 opts = _byteskwargs(opts)
1939 1951
1940 1952 if list:
1941 1953 for name, kind, st in repo.cachevfs.readdir(stat=True):
1942 1954 if name.startswith(b'branch2'):
1943 1955 filtername = name.partition(b'-')[2] or b'unfiltered'
1944 1956 ui.status(b'%s - %s\n'
1945 1957 % (filtername, util.bytecount(st.st_size)))
1946 1958 return
1947 1959 if filter:
1948 1960 repo = repoview.repoview(repo, filter)
1949 1961 else:
1950 1962 repo = repo.unfiltered()
1951 1963 # try once without timer, the filter may not be cached
1952 1964 if branchmap.read(repo) is None:
1953 1965 raise error.Abort(b'No brachmap cached for %s repo'
1954 1966 % (filter or b'unfiltered'))
1955 1967 timer, fm = gettimer(ui, opts)
1956 1968 timer(lambda: branchmap.read(repo) and None)
1957 1969 fm.end()
1958 1970
1959 1971 @command(b'perfloadmarkers')
1960 1972 def perfloadmarkers(ui, repo):
1961 1973 """benchmark the time to parse the on-disk markers for a repo
1962 1974
1963 1975 Result is the number of markers in the repo."""
1964 1976 timer, fm = gettimer(ui)
1965 1977 svfs = getsvfs(repo)
1966 1978 timer(lambda: len(obsolete.obsstore(svfs)))
1967 1979 fm.end()
1968 1980
1969 1981 @command(b'perflrucachedict', formatteropts +
1970 1982 [(b'', b'costlimit', 0, b'maximum total cost of items in cache'),
1971 1983 (b'', b'mincost', 0, b'smallest cost of items in cache'),
1972 1984 (b'', b'maxcost', 100, b'maximum cost of items in cache'),
1973 1985 (b'', b'size', 4, b'size of cache'),
1974 1986 (b'', b'gets', 10000, b'number of key lookups'),
1975 1987 (b'', b'sets', 10000, b'number of key sets'),
1976 1988 (b'', b'mixed', 10000, b'number of mixed mode operations'),
1977 1989 (b'', b'mixedgetfreq', 50, b'frequency of get vs set ops in mixed mode')],
1978 1990 norepo=True)
1979 1991 def perflrucache(ui, mincost=0, maxcost=100, costlimit=0, size=4,
1980 1992 gets=10000, sets=10000, mixed=10000, mixedgetfreq=50, **opts):
1981 1993 opts = _byteskwargs(opts)
1982 1994
1983 1995 def doinit():
1984 1996 for i in _xrange(10000):
1985 1997 util.lrucachedict(size)
1986 1998
1987 1999 costrange = list(range(mincost, maxcost + 1))
1988 2000
1989 2001 values = []
1990 2002 for i in _xrange(size):
1991 2003 values.append(random.randint(0, _maxint))
1992 2004
1993 2005 # Get mode fills the cache and tests raw lookup performance with no
1994 2006 # eviction.
1995 2007 getseq = []
1996 2008 for i in _xrange(gets):
1997 2009 getseq.append(random.choice(values))
1998 2010
1999 2011 def dogets():
2000 2012 d = util.lrucachedict(size)
2001 2013 for v in values:
2002 2014 d[v] = v
2003 2015 for key in getseq:
2004 2016 value = d[key]
2005 2017 value # silence pyflakes warning
2006 2018
2007 2019 def dogetscost():
2008 2020 d = util.lrucachedict(size, maxcost=costlimit)
2009 2021 for i, v in enumerate(values):
2010 2022 d.insert(v, v, cost=costs[i])
2011 2023 for key in getseq:
2012 2024 try:
2013 2025 value = d[key]
2014 2026 value # silence pyflakes warning
2015 2027 except KeyError:
2016 2028 pass
2017 2029
2018 2030 # Set mode tests insertion speed with cache eviction.
2019 2031 setseq = []
2020 2032 costs = []
2021 2033 for i in _xrange(sets):
2022 2034 setseq.append(random.randint(0, _maxint))
2023 2035 costs.append(random.choice(costrange))
2024 2036
2025 2037 def doinserts():
2026 2038 d = util.lrucachedict(size)
2027 2039 for v in setseq:
2028 2040 d.insert(v, v)
2029 2041
2030 2042 def doinsertscost():
2031 2043 d = util.lrucachedict(size, maxcost=costlimit)
2032 2044 for i, v in enumerate(setseq):
2033 2045 d.insert(v, v, cost=costs[i])
2034 2046
2035 2047 def dosets():
2036 2048 d = util.lrucachedict(size)
2037 2049 for v in setseq:
2038 2050 d[v] = v
2039 2051
2040 2052 # Mixed mode randomly performs gets and sets with eviction.
2041 2053 mixedops = []
2042 2054 for i in _xrange(mixed):
2043 2055 r = random.randint(0, 100)
2044 2056 if r < mixedgetfreq:
2045 2057 op = 0
2046 2058 else:
2047 2059 op = 1
2048 2060
2049 2061 mixedops.append((op,
2050 2062 random.randint(0, size * 2),
2051 2063 random.choice(costrange)))
2052 2064
2053 2065 def domixed():
2054 2066 d = util.lrucachedict(size)
2055 2067
2056 2068 for op, v, cost in mixedops:
2057 2069 if op == 0:
2058 2070 try:
2059 2071 d[v]
2060 2072 except KeyError:
2061 2073 pass
2062 2074 else:
2063 2075 d[v] = v
2064 2076
2065 2077 def domixedcost():
2066 2078 d = util.lrucachedict(size, maxcost=costlimit)
2067 2079
2068 2080 for op, v, cost in mixedops:
2069 2081 if op == 0:
2070 2082 try:
2071 2083 d[v]
2072 2084 except KeyError:
2073 2085 pass
2074 2086 else:
2075 2087 d.insert(v, v, cost=cost)
2076 2088
2077 2089 benches = [
2078 2090 (doinit, b'init'),
2079 2091 ]
2080 2092
2081 2093 if costlimit:
2082 2094 benches.extend([
2083 2095 (dogetscost, b'gets w/ cost limit'),
2084 2096 (doinsertscost, b'inserts w/ cost limit'),
2085 2097 (domixedcost, b'mixed w/ cost limit'),
2086 2098 ])
2087 2099 else:
2088 2100 benches.extend([
2089 2101 (dogets, b'gets'),
2090 2102 (doinserts, b'inserts'),
2091 2103 (dosets, b'sets'),
2092 2104 (domixed, b'mixed')
2093 2105 ])
2094 2106
2095 2107 for fn, title in benches:
2096 2108 timer, fm = gettimer(ui, opts)
2097 2109 timer(fn, title=title)
2098 2110 fm.end()
2099 2111
2100 2112 @command(b'perfwrite', formatteropts)
2101 2113 def perfwrite(ui, repo, **opts):
2102 2114 """microbenchmark ui.write
2103 2115 """
2104 2116 opts = _byteskwargs(opts)
2105 2117
2106 2118 timer, fm = gettimer(ui, opts)
2107 2119 def write():
2108 2120 for i in range(100000):
2109 2121 ui.write((b'Testing write performance\n'))
2110 2122 timer(write)
2111 2123 fm.end()
2112 2124
2113 2125 def uisetup(ui):
2114 2126 if (util.safehasattr(cmdutil, b'openrevlog') and
2115 2127 not util.safehasattr(commands, b'debugrevlogopts')):
2116 2128 # for "historical portability":
2117 2129 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
2118 2130 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
2119 2131 # openrevlog() should cause failure, because it has been
2120 2132 # available since 3.5 (or 49c583ca48c4).
2121 2133 def openrevlog(orig, repo, cmd, file_, opts):
2122 2134 if opts.get(b'dir') and not util.safehasattr(repo, b'dirlog'):
2123 2135 raise error.Abort(b"This version doesn't support --dir option",
2124 2136 hint=b"use 3.5 or later")
2125 2137 return orig(repo, cmd, file_, opts)
2126 2138 extensions.wrapfunction(cmdutil, b'openrevlog', openrevlog)
General Comments 0
You need to be logged in to leave comments. Login now