Permalink
Browse files

adding a Profiler instrument

Adding a statistical profiler instrument, it's in a working shape but it could
generate a huge amount of data.
  • Loading branch information...
cyberdelia committed Jun 8, 2012
1 parent ed25919 commit 8d13f850debe669f4dcbfb1834d9bd101150ea07
View
@@ -66,6 +66,15 @@ A specialized timer that calculates the percentage of wall-clock time that was s
with utimer:
do_something()
+Profilers
+---------
+
+A profiler measures the distribution of the duration passed in a every part of the code ::
+
+ profiler = Metrology.profiler('slow-code')
+ with profiler:
+ run_slow_code()
+
Reporters
=========
View
@@ -43,3 +43,11 @@ Health Checks
.. automodule:: metrology.instruments.healthcheck
:members:
+
+
+Profilers
+=========
+
+.. automodule:: metrology.instruments.profiler
+ :members:
+
View
@@ -34,6 +34,10 @@ def histogram(cls, name, histogram=None):
def health_check(cls, name, health_check):
return registry.health_check(name, health_check)
+ @classmethod
+ def profiler(cls, name):
+ return registry.profiler(name)
+
@classmethod
def stop(cls):
return registry.stop()
@@ -3,4 +3,5 @@
from metrology.instruments.gauge import Gauge
from metrology.instruments.histogram import Histogram, HistogramUniform, HistogramExponentiallyDecaying
from metrology.instruments.meter import Meter
+from metrology.instruments.profiler import Profiler
from metrology.instruments.timer import Timer, UtilizationTimer
@@ -0,0 +1,66 @@
+from __future__ import division
+
+import statprof
+
+from os.path import basename
+from collections import defaultdict
+
+from metrology.instruments.histogram import HistogramExponentiallyDecaying
+
+
+__all__ = ['Profiler']
+
+class _Trace(object):
+ def __init__(self, data):
+ self_sample_count = data.self_sample_count
+ cum_sample_count = data.cum_sample_count
+ sample_count = statprof.state.sample_count
+ secs_per_sample = statprof.state.accumulated_time / sample_count
+ self.name = "{0}.{1}.{2}".format(basename(data.key.filename),
+ data.key.name, data.key.lineno)
+ self.percent = self_sample_count / sample_count * 100
+ self.cumulative = cum_sample_count * secs_per_sample
+ self.self = self_sample_count * secs_per_sample
+
+
+class Profiler(object):
+ """
+ A profiler measures the distribution of the duration passed in a every part of the code ::
+
+ profiler = Metrology.profiler('slow-code')
+ with profiler:
+ run_slow_code()
+
+ .. warning::
+ This instrument does not yet work on Windows.
+
+ """
+ def __init__(self, frequency=None, histogram=HistogramExponentiallyDecaying):
+ self.frequency = frequency
+ self.traces = defaultdict(histogram)
+
+ def clear(self):
+ self.histogram.clear()
+
+ def __enter__(self):
+ try:
+ statprof.reset(self.frequency)
+ except AssertionError:
+ pass # statprof is already running
+ statprof.start()
+ return self
+
+ def update(self, key, duration):
+ """Records the duration of a call."""
+ if duration >= 0:
+ self.traces[key].update(duration)
+
+ def __exit__(self, type, value, callback):
+ statprof.stop()
+ for call in statprof.CallData.all_calls.itervalues():
+ trace = _Trace(call)
+ for attr in ('percent', 'cumulative', 'self'):
+ self.update("{0}.{1}".format(trace.name, attr), getattr(trace, attr))
+
+ def stop(self):
+ pass
View
@@ -3,7 +3,7 @@
from threading import RLock
from metrology.exceptions import RegistryException
-from metrology.instruments import Counter, Meter, Timer, UtilizationTimer, HistogramUniform
+from metrology.instruments import Counter, Profiler, Meter, Timer, UtilizationTimer, HistogramUniform
class Registry(object):
@@ -41,6 +41,9 @@ def histogram(self, name, klass=None):
klass = HistogramUniform
return self.add_or_get(name, klass)
+ def profiler(self, name):
+ return self.add_or_get(name, Profiler)
+
def get(self, name):
with self.lock:
return self.metrics[name]
@@ -71,6 +71,14 @@ def write(self):
], [
'median', 'percentile_95th'
])
+ if isinstance(metric, Profiler):
+ for trace_name, trace_metric in metric.traces.items():
+ trace_name = "{0}.{1}".format(name, trace_name)
+ self.send_metric(trace_name, 'histogram', trace_metric, [
+ 'count', 'min', 'max', 'mean', 'stddev',
+ ], [
+ 'median', 'percentile_95th'
+ ])
def send_metric(self, name, type, metric, keys, snapshot_keys=None):
if snapshot_keys is None:
View
@@ -4,4 +4,5 @@ atomic
astrolabe
bintrees
pytest
+statprof
tox
View
@@ -22,7 +22,8 @@
install_requires=[
'astrolabe>=0.1.2',
'atomic>=0.3.2',
- 'bintrees>=1.0.0'
+ 'bintrees>=1.0.0',
+ 'statprof>=0.1.2'
],
include_package_data=True,
classifiers=[
@@ -0,0 +1,17 @@
+import test.pystone
+
+from unittest import TestCase
+
+from metrology.instruments.profiler import Profiler
+
+
+class ProfilerTest(TestCase):
+ def setUp(self):
+ self.profiler = Profiler()
+
+ def tearDown(self):
+ self.profiler.stop()
+
+ def test_profiler(self):
+ with self.profiler:
+ test.pystone.pystones()
@@ -18,6 +18,7 @@ def setUp(self):
Metrology.counter('counter').increment()
Metrology.timer('timer').update(1.5)
Metrology.utilization_timer('utimer').update(1.5)
+ Metrology.histogram('histogram').update(1.5)
def tearDown(self):
self.reporter.stop()
View
@@ -7,6 +7,7 @@ deps =
astrolabe>=0.1
atomic>=0.3.2
bintrees>=1.0.0
+ statprof>=0.1.2
mock
requests
commands = py.test
@@ -17,4 +18,5 @@ deps =
astrolabe>=0.1.1
atomic>=0.3.2
bintrees>=1.0.0
+ statprof>=0.1.2
mock

0 comments on commit 8d13f85

Please sign in to comment.