Patchwork [7,of,9,RFC] statprof: support stacked collection

login
register
mail settings
Submitter Gregory Szorc
Date Aug. 16, 2016, 5:25 a.m.
Message ID <2e162a802b1f2bbcf60e.1471325114@ubuntu-vm-main>
Download mbox | patch
Permalink /patch/16312/
State Superseded
Headers show

Comments

Gregory Szorc - Aug. 16, 2016, 5:25 a.m.
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1471230964 25200
#      Sun Aug 14 20:16:04 2016 -0700
# Node ID 2e162a802b1f2bbcf60e29720dae1337707d842a
# Parent  d23f7839f565d1cb9c3cfa85005dade84f41a2f4
statprof: support stacked collection

Before, statprof theoretically supported starting and stopping
profiling multiple times. But it didn't actually do anything
upon each "stacked" call to start()/stop().

This patch adds state tracking of sorts to start()/stop().

We also have stop() return a data structure with captured
data. This allows consumers to do things like display only the
just-collected data.

Patch

diff --git a/mercurial/statprof.py b/mercurial/statprof.py
--- a/mercurial/statprof.py
+++ b/mercurial/statprof.py
@@ -167,17 +167,17 @@  class ProfileState(object):
 
     def accumulate_time(self, stop_time):
         self.accumulated_time += stop_time - self.last_start_time
 
     def seconds_per_sample(self):
         return self.accumulated_time / len(self.samples)
 
 state = ProfileState()
-
+_statestack = []
 
 class CodeSite(object):
     cache = {}
 
     __slots__ = ('path', 'lineno', 'function', 'source')
 
     def __init__(self, path, lineno, function):
         self.path = path
@@ -244,69 +244,90 @@  class Sample(object):
 
         while frame:
             stack.append(CodeSite.get(frame.f_code.co_filename, frame.f_lineno,
                                       frame.f_code.co_name))
             frame = frame.f_back
 
         return Sample(stack, time)
 
+class CollectedData(object):
+    """Represents collected data for a sampling interval."""
+    def __init__(self, samples, accumulated_time, sample_interval):
+        self.samples = samples
+        self.accumulated_time = accumulated_time
+        self.sample_interval = sample_interval
+
 ###########################################################################
 ## SIGPROF handler
 
 def profile_signal_handler(signum, frame):
     if state.profile_level > 0:
         state.accumulate_time(clock())
 
         state.samples.append(Sample.from_frame(frame, state.accumulated_time))
 
         signal.setitimer(signal.ITIMER_PROF,
             state.sample_interval, 0.0)
         state.last_start_time = clock()
 
 stopthread = threading.Event()
-def samplerthread(tid):
+def samplerthread():
     while not stopthread.is_set():
         state.accumulate_time(clock())
 
-        frame = sys._current_frames()[tid]
+        frame = sys._current_frames()[state.threadid]
         state.samples.append(Sample.from_frame(frame, state.accumulated_time))
 
         state.last_start_time = clock()
         time.sleep(state.sample_interval)
 
     stopthread.clear()
 
 ###########################################################################
 ## Profiling API
 
 def is_active():
     return state.profile_level > 0
 
 lastmechanism = None
 def start(mechanism='thread'):
     '''Install the profiling signal handler, and start profiling.'''
+    # Store old state if present.
+    if state.profile_level > 0:
+        laststate = {
+            'samples': state.samples,
+            'tid': state.threadid,
+            'accumulated_time': state.accumulated_time,
+        }
+        _statestack.append(laststate)
+
+    state.samples = []
+    state.accumulated_time = 0.0
+    frame = inspect.currentframe()
+    tid = [k for k, f in sys._current_frames().items() if f == frame][0]
+    state.threadid = tid
+
     state.profile_level += 1
     if state.profile_level == 1:
         state.last_start_time = clock()
         rpt = state.remaining_prof_time
         state.remaining_prof_time = None
 
         global lastmechanism
         lastmechanism = mechanism
 
         if mechanism == 'signal':
             signal.signal(signal.SIGPROF, profile_signal_handler)
             signal.setitimer(signal.ITIMER_PROF,
                 rpt or state.sample_interval, 0.0)
         elif mechanism == 'thread':
-            frame = inspect.currentframe()
-            tid = [k for k, f in sys._current_frames().items() if f == frame][0]
+
             state.thread = threading.Thread(target=samplerthread,
-                                 args=(tid,), name="samplerthread")
+                                            name="samplerthread")
             state.thread.start()
 
 def stop():
     '''Stop profiling, and uninstall the profiling signal handler.'''
     state.profile_level -= 1
     if state.profile_level == 0:
         if lastmechanism == 'signal':
             rpt = signal.setitimer(signal.ITIMER_PROF, 0.0, 0.0)
@@ -317,16 +338,38 @@  def stop():
             state.thread.join()
 
         state.accumulate_time(clock())
         state.last_start_time = None
         statprofpath = os.environ.get('STATPROF_DEST')
         if statprofpath:
             save_data(statprofpath)
 
+        return CollectedData(state.samples, state.accumulated_time,
+                             state.sample_interval)
+    elif state.profile_level > 0:
+        newstate = _statestack.pop()
+
+        data = CollectedData(state.samples, state.accumulated_time,
+                             state.sample_interval)
+
+        # Merge the just collected data onto the base state since the base
+        # state was active while this one was collecting.
+        newstate['samples'].extend(state.samples)
+        newstate['threadid'] = newstate['tid']
+        newstate['accumulated_time'] += state.accumulated_time
+
+        # Now make the base state the active state.
+        state.samples = newstate['samples']
+        state.threadid = newstate['tid']
+        state.accumulated_time = newstate['accumulated_time']
+
+        return data
+    else:
+        raise Exception('unbalanced calls to start()/stop()')
 
 def save_data(path):
     with open(path, 'w+') as file:
         file.write(str(state.accumulated_time) + '\n')
         for sample in state.samples:
             time = str(sample.time)
             stack = sample.stack
             sites = ['\1'.join([s.path, str(s.lineno), s.function])