Skip to content

Commit 20d8be0

Browse files
committed
Add decorators to make common use cases easy.
1 parent cf4ff07 commit 20d8be0

File tree

2 files changed

+116
-2
lines changed

2 files changed

+116
-2
lines changed

prometheus_client/__init__.py

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
import copy
44
import re
5+
import time
6+
from contextlib import contextmanager
57
from BaseHTTPServer import BaseHTTPRequestHandler
8+
from functools import wraps
69
from threading import Lock
710

811
__all__ = ['Counter', 'Gauge', 'Summary']
@@ -165,6 +168,23 @@ def inc(self, amount=1):
165168
with self._lock:
166169
self._value += amount
167170

171+
@contextmanager
172+
def trackBlockRaises(self):
173+
"""Decorator to increment if a block raises an exception."""
174+
try:
175+
yield
176+
except Exception, e:
177+
self.inc()
178+
raise e
179+
180+
def trackFunctionRaises(self, f):
181+
"""Decorator to increment if a function raises an exception."""
182+
@wraps(f)
183+
def wrapper(*args, **kwargs):
184+
with self.trackBlockRaises():
185+
return f(*args, **kwargs)
186+
return wrapper
187+
168188
def _samples(self):
169189
with self._lock:
170190
return (('', {}, self._value), )
@@ -187,10 +207,31 @@ def dec(self, amount=1):
187207
self._value -= amount
188208

189209
def set(self, value):
190-
"""Set gauge to the given amount."""
210+
"""Set gauge to the given value."""
191211
with self._lock:
192212
self._value = float(value)
193213

214+
def setToCurrentTime(self, value):
215+
"""Set gauge to the current unixtime."""
216+
self.set(time.time())
217+
218+
@contextmanager
219+
def trackBlockInprogress(self):
220+
"""Decorator to track how many of a block are in progress."""
221+
self.inc()
222+
try:
223+
yield
224+
finally:
225+
self.dec()
226+
227+
def trackFunctionInprogress(self, f):
228+
"""Decorator to track how many of a function are in progress."""
229+
@wraps(f)
230+
def wrapper(*args, **kwargs):
231+
with self.trackBlockInprogress():
232+
return f(*args, **kwargs)
233+
return wrapper
234+
194235
def _samples(self):
195236
with self._lock:
196237
return (('', {}, self._value), )
@@ -209,6 +250,24 @@ def observe(self, amount):
209250
self._count += 1
210251
self._sum += amount
211252

253+
@contextmanager
254+
def timeBlock(self):
255+
"""Context manager to time how long a block of code takes in seconds."""
256+
start = time.time()
257+
try:
258+
yield
259+
finally:
260+
# Time can go backwards.
261+
self.observe(max(time.time() - start, 0))
262+
263+
def timeFunction(self, f):
264+
"""Decorator to time long a function takes in seconds."""
265+
@wraps(f)
266+
def wrapper(*args, **kwargs):
267+
with self.timeBlock():
268+
return f(*args, **kwargs)
269+
return wrapper
270+
212271
def _samples(self):
213272
with self._lock:
214273
return (
@@ -257,7 +316,6 @@ def do_GET(self):
257316
s = Summary('ss', 'A summary', ['a', 'b'])
258317
s.labels('c', 'd').observe(17)
259318

260-
261319
from BaseHTTPServer import HTTPServer
262320
server_address = ('', 8000)
263321
httpd = HTTPServer(server_address, MetricsHandler)

tests/test_client.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,34 @@ def test_increment(self):
1818
def test_negative_increment_raises(self):
1919
self.assertRaises(ValueError, self.counter.inc, -1)
2020

21+
def test_function_decorator(self):
22+
@self.counter.trackFunctionRaises
23+
def f(r):
24+
if r:
25+
raise Exception
26+
f(False)
27+
self.assertEquals(0, self.registry.get_sample_value('c'))
28+
raised = False
29+
try:
30+
f(True)
31+
except:
32+
raised = True
33+
self.assertTrue(raised)
34+
self.assertEquals(1, self.registry.get_sample_value('c'))
35+
36+
def test_block_decorator(self):
37+
with self.counter.trackBlockRaises():
38+
pass
39+
self.assertEquals(0, self.registry.get_sample_value('c'))
40+
raised = False
41+
try:
42+
with self.counter.trackBlockRaises():
43+
raise Exception
44+
except:
45+
raised = True
46+
self.assertTrue(raised)
47+
self.assertEquals(1, self.registry.get_sample_value('c'))
48+
2149
class TestGauge(unittest.TestCase):
2250
def setUp(self):
2351
self.registry = CollectorRegistry()
@@ -32,6 +60,20 @@ def test_gauge(self):
3260
self.gauge.set(9)
3361
self.assertEquals(9, self.registry.get_sample_value('g'))
3462

63+
def test_function_decorator(self):
64+
self.assertEquals(0, self.registry.get_sample_value('g'))
65+
@self.gauge.trackFunctionInprogress
66+
def f():
67+
self.assertEquals(1, self.registry.get_sample_value('g'))
68+
f()
69+
self.assertEquals(0, self.registry.get_sample_value('g'))
70+
71+
def test_block_decorator(self):
72+
self.assertEquals(0, self.registry.get_sample_value('g'))
73+
with self.gauge.trackBlockInprogress():
74+
self.assertEquals(1, self.registry.get_sample_value('g'))
75+
self.assertEquals(0, self.registry.get_sample_value('g'))
76+
3577
class TestSummary(unittest.TestCase):
3678
def setUp(self):
3779
self.registry = CollectorRegistry()
@@ -44,6 +86,20 @@ def test_summary(self):
4486
self.assertEquals(1, self.registry.get_sample_value('s_count'))
4587
self.assertEquals(10, self.registry.get_sample_value('s_sum'))
4688

89+
def test_function_decorator(self):
90+
self.assertEquals(0, self.registry.get_sample_value('s_count'))
91+
@self.summary.timeFunction
92+
def f():
93+
pass
94+
f()
95+
self.assertEquals(1, self.registry.get_sample_value('s_count'))
96+
97+
def test_block_decorator(self):
98+
self.assertEquals(0, self.registry.get_sample_value('s_count'))
99+
with self.summary.timeBlock():
100+
pass
101+
self.assertEquals(1, self.registry.get_sample_value('s_count'))
102+
47103
class TestMetricWrapper(unittest.TestCase):
48104
def setUp(self):
49105
self.registry = CollectorRegistry()

0 commit comments

Comments
 (0)