Skip to content

Commit

Permalink
Merge pull request #71 from percipient/default-metric-timed
Browse files Browse the repository at this point in the history
Make the metric optional for the timed decorator.
  • Loading branch information
yannmh committed Jul 23, 2015
2 parents 25088df + b230080 commit 7cb27be
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 3 deletions.
14 changes: 11 additions & 3 deletions datadog/dogstatsd/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,33 +135,41 @@ class _TimedContextManagerDecorator(object):
the context OR in a function call.
"""

def __init__(self, statsd, metric, tags=None, sample_rate=1):
def __init__(self, statsd, metric=None, tags=None, sample_rate=1):
self.statsd = statsd
self.metric = metric
self.tags = tags
self.sample_rate = sample_rate

def __call__(self, func):
"""Decorator which returns the elapsed time of the function call."""
# Default to the function name if metric was not provided.
if not self.metric:
self.metric = '%s.%s' % (func.__module__, func.__name__)

@wraps(func)
def wrapped(*args, **kwargs):
with self:
return func(*args, **kwargs)
return wrapped

def __enter__(self):
if not self.metric:
raise TypeError("Cannot used timed without a metric!")
self.start = time()

def __exit__(self, type, value, traceback):
# Report the elapsed time of the context manager.
self.statsd.timing(self.metric, time() - self.start,
self.tags, self.sample_rate)

def timed(self, metric, tags=None, sample_rate=1):
def timed(self, metric=None, tags=None, sample_rate=1):
"""
A decorator or context manager that will measure the distribution of a
function's/context's run time. Optionally specify a list of tags or a
sample rate.
sample rate. If the metric is not defined as a decorator, the module
name and function name will be used. The metric is required as a context
manager.
::
@statsd.timed('user.query.time', sample_rate=0.5)
Expand Down
38 changes: 38 additions & 0 deletions tests/unit/dogstatsd/test_statsd.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,30 @@ def func(a, b, c=1, d=1):
t.assert_equal('timed.test', name)
self.assert_almost_equal(0.5, float(value), 0.1)

def test_timed_no_metric(self, ):
"""Test using a decorator without providing a metric."""

@self.statsd.timed()
def func(a, b, c=1, d=1):
"""docstring"""
time.sleep(0.5)
return (a, b, c, d)

t.assert_equal('func', func.__name__)
t.assert_equal('docstring', func.__doc__)

result = func(1, 2, d=3)
# Assert it handles args and kwargs correctly.
t.assert_equal(result, (1, 2, 1, 3))

packet = self.recv()
name_value, type_ = packet.split('|')
name, value = name_value.split(':')

t.assert_equal('ms', type_)
t.assert_equal('tests.unit.dogstatsd.test_statsd.func', name)
self.assert_almost_equal(0.5, float(value), 0.1)

def test_timed_context(self):
with self.statsd.timed('timed_context.test'):
time.sleep(0.5)
Expand Down Expand Up @@ -214,6 +238,20 @@ def func(self):
t.assert_equal('timed_context.test.exception', name)
self.assert_almost_equal(0.5, float(value), 0.1)

def test_timed_context_no_metric_exception(self):
"""Test that an exception occurs if using a context manager without a metric."""

def func(self):
with self.statsd.timed():
time.sleep(0.5)

# Ensure the exception was raised.
t.assert_raises(TypeError, func, self)

# Ensure the timing was recorded.
packet = self.recv()
t.assert_equal(packet, None)

def test_batched(self):
self.statsd.open_buffer()
self.statsd.gauge('page.views', 123)
Expand Down

0 comments on commit 7cb27be

Please sign in to comment.