From 9e66c01f061385405b32c7a873afe70deaa4629a Mon Sep 17 00:00:00 2001 From: Uriel Corfa Date: Sun, 12 Apr 2015 21:28:35 -0700 Subject: [PATCH] Add the ability to specify labels as a dict of labelname->labelvalue. This is useful for code that knows some of the labels in different places. An example use case is a helper I'm trying to write that would work as such: c = Counter('http_exceptions_total', 'help', ['method', 'exception_type']) def handle_request(request): with count_exceptions_by_type(c, {'method': request.method}): do_something(request) This helper only knows about the exception_type label and only passes the other labels through. --- README.md | 9 +++++++++ prometheus_client/__init__.py | 21 +++++++++++++++++---- tests/test_client.py | 18 +++++++++++++++++- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 35e36a65..1721208e 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,15 @@ c.labels('get', '/').inc() c.labels('post', '/submit').inc() ``` +Labels can also be provided as a dict: + +```python +from prometheus_client import Counter +c = Counter('my_requests_total', 'HTTP Failures', ['method', 'endpoint']) +c.labels({'method': 'get', 'endpoint': '/'}).inc() +c.labels({'method': 'post', 'endpoint': '/submit'}).inc() +``` + ### Process Collector The Python Client automatically exports metrics about process CPU usage, RAM, diff --git a/prometheus_client/__init__.py b/prometheus_client/__init__.py index a0fd65c0..59af12d2 100644 --- a/prometheus_client/__init__.py +++ b/prometheus_client/__init__.py @@ -111,10 +111,23 @@ def __init__(self, wrappedClass, labelnames, **kwargs): raise ValueError('Invalid label metric name: ' + l) def labels(self, *labelvalues): - '''Return the child for the given labelset.''' - if len(labelvalues) != len(self._labelnames): - raise ValueError('Incorrect label count') - labelvalues = tuple([unicode(l) for l in labelvalues]) + '''Return the child for the given labelset. + + Labels can be provided as a tuple or as a dict: + c = Counter('c', 'counter', ['l', 'm']) + # Set labels by position + c.labels('0', '1').inc() + # Set labels by name + c.labels({'l': '0', 'm': '1'}).inc() + ''' + if len(labelvalues) == 1 and type(labelvalues[0]) == dict: + if sorted(labelvalues[0].keys()) != sorted(self._labelnames): + raise ValueError('Incorrect label names') + labelvalues = tuple([unicode(labelvalues[0][l]) for l in self._labelnames]) + else: + if len(labelvalues) != len(self._labelnames): + raise ValueError('Incorrect label count') + labelvalues = tuple([unicode(l) for l in labelvalues]) with self._lock: if labelvalues not in self._metrics: self._metrics[labelvalues] = self._wrappedClass(**self._kwargs) diff --git a/tests/test_client.py b/tests/test_client.py index 4945131c..d28ed7a6 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -234,7 +234,8 @@ def test_incorrect_label_count_raises(self): def test_labels_coerced_to_string(self): self.counter.labels(None).inc() - self.assertEqual(1, self.registry.get_sample_value('c', {'l': 'None'})) + self.counter.labels({'l': None}).inc() + self.assertEqual(2, self.registry.get_sample_value('c', {'l': 'None'})) self.counter.remove(None) self.assertEqual(None, self.registry.get_sample_value('c', {'l': 'None'})) @@ -243,12 +244,27 @@ def test_non_string_labels_raises(self): class Test(object): __str__ = None self.assertRaises(TypeError, self.counter.labels, Test()) + self.assertRaises(TypeError, self.counter.labels, {'l': Test()}) def test_namespace_subsystem_concatenated(self): c = Counter('c', 'help', namespace='a', subsystem='b', registry=self.registry) c.inc() self.assertEqual(1, self.registry.get_sample_value('a_b_c')) + def test_labels_by_dict(self): + self.counter.labels({'l': 'x'}).inc() + self.assertEqual(1, self.registry.get_sample_value('c', {'l': 'x'})) + self.assertRaises(ValueError, self.counter.labels, {'l': 'x', 'm': 'y'}) + self.assertRaises(ValueError, self.counter.labels, {'m': 'y'}) + self.assertRaises(ValueError, self.counter.labels, {}) + self.two_labels.labels({'a': 'x', 'b': 'y'}).inc() + self.assertEqual(1, self.registry.get_sample_value('two', {'a': 'x', 'b': 'y'})) + self.assertRaises(ValueError, self.two_labels.labels, {'a': 'x', 'b': 'y', 'c': 'z'}) + self.assertRaises(ValueError, self.two_labels.labels, {'a': 'x', 'c': 'z'}) + self.assertRaises(ValueError, self.two_labels.labels, {'b': 'y', 'c': 'z'}) + self.assertRaises(ValueError, self.two_labels.labels, {'c': 'z'}) + self.assertRaises(ValueError, self.two_labels.labels, {}) + def test_invalid_names_raise(self): self.assertRaises(ValueError, Counter, '', 'help') self.assertRaises(ValueError, Counter, '^', 'help')