changeset 42360:3293086ff663

perf: add an option to profile the benchmark section Running a perf command with --profile gather data for the whole command execution, including setup and cleanup. This can significantly alter the data. To work around this we introduce a new option, it trigger the profiling of only one iteration of the benchmarked section.
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Tue, 21 May 2019 15:26:48 +0200
parents 563cd9a72682
children c4b8f8637d7a
files contrib/perf.py tests/test-contrib-perf.t
diffstat 2 files changed, 43 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/contrib/perf.py	Tue May 21 15:08:06 2019 +0200
+++ b/contrib/perf.py	Tue May 21 15:26:48 2019 +0200
@@ -18,6 +18,10 @@
 ``pre-run``
   number of run to perform before starting measurement.
 
+``profile-benchmark``
+  Enable profiling for the benchmarked section.
+  (The first iteration is benchmarked)
+
 ``run-limits``
   Control the number of runs each benchmark will perform. The option value
   should be a list of `<time>-<numberofrun>` pairs. After each run the
@@ -109,6 +113,10 @@
 except ImportError:
     pass
 
+try:
+    from mercurial import profiling
+except ImportError:
+    profiling = None
 
 def identity(a):
     return a
@@ -246,6 +254,9 @@
     configitem(b'perf', b'pre-run',
         default=mercurial.configitems.dynamicdefault,
     )
+    configitem(b'perf', b'profile-benchmark',
+        default=mercurial.configitems.dynamicdefault,
+    )
     configitem(b'perf', b'run-limits',
         default=mercurial.configitems.dynamicdefault,
     )
@@ -257,6 +268,13 @@
         return lambda x: 1
     return len
 
+class noop(object):
+    """dummy context manager"""
+    def __enter__(self):
+        pass
+    def __exit__(self, *args):
+        pass
+
 def gettimer(ui, opts=None):
     """return a timer function and formatter: (timer, formatter)
 
@@ -347,9 +365,14 @@
     if not limits:
         limits = DEFAULTLIMITS
 
+    profiler = None
+    if profiling is not None:
+        if ui.configbool(b"perf", b"profile-benchmark", False):
+            profiler = profiling.profile(ui)
+
     prerun = getint(ui, b"perf", b"pre-run", 0)
     t = functools.partial(_timer, fm, displayall=displayall, limits=limits,
-                          prerun=prerun)
+                          prerun=prerun, profiler=profiler)
     return t, fm
 
 def stub_timer(fm, func, setup=None, title=None):
@@ -376,11 +399,13 @@
 )
 
 def _timer(fm, func, setup=None, title=None, displayall=False,
-           limits=DEFAULTLIMITS, prerun=0):
+           limits=DEFAULTLIMITS, prerun=0, profiler=None):
     gc.collect()
     results = []
     begin = util.timer()
     count = 0
+    if profiler is None:
+        profiler = noop()
     for i in xrange(prerun):
         if setup is not None:
             setup()
@@ -389,8 +414,9 @@
     while keepgoing:
         if setup is not None:
             setup()
-        with timeone() as item:
-            r = func()
+        with profiler:
+            with timeone() as item:
+                r = func()
         count += 1
         results.append(item[0])
         cstop = util.timer()
--- a/tests/test-contrib-perf.t	Tue May 21 15:08:06 2019 +0200
+++ b/tests/test-contrib-perf.t	Tue May 21 15:26:48 2019 +0200
@@ -58,6 +58,10 @@
   "pre-run"
     number of run to perform before starting measurement.
   
+  "profile-benchmark"
+    Enable profiling for the benchmarked section. (The first iteration is
+    benchmarked)
+  
   "run-limits"
     Control the number of runs each benchmark will perform. The option value
     should be a list of '<time>-<numberofrun>' pairs. After each run the
@@ -349,6 +353,15 @@
   searching for changes
   searching for changes
 
+test  profile-benchmark option
+------------------------------
+
+Function to check that statprof ran
+  $ statprofran () {
+  >   egrep 'Sample count:|No samples recorded' > /dev/null
+  > }
+  $ hg perfdiscovery . --config perf.stub=no --config perf.run-limits='0.000000001-1' --config perf.profile-benchmark=yes 2>&1 | statprofran
+
 Check perf.py for historical portability
 ----------------------------------------