statprof: add threading sampler

Summary:
The existing statprof uses signals to perform stack sampling. Those signals can
interfere with system calls though. This patch introduces a way of using python
threads to perform sampling from a background thread.

This also changes the default to be the threaded profiler instead of the
sampling one, since the sampling can crash the proces sometimes.

In some situations this produces a more accurate stack trace.

Test Plan:
```
sudo cp statprof.py /usr/lib64/python2.6/site-packages/statprof.py
hg pull --profile
# Verified I got output and that it was different from the old statprof (it was
# more accurate, as compared with time.time() measurements in the code)
```

Reviewers: #mercurial, ttung, quark

Reviewed By: quark

Subscribers: quark, mjpieters

Differential Revision: https://phabricator.intern.facebook.com/D3402864

Signature: t1:3402864:1465978363:c8455d119cc03c2c475e190aee28587a9d434c4d
This commit is contained in:
Durham Goode 2016-06-15 09:54:18 -07:00
parent 50238ee029
commit b9e78ae8c1

View File

@ -104,7 +104,8 @@ main thread's work patterns.
# no-check-code
from __future__ import division
import json, os, signal, tempfile, sys, getopt
import inspect, json, os, signal, tempfile, sys, getopt, threading, traceback
import time
from collections import defaultdict
from contextlib import contextmanager
from subprocess import call
@ -252,6 +253,18 @@ def profile_signal_handler(signum, frame):
state.sample_interval, 0.0)
state.last_start_time = clock()
stopthread = threading.Event()
def samplerthread(tid):
while not stopthread.is_set():
state.accumulate_time(clock())
frame = sys._current_frames()[tid]
state.samples.append(Sample.from_frame(frame, state.accumulated_time))
state.last_start_time = clock()
time.sleep(state.sample_interval)
stopthread.clear()
###########################################################################
## Profiling API
@ -259,29 +272,43 @@ def profile_signal_handler(signum, frame):
def is_active():
return state.profile_level > 0
def start():
lastmechanism = None
def start(mechanism='thread'):
'''Install the profiling signal handler, and start profiling.'''
state.profile_level += 1
if state.profile_level == 1:
state.last_start_time = clock()
rpt = state.remaining_prof_time
state.remaining_prof_time = None
signal.signal(signal.SIGPROF, profile_signal_handler)
signal.setitimer(signal.ITIMER_PROF,
rpt or state.sample_interval, 0.0)
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")
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)
signal.signal(signal.SIGPROF, signal.SIG_IGN)
state.remaining_prof_time = rpt[0]
elif lastmechanism == 'thread':
stopthread.set()
state.thread.join()
state.accumulate_time(clock())
state.last_start_time = None
rpt = signal.setitimer(signal.ITIMER_PROF, 0.0, 0.0)
signal.signal(signal.SIGPROF, signal.SIG_IGN)
state.remaining_prof_time = rpt[0]
statprofpath = os.environ.get('STATPROF_DEST')
save_data(statprofpath)