@@ -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])