Merge lp:~theiw/txstatsd/txstatsd-logging-processor into lp:txstatsd

Proposed by Ian Wilkinson
Status: Merged
Approved by: Ian Wilkinson
Approved revision: 41
Merged at revision: 41
Proposed branch: lp:~theiw/txstatsd/txstatsd-logging-processor
Merge into: lp:txstatsd
Diff against target: 269 lines (+149/-22)
8 files modified
requirements.txt (+5/-0)
txstatsd/metrics/gaugemetric.py (+32/-0)
txstatsd/server/configurableprocessor.py (+21/-14)
txstatsd/server/loggingprocessor.py (+37/-0)
txstatsd/server/processor.py (+7/-2)
txstatsd/service.py (+1/-5)
txstatsd/tests/test_loggingprocessor.py (+45/-0)
txstatsd/version.py (+1/-1)
To merge this branch: bzr merge lp:~theiw/txstatsd/txstatsd-logging-processor
Reviewer Review Type Date Requested Status
Ian Wilkinson (community) Approve
Lucio Torre (community) Approve
Review via email: mp+77790@code.launchpad.net

Commit message

Support recording metric samples to a log file.

Now includes the GaugeMetricReporter.

Description of the change

Support recording metric samples to a log file.

Now includes the GaugeMetricReporter.

To post a comment you must log in.
Revision history for this message
Lucio Torre (lucio.torre) :
review: Approve
Revision history for this message
Ubuntu One Server Tarmac Bot (ubuntuone-server-tarmac) wrote :
Download full text (14.9 KiB)

The attempt to merge lp:~theiw/txstatsd/txstatsd-logging-processor into lp:txstatsd failed. Below is the output from the failed tests.

txstatsd.tests.metrics.test_histogrammetric
  TestHistogramReporterMetric
    test_histogram_of_numbers_1_through_10000 ... [OK]
    test_histogram_with_zero_recorded_values ... [OK]
txstatsd.tests.metrics.test_timermetric
  TestBlankTimerMetric
    test_count ... [OK]
    test_fifteen_minute_rate ... [OK]
    test_five_minute_rate ... [OK]
    test_max ... [OK]
    test_mean ... [OK]
    test_mean_rate ... [OK]
    test_min ... [OK]
    test_no_values ... [OK]
    test_one_minute_rate ... [OK]
    test_percentiles ... [OK]
    test_std_dev ... [OK]
  TestTimingSeriesEvents
    test_count ... [OK]
    test_max ... [OK]
    test_mean ... [OK]
    test_min ... [OK]
    test_percentiles ... [OK]
    test_std_dev ... [OK]
    test_values ... [OK]
txstatsd.tests.stats.test_ewma
  TestEwmaFifteenMinute
    test_eight_minutes ... [OK]
    test_eleven_minutes ... [OK]
    test_fifteen_minutes ... [OK]
    test_first_tick ... [OK]
    test_five_minutes ... [OK]
    test_four_minutes ... [OK]
    test_fourteen_minutes ... [OK]
    test_nine_minutes ... [OK]
    test_one_minute ... [OK]
    test_seven_minutes ... [OK]
    test_six_minutes ... [OK]
    test_ten_minutes ... [OK]
    test_thirteen_minutes ... [OK]
    test_three_minutes ... [OK]
    test_twelve_minutes ... [OK]
    test_two_minutes ... ...

Revision history for this message
Ian Wilkinson (theiw) wrote :

Tests changed to be python 2.6 friendly.

review: Approve
Revision history for this message
Ubuntu One Server Tarmac Bot (ubuntuone-server-tarmac) wrote :

There are additional revisions which have not been approved in review. Please seek review and approval of these new revisions.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'requirements.txt'
2--- requirements.txt 1970-01-01 00:00:00 +0000
3+++ requirements.txt 2011-10-05 16:03:23 +0000
4@@ -0,0 +1,5 @@
5+Twisted==11.0.0
6+mocker==1.1
7+psutil==0.3.0
8+wsgiref==0.1.2
9+zope.interface==3.8.0
10
11=== modified file 'txstatsd/metrics/gaugemetric.py'
12--- txstatsd/metrics/gaugemetric.py 2011-07-03 11:49:05 +0000
13+++ txstatsd/metrics/gaugemetric.py 2011-10-05 16:03:23 +0000
14@@ -1,4 +1,6 @@
15
16+from string import Template
17+
18 from txstatsd.metrics.metric import Metric
19
20
21@@ -20,3 +22,33 @@
22 def mark(self, value):
23 """Report the C{value} for this gauge."""
24 self.send("%s|g" % value)
25+
26+
27+class GaugeMetricReporter(object):
28+ """A gauge metric is an instantaneous reading of a particular value."""
29+
30+ MESSAGE = (
31+ "$prefix%(key)s.value %(value)s %(timestamp)s\n")
32+
33+ def __init__(self, name, prefix=""):
34+ """Construct a metric we expect to be periodically updated.
35+
36+ @param name: Indicates what is being instrumented.
37+ """
38+ self.name = name
39+
40+ if prefix:
41+ prefix += '.'
42+ self.message = Template(GaugeMetricReporter.MESSAGE).substitute(
43+ prefix=prefix)
44+
45+ self.value = 0
46+
47+ def mark(self, value):
48+ self.value = value
49+
50+ def report(self, timestamp):
51+ return self.message % {
52+ "key": self.name,
53+ "value": self.value,
54+ "timestamp": timestamp}
55
56=== modified file 'txstatsd/server/configurableprocessor.py'
57--- txstatsd/server/configurableprocessor.py 2011-09-14 12:01:10 +0000
58+++ txstatsd/server/configurableprocessor.py 2011-10-05 16:03:23 +0000
59@@ -1,9 +1,8 @@
60
61-from string import Template
62-
63 import time
64
65 from txstatsd.metrics.countermetric import CounterMetricReporter
66+from txstatsd.metrics.gaugemetric import GaugeMetricReporter
67 from txstatsd.metrics.metermetric import MeterMetricReporter
68 from txstatsd.metrics.timermetric import TimerMetricReporter
69 from txstatsd.server.processor import MessageProcessor
70@@ -17,26 +16,18 @@
71 Currently, this extends to:
72 - Allow a prefix to be added to the composed messages sent
73 to the Graphite server.
74+ - Report an instantaneous reading of a particular value.
75 - Report an incrementing and decrementing counter metric.
76 - Report a timer metric which aggregates timing durations and provides
77 duration statistics, plus throughput statistics.
78 """
79
80- #TODO Provide a GaugeMetricReporter (in the manner of the other
81- # Coda Hale metrics).
82- GAUGE_METRIC_MESSAGE = (
83- "$prefix%(key)s.value %(value)s %(timestamp)s\n")
84-
85 def __init__(self, time_function=time.time, message_prefix=""):
86- super(ConfigurableMessageProcessor, self).__init__(time_function)
87+ super(ConfigurableMessageProcessor, self).__init__(
88+ time_function=time_function)
89
90 self.message_prefix = message_prefix
91- if message_prefix:
92- message_prefix += '.'
93-
94- message = Template(ConfigurableMessageProcessor.GAUGE_METRIC_MESSAGE)
95- self.gauge_metric_message = message.substitute(
96- prefix=message_prefix)
97+ self.gauge_metrics = {}
98
99 def compose_timer_metric(self, key, duration):
100 if not key in self.timer_metrics:
101@@ -58,6 +49,12 @@
102 self.counter_metrics[key] = metric
103 self.counter_metrics[key].mark(value)
104
105+ def compose_gauge_metric(self, key, value):
106+ if not key in self.gauge_metrics:
107+ metric = GaugeMetricReporter(key, prefix=self.message_prefix)
108+ self.gauge_metrics[key] = metric
109+ self.gauge_metrics[key].mark(value)
110+
111 def compose_meter_metric(self, key, value):
112 if not key in self.meter_metrics:
113 metric = MeterMetricReporter(key, self.time_function,
114@@ -75,6 +72,16 @@
115
116 return (metrics, events)
117
118+ def flush_gauge_metrics(self, timestamp):
119+ metrics = []
120+ events = 0
121+ for metric in self.gauge_metrics.itervalues():
122+ message = metric.report(timestamp)
123+ metrics.append(message)
124+ events += 1
125+
126+ return (metrics, events)
127+
128 def flush_timer_metrics(self, percent, timestamp):
129 metrics = []
130 events = 0
131
132=== added file 'txstatsd/server/loggingprocessor.py'
133--- txstatsd/server/loggingprocessor.py 1970-01-01 00:00:00 +0000
134+++ txstatsd/server/loggingprocessor.py 2011-10-05 16:03:23 +0000
135@@ -0,0 +1,37 @@
136+
137+import time
138+
139+from txstatsd.server.configurableprocessor import ConfigurableMessageProcessor
140+
141+
142+class LoggingMessageProcessor(ConfigurableMessageProcessor):
143+ """
144+ This specialised C{MessageProcessor} logs the received metrics
145+ using the supplied logger (which should have a callable C{info}
146+ attribute.)
147+ """
148+
149+ def __init__(self, logger, time_function=time.time, message_prefix=""):
150+ super(LoggingMessageProcessor, self).__init__(
151+ time_function=time_function, message_prefix=message_prefix)
152+
153+ logger_info = getattr(logger, 'info', None)
154+ if logger_info is None or not callable(logger_info):
155+ raise TypeError()
156+ self.logger = logger
157+
158+ def flush(self):
159+ """Log all received metric samples to the supplied log file."""
160+ timestamp = int(self.time_function())
161+
162+ def log_metrics(metrics):
163+ for metric in metrics.itervalues():
164+ report = metric.report(timestamp)
165+ for measurement in report.splitlines():
166+ self.logger.info(measurement)
167+
168+ log_metrics(self.counter_metrics)
169+ log_metrics(self.gauge_metrics)
170+ log_metrics(self.meter_metrics)
171+ log_metrics(self.timer_metrics)
172+
173
174=== modified file 'txstatsd/server/processor.py'
175--- txstatsd/server/processor.py 2011-09-14 12:01:10 +0000
176+++ txstatsd/server/processor.py 2011-10-05 16:03:23 +0000
177@@ -132,11 +132,16 @@
178 return self.fail(message)
179
180 try:
181- metric = [float(values[0]), key]
182- self.gauge_metrics.append(metric)
183+ value = float(values[0])
184 except (TypeError, ValueError):
185 self.fail(message)
186
187+ self.compose_gauge_metric(key, value)
188+
189+ def compose_gauge_metric(self, key, value):
190+ metric = [value, key]
191+ self.gauge_metrics.append(metric)
192+
193 def process_meter_metric(self, key, composite, message):
194 values = composite.split(":")
195 if not len(values) == 1:
196
197=== modified file 'txstatsd/service.py'
198--- txstatsd/service.py 2011-09-29 08:50:43 +0000
199+++ txstatsd/service.py 2011-10-05 16:03:23 +0000
200@@ -34,11 +34,7 @@
201
202
203 class OptionsGlue(usage.Options):
204- """Extends usage.Options to also read parameters from a config file.
205-
206- This is made far more complicated than should be necessary, as
207- usage.Options does not appear to have been designed for extension.
208- """
209+ """Extends usage.Options to also read parameters from a config file."""
210
211 optParameters = [
212 ["config", "c", None, "Config file to use."]
213
214=== added file 'txstatsd/tests/test_loggingprocessor.py'
215--- txstatsd/tests/test_loggingprocessor.py 1970-01-01 00:00:00 +0000
216+++ txstatsd/tests/test_loggingprocessor.py 2011-10-05 16:03:23 +0000
217@@ -0,0 +1,45 @@
218+
219+from unittest import TestCase
220+
221+from txstatsd.server.loggingprocessor import LoggingMessageProcessor
222+
223+
224+class FakeMeterMetric(object):
225+ def report(self, *args):
226+ return 'Sample report'
227+
228+
229+class TestLoggingMessageProcessor(TestCase):
230+
231+ def test_logger_with_no_info(self):
232+ def invoker():
233+ logger = 'logger'
234+ LoggingMessageProcessor(logger)
235+
236+ self.assertRaises(TypeError, invoker)
237+
238+ def test_logger_with_non_callable_info(self):
239+ def invoker():
240+ class Logger(object):
241+ def __init__(self):
242+ self.info = 'logger'
243+
244+ logger = Logger()
245+ LoggingMessageProcessor(logger)
246+
247+ self.assertRaises(TypeError, invoker)
248+
249+ def test_logger(self):
250+ class Logger(object):
251+ def __init__(self):
252+ self.log = ''
253+
254+ def info(self, measurement):
255+ self.log += measurement
256+
257+ logger = Logger()
258+ processor = LoggingMessageProcessor(logger)
259+ metric = FakeMeterMetric()
260+ processor.meter_metrics['test'] = metric
261+ processor.flush()
262+ self.assertEqual(metric.report(), logger.log)
263
264=== modified file 'txstatsd/version.py'
265--- txstatsd/version.py 2011-09-29 08:50:43 +0000
266+++ txstatsd/version.py 2011-10-05 16:03:23 +0000
267@@ -1,1 +1,1 @@
268-txstatsd = "0.5.1"
269+txstatsd = "0.6.0"

Subscribers

People subscribed via source and target branches