Merge lp:~lucio.torre/txstatsd/sli-add-size into lp:txstatsd

Proposed by Lucio Torre
Status: Merged
Approved by: Lucio Torre
Approved revision: 96
Merged at revision: 96
Proposed branch: lp:~lucio.torre/txstatsd/sli-add-size
Merge into: lp:txstatsd
Diff against target: 330 lines (+154/-29)
6 files modified
twisted/plugins/sli_plugin.py (+4/-4)
txstatsd/metrics/extendedmetrics.py (+1/-0)
txstatsd/metrics/metrics.py (+27/-9)
txstatsd/metrics/slimetric.py (+30/-15)
txstatsd/tests/metrics/test_sli.py (+73/-1)
txstatsd/tests/test_metrics.py (+19/-0)
To merge this branch: bzr merge lp:~lucio.torre/txstatsd/sli-add-size
Reviewer Review Type Date Requested Status
Facundo Batista (community) Approve
Review via email: mp+110908@code.launchpad.net

Commit message

add sli size

Description of the change

Add size to sli reporting:

from the client side now you can do:
metrics.sli(path, duration, size) to report multi items slis or just
metrics.sli(path, duration) for single items or
metric.sli_error(path) for errors.

from the txstatsd side ive added the error counter and below and above now also take the optional slope value.

So,
 path => label IF below 5 2
means:
 label incoming metrics from path `path` with label `label` if duration < 5 + 2 * size

To post a comment you must log in.
Revision history for this message
Facundo Batista (facundo) wrote :

Like it

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'twisted/plugins/sli_plugin.py'
2--- twisted/plugins/sli_plugin.py 2012-06-05 16:21:15 +0000
3+++ twisted/plugins/sli_plugin.py 2012-06-18 20:53:18 +0000
4@@ -63,11 +63,11 @@
5 cobj = method(*cparams.split(" "))
6 self.config[head][label] = cobj
7
8- def build_above(self, value):
9- return AboveCondition(float(value))
10+ def build_above(self, value, slope=0):
11+ return AboveCondition(float(value), float(slope))
12
13- def build_below(self, value):
14- return BelowCondition(float(value))
15+ def build_below(self, value, slope=0):
16+ return BelowCondition(float(value), float(slope))
17
18 def build_between(self, low, hi):
19 return BetweenCondition(float(low), float(hi))
20
21=== modified file 'txstatsd/metrics/extendedmetrics.py'
22--- txstatsd/metrics/extendedmetrics.py 2011-12-02 17:09:32 +0000
23+++ txstatsd/metrics/extendedmetrics.py 2012-06-18 20:53:18 +0000
24@@ -51,3 +51,4 @@
25 sample_rate)
26 self._metrics[name] = metric
27 self._metrics[name].mark(duration)
28+
29
30=== modified file 'txstatsd/metrics/metrics.py'
31--- txstatsd/metrics/metrics.py 2012-02-13 21:15:25 +0000
32+++ txstatsd/metrics/metrics.py 2012-06-18 20:53:18 +0000
33@@ -7,13 +7,15 @@
34
35
36 class GenericMetric(Metric):
37- def __init__(self, connection, key, name, sample_rate=1):
38- super(GenericMetric, self).__init__(connection, name,
39- sample_rate=sample_rate)
40+ def __init__(self, connection, key, name):
41+ super(GenericMetric, self).__init__(connection, name)
42 self.key = key
43
44- def mark(self, value):
45- self.send("%s|%s" % (value, self.key))
46+ def mark(self, value, extra=None):
47+ if extra is None:
48+ self.send("%s|%s" % (value, self.key))
49+ else:
50+ self.send("%s|%s|%s" % (value, self.key, extra))
51
52
53 class Metrics(object):
54@@ -32,7 +34,7 @@
55 self._metrics = {}
56 self.last_time = 0
57
58- def report(self, name, value, metric_type, sample_rate=1):
59+ def report(self, name, value, metric_type, extra=None):
60 """Report a generic metric.
61
62 Used for server side plugins without client support.
63@@ -41,10 +43,26 @@
64 if not name in self._metrics:
65 metric = GenericMetric(self.connection,
66 metric_type,
67- name,
68- sample_rate)
69+ name)
70 self._metrics[name] = metric
71- self._metrics[name].mark(value)
72+ self._metrics[name].mark(value, extra)
73+
74+ def sli(self, name, duration, size=None):
75+ """Report a service level metric.
76+
77+ The optional size parameter is used with linear thereshold slis.
78+ So for example, to report a download you could use size and the size in
79+ bytes of the file.
80+ """
81+ self.report(name, duration, "sli", size)
82+
83+ def sli_error(self, name):
84+ """Report an error for a service level metric.
85+
86+ When something that is measures for service level errs (no time or size
87+ are required/present) you can use this method to inform it.
88+ """
89+ self.report(name, "error", "sli")
90
91 def gauge(self, name, value, sample_rate=1):
92 """Report an instantaneous reading of a particular value."""
93
94=== modified file 'txstatsd/metrics/slimetric.py'
95--- txstatsd/metrics/slimetric.py 2012-06-05 14:39:32 +0000
96+++ txstatsd/metrics/slimetric.py 2012-06-18 20:53:18 +0000
97@@ -3,20 +3,23 @@
98
99 class BelowCondition(object):
100
101- def __init__(self, value):
102+ def __init__(self, value, slope=0):
103 self.value = value
104+ self.slope = slope
105
106- def __call__(self, value):
107- return value < self.value
108+ def __call__(self, value, size=1):
109+ return value < self.value + self.slope * size
110
111
112 class AboveCondition(object):
113
114- def __init__(self, value):
115+ def __init__(self, value, slope=0):
116 self.value = value
117-
118- def __call__(self, value):
119- return value > self.value
120+ self.slope = slope
121+
122+ def __call__(self, value, size=1):
123+ return value > self.value + self.slope * size
124+
125
126 class BetweenCondition(object):
127
128@@ -24,7 +27,7 @@
129 self.low = low
130 self.hi = hi
131
132- def __call__(self, value):
133+ def __call__(self, value, size=1):
134 return self.low < value < self.hi
135
136
137@@ -38,16 +41,26 @@
138 def clear(self):
139 self.counts = dict((k, 0) for k in self.conditions)
140 self.count = 0
141+ self.error = 0
142
143 def process(self, fields):
144- self.update(float(fields[0]))
145-
146- def update(self, value):
147+ size = 1
148+ if len(fields) == 3:
149+ size = float(fields[2])
150+
151+ value = "error"
152+ if value != fields[0]:
153+ value = float(fields[0])
154+ self.update(value, size)
155+
156+ def update(self, value, size=1):
157 self.count += 1
158- for k, condition in self.conditions.items():
159-
160- if condition(value):
161- self.counts[k] += 1
162+ if value == "error":
163+ self.error += 1
164+ else:
165+ for k, condition in self.conditions.items():
166+ if condition(value, size):
167+ self.counts[k] += 1
168
169 def flush(self, interval, timestamp):
170 metrics = []
171@@ -56,6 +69,8 @@
172 value, timestamp))
173 metrics.append((self.name + ".count",
174 self.count, timestamp))
175+ metrics.append((self.name + ".error",
176+ self.error, timestamp))
177
178 self.clear()
179 return metrics
180
181=== modified file 'txstatsd/tests/metrics/test_sli.py'
182--- txstatsd/tests/metrics/test_sli.py 2012-06-05 16:21:15 +0000
183+++ txstatsd/tests/metrics/test_sli.py 2012-06-18 20:53:18 +0000
184@@ -7,6 +7,7 @@
185 from txstatsd.metrics.slimetric import (
186 SLIMetricReporter, BetweenCondition, AboveCondition, BelowCondition)
187 from txstatsd import service
188+from txstatsd.tests.test_processor import TestMessageProcessor
189
190
191 class TestConditions(TestCase):
192@@ -27,6 +28,28 @@
193 self.assertEquals(c(6), False)
194 self.assertEquals(c(2.6), True)
195
196+ def test_below_linear(self):
197+ c = BelowCondition(5, 1)
198+ self.assertEquals(c(5.5, 1), True)
199+ self.assertEquals(c(6.5, 2), True)
200+ self.assertEquals(c(8.5, 3), False)
201+
202+ def test_above_linear(self):
203+ c = AboveCondition(4, 1)
204+ self.assertEquals(c(5.5, 1), True)
205+ self.assertEquals(c(6.5, 2), True)
206+ self.assertEquals(c(7, 3), False)
207+
208+
209+class TestParsing(TestCase):
210+ def setUp(self):
211+ self.processor = TestMessageProcessor()
212+
213+ def test_parse(self):
214+ self.processor.process('txstatsd.tests.users:100|sli')
215+ self.processor.process('txstatsd.tests.users:100|sli|2')
216+ self.processor.process('txstatsd.tests.users:error|sli')
217+
218
219 class TestMetric(TestCase):
220 def setUp(self):
221@@ -39,6 +62,13 @@
222 self.sli.update(1)
223 self.assertEquals(self.sli.count, 2)
224
225+ def test_count_error(self):
226+ self.sli.update(1)
227+ self.sli.update("error")
228+ self.assertEquals(self.sli.count, 2)
229+ self.assertEquals(self.sli.error, 1)
230+ self.assertEquals(self.sli.counts["red"], 1)
231+
232 def test_count_threshold(self):
233 self.assertEquals(self.sli.count, 0)
234 self.assertEquals(self.sli.counts["red"], 0)
235@@ -55,7 +85,8 @@
236 self.assertEquals(
237 [("test.count", 6, 0),
238 ("test.count_red", 4, 0),
239- ("test.count_yellow", 2, 0)],
240+ ("test.count_yellow", 2, 0),
241+ ("test.error", 0, 0)],
242 rows)
243
244 def test_clear(self):
245@@ -67,6 +98,23 @@
246 self.assertEquals(self.sli.count, 1)
247
248
249+class TestMetricLinear(TestCase):
250+ def setUp(self):
251+ self.sli = SLIMetricReporter('test', {
252+ "red": BelowCondition(5, 1),
253+ "yellow": BelowCondition(3, 1)})
254+
255+ def test_count_threshold(self):
256+ self.assertEquals(self.sli.count, 0)
257+ self.assertEquals(self.sli.counts["red"], 0)
258+ self.assertEquals(self.sli.counts["yellow"], 0)
259+ for i in range(1, 7):
260+ self.sli.update(7, i)
261+ self.assertEquals(self.sli.count, 6)
262+ self.assertEquals(self.sli.counts["red"], 4)
263+ self.assertEquals(self.sli.counts["yellow"], 2)
264+
265+
266 class TestFactory(TestCase):
267 def test_configure(self):
268 class TestOptions(service.OptionsGlue):
269@@ -95,3 +143,27 @@
270 rc = smr.conditions["red"]
271 self.assertTrue(isinstance(rc, AboveCondition))
272 self.assertEquals(rc.value, 4)
273+
274+ def test_configure_linear(self):
275+ class TestOptions(service.OptionsGlue):
276+ optParameters = [["test", "t", "default", "help"]]
277+ config_section = "statsd"
278+
279+ o = TestOptions()
280+ config_file = ConfigParser.RawConfigParser()
281+ config_file.readfp(StringIO("[statsd]\n\n[plugin_sli]\n"
282+ "rules = \n"
283+ " test => red IF below 5 1\n"
284+ " test => green IF above 3 1\n"))
285+ o.configure(config_file)
286+ smf = SLIMetricFactory()
287+ smf.configure(o)
288+ smr = smf.build_metric("", "test")
289+ rc = smr.conditions["red"]
290+ self.assertTrue(isinstance(rc, BelowCondition))
291+ self.assertEquals(rc.value, 5)
292+ self.assertEquals(rc.slope, 1)
293+ rc = smr.conditions["green"]
294+ self.assertTrue(isinstance(rc, AboveCondition))
295+ self.assertEquals(rc.value, 3)
296+ self.assertEquals(rc.slope, 1)
297
298=== modified file 'txstatsd/tests/test_metrics.py'
299--- txstatsd/tests/test_metrics.py 2011-11-28 16:24:39 +0000
300+++ txstatsd/tests/test_metrics.py 2012-06-18 20:53:18 +0000
301@@ -91,6 +91,12 @@
302 self.assertEqual(self.connection.data,
303 'txstatsd.tests.users:pepe|pd')
304
305+ def test_generic_extra(self):
306+ """Test the GenericMetric class."""
307+ self.metrics.report('users', "pepe", "pd", 100)
308+ self.assertEqual(self.connection.data,
309+ 'txstatsd.tests.users:pepe|pd|100')
310+
311 def test_empty_namespace(self):
312 """Test reporting of an empty namespace."""
313 self.metrics.namespace = None
314@@ -118,3 +124,16 @@
315 self.assertEqual(self.connection.data,
316 'txstatsd.tests.counter:9|c')
317
318+ def test_sli(self):
319+ """Test SLI call."""
320+ self.metrics.sli('users', 100)
321+ self.assertEqual(self.connection.data,
322+ 'txstatsd.tests.users:100|sli')
323+
324+ self.metrics.sli('users', 200, 2)
325+ self.assertEqual(self.connection.data,
326+ 'txstatsd.tests.users:200|sli|2')
327+
328+ self.metrics.sli_error('users')
329+ self.assertEqual(self.connection.data,
330+ 'txstatsd.tests.users:error|sli')

Subscribers

People subscribed via source and target branches