Merge lp:~theiw/txstatsd/txstatsd-configurable into lp:txstatsd
- txstatsd-configurable
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Ian Wilkinson |
Approved revision: | 32 |
Merged at revision: | 32 |
Proposed branch: | lp:~theiw/txstatsd/txstatsd-configurable |
Merge into: | lp:txstatsd |
Diff against target: |
424 lines (+205/-31) 8 files modified
txstatsd.conf-example (+5/-1) txstatsd/metrics/metermetric.py (+12/-7) txstatsd/server/configurableprocessor.py (+63/-0) txstatsd/server/processor.py (+35/-17) txstatsd/service.py (+13/-4) txstatsd/tests/test_client.py (+1/-1) txstatsd/tests/test_processor.py (+75/-0) txstatsd/version.py (+1/-1) |
To merge this branch: | bzr merge lp:~theiw/txstatsd/txstatsd-configurable |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ian Wilkinson (community) | Approve | ||
Sidnei da Silva | Approve | ||
Review via email: mp+73771@code.launchpad.net |
Commit message
Description of the change
Introducing the specialised ConfigurableMes
Ubuntu One Server Tarmac Bot (ubuntuone-server-tarmac) 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 Pending.
Ubuntu One Server Tarmac Bot (ubuntuone-server-tarmac) wrote : | # |
The attempt to merge lp:~theiw/txstatsd/txstatsd-configurable into lp:txstatsd failed. Below is the output from the failed tests.
txstatsd.
TestEwmaFifte
test_
test_
test_
test_first_tick ... [OK]
test_
test_
test_
test_
test_one_minute ... [OK]
test_
test_
test_
test_
test_
test_
test_
TestEwmaFiveM
test_
test_
test_
test_first_tick ... [OK]
test_
test_
test_
test_
test_one_minute ... [OK]
test_
test_
test_
test_
test_
test_
test_
TestEwmaOneMinute
test_
test_
test_
test_first_tick ... [OK]
test_
test_four...
Ubuntu One Server Tarmac Bot (ubuntuone-server-tarmac) wrote : | # |
The attempt to merge lp:~theiw/txstatsd/txstatsd-configurable into lp:txstatsd failed. Below is the output from the failed tests.
txstatsd.
TestEwmaFifte
test_
test_
test_
test_first_tick ... [OK]
test_
test_
test_
test_
test_one_minute ... [OK]
test_
test_
test_
test_
test_
test_
test_
TestEwmaFiveM
test_
test_
test_
test_first_tick ... [OK]
test_
test_
test_
test_
test_one_minute ... [OK]
test_
test_
test_
test_
test_
test_
test_
TestEwmaOneMinute
test_
test_
test_
test_first_tick ... [OK]
test_
test_four...
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.
Ubuntu One Server Tarmac Bot (ubuntuone-server-tarmac) wrote : | # |
The attempt to merge lp:~theiw/txstatsd/txstatsd-configurable into lp:txstatsd failed. Below is the output from the failed tests.
txstatsd.
TestEwmaFifte
test_
test_
test_
test_first_tick ... [OK]
test_
test_
test_
test_
test_one_minute ... [OK]
test_
test_
test_
test_
test_
test_
test_
TestEwmaFiveM
test_
test_
test_
test_first_tick ... [OK]
test_
test_
test_
test_
test_one_minute ... [OK]
test_
test_
test_
test_
test_
test_
test_
TestEwmaOneMinute
test_
test_
test_
test_first_tick ... [OK]
test_
test_four...
Ubuntu One Server Tarmac Bot (ubuntuone-server-tarmac) wrote : | # |
The attempt to merge lp:~theiw/txstatsd/txstatsd-configurable into lp:txstatsd failed. Below is the output from the failed tests.
txstatsd.
TestEwmaFifte
test_
test_
test_
test_first_tick ... [OK]
test_
test_
test_
test_
test_one_minute ... [OK]
test_
test_
test_
test_
test_
test_
test_
TestEwmaFiveM
test_
test_
test_
test_first_tick ... [OK]
test_
test_
test_
test_
test_one_minute ... [OK]
test_
test_
test_
test_
test_
test_
test_
TestEwmaOneMinute
test_
test_
test_
test_first_tick ... [OK]
test_
test_four...
Preview Diff
1 | === modified file 'txstatsd.conf-example' |
2 | --- txstatsd.conf-example 2011-08-31 10:45:16 +0000 |
3 | +++ txstatsd.conf-example 2011-09-08 12:54:40 +0000 |
4 | @@ -5,15 +5,19 @@ |
5 | carbon-cache-port: 2003 |
6 | # The UDP port where we will listen. |
7 | listen-port: 8125 |
8 | + |
9 | # The number of milliseconds between each flush. |
10 | flush-interval: 60000 |
11 | + |
12 | # Which additional stats to report {process|net|io|system}. |
13 | report: |
14 | # Prefix to use when reporting stats. |
15 | prefix: |
16 | +# Produce StatsD-compliant messages. |
17 | +statsd-compliance: 1 |
18 | + |
19 | # Support application monitoring. UDP echo is initially supported. |
20 | # Should we receive the monitor-message, we respond with the |
21 | # configured monitor-response. |
22 | monitor-message: txstatsd ping |
23 | monitor-response: txstatsd pong |
24 | - |
25 | |
26 | === modified file 'txstatsd/metrics/metermetric.py' |
27 | --- txstatsd/metrics/metermetric.py 2011-08-25 17:41:37 +0000 |
28 | +++ txstatsd/metrics/metermetric.py 2011-09-08 12:54:40 +0000 |
29 | @@ -1,4 +1,6 @@ |
30 | |
31 | +from string import Template |
32 | + |
33 | import time |
34 | |
35 | from txstatsd.metrics.metric import Metric |
36 | @@ -43,13 +45,13 @@ |
37 | """ |
38 | |
39 | MESSAGE = ( |
40 | - "stats.meter.%(key)s.count %(count)s %(timestamp)s\n" |
41 | - "stats.meter.%(key)s.mean_rate %(mean_rate)s %(timestamp)s\n" |
42 | - "stats.meter.%(key)s.1min_rate %(rate_1min)s %(timestamp)s\n" |
43 | - "stats.meter.%(key)s.5min_rate %(rate_5min)s %(timestamp)s\n" |
44 | - "stats.meter.%(key)s.15min_rate %(rate_15min)s %(timestamp)s\n") |
45 | + "$prefix%(key)s.count %(count)s %(timestamp)s\n" |
46 | + "$prefix%(key)s.mean_rate %(mean_rate)s %(timestamp)s\n" |
47 | + "$prefix%(key)s.1min_rate %(rate_1min)s %(timestamp)s\n" |
48 | + "$prefix%(key)s.5min_rate %(rate_5min)s %(timestamp)s\n" |
49 | + "$prefix%(key)s.15min_rate %(rate_15min)s %(timestamp)s\n") |
50 | |
51 | - def __init__(self, name, wall_time_func=time.time): |
52 | + def __init__(self, name, wall_time_func=time.time, prefix=""): |
53 | """Construct a metric we expect to be periodically updated. |
54 | |
55 | @param name: Indicates what is being instrumented. |
56 | @@ -58,6 +60,9 @@ |
57 | self.name = name |
58 | self.wall_time_func = wall_time_func |
59 | |
60 | + self.message = Template(MeterMetricReporter.MESSAGE).substitute( |
61 | + prefix=prefix + '.') |
62 | + |
63 | self.m1_rate = Ewma.one_minute_ewma() |
64 | self.m5_rate = Ewma.five_minute_ewma() |
65 | self.m15_rate = Ewma.fifteen_minute_ewma() |
66 | @@ -78,7 +83,7 @@ |
67 | self.m15_rate.tick() |
68 | |
69 | def report(self, timestamp): |
70 | - return MeterMetricReporter.MESSAGE % { |
71 | + return self.message % { |
72 | "key": self.name, |
73 | "count": self.count, |
74 | "mean_rate": self.mean_rate(), |
75 | |
76 | === added file 'txstatsd/server/configurableprocessor.py' |
77 | --- txstatsd/server/configurableprocessor.py 1970-01-01 00:00:00 +0000 |
78 | +++ txstatsd/server/configurableprocessor.py 2011-09-08 12:54:40 +0000 |
79 | @@ -0,0 +1,63 @@ |
80 | + |
81 | +from string import Template |
82 | + |
83 | +import time |
84 | + |
85 | +from txstatsd.metrics.metermetric import MeterMetricReporter |
86 | +from txstatsd.server.processor import MessageProcessor |
87 | + |
88 | + |
89 | +class ConfigurableMessageProcessor(MessageProcessor): |
90 | + """ |
91 | + This specialised C{MessageProcessor} supports behaviour |
92 | + that is not StatsD-compliant. |
93 | + Currently, this extends to: |
94 | + - Allow a prefix to be added to the composed messages sent |
95 | + to the Graphite server. |
96 | + """ |
97 | + |
98 | + # Notice: These messages replicate those seen in the |
99 | + # MessageProcessor (excepting the prefix identifier). |
100 | + # In a future release they will be placed in their |
101 | + # respective metric reporter class. |
102 | + # See MeterMetricReporter. |
103 | + COUNTERS_MESSAGE = ( |
104 | + "$prefix%(key)s %(value)s %(timestamp)s\n" |
105 | + "$prefix%(key)s %(count)s %(timestamp)s\n") |
106 | + |
107 | + TIMERS_MESSAGE = ( |
108 | + "$prefix%(key)s.mean %(mean)s %(timestamp)s\n" |
109 | + "$prefix%(key)s.upper %(upper)s %(timestamp)s\n" |
110 | + "$prefix%(key)s.upper_%(percent)s %(threshold_upper)s" |
111 | + " %(timestamp)s\n" |
112 | + "$prefix%(key)s.lower %(lower)s %(timestamp)s\n" |
113 | + "$prefix%(key)s.count %(count)s %(timestamp)s\n") |
114 | + |
115 | + GAUGE_METRIC_MESSAGE = ( |
116 | + "$prefix%(key)s.value %(value)s %(timestamp)s\n") |
117 | + |
118 | + def __init__(self, time_function=time.time, message_prefix=""): |
119 | + super(ConfigurableMessageProcessor, self).__init__(time_function) |
120 | + |
121 | + self.message_prefix = message_prefix |
122 | + if message_prefix: |
123 | + message_prefix += '.' |
124 | + |
125 | + message = Template(ConfigurableMessageProcessor.COUNTERS_MESSAGE) |
126 | + self.counters_message = message.substitute( |
127 | + prefix=message_prefix) |
128 | + |
129 | + message = Template(ConfigurableMessageProcessor.TIMERS_MESSAGE) |
130 | + self.timers_message = message.substitute( |
131 | + prefix=message_prefix) |
132 | + |
133 | + message = Template(ConfigurableMessageProcessor.GAUGE_METRIC_MESSAGE) |
134 | + self.gauge_metric_message = message.substitute( |
135 | + prefix=message_prefix) |
136 | + |
137 | + def compose_meter_metric(self, key, value): |
138 | + if not key in self.meter_metrics: |
139 | + metric = MeterMetricReporter(key, self.time_function, |
140 | + prefix=self.message_prefix) |
141 | + self.meter_metrics[key] = metric |
142 | + self.meter_metrics[key].mark(value) |
143 | |
144 | === modified file 'txstatsd/server/processor.py' |
145 | --- txstatsd/server/processor.py 2011-08-25 17:41:37 +0000 |
146 | +++ txstatsd/server/processor.py 2011-09-08 12:54:40 +0000 |
147 | @@ -12,19 +12,6 @@ |
148 | SLASHES = re.compile("\/+") |
149 | NON_ALNUM = re.compile("[^a-zA-Z_\-0-9\.]") |
150 | RATE = re.compile("^@([\d\.]+)") |
151 | -COUNTERS_MESSAGE = ( |
152 | - "stats.%(key)s %(value)s %(timestamp)s\n" |
153 | - "stats_counts.%(key)s %(count)s %(timestamp)s\n") |
154 | -TIMERS_MESSAGE = ( |
155 | - "stats.timers.%(key)s.mean %(mean)s %(timestamp)s\n" |
156 | - "stats.timers.%(key)s.upper %(upper)s %(timestamp)s\n" |
157 | - "stats.timers.%(key)s.upper_%(percent)s %(threshold_upper)s" |
158 | - " %(timestamp)s\n" |
159 | - "stats.timers.%(key)s.lower %(lower)s %(timestamp)s\n" |
160 | - "stats.timers.%(key)s.count %(count)s %(timestamp)s\n") |
161 | - |
162 | -GAUGE_METRIC_MESSAGE = ( |
163 | - "stats.gauge.%(key)s.value %(value)s %(timestamp)s\n") |
164 | |
165 | |
166 | def normalize_key(key): |
167 | @@ -39,9 +26,36 @@ |
168 | |
169 | |
170 | class MessageProcessor(object): |
171 | + """ |
172 | + This C{MessageProcessor} produces StatsD-compliant messages |
173 | + for publishing to a Graphite server. |
174 | + Metrics behaviour that varies from StatsD should be placed in |
175 | + some specialised C{MessageProcessor} (see L{ConfigurableMessageProcessor |
176 | + <txstatsd.server.configurableprocessor.ConfigurableMessageProcessor>}). |
177 | + """ |
178 | + |
179 | + COUNTERS_MESSAGE = ( |
180 | + "stats.%(key)s %(value)s %(timestamp)s\n" |
181 | + "stats_counts.%(key)s %(count)s %(timestamp)s\n") |
182 | + |
183 | + TIMERS_MESSAGE = ( |
184 | + "stats.timers.%(key)s.mean %(mean)s %(timestamp)s\n" |
185 | + "stats.timers.%(key)s.upper %(upper)s %(timestamp)s\n" |
186 | + "stats.timers.%(key)s.upper_%(percent)s %(threshold_upper)s" |
187 | + " %(timestamp)s\n" |
188 | + "stats.timers.%(key)s.lower %(lower)s %(timestamp)s\n" |
189 | + "stats.timers.%(key)s.count %(count)s %(timestamp)s\n") |
190 | + |
191 | + GAUGE_METRIC_MESSAGE = ( |
192 | + "stats.gauge.%(key)s.value %(value)s %(timestamp)s\n") |
193 | |
194 | def __init__(self, time_function=time.time): |
195 | self.time_function = time_function |
196 | + |
197 | + self.counters_message = MessageProcessor.COUNTERS_MESSAGE |
198 | + self.timers_message = MessageProcessor.TIMERS_MESSAGE |
199 | + self.gauge_metric_message = MessageProcessor.GAUGE_METRIC_MESSAGE |
200 | + |
201 | self.timer_metrics = {} |
202 | self.counter_metrics = {} |
203 | self.gauge_metrics = deque() |
204 | @@ -125,8 +139,12 @@ |
205 | except (TypeError, ValueError): |
206 | self.fail(message) |
207 | |
208 | + self.compose_meter_metric(key, value) |
209 | + |
210 | + def compose_meter_metric(self, key, value): |
211 | if not key in self.meter_metrics: |
212 | - metric = MeterMetricReporter(key, self.time_function) |
213 | + metric = MeterMetricReporter(key, self.time_function, |
214 | + prefix="stats.meter") |
215 | self.meter_metrics[key] = metric |
216 | self.meter_metrics[key].mark(value) |
217 | |
218 | @@ -171,7 +189,7 @@ |
219 | self.counter_metrics[key] = 0 |
220 | |
221 | value = count / interval |
222 | - message = COUNTERS_MESSAGE % { |
223 | + message = self.counters_message % { |
224 | "key": key, |
225 | "value": value, |
226 | "count": count, |
227 | @@ -205,7 +223,7 @@ |
228 | threshold_upper = timers[-1] |
229 | mean = sum(timers) / index |
230 | |
231 | - message = TIMERS_MESSAGE % { |
232 | + message = self.timers_message % { |
233 | "key": key, |
234 | "mean": mean, |
235 | "upper": upper, |
236 | @@ -226,7 +244,7 @@ |
237 | value = metric[0] |
238 | key = metric[1] |
239 | |
240 | - message = GAUGE_METRIC_MESSAGE % { |
241 | + message = self.gauge_metric_message % { |
242 | "key": key, |
243 | "value": value, |
244 | "timestamp": timestamp} |
245 | |
246 | === modified file 'txstatsd/service.py' |
247 | --- txstatsd/service.py 2011-09-01 09:45:05 +0000 |
248 | +++ txstatsd/service.py 2011-09-08 12:54:40 +0000 |
249 | @@ -10,6 +10,7 @@ |
250 | from txstatsd.client import InternalClient |
251 | from txstatsd.metrics.metrics import Metrics |
252 | from txstatsd.server.processor import MessageProcessor |
253 | +from txstatsd.server.configurableprocessor import ConfigurableMessageProcessor |
254 | from txstatsd.server.protocol import GraphiteClientFactory, StatsDServerProtocol |
255 | from txstatsd.report import ReportingService |
256 | |
257 | @@ -93,7 +94,9 @@ |
258 | ["monitor-message", "m", "txstatsd ping", |
259 | "Message we expect from monitoring agent.", str], |
260 | ["monitor-response", "o", "txstatsd pong", |
261 | - "Response we should send monitoring agent.", str] |
262 | + "Response we should send monitoring agent.", str], |
263 | + ["statsd-compliance", "s", 1, |
264 | + "Produce StatsD-compliant messages.", int] |
265 | ] |
266 | |
267 | |
268 | @@ -102,13 +105,19 @@ |
269 | |
270 | service = MultiService() |
271 | service.setName("statsd") |
272 | - processor = MessageProcessor() |
273 | + |
274 | prefix = options["prefix"] |
275 | if prefix is None: |
276 | prefix = socket.gethostname() + ".statsd" |
277 | |
278 | - connection = InternalClient(processor) |
279 | - metrics = Metrics(connection, namespace=prefix) |
280 | + if options["statsd-compliance"]: |
281 | + processor = MessageProcessor() |
282 | + connection = InternalClient(processor) |
283 | + metrics = Metrics(connection, namespace=prefix) |
284 | + else: |
285 | + processor = ConfigurableMessageProcessor(message_prefix=prefix) |
286 | + connection = InternalClient(processor) |
287 | + metrics = Metrics(connection) |
288 | |
289 | if options["report"] is not None: |
290 | reporting = ReportingService() |
291 | |
292 | === modified file 'txstatsd/tests/test_client.py' |
293 | --- txstatsd/tests/test_client.py 2011-09-06 13:25:16 +0000 |
294 | +++ txstatsd/tests/test_client.py 2011-09-08 12:54:40 +0000 |
295 | @@ -20,7 +20,7 @@ |
296 | super(TestClient, self).tearDown() |
297 | |
298 | def test_twistedstatsd_write_with_wellformed_address(self): |
299 | - self.client = TwistedStatsDClient('localhost', 8000) |
300 | + self.client = TwistedStatsDClient('127.0.0.1', 8000) |
301 | protocol = StatsDClientProtocol(self.client) |
302 | reactor.listenUDP(0, protocol) |
303 | |
304 | |
305 | === modified file 'txstatsd/tests/test_processor.py' |
306 | --- txstatsd/tests/test_processor.py 2011-08-26 12:16:45 +0000 |
307 | +++ txstatsd/tests/test_processor.py 2011-09-08 12:54:40 +0000 |
308 | @@ -3,6 +3,7 @@ |
309 | from unittest import TestCase |
310 | |
311 | from txstatsd.server.processor import MessageProcessor |
312 | +from txstatsd.server.configurableprocessor import ConfigurableMessageProcessor |
313 | |
314 | |
315 | class TestMessageProcessor(MessageProcessor): |
316 | @@ -15,6 +16,17 @@ |
317 | self.failures.append(message) |
318 | |
319 | |
320 | +class TestConfigurableMessageProcessor(ConfigurableMessageProcessor): |
321 | + |
322 | + def __init__(self, time_function=time.time, message_prefix=""): |
323 | + super(TestConfigurableMessageProcessor, self).__init__( |
324 | + time_function, |
325 | + message_prefix) |
326 | + |
327 | + def fail(self, message): |
328 | + self.failures.append(message) |
329 | + |
330 | + |
331 | class ProcessMessagesTest(TestCase): |
332 | |
333 | def setUp(self): |
334 | @@ -133,6 +145,38 @@ |
335 | self.assertEqual("statsd.numStats 1 42", messages[1]) |
336 | self.assertEqual(0, self.processor.counter_metrics["gorets"]) |
337 | |
338 | + def test_flush_counter_with_empty_prefix(self): |
339 | + """ |
340 | + Ensure no prefix features if none is supplied. |
341 | + """ |
342 | + configurable_processor = ConfigurableMessageProcessor( |
343 | + time_function=lambda: 42) |
344 | + configurable_processor.counter_metrics["gorets"] = 42 |
345 | + messages = configurable_processor.flush() |
346 | + self.assertEqual(2, len(messages)) |
347 | + counters = messages[0].splitlines() |
348 | + self.assertEqual("gorets 4 42", counters[0]) |
349 | + self.assertEqual("gorets 42 42", counters[1]) |
350 | + self.assertEqual("statsd.numStats 1 42", messages[1]) |
351 | + self.assertEqual( |
352 | + 0, configurable_processor.counter_metrics["gorets"]) |
353 | + |
354 | + def test_flush_counter_with_prefix(self): |
355 | + """ |
356 | + Ensure the prefix features if one is supplied. |
357 | + """ |
358 | + configurable_processor = ConfigurableMessageProcessor( |
359 | + time_function=lambda: 42, message_prefix="test.metric") |
360 | + configurable_processor.counter_metrics["gorets"] = 42 |
361 | + messages = configurable_processor.flush() |
362 | + self.assertEqual(2, len(messages)) |
363 | + counters = messages[0].splitlines() |
364 | + self.assertEqual("test.metric.gorets 4 42", counters[0]) |
365 | + self.assertEqual("test.metric.gorets 42 42", counters[1]) |
366 | + self.assertEqual("statsd.numStats 1 42", messages[1]) |
367 | + self.assertEqual( |
368 | + 0, configurable_processor.counter_metrics["gorets"]) |
369 | + |
370 | def test_flush_counter_one_second_interval(self): |
371 | """ |
372 | It is possible to flush counters with a one-second interval, in which |
373 | @@ -232,6 +276,8 @@ |
374 | |
375 | def setUp(self): |
376 | self.processor = MessageProcessor(time_function=self.wall_clock_time) |
377 | + self.configurable_processor = ConfigurableMessageProcessor( |
378 | + time_function=self.wall_clock_time, message_prefix="test.metric") |
379 | self.time_now = int(time.time()) |
380 | |
381 | def wall_clock_time(self): |
382 | @@ -241,6 +287,35 @@ |
383 | for i in range(1, minutes * 60, 5): |
384 | self.processor.update_metrics() |
385 | |
386 | + def test_flush_meter_metric_with_prefix(self): |
387 | + """ |
388 | + Test the correct rendering of the Graphite report for |
389 | + a meter metric when a prefix is supplied. |
390 | + """ |
391 | + self.configurable_processor.process("gorets:3.0|m") |
392 | + |
393 | + self.time_now += 1 |
394 | + messages = self.configurable_processor.flush() |
395 | + self.assertEqual(2, len(messages)) |
396 | + meter_metric = messages[0].splitlines() |
397 | + self.assertEqual( |
398 | + "test.metric.gorets.count 3.0 %s" % self.time_now, |
399 | + meter_metric[0]) |
400 | + self.assertEqual( |
401 | + "test.metric.gorets.mean_rate 3.0 %s" % self.time_now, |
402 | + meter_metric[1]) |
403 | + self.assertEqual( |
404 | + "test.metric.gorets.1min_rate 0.0 %s" % self.time_now, |
405 | + meter_metric[2]) |
406 | + self.assertEqual( |
407 | + "test.metric.gorets.5min_rate 0.0 %s" % self.time_now, |
408 | + meter_metric[3]) |
409 | + self.assertEqual( |
410 | + "test.metric.gorets.15min_rate 0.0 %s" % self.time_now, |
411 | + meter_metric[4]) |
412 | + self.assertEqual( |
413 | + "statsd.numStats 1 %s" % self.time_now, messages[1]) |
414 | + |
415 | def test_flush_meter_metric(self): |
416 | """ |
417 | Test the correct rendering of the Graphite report for |
418 | |
419 | === modified file 'txstatsd/version.py' |
420 | --- txstatsd/version.py 2011-09-06 13:52:21 +0000 |
421 | +++ txstatsd/version.py 2011-09-08 12:54:40 +0000 |
422 | @@ -1,1 +1,1 @@ |
423 | -txstatsd = "0.2.2" |
424 | +txstatsd = "0.3.0" |
Looks good. I requested a second review from Lucio.