# -*- coding: utf-8 -*- """ profiling.tracing ~~~~~~~~~~~~~~~~~ Profiles deterministically by :func:`sys.setprofile`. :copyright: (c) 2014-2017, What! Studio :license: BSD, see LICENSE for more details. """ from __future__ import absolute_import import sys import threading from profiling import sortkeys from profiling.profiler import Profiler from profiling.stats import ( RecordingStatistics, VoidRecordingStatistics as void) from profiling.tracing.timers import Timer from profiling.utils import deferral from profiling.viewer import fmt, StatisticsTable __all__ = ['TracingProfiler', 'TracingStatisticsTable'] TIMER_CLASS = Timer class TracingStatisticsTable(StatisticsTable): columns = [ ('FUNCTION', 'left', ('weight', 1), sortkeys.by_function), ('CALLS', 'right', (6,), sortkeys.by_own_hits), ('OWN', 'right', (6,), sortkeys.by_own_time), ('/CALL', 'right', (6,), sortkeys.by_own_time_per_call), ('%', 'left', (4,), None), ('DEEP', 'right', (6,), sortkeys.by_deep_time), ('/CALL', 'right', (6,), sortkeys.by_deep_time_per_call), ('%', 'left', (4,), None), ] order = sortkeys.by_deep_time def make_cells(self, node, stats): yield fmt.make_stat_text(stats) yield fmt.make_int_or_na_text(stats.own_hits) yield fmt.make_time_text(stats.own_time) yield fmt.make_time_text(stats.own_time_per_call) yield fmt.make_percent_text(stats.own_time, self.cpu_time) yield fmt.make_time_text(stats.deep_time) yield fmt.make_time_text(stats.deep_time_per_call) yield fmt.make_percent_text(stats.deep_time, self.cpu_time) class TracingProfiler(Profiler): """The tracing profiler.""" table_class = TracingStatisticsTable #: The CPU timer. Usually it is an instance of :class:`profiling.tracing. #: timers.Timer`. timer = None #: The CPU time of profiling overhead. It's the time spent in #: :meth:`_profile`. overhead = 0.0 def __init__(self, base_frame=None, base_code=None, ignored_frames=(), ignored_codes=(), timer=None): timer = timer or TIMER_CLASS() if not isinstance(timer, Timer): raise TypeError('Not a timer instance') base = super(TracingProfiler, self) base.__init__(base_frame, base_code, ignored_frames, ignored_codes) self.timer = timer self._times_entered = {} def _profile(self, frame, event, arg): """The callback function to register by :func:`sys.setprofile`.""" # c = event.startswith('c_') if event.startswith('c_'): return time1 = self.timer() frames = self.frame_stack(frame) if frames: frames.pop() parent_stats = self.stats for f in frames: parent_stats = parent_stats.ensure_child(f.f_code, void) code = frame.f_code frame_key = id(frame) # if c: # event = event[2:] # code = mock_code(arg.__name__) # frame_key = id(arg) # record time2 = self.timer() self.overhead += time2 - time1 if event == 'call': time = time2 - self.overhead self.record_entering(time, code, frame_key, parent_stats) elif event == 'return': time = time1 - self.overhead self.record_leaving(time, code, frame_key, parent_stats) time3 = self.timer() self.overhead += time3 - time2 def record_entering(self, time, code, frame_key, parent_stats): """Entered to a function call.""" stats = parent_stats.ensure_child(code, RecordingStatistics) self._times_entered[(code, frame_key)] = time stats.own_hits += 1 def record_leaving(self, time, code, frame_key, parent_stats): """Left from a function call.""" try: stats = parent_stats.get_child(code) time_entered = self._times_entered.pop((code, frame_key)) except KeyError: return time_elapsed = time - time_entered stats.deep_time += max(0, time_elapsed) def result(self): base = super(TracingProfiler, self) frozen_stats, cpu_time, wall_time = base.result() return (frozen_stats, cpu_time - self.overhead, wall_time) def run(self): if sys.getprofile() is not None: # NOTE: There's no threading.getprofile(). # The profiling function will be stored at threading._profile_hook # but it's not documented. raise RuntimeError('Another profiler already registered') with deferral() as defer: self._times_entered.clear() self.overhead = 0.0 sys.setprofile(self._profile) defer(sys.setprofile, None) threading.setprofile(self._profile) defer(threading.setprofile, None) self.timer.start(self) defer(self.timer.stop) yield