Merge lp:~theiw/txstatsd/txstatsd-metermetric into lp:txstatsd
- txstatsd-metermetric
- Merge into trunk
Proposed by
Ian Wilkinson
Status: | Merged |
---|---|
Approved by: | Sidnei da Silva |
Approved revision: | 25 |
Merged at revision: | 25 |
Proposed branch: | lp:~theiw/txstatsd/txstatsd-metermetric |
Merge into: | lp:txstatsd |
Diff against target: |
756 lines (+627/-1) 10 files modified
README (+3/-0) txstatsd/metrics/metermetric.py (+104/-0) txstatsd/metrics/metrics.py (+11/-0) txstatsd/server/processor.py (+39/-0) txstatsd/service.py (+6/-0) txstatsd/stats/ewma.py (+70/-0) txstatsd/tests/stats/test_ewma.py (+315/-0) txstatsd/tests/test_metrics.py (+6/-0) txstatsd/tests/test_processor.py (+72/-0) txstatsd/version.py (+1/-1) |
To merge this branch: | bzr merge lp:~theiw/txstatsd/txstatsd-metermetric |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Sidnei da Silva | Approve | ||
Lucio Torre (community) | Approve | ||
Review via email: mp+73032@code.launchpad.net |
Commit message
Description of the change
Introduce support for Coda Hale's meter metric.
To post a comment you must log in.
Revision history for this message
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 'README' |
2 | --- README 2011-06-20 19:11:22 +0000 |
3 | +++ README 2011-08-26 12:28:49 +0000 |
4 | @@ -11,6 +11,9 @@ |
5 | * The txstatsd python package, containing a server and a client implementation |
6 | for the statsd protocol. |
7 | |
8 | +* The metrics support borrows from Coda Hale's Metrics project |
9 | + https://github.com/codahale/metrics. |
10 | + |
11 | License |
12 | ------- |
13 | |
14 | |
15 | === added file 'txstatsd/metrics/metermetric.py' |
16 | --- txstatsd/metrics/metermetric.py 1970-01-01 00:00:00 +0000 |
17 | +++ txstatsd/metrics/metermetric.py 2011-08-26 12:28:49 +0000 |
18 | @@ -0,0 +1,104 @@ |
19 | + |
20 | +import time |
21 | + |
22 | +from txstatsd.metrics.metric import Metric |
23 | +from txstatsd.stats.ewma import Ewma |
24 | + |
25 | + |
26 | +class MeterMetric(Metric): |
27 | + """ |
28 | + A meter metric which measures mean throughput and one-, five-, and |
29 | + fifteen-minute exponentially-weighted moving average throughputs. |
30 | + |
31 | + See: |
32 | + - U{EMA |
33 | + <http://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average>} |
34 | + """ |
35 | + |
36 | + def __init__(self, connection, name, sample_rate=1): |
37 | + """Construct a metric that reports samples to the supplied |
38 | + C{connection}. |
39 | + |
40 | + @param connection: The connection endpoint representing |
41 | + the StatsD server. |
42 | + @param name: Indicates what is being instrumented. |
43 | + @param sample_rate: Restrict the number of samples sent |
44 | + to the StatsD server based on the supplied C{sample_rate}. |
45 | + """ |
46 | + Metric.__init__(self, connection, name, sample_rate=sample_rate) |
47 | + |
48 | + def mark(self, value=1): |
49 | + """Mark the occurrence of a given number (C{value}) of events.""" |
50 | + self.send("%s|m" % value) |
51 | + |
52 | + |
53 | +class MeterMetricReporter(object): |
54 | + """ |
55 | + A meter metric which measures mean throughput and one-, five-, and |
56 | + fifteen-minute exponentially-weighted moving average throughputs. |
57 | + |
58 | + See: |
59 | + - U{EMA |
60 | + <http://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average>} |
61 | + """ |
62 | + |
63 | + MESSAGE = ( |
64 | + "stats.meter.%(key)s.count %(count)s %(timestamp)s\n" |
65 | + "stats.meter.%(key)s.mean_rate %(mean_rate)s %(timestamp)s\n" |
66 | + "stats.meter.%(key)s.1min_rate %(rate_1min)s %(timestamp)s\n" |
67 | + "stats.meter.%(key)s.5min_rate %(rate_5min)s %(timestamp)s\n" |
68 | + "stats.meter.%(key)s.15min_rate %(rate_15min)s %(timestamp)s\n") |
69 | + |
70 | + def __init__(self, name, wall_time_func=time.time): |
71 | + """Construct a metric we expect to be periodically updated. |
72 | + |
73 | + @param name: Indicates what is being instrumented. |
74 | + @param wall_time_func: Function for obtaining wall time. |
75 | + """ |
76 | + self.name = name |
77 | + self.wall_time_func = wall_time_func |
78 | + |
79 | + self.m1_rate = Ewma.one_minute_ewma() |
80 | + self.m5_rate = Ewma.five_minute_ewma() |
81 | + self.m15_rate = Ewma.fifteen_minute_ewma() |
82 | + self.count = 0 |
83 | + self.start_time = self.wall_time_func() |
84 | + |
85 | + def mark(self, value=1): |
86 | + """Mark the occurrence of a given number of events.""" |
87 | + self.count += value |
88 | + self.m1_rate.update(value) |
89 | + self.m5_rate.update(value) |
90 | + self.m15_rate.update(value) |
91 | + |
92 | + def tick(self): |
93 | + """Updates the moving averages.""" |
94 | + self.m1_rate.tick() |
95 | + self.m5_rate.tick() |
96 | + self.m15_rate.tick() |
97 | + |
98 | + def report(self, timestamp): |
99 | + return MeterMetricReporter.MESSAGE % { |
100 | + "key": self.name, |
101 | + "count": self.count, |
102 | + "mean_rate": self.mean_rate(), |
103 | + "rate_1min": self.one_minute_rate(), |
104 | + "rate_5min": self.five_minute_rate(), |
105 | + "rate_15min": self.fifteen_minute_rate(), |
106 | + "timestamp": timestamp} |
107 | + |
108 | + def fifteen_minute_rate(self): |
109 | + return self.m15_rate.rate |
110 | + |
111 | + def five_minute_rate(self): |
112 | + return self.m5_rate.rate |
113 | + |
114 | + def one_minute_rate(self): |
115 | + return self.m1_rate.rate |
116 | + |
117 | + def mean_rate(self): |
118 | + if self.count == 0: |
119 | + return 0.0 |
120 | + else: |
121 | + elapsed = self.wall_time_func() - self.start_time |
122 | + return float(self.count) / elapsed |
123 | |
124 | === modified file 'txstatsd/metrics/metrics.py' |
125 | --- txstatsd/metrics/metrics.py 2011-07-27 15:02:32 +0000 |
126 | +++ txstatsd/metrics/metrics.py 2011-08-26 12:28:49 +0000 |
127 | @@ -1,5 +1,6 @@ |
128 | |
129 | from txstatsd.metrics.gaugemetric import GaugeMetric |
130 | +from txstatsd.metrics.metermetric import MeterMetric |
131 | from txstatsd.metrics.metric import Metric |
132 | |
133 | |
134 | @@ -28,6 +29,16 @@ |
135 | self._metrics[name] = gauge_metric |
136 | self._metrics[name].mark(value) |
137 | |
138 | + def meter(self, name, value, sample_rate=1): |
139 | + """Mark the occurrence of a given number of events.""" |
140 | + name = self.fully_qualify_name(name) |
141 | + if not name in self._metrics: |
142 | + meter_metric = MeterMetric(self.connection, |
143 | + name, |
144 | + sample_rate) |
145 | + self._metrics[name] = meter_metric |
146 | + self._metrics[name].mark(value) |
147 | + |
148 | def increment(self, name, value=1, sample_rate=1): |
149 | """Report and increase in name by count.""" |
150 | name = self.fully_qualify_name(name) |
151 | |
152 | === modified file 'txstatsd/server/processor.py' |
153 | --- txstatsd/server/processor.py 2011-07-25 16:09:37 +0000 |
154 | +++ txstatsd/server/processor.py 2011-08-26 12:28:49 +0000 |
155 | @@ -5,6 +5,8 @@ |
156 | |
157 | from twisted.python import log |
158 | |
159 | +from txstatsd.metrics.metermetric import MeterMetricReporter |
160 | + |
161 | |
162 | SPACES = re.compile("\s+") |
163 | SLASHES = re.compile("\/+") |
164 | @@ -43,6 +45,7 @@ |
165 | self.timer_metrics = {} |
166 | self.counter_metrics = {} |
167 | self.gauge_metrics = deque() |
168 | + self.meter_metrics = {} |
169 | |
170 | def fail(self, message): |
171 | """Log and discard malformed message.""" |
172 | @@ -72,6 +75,8 @@ |
173 | self.process_timer_metric(key, fields[0], message) |
174 | elif fields[1] == "g": |
175 | self.process_gauge_metric(key, fields[0], message) |
176 | + elif fields[1] == "m": |
177 | + self.process_meter_metric(key, fields[0], message) |
178 | else: |
179 | return self.fail(message) |
180 | |
181 | @@ -110,6 +115,21 @@ |
182 | except (TypeError, ValueError): |
183 | self.fail(message) |
184 | |
185 | + def process_meter_metric(self, key, composite, message): |
186 | + values = composite.split(":") |
187 | + if not len(values) == 1: |
188 | + return self.fail(message) |
189 | + |
190 | + try: |
191 | + value = float(values[0]) |
192 | + except (TypeError, ValueError): |
193 | + self.fail(message) |
194 | + |
195 | + if not key in self.meter_metrics: |
196 | + metric = MeterMetricReporter(key, self.time_function) |
197 | + self.meter_metrics[key] = metric |
198 | + self.meter_metrics[key].mark(value) |
199 | + |
200 | def flush(self, interval=10000, percent=90): |
201 | """ |
202 | Flush all queued stats, computing a normalized count based on |
203 | @@ -136,6 +156,11 @@ |
204 | messages.extend(gauge_metrics) |
205 | num_stats += events |
206 | |
207 | + meter_metrics, events = self.flush_meter_metrics(timestamp) |
208 | + if events > 0: |
209 | + messages.extend(meter_metrics) |
210 | + num_stats += events |
211 | + |
212 | messages.append("statsd.numStats %s %s" % (num_stats, timestamp)) |
213 | return messages |
214 | |
215 | @@ -211,3 +236,17 @@ |
216 | self.gauge_metrics.clear() |
217 | |
218 | return (metrics, events) |
219 | + |
220 | + def flush_meter_metrics(self, timestamp): |
221 | + metrics = [] |
222 | + events = 0 |
223 | + for metric in self.meter_metrics.itervalues(): |
224 | + message = metric.report(timestamp) |
225 | + metrics.append(message) |
226 | + events += 1 |
227 | + |
228 | + return (metrics, events) |
229 | + |
230 | + def update_metrics(self): |
231 | + for metric in self.meter_metrics.itervalues(): |
232 | + metric.tick() |
233 | |
234 | === modified file 'txstatsd/service.py' |
235 | --- txstatsd/service.py 2011-07-27 20:31:33 +0000 |
236 | +++ txstatsd/service.py 2011-08-26 12:28:49 +0000 |
237 | @@ -117,6 +117,12 @@ |
238 | report_name.upper(), ()): |
239 | reporting.schedule(reporter, 10, metrics.gauge) |
240 | |
241 | + # Schedule updates for those metrics expecting to be |
242 | + # periodically updated, for example the meter metric. |
243 | + metrics_updater = ReportingService() |
244 | + metrics_updater.setServiceParent(service) |
245 | + metrics_updater.schedule(processor.update_metrics, 5, None) |
246 | + |
247 | factory = GraphiteClientFactory(processor, options["flush-interval"]) |
248 | client = TCPClient(options["carbon-cache-host"], |
249 | options["carbon-cache-port"], |
250 | |
251 | === added directory 'txstatsd/stats' |
252 | === added file 'txstatsd/stats/__init__.py' |
253 | === added file 'txstatsd/stats/ewma.py' |
254 | --- txstatsd/stats/ewma.py 1970-01-01 00:00:00 +0000 |
255 | +++ txstatsd/stats/ewma.py 2011-08-26 12:28:49 +0000 |
256 | @@ -0,0 +1,70 @@ |
257 | + |
258 | +""" |
259 | +An exponentially-weighted moving average. |
260 | + |
261 | +See: |
262 | +- U{UNIX Load Average Part 1: How It Works |
263 | + <http://www.teamquest.com/pdfs/whitepaper/ldavg1.pdf>} |
264 | +- U{UNIX Load Average Part 2: Not Your Average Average |
265 | + <http://www.teamquest.com/pdfs/whitepaper/ldavg2.pdf>} |
266 | +""" |
267 | + |
268 | +import math |
269 | + |
270 | + |
271 | +class Ewma(object): |
272 | + M1_ALPHA = 1 - math.exp(-5 / 60.0) |
273 | + M5_ALPHA = 1 - math.exp(-5 / 60.0 / 5) |
274 | + M15_ALPHA = 1 - math.exp(-5 / 60.0 / 15) |
275 | + |
276 | + @classmethod |
277 | + def one_minute_ewma(cls): |
278 | + """ |
279 | + Creates a new C{Ewma} which is equivalent to the UNIX one minute |
280 | + load average and which expects to be ticked every 5 seconds. |
281 | + """ |
282 | + return Ewma(Ewma.M1_ALPHA, 5) |
283 | + |
284 | + @classmethod |
285 | + def five_minute_ewma(cls): |
286 | + """ |
287 | + Creates a new C{Ewma} which is equivalent to the UNIX five minute |
288 | + load average and which expects to be ticked every 5 seconds. |
289 | + """ |
290 | + return Ewma(Ewma.M5_ALPHA, 5) |
291 | + |
292 | + @classmethod |
293 | + def fifteen_minute_ewma(cls): |
294 | + """ |
295 | + Creates a new C{Ewma} which is equivalent to the UNIX fifteen |
296 | + minute load average and which expects to be ticked every 5 seconds. |
297 | + """ |
298 | + return Ewma(Ewma.M15_ALPHA, 5) |
299 | + |
300 | + def __init__(self, alpha, interval): |
301 | + """Create a new C{Ewma} with a specific smoothing constant. |
302 | + |
303 | + @param alpha: The smoothing constant. |
304 | + @param interval: The expected tick interval in seconds. |
305 | + """ |
306 | + self.interval = interval |
307 | + self.alpha = float(alpha) |
308 | + |
309 | + self.initialized = False |
310 | + self.rate = 0.0 |
311 | + self.uncounted = 0 |
312 | + |
313 | + def update(self, n): |
314 | + """Update the moving average with a new value.""" |
315 | + self.uncounted += n |
316 | + |
317 | + def tick(self): |
318 | + """Mark the passage of time and decay the current rate accordingly.""" |
319 | + count = self.uncounted |
320 | + self.uncounted = 0 |
321 | + instant_rate = float(count) / self.interval |
322 | + if self.initialized: |
323 | + self.rate += (self.alpha * (instant_rate - self.rate)) |
324 | + else: |
325 | + self.rate = instant_rate |
326 | + self.initialized = True |
327 | |
328 | === added directory 'txstatsd/tests/stats' |
329 | === added file 'txstatsd/tests/stats/__init__.py' |
330 | === added file 'txstatsd/tests/stats/test_ewma.py' |
331 | --- txstatsd/tests/stats/test_ewma.py 1970-01-01 00:00:00 +0000 |
332 | +++ txstatsd/tests/stats/test_ewma.py 2011-08-26 12:28:49 +0000 |
333 | @@ -0,0 +1,315 @@ |
334 | + |
335 | +import math |
336 | +from unittest import TestCase |
337 | + |
338 | +from txstatsd.stats.ewma import Ewma |
339 | + |
340 | + |
341 | +def mark_minutes(minutes, ewma): |
342 | + for i in range(1, minutes * 60, 5): |
343 | + ewma.tick() |
344 | + |
345 | +class TestEwmaOneMinute(TestCase): |
346 | + def setUp(self): |
347 | + self.ewma = Ewma.one_minute_ewma() |
348 | + self.ewma.update(3) |
349 | + self.ewma.tick() |
350 | + |
351 | + def test_first_tick(self): |
352 | + self.assertTrue( |
353 | + (math.fabs(self.ewma.rate - 0.6) < 0.000001), |
354 | + 'Should have a rate of 0.6 events/sec after the first tick') |
355 | + |
356 | + def test_one_minute(self): |
357 | + mark_minutes(1, self.ewma) |
358 | + self.assertTrue( |
359 | + (math.fabs(self.ewma.rate - 0.22072766) < 0.00000001), |
360 | + 'Should have a rate of 0.22072766 events/sec after 1 minute') |
361 | + |
362 | + def test_two_minutes(self): |
363 | + mark_minutes(2, self.ewma) |
364 | + self.assertTrue( |
365 | + (math.fabs(self.ewma.rate - 0.08120117) < 0.00000001), |
366 | + 'Should have a rate of 0.08120117 events/sec after 2 minutes') |
367 | + |
368 | + def test_three_minutes(self): |
369 | + mark_minutes(3, self.ewma) |
370 | + self.assertTrue( |
371 | + (math.fabs(self.ewma.rate - 0.02987224) < 0.00000001), |
372 | + 'Should have a rate of 0.02987224 events/sec after 3 minutes') |
373 | + |
374 | + def test_four_minutes(self): |
375 | + mark_minutes(4, self.ewma) |
376 | + self.assertTrue( |
377 | + (math.fabs(self.ewma.rate - 0.01098938) < 0.00000001), |
378 | + 'Should have a rate of 0.01098938 events/sec after 4 minutes') |
379 | + |
380 | + def test_five_minutes(self): |
381 | + mark_minutes(5, self.ewma) |
382 | + self.assertTrue( |
383 | + (math.fabs(self.ewma.rate - 0.00404277) < 0.00000001), |
384 | + 'Should have a rate of 0.00404277 events/sec after 5 minutes') |
385 | + |
386 | + def test_six_minutes(self): |
387 | + mark_minutes(6, self.ewma) |
388 | + self.assertTrue( |
389 | + (math.fabs(self.ewma.rate - 0.00148725) < 0.00000001), |
390 | + 'Should have a rate of 0.00148725 events/sec after 6 minutes') |
391 | + |
392 | + def test_seven_minutes(self): |
393 | + mark_minutes(7, self.ewma) |
394 | + self.assertTrue( |
395 | + (math.fabs(self.ewma.rate - 0.00054713) < 0.00000001), |
396 | + 'Should have a rate of 0.00054713 events/sec after 7 minutes') |
397 | + |
398 | + def test_eight_minutes(self): |
399 | + mark_minutes(8, self.ewma) |
400 | + self.assertTrue( |
401 | + (math.fabs(self.ewma.rate - 0.00020128) < 0.00000001), |
402 | + 'Should have a rate of 0.00020128 events/sec after 8 minutes') |
403 | + |
404 | + def test_nine_minutes(self): |
405 | + mark_minutes(9, self.ewma) |
406 | + self.assertTrue( |
407 | + (math.fabs(self.ewma.rate - 0.00007405) < 0.00000001), |
408 | + 'Should have a rate of 0.00007405 events/sec after 9 minutes') |
409 | + |
410 | + def test_ten_minutes(self): |
411 | + mark_minutes(10, self.ewma) |
412 | + self.assertTrue( |
413 | + (math.fabs(self.ewma.rate - 0.00002724) < 0.00000001), |
414 | + 'Should have a rate of 0.00002724 events/sec after 10 minutes') |
415 | + |
416 | + def test_eleven_minutes(self): |
417 | + mark_minutes(11, self.ewma) |
418 | + self.assertTrue( |
419 | + (math.fabs(self.ewma.rate - 0.00001002) < 0.00000001), |
420 | + 'Should have a rate of 0.00001002 events/sec after 11 minutes') |
421 | + |
422 | + def test_twelve_minutes(self): |
423 | + mark_minutes(12, self.ewma) |
424 | + self.assertTrue( |
425 | + (math.fabs(self.ewma.rate - 0.00000369) < 0.00000001), |
426 | + 'Should have a rate of 0.00000369 events/sec after 12 minutes') |
427 | + |
428 | + def test_thirteen_minutes(self): |
429 | + mark_minutes(13, self.ewma) |
430 | + self.assertTrue( |
431 | + (math.fabs(self.ewma.rate - 0.00000136) < 0.00000001), |
432 | + 'Should have a rate of 0.00000136 events/sec after 13 minutes') |
433 | + |
434 | + def test_fourteen_minutes(self): |
435 | + mark_minutes(14, self.ewma) |
436 | + self.assertTrue( |
437 | + (math.fabs(self.ewma.rate - 0.00000050) < 0.00000001), |
438 | + 'Should have a rate of 0.00000050 events/sec after 14 minutes') |
439 | + |
440 | + def test_fifteen_minutes(self): |
441 | + mark_minutes(15, self.ewma) |
442 | + self.assertTrue( |
443 | + (math.fabs(self.ewma.rate - 0.00000018) < 0.00000001), |
444 | + 'Should have a rate of 0.00000018 events/sec after 15 minutes') |
445 | + |
446 | + |
447 | +class TestEwmaFiveMinute(TestCase): |
448 | + def setUp(self): |
449 | + self.ewma = Ewma.five_minute_ewma() |
450 | + self.ewma.update(3) |
451 | + self.ewma.tick() |
452 | + |
453 | + def test_first_tick(self): |
454 | + self.assertTrue( |
455 | + (math.fabs(self.ewma.rate - 0.6) < 0.000001), |
456 | + 'Should have a rate of 0.6 events/sec after the first tick') |
457 | + |
458 | + def test_one_minute(self): |
459 | + mark_minutes(1, self.ewma) |
460 | + self.assertTrue( |
461 | + (math.fabs(self.ewma.rate - 0.49123845) < 0.00000001), |
462 | + 'Should have a rate of 0.49123845 events/sec after 1 minute') |
463 | + |
464 | + def test_two_minutes(self): |
465 | + mark_minutes(2, self.ewma) |
466 | + self.assertTrue( |
467 | + (math.fabs(self.ewma.rate - 0.40219203) < 0.00000001), |
468 | + 'Should have a rate of 0.40219203 events/sec after 2 minutes') |
469 | + |
470 | + def test_three_minutes(self): |
471 | + mark_minutes(3, self.ewma) |
472 | + self.assertTrue( |
473 | + (math.fabs(self.ewma.rate - 0.32928698) < 0.00000001), |
474 | + 'Should have a rate of 0.32928698 events/sec after 3 minutes') |
475 | + |
476 | + def test_four_minutes(self): |
477 | + mark_minutes(4, self.ewma) |
478 | + self.assertTrue( |
479 | + (math.fabs(self.ewma.rate - 0.26959738) < 0.00000001), |
480 | + 'Should have a rate of 0.26959738 events/sec after 4 minutes') |
481 | + |
482 | + def test_five_minutes(self): |
483 | + mark_minutes(5, self.ewma) |
484 | + self.assertTrue( |
485 | + (math.fabs(self.ewma.rate - 0.22072766) < 0.00000001), |
486 | + 'Should have a rate of 0.22072766 events/sec after 5 minutes') |
487 | + |
488 | + def test_six_minutes(self): |
489 | + mark_minutes(6, self.ewma) |
490 | + self.assertTrue( |
491 | + (math.fabs(self.ewma.rate - 0.18071653) < 0.00000001), |
492 | + 'Should have a rate of 0.18071653 events/sec after 6 minutes') |
493 | + |
494 | + def test_seven_minutes(self): |
495 | + mark_minutes(7, self.ewma) |
496 | + self.assertTrue( |
497 | + (math.fabs(self.ewma.rate - 0.14795818) < 0.00000001), |
498 | + 'Should have a rate of 0.14795818 events/sec after 7 minutes') |
499 | + |
500 | + def test_eight_minutes(self): |
501 | + mark_minutes(8, self.ewma) |
502 | + self.assertTrue( |
503 | + (math.fabs(self.ewma.rate - 0.12113791) < 0.00000001), |
504 | + 'Should have a rate of 0.12113791 events/sec after 8 minutes') |
505 | + |
506 | + def test_nine_minutes(self): |
507 | + mark_minutes(9, self.ewma) |
508 | + self.assertTrue( |
509 | + (math.fabs(self.ewma.rate - 0.09917933) < 0.00000001), |
510 | + 'Should have a rate of 0.09917933 events/sec after 9 minutes') |
511 | + |
512 | + def test_ten_minutes(self): |
513 | + mark_minutes(10, self.ewma) |
514 | + self.assertTrue( |
515 | + (math.fabs(self.ewma.rate - 0.08120117) < 0.00000001), |
516 | + 'Should have a rate of 0.08120117 events/sec after 10 minutes') |
517 | + |
518 | + def test_eleven_minutes(self): |
519 | + mark_minutes(11, self.ewma) |
520 | + self.assertTrue( |
521 | + (math.fabs(self.ewma.rate - 0.06648190) < 0.00000001), |
522 | + 'Should have a rate of 0.06648190 events/sec after 11 minutes') |
523 | + |
524 | + def test_twelve_minutes(self): |
525 | + mark_minutes(12, self.ewma) |
526 | + self.assertTrue( |
527 | + (math.fabs(self.ewma.rate - 0.05443077) < 0.00000001), |
528 | + 'Should have a rate of 0.05443077 events/sec after 12 minutes') |
529 | + |
530 | + def test_thirteen_minutes(self): |
531 | + mark_minutes(13, self.ewma) |
532 | + self.assertTrue( |
533 | + (math.fabs(self.ewma.rate - 0.04456415) < 0.00000001), |
534 | + 'Should have a rate of 0.04456415 events/sec after 13 minutes') |
535 | + |
536 | + def test_fourteen_minutes(self): |
537 | + mark_minutes(14, self.ewma) |
538 | + self.assertTrue( |
539 | + (math.fabs(self.ewma.rate - 0.03648604) < 0.00000001), |
540 | + 'Should have a rate of 0.03648604 events/sec after 14 minutes') |
541 | + |
542 | + def test_fifteen_minutes(self): |
543 | + mark_minutes(15, self.ewma) |
544 | + self.assertTrue( |
545 | + (math.fabs(self.ewma.rate - 0.02987224) < 0.00000001), |
546 | + 'Should have a rate of 0.02987224 events/sec after 15 minutes') |
547 | + |
548 | + |
549 | +class TestEwmaFifteenMinute(TestCase): |
550 | + def setUp(self): |
551 | + self.ewma = Ewma.fifteen_minute_ewma() |
552 | + self.ewma.update(3) |
553 | + self.ewma.tick() |
554 | + |
555 | + def test_first_tick(self): |
556 | + self.assertTrue( |
557 | + (math.fabs(self.ewma.rate - 0.6) < 0.000001), |
558 | + 'Should have a rate of 0.6 events/sec after the first tick') |
559 | + |
560 | + def test_one_minute(self): |
561 | + mark_minutes(1, self.ewma) |
562 | + self.assertTrue( |
563 | + (math.fabs(self.ewma.rate - 0.56130419) < 0.00000001), |
564 | + 'Should have a rate of 0.56130419 events/sec after 1 minute') |
565 | + |
566 | + def test_two_minutes(self): |
567 | + mark_minutes(2, self.ewma) |
568 | + self.assertTrue( |
569 | + (math.fabs(self.ewma.rate - 0.52510399) < 0.00000001), |
570 | + 'Should have a rate of 0.52510399 events/sec after 2 minutes') |
571 | + |
572 | + def test_three_minutes(self): |
573 | + mark_minutes(3, self.ewma) |
574 | + self.assertTrue( |
575 | + (math.fabs(self.ewma.rate - 0.49123845) < 0.00000001), |
576 | + 'Should have a rate of 0.49123845 events/sec after 3 minutes') |
577 | + |
578 | + def test_four_minutes(self): |
579 | + mark_minutes(4, self.ewma) |
580 | + self.assertTrue( |
581 | + (math.fabs(self.ewma.rate - 0.45955700) < 0.00000001), |
582 | + 'Should have a rate of 0.45955700 events/sec after 4 minutes') |
583 | + |
584 | + def test_five_minutes(self): |
585 | + mark_minutes(5, self.ewma) |
586 | + self.assertTrue( |
587 | + (math.fabs(self.ewma.rate - 0.42991879) < 0.00000001), |
588 | + 'Should have a rate of 0.42991879 events/sec after 5 minutes') |
589 | + |
590 | + def test_six_minutes(self): |
591 | + mark_minutes(6, self.ewma) |
592 | + self.assertTrue( |
593 | + (math.fabs(self.ewma.rate - 0.40219203) < 0.00000001), |
594 | + 'Should have a rate of 0.40219203 events/sec after 6 minutes') |
595 | + |
596 | + def test_seven_minutes(self): |
597 | + mark_minutes(7, self.ewma) |
598 | + self.assertTrue( |
599 | + (math.fabs(self.ewma.rate - 0.37625345) < 0.00000001), |
600 | + 'Should have a rate of 0.37625345 events/sec after 7 minutes') |
601 | + |
602 | + def test_eight_minutes(self): |
603 | + mark_minutes(8, self.ewma) |
604 | + self.assertTrue( |
605 | + (math.fabs(self.ewma.rate - 0.35198773) < 0.00000001), |
606 | + 'Should have a rate of 0.35198773 events/sec after 8 minutes') |
607 | + |
608 | + def test_nine_minutes(self): |
609 | + mark_minutes(9, self.ewma) |
610 | + self.assertTrue( |
611 | + (math.fabs(self.ewma.rate - 0.32928698) < 0.00000001), |
612 | + 'Should have a rate of 0.32928698 events/sec after 9 minutes') |
613 | + |
614 | + def test_ten_minutes(self): |
615 | + mark_minutes(10, self.ewma) |
616 | + self.assertTrue( |
617 | + (math.fabs(self.ewma.rate - 0.30805027) < 0.00000001), |
618 | + 'Should have a rate of 0.30805027 events/sec after 10 minutes') |
619 | + |
620 | + def test_eleven_minutes(self): |
621 | + mark_minutes(11, self.ewma) |
622 | + self.assertTrue( |
623 | + (math.fabs(self.ewma.rate - 0.28818318) < 0.00000001), |
624 | + 'Should have a rate of 0.28818318 events/sec after 11 minutes') |
625 | + |
626 | + def test_twelve_minutes(self): |
627 | + mark_minutes(12, self.ewma) |
628 | + self.assertTrue( |
629 | + (math.fabs(self.ewma.rate - 0.26959738) < 0.00000001), |
630 | + 'Should have a rate of 0.26959738 events/sec after 12 minutes') |
631 | + |
632 | + def test_thirteen_minutes(self): |
633 | + mark_minutes(13, self.ewma) |
634 | + self.assertTrue( |
635 | + (math.fabs(self.ewma.rate - 0.25221023) < 0.00000001), |
636 | + 'Should have a rate of 0.25221023 events/sec after 13 minutes') |
637 | + |
638 | + def test_fourteen_minutes(self): |
639 | + mark_minutes(14, self.ewma) |
640 | + self.assertTrue( |
641 | + (math.fabs(self.ewma.rate - 0.23594443) < 0.00000001), |
642 | + 'Should have a rate of 0.23594443 events/sec after 14 minutes') |
643 | + |
644 | + def test_fifteen_minutes(self): |
645 | + mark_minutes(15, self.ewma) |
646 | + self.assertTrue( |
647 | + (math.fabs(self.ewma.rate - 0.22072766) < 0.00000001), |
648 | + 'Should have a rate of 0.22072766 events/sec after 15 minutes') |
649 | |
650 | === modified file 'txstatsd/tests/test_metrics.py' |
651 | --- txstatsd/tests/test_metrics.py 2011-07-03 11:49:05 +0000 |
652 | +++ txstatsd/tests/test_metrics.py 2011-08-26 12:28:49 +0000 |
653 | @@ -32,6 +32,12 @@ |
654 | self.assertEqual(self.connection.data, |
655 | 'txstatsd.tests.gauge:102|g') |
656 | |
657 | + def test_meter(self): |
658 | + """Test reporting of a meter metric sample.""" |
659 | + self.metrics.meter('meter', 3) |
660 | + self.assertEqual(self.connection.data, |
661 | + 'txstatsd.tests.meter:3|m') |
662 | + |
663 | def test_counter(self): |
664 | """Test the increment and decrement operations.""" |
665 | self.metrics.increment('counter', 18) |
666 | |
667 | === modified file 'txstatsd/tests/test_processor.py' |
668 | --- txstatsd/tests/test_processor.py 2011-07-04 03:00:04 +0000 |
669 | +++ txstatsd/tests/test_processor.py 2011-08-26 12:28:49 +0000 |
670 | @@ -1,3 +1,5 @@ |
671 | +import time |
672 | + |
673 | from unittest import TestCase |
674 | |
675 | from txstatsd.server.processor import MessageProcessor |
676 | @@ -224,3 +226,73 @@ |
677 | self.assertEqual( |
678 | "statsd.numStats 1 42", messages[1]) |
679 | self.assertEqual(0, len(self.processor.gauge_metrics)) |
680 | + |
681 | + |
682 | +class FlushMeterMetricMessagesTest(TestCase): |
683 | + |
684 | + def setUp(self): |
685 | + self.processor = MessageProcessor(time_function=self.wall_clock_time) |
686 | + self.time_now = int(time.time()) |
687 | + |
688 | + def wall_clock_time(self): |
689 | + return self.time_now |
690 | + |
691 | + def mark_minutes(self, minutes): |
692 | + for i in range(1, minutes * 60, 5): |
693 | + self.processor.update_metrics() |
694 | + |
695 | + def test_flush_meter_metric(self): |
696 | + """ |
697 | + Test the correct rendering of the Graphite report for |
698 | + a meter metric. |
699 | + """ |
700 | + self.processor.process("gorets:3.0|m") |
701 | + |
702 | + self.time_now += 1 |
703 | + messages = self.processor.flush() |
704 | + self.assertEqual(2, len(messages)) |
705 | + meter_metric = messages[0].splitlines() |
706 | + self.assertEqual( |
707 | + "stats.meter.gorets.count 3.0 %s" % self.time_now, |
708 | + meter_metric[0]) |
709 | + self.assertEqual( |
710 | + "stats.meter.gorets.mean_rate 3.0 %s" % self.time_now, |
711 | + meter_metric[1]) |
712 | + self.assertEqual( |
713 | + "stats.meter.gorets.1min_rate 0.0 %s" % self.time_now, |
714 | + meter_metric[2]) |
715 | + self.assertEqual( |
716 | + "stats.meter.gorets.5min_rate 0.0 %s" % self.time_now, |
717 | + meter_metric[3]) |
718 | + self.assertEqual( |
719 | + "stats.meter.gorets.15min_rate 0.0 %s" % self.time_now, |
720 | + meter_metric[4]) |
721 | + self.assertEqual( |
722 | + "statsd.numStats 1 %s" % self.time_now, messages[1]) |
723 | + |
724 | + # As we are employing the expected results from test_ewma.py |
725 | + # we perform the initial tick(), before advancing the clock 60sec. |
726 | + self.processor.update_metrics() |
727 | + |
728 | + self.mark_minutes(1) |
729 | + self.time_now += 60 |
730 | + messages = self.processor.flush() |
731 | + self.assertEqual(2, len(messages)) |
732 | + meter_metric = messages[0].splitlines() |
733 | + self.assertEqual( |
734 | + "stats.meter.gorets.count 3.0 %s" % self.time_now, |
735 | + meter_metric[0]) |
736 | + self.assertTrue( |
737 | + meter_metric[1].startswith( |
738 | + "stats.meter.gorets.mean_rate 0.04918032")) |
739 | + self.assertTrue( |
740 | + meter_metric[2].startswith( |
741 | + "stats.meter.gorets.1min_rate 0.22072766")) |
742 | + self.assertTrue( |
743 | + meter_metric[3].startswith( |
744 | + "stats.meter.gorets.5min_rate 0.49123845")) |
745 | + self.assertTrue( |
746 | + meter_metric[4].startswith( |
747 | + "stats.meter.gorets.15min_rate 0.5613041")) |
748 | + self.assertEqual( |
749 | + "statsd.numStats 1 %s" % self.time_now, messages[1]) |
750 | |
751 | === modified file 'txstatsd/version.py' |
752 | --- txstatsd/version.py 2011-08-23 02:05:21 +0000 |
753 | +++ txstatsd/version.py 2011-08-26 12:28:49 +0000 |
754 | @@ -1,1 +1,1 @@ |
755 | -txstatsd = "0.1.3" |
756 | +txstatsd = "0.2.0" |
im under the impression that those would be better implemented as functions on the graphite side, but other than that looks good to me. +1