From 1041d23f10f7796a1ee5f25fb3eb602dd963a735 Mon Sep 17 00:00:00 2001 From: Matt Wilder Date: Tue, 2 Feb 2021 12:36:53 -0800 Subject: [PATCH 1/3] Add tags support to GraphiteBridge Graphite has support for tagged metrics with a syntax very similar to the non-tagged format. Update GraphiteBridge to support it. Signed-off-by: Matt Wilder --- README.md | 13 +++++++++- prometheus_client/bridge/graphite.py | 13 +++++++--- tests/test_graphite_bridge.py | 37 ++++++++++++++++++++++++++-- 3 files changed, 57 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 6c12bbd6..1eb933cb 100644 --- a/README.md +++ b/README.md @@ -440,13 +440,24 @@ Metrics are pushed over TCP in the Graphite plaintext format. ```python from prometheus_client.bridge.graphite import GraphiteBridge -gb = GraphiteBridge(('graphite.your.org', 2003)) +gb = GraphiteBridge('graphite.your.org', 2003) # Push once. gb.push() # Push every 10 seconds in a daemon thread. gb.start(10.0) ``` +Graphite [tags](https://grafana.com/blog/2018/01/11/graphite-1.1-teaching-an-old-dog-new-tricks/) are also supported. + +```python +from prometheus_client.bridge.graphite import GraphiteBridge + +gb = GraphiteBridge('graphite.your.org', 2003, tags=True) +c = Counter('my_requests_total', 'HTTP Failures', ['method', 'endpoint']) +c.labels('get', '/').inc() +gb.push() +``` + ## Custom Collectors Sometimes it is not possible to directly instrument code, as it is not diff --git a/prometheus_client/bridge/graphite.py b/prometheus_client/bridge/graphite.py index 713d8c09..0cb43aaa 100755 --- a/prometheus_client/bridge/graphite.py +++ b/prometheus_client/bridge/graphite.py @@ -46,9 +46,10 @@ def run(self): class GraphiteBridge(object): - def __init__(self, address, registry=REGISTRY, timeout_seconds=30, _timer=time.time): + def __init__(self, address, registry=REGISTRY, tags=False, timeout_seconds=30, _timer=time.time): self._address = address self._registry = registry + self._tags = tags self._timeout = timeout_seconds self._timer = _timer @@ -63,8 +64,14 @@ def push(self, prefix=''): for metric in self._registry.collect(): for s in metric.samples: if s.labels: - labelstr = '.' + '.'.join( - ['{0}.{1}'.format( + if self._tags: + sep = ';' + fmt = '{0}={1}' + else: + sep = '.' + fmt = '{0}.{1}' + labelstr = sep + sep.join( + [fmt.format( _sanitize(k), _sanitize(v)) for k, v in sorted(s.labels.items())]) else: diff --git a/tests/test_graphite_bridge.py b/tests/test_graphite_bridge.py index 86dc425f..73b38204 100644 --- a/tests/test_graphite_bridge.py +++ b/tests/test_graphite_bridge.py @@ -35,8 +35,11 @@ def run(self): self.t.start() # Explicitly use localhost as the target host, since connecting to 0.0.0.0 fails on Windows - address = ('localhost', server.server_address[1]) - self.gb = GraphiteBridge(address, self.registry, _timer=fake_timer) + self.address = ('localhost', server.server_address[1]) + self.gb = GraphiteBridge(self.address, self.registry, _timer=fake_timer) + + def _use_tags(self): + self.gb = GraphiteBridge(self.address, self.registry, tags=True, _timer=fake_timer) def test_nolabels(self): gauge = Gauge('g', 'help', registry=self.registry) @@ -56,6 +59,16 @@ def test_labels(self): self.assertEqual(b'labels.a.c.b.d 1.0 1434898897\n', self.data) + def test_labels_tags(self): + self._use_tags() + labels = Gauge('labels', 'help', ['a', 'b'], registry=self.registry) + labels.labels('c', 'd').inc() + + self.gb.push() + self.t.join() + + self.assertEqual(b'labels;a=c;b=d 1.0 1434898897\n', self.data) + def test_prefix(self): labels = Gauge('labels', 'help', ['a', 'b'], registry=self.registry) labels.labels('c', 'd').inc() @@ -65,6 +78,16 @@ def test_prefix(self): self.assertEqual(b'pre.fix.labels.a.c.b.d 1.0 1434898897\n', self.data) + def test_prefix_tags(self): + self._use_tags() + labels = Gauge('labels', 'help', ['a', 'b'], registry=self.registry) + labels.labels('c', 'd').inc() + + self.gb.push(prefix='pre.fix') + self.t.join() + + self.assertEqual(b'pre.fix.labels;a=c;b=d 1.0 1434898897\n', self.data) + def test_sanitizing(self): labels = Gauge('labels', 'help', ['a'], registry=self.registry) labels.labels('c.:8').inc() @@ -73,3 +96,13 @@ def test_sanitizing(self): self.t.join() self.assertEqual(b'labels.a.c__8 1.0 1434898897\n', self.data) + + def test_sanitizing_tags(self): + self._use_tags() + labels = Gauge('labels', 'help', ['a'], registry=self.registry) + labels.labels('c.:8').inc() + + self.gb.push() + self.t.join() + + self.assertEqual(b'labels;a=c__8 1.0 1434898897\n', self.data) From d1f3647103816bac72fd1af28c7d8189ff785034 Mon Sep 17 00:00:00 2001 From: Matt Wilder Date: Thu, 4 Feb 2021 13:53:06 -0800 Subject: [PATCH 2/3] Fix doc bugs and GraphiteBridge kwargs order * Fix `GraphiteBridge` calls in README.md * Place the new `tags` kwarg at the end of the `GraphiteBridge` constructor. Signed-off-by: Matt Wilder --- README.md | 4 ++-- prometheus_client/bridge/graphite.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1eb933cb..d3f2bf93 100644 --- a/README.md +++ b/README.md @@ -440,7 +440,7 @@ Metrics are pushed over TCP in the Graphite plaintext format. ```python from prometheus_client.bridge.graphite import GraphiteBridge -gb = GraphiteBridge('graphite.your.org', 2003) +gb = GraphiteBridge(('graphite.your.org', 2003)) # Push once. gb.push() # Push every 10 seconds in a daemon thread. @@ -452,7 +452,7 @@ Graphite [tags](https://grafana.com/blog/2018/01/11/graphite-1.1-teaching-an-old ```python from prometheus_client.bridge.graphite import GraphiteBridge -gb = GraphiteBridge('graphite.your.org', 2003, tags=True) +gb = GraphiteBridge(('graphite.your.org', 2003, tags=True)) c = Counter('my_requests_total', 'HTTP Failures', ['method', 'endpoint']) c.labels('get', '/').inc() gb.push() diff --git a/prometheus_client/bridge/graphite.py b/prometheus_client/bridge/graphite.py index 0cb43aaa..35ef8493 100755 --- a/prometheus_client/bridge/graphite.py +++ b/prometheus_client/bridge/graphite.py @@ -46,7 +46,7 @@ def run(self): class GraphiteBridge(object): - def __init__(self, address, registry=REGISTRY, tags=False, timeout_seconds=30, _timer=time.time): + def __init__(self, address, registry=REGISTRY, timeout_seconds=30, _timer=time.time, tags=False): self._address = address self._registry = registry self._tags = tags From 7f83b9ef8567a097570f7ba177b06f20cf117596 Mon Sep 17 00:00:00 2001 From: Matt Wilder Date: Thu, 4 Feb 2021 14:21:21 -0800 Subject: [PATCH 3/3] Fix GraphiteBridge example again. Signed-off-by: Matt Wilder --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d3f2bf93..7e5051ba 100644 --- a/README.md +++ b/README.md @@ -452,7 +452,7 @@ Graphite [tags](https://grafana.com/blog/2018/01/11/graphite-1.1-teaching-an-old ```python from prometheus_client.bridge.graphite import GraphiteBridge -gb = GraphiteBridge(('graphite.your.org', 2003, tags=True)) +gb = GraphiteBridge(('graphite.your.org', 2003), tags=True) c = Counter('my_requests_total', 'HTTP Failures', ['method', 'endpoint']) c.labels('get', '/').inc() gb.push()