diff options
author | James Socol <me@jamessocol.com> | 2014-01-03 01:00:10 -0500 |
---|---|---|
committer | James Socol <me@jamessocol.com> | 2014-01-03 01:08:08 -0500 |
commit | 26302de5a9a2993ccc5a29953fbba4eb3a20d5fe (patch) | |
tree | 01a26f42fe7ab3483bebce4db0dbee50ba80ed3e | |
parent | d2b477620661a6029a773a1a351cedff81cae300 (diff) | |
download | pystatsd-26302de5a9a2993ccc5a29953fbba4eb3a20d5fe.tar.gz |
Give timers manual start/stop methods.
-rw-r--r-- | docs/timing.rst | 52 | ||||
-rw-r--r-- | statsd/__init__.py | 2 | ||||
-rw-r--r-- | statsd/client.py | 25 | ||||
-rw-r--r-- | statsd/tests.py | 47 |
4 files changed, 115 insertions, 11 deletions
diff --git a/docs/timing.rst b/docs/timing.rst index 025647b..133da91 100644 --- a/docs/timing.rst +++ b/docs/timing.rst @@ -8,6 +8,8 @@ Using Timers application performance. Statsd provides a number of ways to use them to instrument your code. +There are four ways to use timers. + Calling ``timing`` manually =========================== @@ -67,3 +69,53 @@ be sent to the statsd server. # Timing information will be sent every time the function is called. myfunc(1, 2) myfunc(3, 7) + + +Using a Timer directly +====================== + +:py:class:`statsd.client.Timer` objects function as context managers and +as decorators, but they can also be used directly. (Flat is, after all, +better than nested.) + +:: + + from statsd import StatsClient + + statsd = StatsClient() + + foo_timer = statsd.timer('foo') + foo_timer.start() + # Do something fun. + foo_timer.stop() + +When :py:meth:`statsd.client.Timer.stop` is called, a `timing stat +<timer-type>`_ will automatically be sent to StatsD. You can over ride +this behavior with the ``send=False`` keyword argument to ``stop()``:: + + foo_timer.stop(send=False) + foo_timer.send() + +Use :py:meth:`statsd.client.Timer.send` to send the stat when you're +ready. + +.. note:: + This use of timers is compatible with `Pipelines <pipeline-chapter>`_ + but be careful with the ``send()`` method. It *must* be called for + the stat to be included when the Pipeline finally sends data, but + ``send()`` will *not* immediately cause data to be sent in the + context of a Pipeline. For example:: + + with statsd.pipeline() as pipe: + foo_timer = pipe.timer('foo').start() + # Do something... + pipe.incr('bar') + foo_timer.stop() # Will be sent when the managed block exits. + + with statsd.pipeline() as pipe: + foo_timer = pipe.timer('foo').start() + # Do something... + pipe.incr('bar') + foo_timer.stop(send=False) # Will not be sent. + foo_timer.send() # Will be sent when the managed block exits. + # Do something else... diff --git a/statsd/__init__.py b/statsd/__init__.py index e47a620..6af74c5 100644 --- a/statsd/__init__.py +++ b/statsd/__init__.py @@ -16,7 +16,7 @@ except ImportError: settings = None from .client import StatsClient -from ._version import __version__ +from ._version import __version__ # noqa __all__ = ['StatsClient', 'statsd'] diff --git a/statsd/client.py b/statsd/client.py index 78c3a99..0770247 100644 --- a/statsd/client.py +++ b/statsd/client.py @@ -25,19 +25,31 @@ class Timer(object): return wrapper def __enter__(self): - self.start = time.time() - return self + return self.start() def __exit__(self, typ, value, tb): - dt = time.time() - self.start - self.ms = int(round(1000 * dt)) # Convert to ms. + self.stop() + + def start(self): + self._start_time = time.time() + return self + + def stop(self, send=True): + dt = time.time() - self._start_time + self.ms = int(round(1000 * dt)) # Convert to milliseconds. + if send: + self.send() + return self + + def send(self): self.client.timing(self.stat, self.ms, self.rate) class StatsClient(object): """A client for statsd.""" - def __init__(self, host='localhost', port=8125, prefix=None, maxudpsize=512): + def __init__(self, host='localhost', port=8125, prefix=None, + maxudpsize=512): """Create a new client.""" self._addr = (socket.gethostbyname(host), port) self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) @@ -106,7 +118,8 @@ class Pipeline(StatsClient): self._stats = [] def _after(self, data): - self._stats.append(data) + if data is not None: + self._stats.append(data) def __enter__(self): return self diff --git a/statsd/tests.py b/statsd/tests.py index 75bee3d..6c7d7ad 100644 --- a/statsd/tests.py +++ b/statsd/tests.py @@ -18,13 +18,11 @@ def _client(prefix=None): return sc -def _sock_check(cl, count, val): +def _sock_check(cl, count, val=None): eq_(cl._sock.sendto.call_count, count) - if val: + if val is not None: val = val.encode('ascii') eq_(cl._sock.sendto.call_args, ((val, ADDR), {})) - else: - eq_(cl._sock.sendto.call_args, None) class assert_raises(object): @@ -288,6 +286,37 @@ def test_timer_decorator_exceptions(): _timer_check(sc, 1, 'foo', 'ms') +def test_imperative_timer(): + sc = _client() + + t = sc.timer('foo').start() + t.stop() + + _timer_check(sc, 1, 'foo', 'ms') + + +def test_imperative_timer_no_send(): + sc = _client() + + t = sc.timer('foo').start() + t.stop(send=False) + _sock_check(sc, 0) + + t.send() + _timer_check(sc, 1, 'foo', 'ms') + + +@mock.patch.object(random, 'random', lambda: -1) +def test_imperative_timer_rate(): + sc = _client() + + t = sc.timer('foo', rate=0.5) + t.start() + t.stop() + + _timer_check(sc, 1, 'foo', 'ms@0.5') + + def test_pipeline(): sc = _client() pipe = sc.pipeline() @@ -303,6 +332,7 @@ def test_pipeline_null(): sc = _client() pipe = sc.pipeline() pipe.send() + _sock_check(sc, 0) def test_pipeline_manager(): @@ -332,6 +362,15 @@ def test_pipeline_timer_decorator(): _timer_check(sc, 1, 'foo', 'ms') +def test_pipeline_timer_imperative(): + sc = _client() + with sc.pipeline() as pipe: + t = pipe.timer('foo').start() + t.stop() + _sock_check(sc, 0) + _timer_check(sc, 1, 'foo', 'ms') + + def test_pipeline_empty(): """Pipelines should be empty after a send() call.""" sc = _client() |