perf.py 16.8 KB
Newer Older
1
# perf.py - performance test routines
2
'''helper extension to measure performance'''
3

4
from mercurial import cmdutil, scmutil, util, commands, obsolete
5
from mercurial import repoview, branchmap, merge, copies, error
6
import time, os, sys
7
import functools
8

9
10
formatteropts = commands.formatteropts

11
12
13
cmdtable = {}
command = cmdutil.command(cmdtable)

14
15
16
17
18
def gettimer(ui, opts=None):
    """return a timer function and formatter: (timer, formatter)

    This functions exist to gather the creation of formatter in a single
    place instead of duplicating it in all performance command."""
19
20

    # enforce an idle period before execution to counteract power management
21
    # experimental config: perf.presleep
22
23
    time.sleep(ui.configint("perf", "presleep", 1))

24
25
26
27
28
29
30
31
32
33
    if opts is None:
        opts = {}
    # redirect all to stderr
    ui = ui.copy()
    ui.fout = ui.ferr
    # get a formatter
    fm = ui.formatter('perf', opts)
    return functools.partial(_timer, fm), fm

def _timer(fm, func, title=None):
34
35
36
    results = []
    begin = time.time()
    count = 0
37
    while True:
38
39
40
41
42
43
44
45
46
47
48
49
        ostart = os.times()
        cstart = time.time()
        r = func()
        cstop = time.time()
        ostop = os.times()
        count += 1
        a, b = ostart, ostop
        results.append((cstop - cstart, b[0] - a[0], b[1]-a[1]))
        if cstop - begin > 3 and count >= 100:
            break
        if cstop - begin > 10 and count >= 3:
            break
50
51
52

    fm.startitem()

53
    if title:
54
        fm.write('title', '! %s\n', title)
55
    if r:
56
        fm.write('result', '! result: %s\n', r)
57
    m = min(results)
58
59
60
61
62
63
64
    fm.plain('!')
    fm.write('wall', ' wall %f', m[0])
    fm.write('comb', ' comb %f', m[1] + m[2])
    fm.write('user', ' user %f', m[1])
    fm.write('sys',  ' sys %f', m[2])
    fm.write('count',  ' (best of %d)', count)
    fm.plain('\n')
65

66
67
68
@command('perfwalk', formatteropts)
def perfwalk(ui, repo, *pats, **opts):
    timer, fm = gettimer(ui, opts)
69
    try:
70
        m = scmutil.match(repo[None], pats, {})
71
        timer(lambda: len(list(repo.dirstate.walk(m, [], True, False))))
72
    except Exception:
73
        try:
74
            m = scmutil.match(repo[None], pats, {})
75
            timer(lambda: len([b for a, b, c in repo.dirstate.statwalk([], m)]))
76
        except Exception:
77
            timer(lambda: len(list(cmdutil.walk(repo, pats, {}))))
78
    fm.end()
79

80
81
82
@command('perfannotate', formatteropts)
def perfannotate(ui, repo, f, **opts):
    timer, fm = gettimer(ui, opts)
83
84
    fc = repo['.'][f]
    timer(lambda: len(fc.annotate(True)))
85
    fm.end()
86

87
88
@command('perfstatus',
         [('u', 'unknown', False,
89
           'ask status to look for unknown files')] + formatteropts)
90
def perfstatus(ui, repo, **opts):
91
    #m = match.always(repo.root, repo.getcwd())
Brodie Rao's avatar
Brodie Rao committed
92
93
    #timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
    #                                                False))))
Matt Mackall's avatar
Matt Mackall committed
94
    timer, fm = gettimer(ui, opts)
95
    timer(lambda: sum(map(len, repo.status(unknown=opts['unknown']))))
96
    fm.end()
97

98
99
100
@command('perfaddremove', formatteropts)
def perfaddremove(ui, repo, **opts):
    timer, fm = gettimer(ui, opts)
101
102
103
    try:
        oldquiet = repo.ui.quiet
        repo.ui.quiet = True
104
        matcher = scmutil.match(repo[None])
105
        timer(lambda: scmutil.addremove(repo, matcher, "", dry_run=True))
106
107
    finally:
        repo.ui.quiet = oldquiet
108
        fm.end()
109

110
111
112
113
114
115
116
117
118
def clearcaches(cl):
    # behave somewhat consistently across internal API changes
    if util.safehasattr(cl, 'clearcaches'):
        cl.clearcaches()
    elif util.safehasattr(cl, '_nodecache'):
        from mercurial.node import nullid, nullrev
        cl._nodecache = {nullid: nullrev}
        cl._nodepos = None

119
120
121
@command('perfheads', formatteropts)
def perfheads(ui, repo, **opts):
    timer, fm = gettimer(ui, opts)
122
123
124
125
126
    cl = repo.changelog
    def d():
        len(cl.headrevs())
        clearcaches(cl)
    timer(d)
127
    fm.end()
128

129
130
@command('perftags', formatteropts)
def perftags(ui, repo, **opts):
131
132
    import mercurial.changelog
    import mercurial.manifest
133
    timer, fm = gettimer(ui, opts)
134
    def t():
135
136
        repo.changelog = mercurial.changelog.changelog(repo.svfs)
        repo.manifest = mercurial.manifest.manifest(repo.svfs)
137
        repo._tags = None
138
139
        return len(repo.tags())
    timer(t)
140
    fm.end()
141

142
143
144
@command('perfancestors', formatteropts)
def perfancestors(ui, repo, **opts):
    timer, fm = gettimer(ui, opts)
145
146
    heads = repo.changelog.headrevs()
    def d():
147
        for a in repo.changelog.ancestors(heads):
148
149
            pass
    timer(d)
150
    fm.end()
151

152
153
154
@command('perfancestorset', formatteropts)
def perfancestorset(ui, repo, revset, **opts):
    timer, fm = gettimer(ui, opts)
155
156
157
    revs = repo.revs(revset)
    heads = repo.changelog.headrevs()
    def d():
158
        s = repo.changelog.ancestors(heads)
159
160
161
        for rev in revs:
            rev in s
    timer(d)
162
    fm.end()
163

164
165
166
@command('perfdirs', formatteropts)
def perfdirs(ui, repo, **opts):
    timer, fm = gettimer(ui, opts)
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
167
168
169
170
171
172
    dirstate = repo.dirstate
    'a' in dirstate
    def d():
        dirstate.dirs()
        del dirstate._dirs
    timer(d)
173
    fm.end()
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
174

175
176
177
@command('perfdirstate', formatteropts)
def perfdirstate(ui, repo, **opts):
    timer, fm = gettimer(ui, opts)
178
179
180
181
182
    "a" in repo.dirstate
    def d():
        repo.dirstate.invalidate()
        "a" in repo.dirstate
    timer(d)
183
    fm.end()
184

185
186
187
@command('perfdirstatedirs', formatteropts)
def perfdirstatedirs(ui, repo, **opts):
    timer, fm = gettimer(ui, opts)
188
189
190
191
192
    "a" in repo.dirstate
    def d():
        "a" in repo.dirstate._dirs
        del repo.dirstate._dirs
    timer(d)
193
    fm.end()
194

195
@command('perfdirstatefoldmap', formatteropts)
196
def perfdirstatefoldmap(ui, repo, **opts):
197
    timer, fm = gettimer(ui, opts)
198
199
200
    dirstate = repo.dirstate
    'a' in dirstate
    def d():
201
202
203
204
205
        dirstate._filefoldmap.get('a')
        del dirstate._filefoldmap
    timer(d)
    fm.end()

206
207
208
@command('perfdirfoldmap', formatteropts)
def perfdirfoldmap(ui, repo, **opts):
    timer, fm = gettimer(ui, opts)
209
210
211
212
213
    dirstate = repo.dirstate
    'a' in dirstate
    def d():
        dirstate._dirfoldmap.get('a')
        del dirstate._dirfoldmap
214
215
        del dirstate._dirs
    timer(d)
216
    fm.end()
217

218
219
220
@command('perfdirstatewrite', formatteropts)
def perfdirstatewrite(ui, repo, **opts):
    timer, fm = gettimer(ui, opts)
221
222
223
224
    ds = repo.dirstate
    "a" in ds
    def d():
        ds._dirty = True
225
        ds.write(repo.currenttransaction())
226
    timer(d)
227
    fm.end()
228

229
@command('perfmergecalculate',
230
231
232
         [('r', 'rev', '.', 'rev to merge against')] + formatteropts)
def perfmergecalculate(ui, repo, rev, **opts):
    timer, fm = gettimer(ui, opts)
233
234
235
236
237
238
239
240
241
    wctx = repo[None]
    rctx = scmutil.revsingle(repo, rev, rev)
    ancestor = wctx.ancestor(rctx)
    # we don't want working dir files to be stat'd in the benchmark, so prime
    # that cache
    wctx.dirty()
    def d():
        # acceptremote is True because we don't want prompts in the middle of
        # our benchmark
242
243
        merge.calculateupdates(repo, wctx, rctx, [ancestor], False, False,
                               False, acceptremote=True, followcopies=True)
244
    timer(d)
245
    fm.end()
246

247
@command('perfpathcopies', [], "REV REV")
248
249
def perfpathcopies(ui, repo, rev1, rev2, **opts):
    timer, fm = gettimer(ui, opts)
250
251
252
253
254
    ctx1 = scmutil.revsingle(repo, rev1, rev1)
    ctx2 = scmutil.revsingle(repo, rev2, rev2)
    def d():
        copies.pathcopies(ctx1, ctx2)
    timer(d)
255
    fm.end()
256

257
@command('perfmanifest', [], 'REV')
258
259
def perfmanifest(ui, repo, rev, **opts):
    timer, fm = gettimer(ui, opts)
260
261
    ctx = scmutil.revsingle(repo, rev, rev)
    t = ctx.manifestnode()
262
    def d():
263
264
        repo.manifest._mancache.clear()
        repo.manifest._cache = None
265
        repo.manifest.read(t)
266
    timer(d)
267
    fm.end()
268

269
270
271
@command('perfchangeset', formatteropts)
def perfchangeset(ui, repo, rev, **opts):
    timer, fm = gettimer(ui, opts)
272
273
    n = repo[rev].node()
    def d():
274
        repo.changelog.read(n)
Matt Mackall's avatar
Matt Mackall committed
275
        #repo.changelog._cache = None
276
    timer(d)
277
    fm.end()
278

279
280
@command('perfindex', formatteropts)
def perfindex(ui, repo, **opts):
281
    import mercurial.revlog
282
    timer, fm = gettimer(ui, opts)
Matt Mackall's avatar
Matt Mackall committed
283
    mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
284
    n = repo["tip"].node()
285
    def d():
286
        cl = mercurial.revlog.revlog(repo.svfs, "00changelog.i")
287
        cl.rev(n)
288
    timer(d)
289
    fm.end()
290

291
292
293
@command('perfstartup', formatteropts)
def perfstartup(ui, repo, **opts):
    timer, fm = gettimer(ui, opts)
294
295
296
297
    cmd = sys.argv[0]
    def d():
        os.system("HGRCPATH= %s version -q > /dev/null" % cmd)
    timer(d)
298
    fm.end()
299

300
301
302
@command('perfparents', formatteropts)
def perfparents(ui, repo, **opts):
    timer, fm = gettimer(ui, opts)
303
304
    if len(repo.changelog) < 1000:
        raise error.Abort("repo needs 1000 commits for this test")
305
    repo = repo.unfiltered()
306
307
308
309
310
    nl = [repo.changelog.node(i) for i in xrange(1000)]
    def d():
        for n in nl:
            repo.changelog.parents(n)
    timer(d)
311
    fm.end()
312

313
@command('perfctxfiles', formatteropts)
314
def perfctxfiles(ui, repo, x, **opts):
315
    x = int(x)
316
    timer, fm = gettimer(ui, opts)
317
318
319
320
321
    def d():
        len(repo[x].files())
    timer(d)
    fm.end()

322
@command('perfrawfiles', formatteropts)
323
def perfrawfiles(ui, repo, x, **opts):
324
    x = int(x)
325
    timer, fm = gettimer(ui, opts)
326
327
328
329
330
331
    cl = repo.changelog
    def d():
        len(cl.read(x)[3])
    timer(d)
    fm.end()

332
333
334
@command('perflookup', formatteropts)
def perflookup(ui, repo, rev, **opts):
    timer, fm = gettimer(ui, opts)
335
    timer(lambda: len(repo.lookup(rev)))
336
    fm.end()
337

338
339
340
@command('perfrevrange', formatteropts)
def perfrevrange(ui, repo, *specs, **opts):
    timer, fm = gettimer(ui, opts)
341
342
    revrange = scmutil.revrange
    timer(lambda: len(revrange(repo, specs)))
343
    fm.end()
344

345
346
347
@command('perfnodelookup', formatteropts)
def perfnodelookup(ui, repo, rev, **opts):
    timer, fm = gettimer(ui, opts)
Matt Mackall's avatar
Matt Mackall committed
348
349
350
    import mercurial.revlog
    mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
    n = repo[rev].node()
351
    cl = mercurial.revlog.revlog(repo.svfs, "00changelog.i")
352
353
    def d():
        cl.rev(n)
354
        clearcaches(cl)
355
    timer(d)
356
    fm.end()
357

358
@command('perflog',
359
         [('', 'rename', False, 'ask log to follow renames')] + formatteropts)
360
def perflog(ui, repo, **opts):
361
    timer, fm = gettimer(ui, opts)
362
    ui.pushbuffer()
363
364
    timer(lambda: commands.log(ui, repo, rev=[], date='', user='',
                               copies=opts.get('rename')))
365
    ui.popbuffer()
366
    fm.end()
367

368
369
@command('perfmoonwalk', formatteropts)
def perfmoonwalk(ui, repo, **opts):
370
371
372
373
    """benchmark walking the changelog backwards

    This also loads the changelog data for each revision in the changelog.
    """
374
    timer, fm = gettimer(ui, opts)
375
376
377
378
379
    def moonwalk():
        for i in xrange(len(repo), -1, -1):
            ctx = repo[i]
            ctx.branch() # read changelog data (in addition to the index)
    timer(moonwalk)
380
    fm.end()
381

382
383
384
@command('perftemplating', formatteropts)
def perftemplating(ui, repo, **opts):
    timer, fm = gettimer(ui, opts)
385
386
387
388
389
    ui.pushbuffer()
    timer(lambda: commands.log(ui, repo, rev=[], date='', user='',
                               template='{date|shortdate} [{rev}:{node|short}]'
                               ' {author|person}: {desc|firstline}\n'))
    ui.popbuffer()
390
    fm.end()
391

392
393
394
@command('perfcca', formatteropts)
def perfcca(ui, repo, **opts):
    timer, fm = gettimer(ui, opts)
395
    timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
396
    fm.end()
397

398
399
400
@command('perffncacheload', formatteropts)
def perffncacheload(ui, repo, **opts):
    timer, fm = gettimer(ui, opts)
401
    s = repo.store
402
403
404
    def d():
        s.fncache._load()
    timer(d)
405
    fm.end()
406

407
408
409
@command('perffncachewrite', formatteropts)
def perffncachewrite(ui, repo, **opts):
    timer, fm = gettimer(ui, opts)
410
    s = repo.store
411
    s.fncache._load()
timeless's avatar
timeless committed
412
413
    lock = repo.lock()
    tr = repo.transaction('perffncachewrite')
414
415
    def d():
        s.fncache._dirty = True
timeless's avatar
timeless committed
416
        s.fncache.write(tr)
417
    timer(d)
timeless's avatar
timeless committed
418
    lock.release()
419
    fm.end()
420

421
422
423
@command('perffncacheencode', formatteropts)
def perffncacheencode(ui, repo, **opts):
    timer, fm = gettimer(ui, opts)
424
    s = repo.store
Adrian Buehlmann's avatar
Adrian Buehlmann committed
425
426
427
428
429
    s.fncache._load()
    def d():
        for p in s.fncache.entries:
            s.encode(p)
    timer(d)
430
    fm.end()
Adrian Buehlmann's avatar
Adrian Buehlmann committed
431

432
433
@command('perfdiffwd', formatteropts)
def perfdiffwd(ui, repo, **opts):
434
    """Profile diff of working directory changes"""
435
    timer, fm = gettimer(ui, opts)
436
437
438
439
440
441
442
443
444
445
446
447
448
449
    options = {
        'w': 'ignore_all_space',
        'b': 'ignore_space_change',
        'B': 'ignore_blank_lines',
        }

    for diffopt in ('', 'w', 'b', 'B', 'wB'):
        opts = dict((options[c], '1') for c in diffopt)
        def d():
            ui.pushbuffer()
            commands.diff(ui, repo, **opts)
            ui.popbuffer()
        title = 'diffopts: %s' % (diffopt and ('-' + diffopt) or 'none')
        timer(d, title)
450
    fm.end()
451

452
@command('perfrevlog',
453
         [('d', 'dist', 100, 'distance between the revisions')] + formatteropts,
454
         "[INDEXFILE]")
455
def perfrevlog(ui, repo, file_, **opts):
456
    timer, fm = gettimer(ui, opts)
457
458
459
460
461
462
463
464
    from mercurial import revlog
    dist = opts['dist']
    def d():
        r = revlog.revlog(lambda fn: open(fn, 'rb'), file_)
        for x in xrange(0, len(r), dist):
            r.revision(r.node(x))

    timer(d)
465
    fm.end()
466

467
@command('perfrevset',
468
469
         [('C', 'clear', False, 'clear volatile cache between each call.'),
          ('', 'contexts', False, 'obtain changectx for each revision')]
470
         + formatteropts, "REVSET")
471
def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
472
473
    """benchmark the execution time of a revset

474
    Use the --clean option if need to evaluate the impact of build volatile
475
476
    revisions set cache on the revset execution. Volatile cache hold filtered
    and obsolete related cache."""
477
    timer, fm = gettimer(ui, opts)
478
    def d():
479
480
        if clear:
            repo.invalidatevolatilesets()
481
482
483
484
        if contexts:
            for ctx in repo.set(expr): pass
        else:
            for r in repo.revs(expr): pass
485
    timer(d)
486
    fm.end()
487

488
489
@command('perfvolatilesets', formatteropts)
def perfvolatilesets(ui, repo, *names, **opts):
490
491
492
    """benchmark the computation of various volatile set

    Volatile set computes element related to filtering and obsolescence."""
493
    timer, fm = gettimer(ui, opts)
494
495
496
497
498
499
500
501
    repo = repo.unfiltered()

    def getobs(name):
        def d():
            repo.invalidatevolatilesets()
            obsolete.getrevs(repo, name)
        return d

502
503
504
505
506
    allobs = sorted(obsolete.cachefuncs)
    if names:
        allobs = [n for n in allobs if n in names]

    for name in allobs:
507
508
509
510
511
        timer(getobs(name), title=name)

    def getfiltered(name):
        def d():
            repo.invalidatevolatilesets()
Pierre-Yves David's avatar
Pierre-Yves David committed
512
            repoview.filterrevs(repo, name)
513
514
        return d

515
516
517
518
519
    allfilter = sorted(repoview.filtertable)
    if names:
        allfilter = [n for n in allfilter if n in names]

    for name in allfilter:
520
        timer(getfiltered(name), title=name)
521
    fm.end()
522
523
524
525

@command('perfbranchmap',
         [('f', 'full', False,
           'Includes build time of subset'),
526
527
         ] + formatteropts)
def perfbranchmap(ui, repo, full=False, **opts):
528
529
530
531
    """benchmark the update of a branchmap

    This benchmarks the full repo.branchmap() call with read and write disabled
    """
532
    timer, fm = gettimer(ui, opts)
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
    def getbranchmap(filtername):
        """generate a benchmark function for the filtername"""
        if filtername is None:
            view = repo
        else:
            view = repo.filtered(filtername)
        def d():
            if full:
                view._branchcaches.clear()
            else:
                view._branchcaches.pop(filtername, None)
            view.branchmap()
        return d
    # add filter in smaller subset to bigger subset
    possiblefilters = set(repoview.filtertable)
    allfilters = []
    while possiblefilters:
        for name in possiblefilters:
551
            subset = branchmap.subsettable.get(name)
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
            if subset not in possiblefilters:
                break
        else:
            assert False, 'subset cycle %s!' % possiblefilters
        allfilters.append(name)
        possiblefilters.remove(name)

    # warm the cache
    if not full:
        for name in allfilters:
            repo.filtered(name).branchmap()
    # add unfiltered
    allfilters.append(None)
    oldread = branchmap.read
    oldwrite = branchmap.branchcache.write
    try:
        branchmap.read = lambda repo: None
        branchmap.write = lambda repo: None
        for name in allfilters:
            timer(getbranchmap(name), title=str(name))
    finally:
        branchmap.read = oldread
        branchmap.branchcache.write = oldwrite
575
    fm.end()
576
577
578
579
580
581
582

@command('perfloadmarkers')
def perfloadmarkers(ui, repo):
    """benchmark the time to parse the on-disk markers for a repo

    Result is the number of markers in the repo."""
    timer, fm = gettimer(ui)
583
    timer(lambda: len(obsolete.obsstore(repo.svfs)))
584
    fm.end()