Merge lp:~ricardokirkner/txstatsd/gauge-with-delta into lp:txstatsd

Proposed by Ricardo Kirkner on 2014-10-03
Status: Merged
Approved by: Ricardo Kirkner on 2014-10-09
Approved revision: 111
Merged at revision: 110
Proposed branch: lp:~ricardokirkner/txstatsd/gauge-with-delta
Merge into: lp:txstatsd
Diff against target: 197 lines (+108/-10)
5 files modified
txstatsd/metrics/gaugemetric.py (+10/-4)
txstatsd/metrics/metrics.py (+2/-2)
txstatsd/server/processor.py (+13/-3)
txstatsd/tests/test_metrics.py (+13/-1)
txstatsd/tests/test_processor.py (+70/-0)
To merge this branch: bzr merge lp:~ricardokirkner/txstatsd/gauge-with-delta
Reviewer Review Type Date Requested Status
Lucio Torre (community) 2014-10-03 Approve on 2014-10-09
James Westby (community) Approve on 2014-10-09
Review via email: mp+237132@code.launchpad.net

Commit message

added support for gauges with delta values

Description of the change

Support gauges with delta values.

This feature is supported by recent statsd versions, but not yet supported by txstatsd.

For reference:

Statsd Server
https://github.com/etsy/statsd/blob/master/docs/metric_types.md#gauges

Statsd client
http://statsd.readthedocs.org/en/latest/types.html#gauge-deltas

To post a comment you must log in.
Lucio Torre (lucio.torre) wrote :

this api seems like counters, have you looked at that?

review: Needs Information
Ricardo Kirkner (ricardokirkner) wrote :

> this api seems like counters, have you looked at that?

Haven't looked into txstatsd counters in detail yet, but will do. I added this gauge delta support because it's supported by the "official" statsd server, so clients (other than txstatsd) are starting to support it.

Ricardo Kirkner (ricardokirkner) wrote :

> > this api seems like counters, have you looked at that?
>
> Haven't looked into txstatsd counters in detail yet, but will do. I added this
> gauge delta support because it's supported by the "official" statsd server, so
> clients (other than txstatsd) are starting to support it.

counters are supposed to be reset to 0 every 10s (ie, after every flush)... gauges are not; if the value didn't change statsd will send the latest available value.
Even if counters implement the same api as gauges for this in txstatsd, it's not possible to use non-txstatsd clients with a txstatsd server as the standard api for counters in statsd doesn't support deltas. This is why I'm adding delta support for gauges (so that we can use other statsd clients with a txstatsd server)

James Westby (james-w) wrote :

This looks ok to me, with 1 comment.

James Westby (james-w) :
review: Approve
Ubuntu One Auto Pilot (otto-pilot) wrote :

Voting does not meet specified criteria. Required: Approve >= 1, Disapprove == 0, Needs Fixing == 0, Needs Information == 0, Resubmit == 0, Pending == 0. Got: 1 Approve, 1 Needs Information.

Lucio Torre (lucio.torre) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'txstatsd/metrics/gaugemetric.py'
2--- txstatsd/metrics/gaugemetric.py 2012-06-28 17:29:26 +0000
3+++ txstatsd/metrics/gaugemetric.py 2014-10-03 19:54:06 +0000
4@@ -37,9 +37,12 @@
5 """
6 Metric.__init__(self, connection, name, sample_rate=sample_rate)
7
8- def mark(self, value):
9+ def mark(self, value, delta=False):
10 """Report the C{value} for this gauge."""
11- self.send("%s|g" % value)
12+ if delta and value > 0:
13+ self.send("+%s|g" % value)
14+ else:
15+ self.send("%s|g" % value)
16
17
18 class GaugeMetricReporter(object):
19@@ -57,8 +60,11 @@
20 self.prefix = prefix
21 self.value = 0
22
23- def mark(self, value):
24- self.value = value
25+ def mark(self, value, delta=False):
26+ if delta:
27+ self.value += value
28+ else:
29+ self.value = value
30
31 def report(self, timestamp):
32 return [(self.prefix + self.name + ".value", self.value, timestamp)]
33
34=== modified file 'txstatsd/metrics/metrics.py'
35--- txstatsd/metrics/metrics.py 2012-06-28 17:29:26 +0000
36+++ txstatsd/metrics/metrics.py 2014-10-03 19:54:06 +0000
37@@ -84,7 +84,7 @@
38 """
39 self.report(name, "error", "sli")
40
41- def gauge(self, name, value, sample_rate=1):
42+ def gauge(self, name, value, sample_rate=1, delta=False):
43 """Report an instantaneous reading of a particular value."""
44 name = self.fully_qualify_name(name)
45 if not name in self._metrics:
46@@ -92,7 +92,7 @@
47 name,
48 sample_rate)
49 self._metrics[name] = gauge_metric
50- self._metrics[name].mark(value)
51+ self._metrics[name].mark(value, delta=delta)
52
53 def meter(self, name, value=1, sample_rate=1):
54 """Mark the occurrence of a given number of events."""
55
56=== modified file 'txstatsd/server/processor.py'
57--- txstatsd/server/processor.py 2013-10-15 20:56:49 +0000
58+++ txstatsd/server/processor.py 2014-10-03 19:54:06 +0000
59@@ -191,15 +191,25 @@
60 if not len(values) == 1:
61 return self.fail(message)
62
63+ delta = False
64+ value = values[0]
65+ if value.startswith('+') or value.startswith('-'):
66+ delta = True
67 try:
68- value = float(values[0])
69+ value = float(value)
70 except (TypeError, ValueError):
71 self.fail(message)
72
73- self.compose_gauge_metric(key, value)
74+ self.compose_gauge_metric(key, value, delta=delta)
75
76- def compose_gauge_metric(self, key, value):
77+ def compose_gauge_metric(self, key, value, delta=False):
78 metric = [value, key]
79+ if delta:
80+ for item in reversed(self.gauge_metrics):
81+ if item[1] == key:
82+ old_value = item[0]
83+ metric = [old_value + value, key]
84+ break
85 self.gauge_metrics.append(metric)
86
87 def process_meter_metric(self, key, composite, message):
88
89=== modified file 'txstatsd/tests/test_metrics.py'
90--- txstatsd/tests/test_metrics.py 2013-03-06 21:50:26 +0000
91+++ txstatsd/tests/test_metrics.py 2014-10-03 19:54:06 +0000
92@@ -49,11 +49,23 @@
93 self.metrics = Metrics(self.connection, 'txstatsd.tests')
94
95 def test_gauge(self):
96- """Test reporting of a gauge metric sample."""
97+ """Test reporting of an absolute gauge metric sample."""
98 self.metrics.gauge('gauge', 102)
99 self.assertEqual(self.connection.data,
100 b'txstatsd.tests.gauge:102|g')
101
102+ def test_gauge_with_positive_delta(self):
103+ """Test reporting of a relative gauge metric sample."""
104+ self.metrics.gauge('gauge', 102, delta=True)
105+ self.assertEqual(self.connection.data,
106+ b'txstatsd.tests.gauge:+102|g')
107+
108+ def test_gauge_with_negative_delta(self):
109+ """Test reporting of a relative gauge metric sample."""
110+ self.metrics.gauge('gauge', -102, delta=True)
111+ self.assertEqual(self.connection.data,
112+ b'txstatsd.tests.gauge:-102|g')
113+
114 def test_meter(self):
115 """Test reporting of a meter metric sample."""
116 self.metrics.meter('meter', 3)
117
118=== modified file 'txstatsd/tests/test_processor.py'
119--- txstatsd/tests/test_processor.py 2013-10-15 20:56:49 +0000
120+++ txstatsd/tests/test_processor.py 2014-10-03 19:54:06 +0000
121@@ -113,6 +113,76 @@
122 [9.6, 'gorets'],
123 self.processor.gauge_metrics.pop())
124
125+ def test_receive_gauge_metrics_positive_delta(self):
126+ """
127+ A gauge metric message takes the form:
128+ '<name>:count|g' if the value is considered absolute
129+ '<name>:[+-]count|g' if the value is considered relative
130+ 'g' indicates this is a gauge metrics message.
131+ """
132+ self.processor.process("gorets:9.6|g")
133+ self.processor.process("gorets:+1|g")
134+ self.assertEqual(2, len(self.processor.gauge_metrics))
135+ self.assertEqual(
136+ [10.6, 'gorets'],
137+ self.processor.gauge_metrics.pop())
138+
139+ def test_receive_gauge_metrics_positive_delta_without_initial_value(self):
140+ """
141+ A gauge metric message takes the form:
142+ '<name>:count|g' if the value is considered absolute
143+ '<name>:[+-]count|g' if the value is considered relative
144+ 'g' indicates this is a gauge metrics message.
145+ """
146+ self.processor.process("gorets:+1|g")
147+ self.assertEqual(1, len(self.processor.gauge_metrics))
148+ self.assertEqual(
149+ [1, 'gorets'],
150+ self.processor.gauge_metrics.pop())
151+
152+ def test_receive_gauge_metrics_multiple_deltas(self):
153+ """
154+ A gauge metric message takes the form:
155+ '<name>:count|g' if the value is considered absolute
156+ '<name>:[+-]count|g' if the value is considered relative
157+ 'g' indicates this is a gauge metrics message.
158+ """
159+ self.processor.process("gorets:+1|g")
160+ self.processor.process("gorets:+1|g")
161+ self.processor.process("gorets:+1|g")
162+ self.processor.process("gorets:+1|g")
163+ self.assertEqual(4, len(self.processor.gauge_metrics))
164+ self.assertEqual(
165+ [4, 'gorets'],
166+ self.processor.gauge_metrics.pop())
167+
168+ def test_receive_gauge_metrics_negative_delta(self):
169+ """
170+ A gauge metric message takes the form:
171+ '<name>:count|g' if the value is considered absolute
172+ '<name>:[+-]count|g' if the value is considered relative
173+ 'g' indicates this is a gauge metrics message.
174+ """
175+ self.processor.process("gorets:9.6|g")
176+ self.processor.process("gorets:-1.2|g")
177+ self.assertEqual(2, len(self.processor.gauge_metrics))
178+ self.assertEqual(
179+ [8.4, 'gorets'],
180+ self.processor.gauge_metrics.pop())
181+
182+ def test_receive_gauge_metrics_negative_delta_without_initial_value(self):
183+ """
184+ A gauge metric message takes the form:
185+ '<name>:count|g' if the value is considered absolute
186+ '<name>:[+-]count|g' if the value is considered relative
187+ 'g' indicates this is a gauge metrics message.
188+ """
189+ self.processor.process("gorets:-1.2|g")
190+ self.assertEqual(1, len(self.processor.gauge_metrics))
191+ self.assertEqual(
192+ [-1.2, 'gorets'],
193+ self.processor.gauge_metrics.pop())
194+
195 def test_receive_distinct_metric(self):
196 """
197 A distinct metric message takes the form:

Subscribers

People subscribed via source and target branches