Merge lp:~theiw/txstatsd/txstatsd-metermetric into lp:txstatsd

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
Reviewer Review Type Date Requested Status
Sidnei da Silva Approve
Lucio Torre (community) Approve
Review via email: mp+73032@code.launchpad.net

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
Revision history for this message
Sidnei da Silva (sidnei) wrote :

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

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"

Subscribers

People subscribed via source and target branches