contrib: add "hgperf" command to measure performance of commands easily
Newly added "hgperf" command repeats "dispatch.runcommand()" for
specified Mercurial command and measure performance of it in the same
manner of "contrib/perf.py" extension.
Users (mainly developers) can examine performance of the target
command easily, without adding new entry for it to "contrib/perf.py"
extension.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/hgperf Sat Feb 15 19:51:20 2014 +0900
@@ -0,0 +1,100 @@
+#!/usr/bin/env python
+#
+# hgperf - measure performance of Mercurial commands
+#
+# Copyright 2014 Matt Mackall <mpm@selenic.com>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+'''measure performance of Mercurial commands
+
+Using ``hgperf`` instead of ``hg`` measures performance of the target
+Mercurial command. For example, the execution below measures
+performance of :hg:`heads --topo`::
+
+ $ hgperf heads --topo
+
+All command output via ``ui`` is suppressed, and just measurement
+result is displayed: see also "perf" extension in "contrib".
+
+Costs of processing before dispatching to the command function like
+below are not measured::
+
+ - parsing command line (e.g. option validity check)
+ - reading configuration files in
+
+But ``pre-`` and ``post-`` hook invocation for the target command is
+measured, even though these are invoked before or after dispatching to
+the command function, because these may be required to repeat
+execution of the target command correctly.
+'''
+
+import os
+import sys
+
+libdir = '@LIBDIR@'
+
+if libdir != '@' 'LIBDIR' '@':
+ if not os.path.isabs(libdir):
+ libdir = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+ libdir)
+ libdir = os.path.abspath(libdir)
+ sys.path.insert(0, libdir)
+
+# enable importing on demand to reduce startup time
+try:
+ from mercurial import demandimport; demandimport.enable()
+except ImportError:
+ import sys
+ sys.stderr.write("abort: couldn't find mercurial libraries in [%s]\n" %
+ ' '.join(sys.path))
+ sys.stderr.write("(check your install and PYTHONPATH)\n")
+ sys.exit(-1)
+
+import mercurial.util
+import mercurial.dispatch
+
+import time
+
+def timer(func, title=None):
+ results = []
+ begin = time.time()
+ count = 0
+ while True:
+ 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
+ if title:
+ sys.stderr.write("! %s\n" % title)
+ if r:
+ sys.stderr.write("! result: %s\n" % r)
+ m = min(results)
+ sys.stderr.write("! wall %f comb %f user %f sys %f (best of %d)\n"
+ % (m[0], m[1] + m[2], m[1], m[2], count))
+
+orgruncommand = mercurial.dispatch.runcommand
+
+def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
+ ui.pushbuffer()
+ lui.pushbuffer()
+ timer(lambda : orgruncommand(lui, repo, cmd, fullargs, ui,
+ options, d, cmdpats, cmdoptions))
+ ui.popbuffer()
+ lui.popbuffer()
+
+mercurial.dispatch.runcommand = runcommand
+
+for fp in (sys.stdin, sys.stdout, sys.stderr):
+ mercurial.util.setbinary(fp)
+
+mercurial.dispatch.run()