|
"""A simple Python callstack sampler.""" |
|
|
|
import contextlib |
|
import datetime |
|
import signal |
|
import traceback |
|
|
|
|
|
class CallstackSampler(object): |
|
"""A simple signal-based Python callstack sampler. |
|
""" |
|
|
|
def __init__(self, interval=None): |
|
self.stacks = [] |
|
self.interval = 0.001 if interval is None else interval |
|
|
|
def _sample(self, signum, frame): |
|
"""Samples the current stack.""" |
|
del signum |
|
stack = traceback.extract_stack(frame) |
|
formatted_stack = [] |
|
formatted_stack.append(datetime.datetime.utcnow()) |
|
for filename, lineno, function_name, text in stack: |
|
formatted_frame = '{}:{}({})({})'.format(filename, lineno, function_name, |
|
text) |
|
formatted_stack.append(formatted_frame) |
|
self.stacks.append(formatted_stack) |
|
signal.setitimer(signal.ITIMER_VIRTUAL, self.interval, 0) |
|
|
|
@contextlib.contextmanager |
|
def profile(self): |
|
signal.signal(signal.SIGVTALRM, self._sample) |
|
signal.setitimer(signal.ITIMER_VIRTUAL, self.interval, 0) |
|
try: |
|
yield |
|
finally: |
|
signal.setitimer(signal.ITIMER_VIRTUAL, 0) |
|
|
|
def save(self, fname): |
|
with open(fname, 'w') as f: |
|
for s in self.stacks: |
|
for l in s: |
|
f.write('%s\n' % l) |
|
f.write('\n') |
|
|
|
|
|
@contextlib.contextmanager |
|
def callstack_sampling(filename, interval=None): |
|
"""Periodically samples the Python callstack. |
|
|
|
Args: |
|
filename: the filename |
|
interval: the sampling interval, in seconds. Defaults to 0.001. |
|
|
|
Yields: |
|
nothing |
|
""" |
|
sampler = CallstackSampler(interval=interval) |
|
with sampler.profile(): |
|
yield |
|
sampler.save(filename) |
|
|
|
|