Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to add labels inside a context manager #730

Merged
merged 4 commits into from
Dec 15, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions prometheus_client/context_managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,21 @@ def _new_timer(self):

def __enter__(self):
self._start = default_timer()
return self

def __exit__(self, typ, value, traceback):
# Time can go backwards.
duration = max(default_timer() - self._start, 0)
instance = self._callback.__self__
instance._raise_if_not_observable()
self._callback(duration)

def labels(self, *args, **kw):
instance = self._callback.__self__
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love the references back to __self__ and __name__. I wonder if we could avoid that, but haven't come up with a great way yet. Perhaps a second variable to __init__ could help?

self._callback = getattr(
instance.labels(*args, **kw),
self._callback.__name__)

def __call__(self, f):
def wrapped(func, *args, **kwargs):
# Obtaining new instance of timer every time
Expand Down
2 changes: 0 additions & 2 deletions prometheus_client/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,6 @@ def time(self):

Can be used as a function decorator or context manager.
"""
self._raise_if_not_observable()
return Timer(self.set)

def set_function(self, f):
Expand Down Expand Up @@ -475,7 +474,6 @@ def time(self):

Can be used as a function decorator or context manager.
"""
self._raise_if_not_observable()
return Timer(self.observe)

def _child_samples(self):
Expand Down
36 changes: 34 additions & 2 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,13 +222,25 @@ def test_time_block_decorator(self):
time.sleep(.001)
self.assertNotEqual(0, self.registry.get_sample_value('g'))

def test_time_block_decorator_with_label(self):
value = self.registry.get_sample_value
self.assertEqual(None, value('g2', {'label1': 'foo'}))
with self.gauge_with_label.time() as metric:
metric.labels('foo')
self.assertLess(0, value('g2', {'label1': 'foo'}))

def test_track_in_progress_not_observable(self):
g = Gauge('test', 'help', labelnames=('label',), registry=self.registry)
assert_not_observable(g.track_inprogress)

def test_timer_not_observable(self):
g = Gauge('test', 'help', labelnames=('label',), registry=self.registry)
assert_not_observable(g.time)

def manager():
with g.time():
pass

assert_not_observable(manager)


class TestSummary(unittest.TestCase):
Expand Down Expand Up @@ -318,10 +330,21 @@ def test_block_decorator(self):
pass
self.assertEqual(1, self.registry.get_sample_value('s_count'))

def test_block_decorator_with_label(self):
value = self.registry.get_sample_value
self.assertEqual(None, value('s_with_labels_count', {'label1': 'foo'}))
with self.summary_with_labels.time() as metric:
metric.labels('foo')
self.assertEqual(1, value('s_with_labels_count', {'label1': 'foo'}))

def test_timer_not_observable(self):
s = Summary('test', 'help', labelnames=('label',), registry=self.registry)

assert_not_observable(s.time)
def manager():
with s.time():
pass

assert_not_observable(manager)


class TestHistogram(unittest.TestCase):
Expand Down Expand Up @@ -435,6 +458,15 @@ def test_block_decorator(self):
self.assertEqual(1, self.registry.get_sample_value('h_count'))
self.assertEqual(1, self.registry.get_sample_value('h_bucket', {'le': '+Inf'}))

def test_block_decorator_with_label(self):
value = self.registry.get_sample_value
self.assertEqual(None, value('hl_count', {'l': 'a'}))
self.assertEqual(None, value('hl_bucket', {'le': '+Inf', 'l': 'a'}))
with self.labels.time() as metric:
metric.labels('a')
self.assertEqual(1, value('hl_count', {'l': 'a'}))
self.assertEqual(1, value('hl_bucket', {'le': '+Inf', 'l': 'a'}))

def test_exemplar_invalid_label_name(self):
self.assertRaises(ValueError, self.histogram.observe, 3.0, exemplar={':o)': 'smile'})
self.assertRaises(ValueError, self.histogram.observe, 3.0, exemplar={'1': 'number'})
Expand Down