Merge lp:~ahasenack/txstatsd/txstatsd-packaging into lp:txstatsd
- txstatsd-packaging
- Merge into trunk
Status: | Superseded |
---|---|
Proposed branch: | lp:~ahasenack/txstatsd/txstatsd-packaging |
Merge into: | lp:txstatsd |
Diff against target: |
1035 lines (+792/-22) 20 files modified
.bzrignore (+1/-0) debian/changelog (+5/-0) debian/compat (+1/-0) debian/control (+15/-0) debian/copyright (+43/-0) debian/docs (+4/-0) debian/postinst (+39/-0) debian/postrm (+37/-0) debian/preinst (+35/-0) debian/prerm (+38/-0) debian/rules (+13/-0) example-stats-client.tac (+10/-3) statsd.tac (+1/-2) txstatsd/metrics.py (+24/-2) txstatsd/process.py (+171/-0) txstatsd/protocol.py (+9/-8) txstatsd/report.py (+46/-0) txstatsd/service.py (+32/-7) txstatsd/tests/test_process.py (+266/-0) txstatsd/tests/test_service.py (+2/-0) |
To merge this branch: | bzr merge lp:~ahasenack/txstatsd/txstatsd-packaging |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Landscape | Pending | ||
Landscape | Pending | ||
Review via email: mp+67638@code.launchpad.net |
Commit message
Description of the change
This branch adds a debian/ structure to the txstatsd source tree. It builds and installs, and python sees the module after installation, as does twistd.
The {post,pre}inst and {post,pre}rm scripts are straight from dh_make, I didn't change those, and seem to do the right thing. After installation, I checked and postinst had a section added by dh_pysupport.
These are the lint warnings I get:
W: python-txstatsd source: out-of-
W: python-txstatsd: maintainer-
W: python-txstatsd: maintainer-
Sidnei da Silva (sidnei) wrote : | # |
- 15. By Andreas Hasenack
-
Merged with trunk (the ~landscape one, not upstream).
- 16. By Andreas Hasenack
-
Added dependency for python-psutil.
- 17. By Andreas Hasenack
-
Include example-
stats-client. tac in the documentation. - 18. By Andreas Hasenack
-
One more example file for the docs.
- 19. By Andreas Hasenack
-
- Merged with trunk again.
- Added statsd.tac to the documentation directory. - 20. By Andreas Hasenack
-
Change source package name to just txstatsd.
- 21. By Andreas Hasenack
-
- Changed source package name to just txstatsd
- Changed build dependency from python-twisted to python-twisted-core - 22. By Andreas Hasenack
-
Merged with trunk to grab missing report.py.
- 23. By Andreas Hasenack
-
Adjusted dependencies.
- 24. By Andreas Hasenack
-
Actually, no need for python-dev as this is noarch.
- 25. By Andreas Hasenack
-
Fixed version/release numbers.
Unmerged revisions
- 25. By Andreas Hasenack
-
Fixed version/release numbers.
- 24. By Andreas Hasenack
-
Actually, no need for python-dev as this is noarch.
- 23. By Andreas Hasenack
-
Adjusted dependencies.
- 22. By Andreas Hasenack
-
Merged with trunk to grab missing report.py.
- 21. By Andreas Hasenack
-
- Changed source package name to just txstatsd
- Changed build dependency from python-twisted to python-twisted-core - 20. By Andreas Hasenack
-
Change source package name to just txstatsd.
- 19. By Andreas Hasenack
-
- Merged with trunk again.
- Added statsd.tac to the documentation directory. - 18. By Andreas Hasenack
-
One more example file for the docs.
- 17. By Andreas Hasenack
-
Include example-
stats-client. tac in the documentation. - 16. By Andreas Hasenack
-
Added dependency for python-psutil.
Preview Diff
1 | === modified file '.bzrignore' | |||
2 | --- .bzrignore 2011-06-20 19:11:22 +0000 | |||
3 | +++ .bzrignore 2011-07-12 14:22:56 +0000 | |||
4 | @@ -1,1 +1,2 @@ | |||
5 | 1 | _trial_temp* | 1 | _trial_temp* |
6 | 2 | twisted/plugins/dropin.cache | ||
7 | 2 | 3 | ||
8 | === added directory 'debian' | |||
9 | === added file 'debian/changelog' | |||
10 | --- debian/changelog 1970-01-01 00:00:00 +0000 | |||
11 | +++ debian/changelog 2011-07-12 14:22:56 +0000 | |||
12 | @@ -0,0 +1,5 @@ | |||
13 | 1 | txstatsd (0.1.0-0ubuntu1) unstable; urgency=low | ||
14 | 2 | |||
15 | 3 | * Initial Release. | ||
16 | 4 | |||
17 | 5 | -- Andreas Hasenack <andreas@canonical.com> Mon, 11 Jul 2011 19:29:34 -0300 | ||
18 | 0 | 6 | ||
19 | === added file 'debian/compat' | |||
20 | --- debian/compat 1970-01-01 00:00:00 +0000 | |||
21 | +++ debian/compat 2011-07-12 14:22:56 +0000 | |||
22 | @@ -0,0 +1,1 @@ | |||
23 | 1 | 7 | ||
24 | 0 | 2 | ||
25 | === added file 'debian/control' | |||
26 | --- debian/control 1970-01-01 00:00:00 +0000 | |||
27 | +++ debian/control 2011-07-12 14:22:56 +0000 | |||
28 | @@ -0,0 +1,15 @@ | |||
29 | 1 | Source: txstatsd | ||
30 | 2 | Section: python | ||
31 | 3 | Priority: extra | ||
32 | 4 | Maintainer: Andreas Hasenack <andreas@canonical.com> | ||
33 | 5 | Build-Depends: debhelper (>= 7), python-twisted-core, python-support | ||
34 | 6 | Standards-Version: 3.8.3 | ||
35 | 7 | Homepage: https://launchpad.net/txstatsd | ||
36 | 8 | |||
37 | 9 | Package: python-txstatsd | ||
38 | 10 | Architecture: all | ||
39 | 11 | Depends: ${python:Depends}, ${misc:Depends}, python-psutil, python-twisted-core | ||
40 | 12 | Description: A network daemon for aggregating statistics | ||
41 | 13 | A network daemon for aggregating statistics (counters and timers), rolling | ||
42 | 14 | them up, then sending them to graphite. Really a port of | ||
43 | 15 | https://github.com/etsy/statsd to twisted. | ||
44 | 0 | 16 | ||
45 | === added file 'debian/copyright' | |||
46 | --- debian/copyright 1970-01-01 00:00:00 +0000 | |||
47 | +++ debian/copyright 2011-07-12 14:22:56 +0000 | |||
48 | @@ -0,0 +1,43 @@ | |||
49 | 1 | This work was packaged for Debian by: | ||
50 | 2 | |||
51 | 3 | Andreas Hasenack <andreas@canonical.com> on Mon, 11 Jul 2011 19:29:34 -0300 | ||
52 | 4 | |||
53 | 5 | It was downloaded from: | ||
54 | 6 | |||
55 | 7 | https://launchpad.net/txstatsd | ||
56 | 8 | |||
57 | 9 | Upstream Author(s): | ||
58 | 10 | |||
59 | 11 | Sidnei da Silva <sidnei@canonical.com> | ||
60 | 12 | |||
61 | 13 | Copyright: | ||
62 | 14 | |||
63 | 15 | <Copyright (C) 2011 Canonical Services Ltd> | ||
64 | 16 | |||
65 | 17 | License: | ||
66 | 18 | |||
67 | 19 | Permission is hereby granted, free of charge, to any person obtaining | ||
68 | 20 | a copy of this software and associated documentation files (the | ||
69 | 21 | "Software"), to deal in the Software without restriction, including | ||
70 | 22 | without limitation the rights to use, copy, modify, merge, publish, | ||
71 | 23 | distribute, sublicense, and/or sell copies of the Software, and to | ||
72 | 24 | permit persons to whom the Software is furnished to do so, subject to | ||
73 | 25 | the following conditions: | ||
74 | 26 | |||
75 | 27 | The above copyright notice and this permission notice shall be | ||
76 | 28 | included in all copies or substantial portions of the Software. | ||
77 | 29 | |||
78 | 30 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
79 | 31 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
80 | 32 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | ||
81 | 33 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | ||
82 | 34 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | ||
83 | 35 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | ||
84 | 36 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
85 | 37 | |||
86 | 38 | The Debian packaging is: | ||
87 | 39 | |||
88 | 40 | Copyright (C) 2011 Andreas Hasenack <andreas@canonical.com> | ||
89 | 41 | |||
90 | 42 | and is licensed under MIT. | ||
91 | 43 | |||
92 | 0 | 44 | ||
93 | === added file 'debian/docs' | |||
94 | --- debian/docs 1970-01-01 00:00:00 +0000 | |||
95 | +++ debian/docs 2011-07-12 14:22:56 +0000 | |||
96 | @@ -0,0 +1,4 @@ | |||
97 | 1 | README | ||
98 | 2 | example-stats-client.tac | ||
99 | 3 | txstatsd.conf-example | ||
100 | 4 | statsd.tac | ||
101 | 0 | 5 | ||
102 | === added file 'debian/postinst' | |||
103 | --- debian/postinst 1970-01-01 00:00:00 +0000 | |||
104 | +++ debian/postinst 2011-07-12 14:22:56 +0000 | |||
105 | @@ -0,0 +1,39 @@ | |||
106 | 1 | #!/bin/sh | ||
107 | 2 | # postinst script for python-txstatsd | ||
108 | 3 | # | ||
109 | 4 | # see: dh_installdeb(1) | ||
110 | 5 | |||
111 | 6 | set -e | ||
112 | 7 | |||
113 | 8 | # summary of how this script can be called: | ||
114 | 9 | # * <postinst> `configure' <most-recently-configured-version> | ||
115 | 10 | # * <old-postinst> `abort-upgrade' <new version> | ||
116 | 11 | # * <conflictor's-postinst> `abort-remove' `in-favour' <package> | ||
117 | 12 | # <new-version> | ||
118 | 13 | # * <postinst> `abort-remove' | ||
119 | 14 | # * <deconfigured's-postinst> `abort-deconfigure' `in-favour' | ||
120 | 15 | # <failed-install-package> <version> `removing' | ||
121 | 16 | # <conflicting-package> <version> | ||
122 | 17 | # for details, see http://www.debian.org/doc/debian-policy/ or | ||
123 | 18 | # the debian-policy package | ||
124 | 19 | |||
125 | 20 | |||
126 | 21 | case "$1" in | ||
127 | 22 | configure) | ||
128 | 23 | ;; | ||
129 | 24 | |||
130 | 25 | abort-upgrade|abort-remove|abort-deconfigure) | ||
131 | 26 | ;; | ||
132 | 27 | |||
133 | 28 | *) | ||
134 | 29 | echo "postinst called with unknown argument \`$1'" >&2 | ||
135 | 30 | exit 1 | ||
136 | 31 | ;; | ||
137 | 32 | esac | ||
138 | 33 | |||
139 | 34 | # dh_installdeb will replace this with shell code automatically | ||
140 | 35 | # generated by other debhelper scripts. | ||
141 | 36 | |||
142 | 37 | #DEBHELPER# | ||
143 | 38 | |||
144 | 39 | exit 0 | ||
145 | 0 | 40 | ||
146 | === added file 'debian/postrm' | |||
147 | --- debian/postrm 1970-01-01 00:00:00 +0000 | |||
148 | +++ debian/postrm 2011-07-12 14:22:56 +0000 | |||
149 | @@ -0,0 +1,37 @@ | |||
150 | 1 | #!/bin/sh | ||
151 | 2 | # postrm script for python-txstatsd | ||
152 | 3 | # | ||
153 | 4 | # see: dh_installdeb(1) | ||
154 | 5 | |||
155 | 6 | set -e | ||
156 | 7 | |||
157 | 8 | # summary of how this script can be called: | ||
158 | 9 | # * <postrm> `remove' | ||
159 | 10 | # * <postrm> `purge' | ||
160 | 11 | # * <old-postrm> `upgrade' <new-version> | ||
161 | 12 | # * <new-postrm> `failed-upgrade' <old-version> | ||
162 | 13 | # * <new-postrm> `abort-install' | ||
163 | 14 | # * <new-postrm> `abort-install' <old-version> | ||
164 | 15 | # * <new-postrm> `abort-upgrade' <old-version> | ||
165 | 16 | # * <disappearer's-postrm> `disappear' <overwriter> | ||
166 | 17 | # <overwriter-version> | ||
167 | 18 | # for details, see http://www.debian.org/doc/debian-policy/ or | ||
168 | 19 | # the debian-policy package | ||
169 | 20 | |||
170 | 21 | |||
171 | 22 | case "$1" in | ||
172 | 23 | purge|remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) | ||
173 | 24 | ;; | ||
174 | 25 | |||
175 | 26 | *) | ||
176 | 27 | echo "postrm called with unknown argument \`$1'" >&2 | ||
177 | 28 | exit 1 | ||
178 | 29 | ;; | ||
179 | 30 | esac | ||
180 | 31 | |||
181 | 32 | # dh_installdeb will replace this with shell code automatically | ||
182 | 33 | # generated by other debhelper scripts. | ||
183 | 34 | |||
184 | 35 | #DEBHELPER# | ||
185 | 36 | |||
186 | 37 | exit 0 | ||
187 | 0 | 38 | ||
188 | === added file 'debian/preinst' | |||
189 | --- debian/preinst 1970-01-01 00:00:00 +0000 | |||
190 | +++ debian/preinst 2011-07-12 14:22:56 +0000 | |||
191 | @@ -0,0 +1,35 @@ | |||
192 | 1 | #!/bin/sh | ||
193 | 2 | # preinst script for python-txstatsd | ||
194 | 3 | # | ||
195 | 4 | # see: dh_installdeb(1) | ||
196 | 5 | |||
197 | 6 | set -e | ||
198 | 7 | |||
199 | 8 | # summary of how this script can be called: | ||
200 | 9 | # * <new-preinst> `install' | ||
201 | 10 | # * <new-preinst> `install' <old-version> | ||
202 | 11 | # * <new-preinst> `upgrade' <old-version> | ||
203 | 12 | # * <old-preinst> `abort-upgrade' <new-version> | ||
204 | 13 | # for details, see http://www.debian.org/doc/debian-policy/ or | ||
205 | 14 | # the debian-policy package | ||
206 | 15 | |||
207 | 16 | |||
208 | 17 | case "$1" in | ||
209 | 18 | install|upgrade) | ||
210 | 19 | ;; | ||
211 | 20 | |||
212 | 21 | abort-upgrade) | ||
213 | 22 | ;; | ||
214 | 23 | |||
215 | 24 | *) | ||
216 | 25 | echo "preinst called with unknown argument \`$1'" >&2 | ||
217 | 26 | exit 1 | ||
218 | 27 | ;; | ||
219 | 28 | esac | ||
220 | 29 | |||
221 | 30 | # dh_installdeb will replace this with shell code automatically | ||
222 | 31 | # generated by other debhelper scripts. | ||
223 | 32 | |||
224 | 33 | #DEBHELPER# | ||
225 | 34 | |||
226 | 35 | exit 0 | ||
227 | 0 | 36 | ||
228 | === added file 'debian/prerm' | |||
229 | --- debian/prerm 1970-01-01 00:00:00 +0000 | |||
230 | +++ debian/prerm 2011-07-12 14:22:56 +0000 | |||
231 | @@ -0,0 +1,38 @@ | |||
232 | 1 | #!/bin/sh | ||
233 | 2 | # prerm script for python-txstatsd | ||
234 | 3 | # | ||
235 | 4 | # see: dh_installdeb(1) | ||
236 | 5 | |||
237 | 6 | set -e | ||
238 | 7 | |||
239 | 8 | # summary of how this script can be called: | ||
240 | 9 | # * <prerm> `remove' | ||
241 | 10 | # * <old-prerm> `upgrade' <new-version> | ||
242 | 11 | # * <new-prerm> `failed-upgrade' <old-version> | ||
243 | 12 | # * <conflictor's-prerm> `remove' `in-favour' <package> <new-version> | ||
244 | 13 | # * <deconfigured's-prerm> `deconfigure' `in-favour' | ||
245 | 14 | # <package-being-installed> <version> `removing' | ||
246 | 15 | # <conflicting-package> <version> | ||
247 | 16 | # for details, see http://www.debian.org/doc/debian-policy/ or | ||
248 | 17 | # the debian-policy package | ||
249 | 18 | |||
250 | 19 | |||
251 | 20 | case "$1" in | ||
252 | 21 | remove|upgrade|deconfigure) | ||
253 | 22 | ;; | ||
254 | 23 | |||
255 | 24 | failed-upgrade) | ||
256 | 25 | ;; | ||
257 | 26 | |||
258 | 27 | *) | ||
259 | 28 | echo "prerm called with unknown argument \`$1'" >&2 | ||
260 | 29 | exit 1 | ||
261 | 30 | ;; | ||
262 | 31 | esac | ||
263 | 32 | |||
264 | 33 | # dh_installdeb will replace this with shell code automatically | ||
265 | 34 | # generated by other debhelper scripts. | ||
266 | 35 | |||
267 | 36 | #DEBHELPER# | ||
268 | 37 | |||
269 | 38 | exit 0 | ||
270 | 0 | 39 | ||
271 | === added file 'debian/rules' | |||
272 | --- debian/rules 1970-01-01 00:00:00 +0000 | |||
273 | +++ debian/rules 2011-07-12 14:22:56 +0000 | |||
274 | @@ -0,0 +1,13 @@ | |||
275 | 1 | #!/usr/bin/make -f | ||
276 | 2 | # -*- makefile -*- | ||
277 | 3 | # Sample debian/rules that uses debhelper. | ||
278 | 4 | # This file was originally written by Joey Hess and Craig Small. | ||
279 | 5 | # As a special exception, when this file is copied by dh-make into a | ||
280 | 6 | # dh-make output file, you may use that output file without restriction. | ||
281 | 7 | # This special exception was added by Craig Small in version 0.37 of dh-make. | ||
282 | 8 | |||
283 | 9 | # Uncomment this to turn on verbose mode. | ||
284 | 10 | #export DH_VERBOSE=1 | ||
285 | 11 | |||
286 | 12 | %: | ||
287 | 13 | dh $@ | ||
288 | 0 | 14 | ||
289 | === modified file 'example-stats-client.tac' | |||
290 | --- example-stats-client.tac 2011-06-23 14:19:21 +0000 | |||
291 | +++ example-stats-client.tac 2011-07-12 14:22:56 +0000 | |||
292 | @@ -8,11 +8,18 @@ | |||
293 | 8 | 8 | ||
294 | 9 | from txstatsd.protocol import StatsDClientProtocol | 9 | from txstatsd.protocol import StatsDClientProtocol |
295 | 10 | from txstatsd.metrics import TransportMeter | 10 | from txstatsd.metrics import TransportMeter |
296 | 11 | from txstatsd.process import PROCESS_STATS | ||
297 | 12 | from txstatsd.report import ReportingService | ||
298 | 11 | 13 | ||
299 | 12 | 14 | ||
300 | 13 | application = Application("example-stats-client") | 15 | application = Application("example-stats-client") |
303 | 14 | meter = TransportMeter(prefix=socket.gethostname()) | 16 | meter = TransportMeter(prefix=socket.gethostname() + ".example-client") |
304 | 15 | 17 | ||
305 | 18 | reporting = ReportingService() | ||
306 | 19 | reporting.setServiceParent(application) | ||
307 | 20 | |||
308 | 21 | for report in PROCESS_STATS: | ||
309 | 22 | reporting.schedule(report, 10, meter.increment) | ||
310 | 16 | 23 | ||
311 | 17 | def random_walker(name): | 24 | def random_walker(name): |
312 | 18 | """Meters a random walk.""" | 25 | """Meters a random walk.""" |
313 | @@ -35,5 +42,5 @@ | |||
314 | 35 | t.start(0.5, now=False) | 42 | t.start(0.5, now=False) |
315 | 36 | 43 | ||
316 | 37 | 44 | ||
318 | 38 | protocol = StatsDClientProtocol("127.0.0.1", 8125, meter) | 45 | protocol = StatsDClientProtocol("127.0.0.1", 8125, meter, 6000) |
319 | 39 | reactor.listenUDP(0, protocol) | 46 | reactor.listenUDP(0, protocol) |
320 | 40 | 47 | ||
321 | === modified file 'statsd.tac' | |||
322 | --- statsd.tac 2011-07-04 21:43:01 +0000 | |||
323 | +++ statsd.tac 2011-07-12 14:22:56 +0000 | |||
324 | @@ -4,6 +4,5 @@ | |||
325 | 4 | 4 | ||
326 | 5 | application = Application("statsd") | 5 | application = Application("statsd") |
327 | 6 | 6 | ||
329 | 7 | statsd_service = service.createService(service.StatsdOptions()) | 7 | statsd_service = service.createService(service.StatsDOptions()) |
330 | 8 | statsd_service.setServiceParent(application) | 8 | statsd_service.setServiceParent(application) |
331 | 9 | |||
332 | 10 | 9 | ||
333 | === modified file 'txstatsd/metrics.py' | |||
334 | --- txstatsd/metrics.py 2011-07-05 05:27:22 +0000 | |||
335 | +++ txstatsd/metrics.py 2011-07-12 14:22:56 +0000 | |||
336 | @@ -55,6 +55,18 @@ | |||
337 | 55 | raise NotImplementedError() | 55 | raise NotImplementedError() |
338 | 56 | 56 | ||
339 | 57 | 57 | ||
340 | 58 | class InProcessMeter(BaseMeter): | ||
341 | 59 | """A meter that can be used inside the C{StatsD} daemon itself.""" | ||
342 | 60 | |||
343 | 61 | def __init__(self, processor, prefix="", sample_rate=1): | ||
344 | 62 | self.processor = processor | ||
345 | 63 | BaseMeter.__init__(self, prefix=prefix, sample_rate=sample_rate) | ||
346 | 64 | |||
347 | 65 | def write(self, data): | ||
348 | 66 | """Pass the data along directly to the C{Processor}.""" | ||
349 | 67 | self.processor.process(data) | ||
350 | 68 | |||
351 | 69 | |||
352 | 58 | class Meter(BaseMeter): | 70 | class Meter(BaseMeter): |
353 | 59 | """A trivial, non-Twisted-dependent meter.""" | 71 | """A trivial, non-Twisted-dependent meter.""" |
354 | 60 | 72 | ||
355 | @@ -118,12 +130,22 @@ | |||
356 | 118 | host = None | 130 | host = None |
357 | 119 | port = None | 131 | port = None |
358 | 120 | 132 | ||
360 | 121 | def connected(self, transport, host, port): | 133 | def __init__(self, prefix="", sample_rate=1, |
361 | 134 | connect_callback=None, disconnect_callback=None): | ||
362 | 135 | self.connect_callback = connect_callback | ||
363 | 136 | self.disconnect_callback = disconnect_callback | ||
364 | 137 | BaseMeter.__init__(self, prefix=prefix, sample_rate=sample_rate) | ||
365 | 138 | |||
366 | 139 | def connect(self, transport, host, port): | ||
367 | 122 | self.transport = transport | 140 | self.transport = transport |
368 | 123 | self.host = host | 141 | self.host = host |
369 | 124 | self.port = port | 142 | self.port = port |
370 | 143 | if self.connect_callback is not None: | ||
371 | 144 | self.connect_callback() | ||
372 | 125 | 145 | ||
374 | 126 | def disconnected(self): | 146 | def disconnect(self): |
375 | 147 | if self.disconnect_callback is not None: | ||
376 | 148 | self.disconnect_callback() | ||
377 | 127 | self.transport = self.host = self.port = None | 149 | self.transport = self.host = self.port = None |
378 | 128 | 150 | ||
379 | 129 | def write(self, data): | 151 | def write(self, data): |
380 | 130 | 152 | ||
381 | === added file 'txstatsd/process.py' | |||
382 | --- txstatsd/process.py 1970-01-01 00:00:00 +0000 | |||
383 | +++ txstatsd/process.py 2011-07-12 14:22:56 +0000 | |||
384 | @@ -0,0 +1,171 @@ | |||
385 | 1 | import os | ||
386 | 2 | import socket | ||
387 | 3 | import psutil | ||
388 | 4 | |||
389 | 5 | from functools import update_wrapper | ||
390 | 6 | |||
391 | 7 | from twisted.internet import defer, fdesc, error | ||
392 | 8 | |||
393 | 9 | |||
394 | 10 | MEMINFO_KEYS = ("MemTotal:", "MemFree:", "Buffers:", | ||
395 | 11 | "Cached:", "SwapCached:", "SwapTotal:", | ||
396 | 12 | "SwapFree:") | ||
397 | 13 | |||
398 | 14 | MULTIPLIERS = {"kB": 1024, "mB": 1024 * 1024} | ||
399 | 15 | |||
400 | 16 | |||
401 | 17 | def load_file(filename): | ||
402 | 18 | """Load a file into memory with non blocking reads.""" | ||
403 | 19 | |||
404 | 20 | fd = os.open(filename, os.O_RDONLY) | ||
405 | 21 | fdesc.setNonBlocking(fd) | ||
406 | 22 | |||
407 | 23 | chunks = [] | ||
408 | 24 | d = defer.Deferred() | ||
409 | 25 | |||
410 | 26 | def read_loop(data=None): | ||
411 | 27 | """Inner loop.""" | ||
412 | 28 | if data is not None: | ||
413 | 29 | chunks.append(data) | ||
414 | 30 | r = fdesc.readFromFD(fd, read_loop) | ||
415 | 31 | if isinstance(r, error.ConnectionDone): | ||
416 | 32 | os.close(fd) | ||
417 | 33 | d.callback("".join(chunks)) | ||
418 | 34 | elif r is not None: | ||
419 | 35 | os.close(fd) | ||
420 | 36 | d.errback(r) | ||
421 | 37 | |||
422 | 38 | read_loop("") | ||
423 | 39 | return d | ||
424 | 40 | |||
425 | 41 | |||
426 | 42 | def parse_meminfo(data, prefix="sys.mem"): | ||
427 | 43 | """Parse data from /proc/meminfo.""" | ||
428 | 44 | result = {} | ||
429 | 45 | |||
430 | 46 | for line in data.split("\n"): | ||
431 | 47 | if not line: | ||
432 | 48 | continue | ||
433 | 49 | parts = [x for x in line.split(" ") if x] | ||
434 | 50 | if not parts[0] in MEMINFO_KEYS: | ||
435 | 51 | continue | ||
436 | 52 | |||
437 | 53 | multiple = 1 | ||
438 | 54 | |||
439 | 55 | if len(parts) == 3: | ||
440 | 56 | multiple = MULTIPLIERS[parts[2]] | ||
441 | 57 | |||
442 | 58 | # remove ':' | ||
443 | 59 | label = parts[0][:-1] | ||
444 | 60 | amount = int(parts[1]) * multiple | ||
445 | 61 | result[prefix + "." + label] = amount | ||
446 | 62 | |||
447 | 63 | return result | ||
448 | 64 | |||
449 | 65 | |||
450 | 66 | def parse_loadavg(data, prefix="sys.loadavg"): | ||
451 | 67 | """Parse data from /proc/loadavg.""" | ||
452 | 68 | return dict(zip( | ||
453 | 69 | (prefix + ".oneminute", | ||
454 | 70 | prefix + ".fiveminutes", | ||
455 | 71 | prefix + ".fifthteenminutes"), | ||
456 | 72 | [float(x) for x in data.split()[:3]])) | ||
457 | 73 | |||
458 | 74 | |||
459 | 75 | def report_process_memory_and_cpu(process=psutil.Process(os.getpid()), | ||
460 | 76 | prefix="proc"): | ||
461 | 77 | """Report memory and CPU stats for C{process}.""" | ||
462 | 78 | vsize, rss = process.get_memory_info() | ||
463 | 79 | utime, stime = process.get_cpu_times() | ||
464 | 80 | result = {prefix + ".cpu.percent": process.get_cpu_percent(), | ||
465 | 81 | prefix + ".cpu.user": utime, | ||
466 | 82 | prefix + ".cpu.system": stime, | ||
467 | 83 | prefix + ".memory.percent": process.get_memory_percent(), | ||
468 | 84 | prefix + ".memory.vsize": vsize, | ||
469 | 85 | prefix + ".memory.rss": rss} | ||
470 | 86 | if getattr(process, "get_num_threads", None) is not None: | ||
471 | 87 | result[prefix + ".threads"] = process.get_num_threads() | ||
472 | 88 | return result | ||
473 | 89 | |||
474 | 90 | |||
475 | 91 | def report_process_io_counters(process=psutil.Process(os.getpid()), | ||
476 | 92 | prefix="proc.io"): | ||
477 | 93 | """Report IO statistics for C{process}.""" | ||
478 | 94 | result = {} | ||
479 | 95 | if getattr(process, "get_io_counters", None) is not None: | ||
480 | 96 | (read_count, write_count, | ||
481 | 97 | read_bytes, write_bytes) = process.get_io_counters() | ||
482 | 98 | result.update({ | ||
483 | 99 | prefix + ".read.count": read_count, | ||
484 | 100 | prefix + ".write.count": write_count, | ||
485 | 101 | prefix + ".read.bytes": read_bytes, | ||
486 | 102 | prefix + ".write.bytes": write_bytes}) | ||
487 | 103 | return result | ||
488 | 104 | |||
489 | 105 | |||
490 | 106 | def report_process_net_stats(process=psutil.Process(os.getpid()), | ||
491 | 107 | prefix="proc.net"): | ||
492 | 108 | """Report active connection statistics for C{process}.""" | ||
493 | 109 | result = {} | ||
494 | 110 | if getattr(process, "get_connections", None) is not None: | ||
495 | 111 | for connection in process.get_connections(): | ||
496 | 112 | fd, family, _type, laddr, raddr, status = connection | ||
497 | 113 | if _type == socket.SOCK_STREAM: | ||
498 | 114 | key = prefix + ".status.%s" % status.lower() | ||
499 | 115 | if not key in result: | ||
500 | 116 | result[key] = 1 | ||
501 | 117 | else: | ||
502 | 118 | result[key] += 1 | ||
503 | 119 | return result | ||
504 | 120 | |||
505 | 121 | |||
506 | 122 | def report_system_stats(prefix="sys"): | ||
507 | 123 | cpu_times = psutil.cpu_times() | ||
508 | 124 | return {prefix + ".cpu.idle": cpu_times.idle, | ||
509 | 125 | prefix + ".cpu.iowait": cpu_times.iowait, | ||
510 | 126 | prefix + ".cpu.irq": cpu_times.irq, | ||
511 | 127 | prefix + ".cpu.nice": cpu_times.nice, | ||
512 | 128 | prefix + ".cpu.system": cpu_times.system, | ||
513 | 129 | prefix + ".cpu.user": cpu_times.user} | ||
514 | 130 | |||
515 | 131 | |||
516 | 132 | def report_threadpool_stats(threadpool, prefix="threadpool"): | ||
517 | 133 | """Report stats about a given threadpool.""" | ||
518 | 134 | def report(): | ||
519 | 135 | return {prefix + ".working": len(threadpool.working), | ||
520 | 136 | prefix + ".queue": threadpool.q.qsize(), | ||
521 | 137 | prefix + ".waiters": len(threadpool.waiters), | ||
522 | 138 | prefix + ".threads": len(threadpool.threads)} | ||
523 | 139 | update_wrapper(report, report_threadpool_stats) | ||
524 | 140 | return report | ||
525 | 141 | |||
526 | 142 | |||
527 | 143 | def report_reactor_stats(reactor, prefix="reactor"): | ||
528 | 144 | """Report statistics about a twisted reactor.""" | ||
529 | 145 | def report(): | ||
530 | 146 | return {prefix + ".readers": len(reactor.getReaders()), | ||
531 | 147 | prefix + ".writers": len(reactor.getWriters())} | ||
532 | 148 | |||
533 | 149 | update_wrapper(report, report_reactor_stats) | ||
534 | 150 | return report | ||
535 | 151 | |||
536 | 152 | |||
537 | 153 | def report_file_stats(filename, parser): | ||
538 | 154 | """Read statistics from a file and report them.""" | ||
539 | 155 | def report(): | ||
540 | 156 | deferred = load_file(filename) | ||
541 | 157 | deferred.addCallback(parser) | ||
542 | 158 | return deferred | ||
543 | 159 | update_wrapper(report, report_file_stats) | ||
544 | 160 | return report | ||
545 | 161 | |||
546 | 162 | |||
547 | 163 | PROCESS_STATS = (report_process_memory_and_cpu,) | ||
548 | 164 | |||
549 | 165 | IO_STATS = (report_process_io_counters,) | ||
550 | 166 | |||
551 | 167 | NET_STATS = (report_process_net_stats,) | ||
552 | 168 | |||
553 | 169 | SYSTEM_STATS = (report_file_stats("/proc/meminfo", parse_meminfo), | ||
554 | 170 | report_file_stats("/proc/loadavg", parse_loadavg), | ||
555 | 171 | report_system_stats) | ||
556 | 0 | 172 | ||
557 | === modified file 'txstatsd/protocol.py' | |||
558 | --- txstatsd/protocol.py 2011-06-22 00:18:58 +0000 | |||
559 | +++ txstatsd/protocol.py 2011-07-12 14:22:56 +0000 | |||
560 | @@ -1,7 +1,7 @@ | |||
561 | 1 | import logging | 1 | import logging |
562 | 2 | 2 | ||
563 | 3 | from twisted.python import log | 3 | from twisted.python import log |
565 | 4 | from twisted.internet import task | 4 | from twisted.internet import task, defer |
566 | 5 | from twisted.protocols.basic import LineOnlyReceiver | 5 | from twisted.protocols.basic import LineOnlyReceiver |
567 | 6 | from twisted.internet.protocol import ( | 6 | from twisted.internet.protocol import ( |
568 | 7 | DatagramProtocol, ReconnectingClientFactory) | 7 | DatagramProtocol, ReconnectingClientFactory) |
569 | @@ -27,21 +27,22 @@ | |||
570 | 27 | class StatsDClientProtocol(DatagramProtocol): | 27 | class StatsDClientProtocol(DatagramProtocol): |
571 | 28 | """A Twisted-based implementation of the StatsD client protocol. | 28 | """A Twisted-based implementation of the StatsD client protocol. |
572 | 29 | 29 | ||
574 | 30 | Data is sent via ConnectedUDP to a StatsD server for aggregation. | 30 | Data is sent via UDP to a StatsD server for aggregation. |
575 | 31 | """ | 31 | """ |
576 | 32 | 32 | ||
578 | 33 | def __init__(self, host, port, meter): | 33 | def __init__(self, host, port, meter, interval=None): |
579 | 34 | self.host = host | 34 | self.host = host |
580 | 35 | self.port = port | 35 | self.port = port |
581 | 36 | self.meter = meter | 36 | self.meter = meter |
582 | 37 | self.interval = interval | ||
583 | 37 | 38 | ||
584 | 38 | def startProtocol(self): | 39 | def startProtocol(self): |
585 | 39 | """Connect to destination host.""" | 40 | """Connect to destination host.""" |
587 | 40 | self.meter.connected(self.transport, self.host, self.port) | 41 | self.meter.connect(self.transport, self.host, self.port) |
588 | 41 | 42 | ||
589 | 42 | def stopProtocol(self): | 43 | def stopProtocol(self): |
590 | 43 | """Connection was lost.""" | 44 | """Connection was lost.""" |
592 | 44 | self.meter.disconnected() | 45 | self.meter.disconnect() |
593 | 45 | 46 | ||
594 | 46 | 47 | ||
595 | 47 | class GraphiteProtocol(LineOnlyReceiver): | 48 | class GraphiteProtocol(LineOnlyReceiver): |
596 | @@ -65,7 +66,7 @@ | |||
597 | 65 | log.msg("Connected. Scheduling flush to now + %ds." % | 66 | log.msg("Connected. Scheduling flush to now + %ds." % |
598 | 66 | (self.interval / 1000), logLevel=logging.DEBUG) | 67 | (self.interval / 1000), logLevel=logging.DEBUG) |
599 | 67 | self.flush_task = task.LoopingCall(self.flushProcessor) | 68 | self.flush_task = task.LoopingCall(self.flushProcessor) |
601 | 68 | self.flush_task.start(self.interval / 1000) | 69 | self.flush_task.start(self.interval / 1000, False) |
602 | 69 | 70 | ||
603 | 70 | def connectionLost(self, reason): | 71 | def connectionLost(self, reason): |
604 | 71 | """ | 72 | """ |
605 | @@ -77,12 +78,12 @@ | |||
606 | 77 | log.msg("Canceling scheduled flush.", logLevel=logging.DEBUG) | 78 | log.msg("Canceling scheduled flush.", logLevel=logging.DEBUG) |
607 | 78 | self.flush_task.stop() | 79 | self.flush_task.stop() |
608 | 79 | 80 | ||
609 | 81 | @defer.inlineCallbacks | ||
610 | 80 | def flushProcessor(self): | 82 | def flushProcessor(self): |
611 | 81 | """Flush messages queued in the processor to Graphite.""" | 83 | """Flush messages queued in the processor to Graphite.""" |
612 | 82 | log.msg("Flushing messages.", logLevel=logging.DEBUG) | ||
613 | 83 | for message in self.processor.flush(interval=self.interval): | 84 | for message in self.processor.flush(interval=self.interval): |
614 | 84 | for line in message.splitlines(): | 85 | for line in message.splitlines(): |
616 | 85 | self.sendLine(line) | 86 | yield self.sendLine(line) |
617 | 86 | 87 | ||
618 | 87 | 88 | ||
619 | 88 | class GraphiteClientFactory(ReconnectingClientFactory): | 89 | class GraphiteClientFactory(ReconnectingClientFactory): |
620 | 89 | 90 | ||
621 | === added file 'txstatsd/report.py' | |||
622 | --- txstatsd/report.py 1970-01-01 00:00:00 +0000 | |||
623 | +++ txstatsd/report.py 2011-07-12 14:22:56 +0000 | |||
624 | @@ -0,0 +1,46 @@ | |||
625 | 1 | from twisted.internet.defer import maybeDeferred | ||
626 | 2 | from twisted.internet.task import LoopingCall | ||
627 | 3 | from twisted.python import log | ||
628 | 4 | |||
629 | 5 | from functools import wraps | ||
630 | 6 | |||
631 | 7 | from twisted.application.service import Service | ||
632 | 8 | |||
633 | 9 | |||
634 | 10 | class ReportingService(Service): | ||
635 | 11 | |||
636 | 12 | def __init__(self): | ||
637 | 13 | self.tasks = [] | ||
638 | 14 | |||
639 | 15 | def schedule(self, function, interval, report_function): | ||
640 | 16 | """ | ||
641 | 17 | Schedule C{function} to be called every C{interval} seconds and then | ||
642 | 18 | report gathered metrics to C{Graphite} using C{report_function}. | ||
643 | 19 | """ | ||
644 | 20 | task = LoopingCall(self.wrapped(function, report_function)) | ||
645 | 21 | self.tasks.append((task, interval)) | ||
646 | 22 | |||
647 | 23 | def wrapped(self, function, report_function): | ||
648 | 24 | def report_metrics(metrics): | ||
649 | 25 | """For each metric returned, call C{report_function} with it.""" | ||
650 | 26 | for name, value in metrics.items(): | ||
651 | 27 | report_function(name, value) | ||
652 | 28 | return metrics | ||
653 | 29 | |||
654 | 30 | @wraps(function) | ||
655 | 31 | def wrapper(): | ||
656 | 32 | """Wrap C{function} to report metrics or log a failure.""" | ||
657 | 33 | deferred = maybeDeferred(function) | ||
658 | 34 | deferred.addCallback(report_metrics) | ||
659 | 35 | deferred.addErrback(lambda failure: log.err( | ||
660 | 36 | failure, "Error while processing %s" % function.func_name)) | ||
661 | 37 | return deferred | ||
662 | 38 | return wrapper | ||
663 | 39 | |||
664 | 40 | def startService(self): | ||
665 | 41 | for task, interval in self.tasks: | ||
666 | 42 | task.start(interval, now=False) | ||
667 | 43 | |||
668 | 44 | def stopService(self): | ||
669 | 45 | for task, interval in self.tasks: | ||
670 | 46 | task.stop() | ||
671 | 0 | 47 | ||
672 | === modified file 'txstatsd/service.py' | |||
673 | --- txstatsd/service.py 2011-07-05 05:27:22 +0000 | |||
674 | +++ txstatsd/service.py 2011-07-12 14:22:56 +0000 | |||
675 | @@ -1,11 +1,16 @@ | |||
676 | 1 | import socket | ||
677 | 1 | import ConfigParser | 2 | import ConfigParser |
678 | 2 | 3 | ||
679 | 3 | from twisted.application.internet import TCPClient, UDPServer | 4 | from twisted.application.internet import TCPClient, UDPServer |
680 | 4 | from twisted.application.service import MultiService | 5 | from twisted.application.service import MultiService |
681 | 6 | from twisted.internet import reactor | ||
682 | 5 | from twisted.python import usage, util | 7 | from twisted.python import usage, util |
683 | 6 | 8 | ||
684 | 9 | from txstatsd import process | ||
685 | 10 | from txstatsd.metrics import InProcessMeter | ||
686 | 7 | from txstatsd.processor import MessageProcessor | 11 | from txstatsd.processor import MessageProcessor |
687 | 8 | from txstatsd.protocol import GraphiteClientFactory, StatsDServerProtocol | 12 | from txstatsd.protocol import GraphiteClientFactory, StatsDServerProtocol |
688 | 13 | from txstatsd.report import ReportingService | ||
689 | 9 | 14 | ||
690 | 10 | _unset = object() | 15 | _unset = object() |
691 | 11 | 16 | ||
692 | @@ -73,13 +78,17 @@ | |||
693 | 73 | """ | 78 | """ |
694 | 74 | glue_parameters = [ | 79 | glue_parameters = [ |
695 | 75 | ["carbon-cache-host", "h", "localhost", | 80 | ["carbon-cache-host", "h", "localhost", |
697 | 76 | "The host where carbon cache is listening."], | 81 | "The host where carbon cache is listening."], |
698 | 77 | ["carbon-cache-port", "p", 2003, | 82 | ["carbon-cache-port", "p", 2003, |
700 | 78 | "The port where carbon cache is listening.", int], | 83 | "The port where carbon cache is listening.", int], |
701 | 79 | ["listen-port", "l", 8125, | 84 | ["listen-port", "l", 8125, |
703 | 80 | "The UDP port where we will listen.", int], | 85 | "The UDP port where we will listen.", int], |
704 | 81 | ["flush-interval", "i", 10000, | 86 | ["flush-interval", "i", 10000, |
706 | 82 | "The number of milliseconds between each flush.", int], | 87 | "The number of milliseconds between each flush.", int], |
707 | 88 | ["prefix", "p", None, | ||
708 | 89 | "Prefix to use when reporting stats.", str], | ||
709 | 90 | ["report", "r", None, | ||
710 | 91 | "Which additional stats to report {process|net|io|system}.", str], | ||
711 | 83 | ] | 92 | ] |
712 | 84 | 93 | ||
713 | 85 | 94 | ||
714 | @@ -89,11 +98,27 @@ | |||
715 | 89 | service = MultiService() | 98 | service = MultiService() |
716 | 90 | service.setName("statsd") | 99 | service.setName("statsd") |
717 | 91 | processor = MessageProcessor() | 100 | processor = MessageProcessor() |
718 | 101 | prefix = options["prefix"] | ||
719 | 102 | if prefix is None: | ||
720 | 103 | prefix = socket.gethostname() + ".statsd" | ||
721 | 104 | |||
722 | 105 | meter = InProcessMeter(processor, prefix=prefix) | ||
723 | 106 | |||
724 | 107 | if options["report"] is not None: | ||
725 | 108 | reporting = ReportingService() | ||
726 | 109 | reporting.setServiceParent(service) | ||
727 | 110 | reporting.schedule( | ||
728 | 111 | process.report_reactor_stats(reactor), 10, meter.increment) | ||
729 | 112 | reports = [name.strip() for name in options["report"].split(",")] | ||
730 | 113 | for report_name in reports: | ||
731 | 114 | for reporter in getattr(process, "%s_STATS" % | ||
732 | 115 | report_name.upper(), ()): | ||
733 | 116 | reporting.schedule(reporter, 10, meter.increment) | ||
734 | 92 | 117 | ||
735 | 93 | factory = GraphiteClientFactory(processor, options["flush-interval"]) | 118 | factory = GraphiteClientFactory(processor, options["flush-interval"]) |
739 | 94 | client = TCPClient( | 119 | client = TCPClient(options["carbon-cache-host"], |
740 | 95 | options["carbon-cache-host"], options["carbon-cache-port"], | 120 | options["carbon-cache-port"], |
741 | 96 | factory) | 121 | factory) |
742 | 97 | client.setServiceParent(service) | 122 | client.setServiceParent(service) |
743 | 98 | 123 | ||
744 | 99 | listener = UDPServer(options["listen-port"], | 124 | listener = UDPServer(options["listen-port"], |
745 | 100 | 125 | ||
746 | === added file 'txstatsd/tests/test_process.py' | |||
747 | --- txstatsd/tests/test_process.py 1970-01-01 00:00:00 +0000 | |||
748 | +++ txstatsd/tests/test_process.py 2011-07-12 14:22:56 +0000 | |||
749 | @@ -0,0 +1,266 @@ | |||
750 | 1 | import os | ||
751 | 2 | import psutil | ||
752 | 3 | |||
753 | 4 | from mocker import MockerTestCase | ||
754 | 5 | from twisted.internet import defer | ||
755 | 6 | from twisted.trial.unittest import TestCase | ||
756 | 7 | |||
757 | 8 | from txstatsd.process import ( | ||
758 | 9 | load_file, parse_meminfo, parse_loadavg, | ||
759 | 10 | report_process_memory_and_cpu, report_process_io_counters, | ||
760 | 11 | report_process_net_stats, report_system_stats, | ||
761 | 12 | report_reactor_stats, report_threadpool_stats) | ||
762 | 13 | |||
763 | 14 | |||
764 | 15 | meminfo = """\ | ||
765 | 16 | MemTotal: 8190436 kB | ||
766 | 17 | MemFree: 995724 kB | ||
767 | 18 | Buffers: 8052 kB | ||
768 | 19 | Cached: 344824 kB | ||
769 | 20 | SwapCached: 170828 kB | ||
770 | 21 | Active: 4342436 kB | ||
771 | 22 | Inactive: 907076 kB | ||
772 | 23 | Active(anon): 4210168 kB | ||
773 | 24 | Inactive(anon): 756096 kB | ||
774 | 25 | Active(file): 132268 kB | ||
775 | 26 | Inactive(file): 150980 kB | ||
776 | 27 | Unevictable: 641692 kB | ||
777 | 28 | Mlocked: 641676 kB | ||
778 | 29 | SwapTotal: 23993336 kB | ||
779 | 30 | SwapFree: 22750588 kB | ||
780 | 31 | Dirty: 740 kB | ||
781 | 32 | Writeback: 0 kB | ||
782 | 33 | AnonPages: 5453396 kB | ||
783 | 34 | Mapped: 259524 kB | ||
784 | 35 | Shmem: 69952 kB | ||
785 | 36 | Slab: 142444 kB | ||
786 | 37 | SReclaimable: 83188 kB | ||
787 | 38 | SUnreclaim: 59256 kB | ||
788 | 39 | KernelStack: 16144 kB | ||
789 | 40 | PageTables: 88384 kB | ||
790 | 41 | NFS_Unstable: 0 kB | ||
791 | 42 | Bounce: 0 kB | ||
792 | 43 | WritebackTmp: 0 kB | ||
793 | 44 | CommitLimit: 28088552 kB | ||
794 | 45 | Committed_AS: 11178564 kB | ||
795 | 46 | VmallocTotal: 34359738367 kB | ||
796 | 47 | VmallocUsed: 156208 kB | ||
797 | 48 | VmallocChunk: 34359579400 kB | ||
798 | 49 | HardwareCorrupted: 0 kB | ||
799 | 50 | HugePages_Total: 0 | ||
800 | 51 | HugePages_Free: 0 | ||
801 | 52 | HugePages_Rsvd: 0 | ||
802 | 53 | HugePages_Surp: 0 | ||
803 | 54 | Hugepagesize: 2048 kB | ||
804 | 55 | DirectMap4k: 7968640 kB | ||
805 | 56 | DirectMap2M: 415744 kB""" | ||
806 | 57 | |||
807 | 58 | |||
808 | 59 | class TestSystemPerformance(TestCase, MockerTestCase): | ||
809 | 60 | """Test system performance monitoring.""" | ||
810 | 61 | |||
811 | 62 | def assertSuccess(self, deferred, result=None): | ||
812 | 63 | """ | ||
813 | 64 | Assert that the given C{deferred} results in the given C{result}. | ||
814 | 65 | """ | ||
815 | 66 | self.assertTrue(isinstance(deferred, defer.Deferred)) | ||
816 | 67 | return deferred.addCallback(self.assertEqual, result) | ||
817 | 68 | |||
818 | 69 | def test_read(self): | ||
819 | 70 | """We can read files non blocking.""" | ||
820 | 71 | d = load_file(__file__) | ||
821 | 72 | return self.assertSuccess(d, open(__file__).read()) | ||
822 | 73 | |||
823 | 74 | def test_loadinfo(self): | ||
824 | 75 | """We understand loadinfo.""" | ||
825 | 76 | loadinfo = "1.02 1.08 1.14 2/2015 19420" | ||
826 | 77 | self.assertEqual(parse_loadavg(loadinfo), { | ||
827 | 78 | "sys.loadavg.oneminute": 1.02, | ||
828 | 79 | "sys.loadavg.fiveminutes": 1.08, | ||
829 | 80 | "sys.loadavg.fifthteenminutes": 1.14}) | ||
830 | 81 | |||
831 | 82 | def test_meminfo(self): | ||
832 | 83 | """We understand meminfo.""" | ||
833 | 84 | r = parse_meminfo(meminfo) | ||
834 | 85 | self.assertEqual(r['sys.mem.Buffers'], 8052 * 1024) | ||
835 | 86 | self.assert_('sys.mem.HugePages_Rsvd' not in r) | ||
836 | 87 | |||
837 | 88 | def test_statinfo(self): | ||
838 | 89 | """System stat info is collected through psutil.""" | ||
839 | 90 | cpu_times = psutil.cpu_times() | ||
840 | 91 | mock = self.mocker.replace("psutil.cpu_times") | ||
841 | 92 | self.expect(mock()).result(cpu_times) | ||
842 | 93 | self.mocker.replay() | ||
843 | 94 | |||
844 | 95 | result = report_system_stats() | ||
845 | 96 | self.assertEqual(cpu_times.idle, result["sys.cpu.idle"]) | ||
846 | 97 | self.assertEqual(cpu_times.iowait, result["sys.cpu.iowait"]) | ||
847 | 98 | self.assertEqual(cpu_times.irq, result["sys.cpu.irq"]) | ||
848 | 99 | self.assertEqual(cpu_times.nice, result["sys.cpu.nice"]) | ||
849 | 100 | self.assertEqual(cpu_times.system, result["sys.cpu.system"]) | ||
850 | 101 | self.assertEqual(cpu_times.user, result["sys.cpu.user"]) | ||
851 | 102 | |||
852 | 103 | def test_self_statinfo(self): | ||
853 | 104 | """ | ||
854 | 105 | Process stat info is collected through psutil. | ||
855 | 106 | |||
856 | 107 | If the L{Process} implementation does not have C{get_num_threads} then | ||
857 | 108 | the number of threads will not be included in the output. | ||
858 | 109 | """ | ||
859 | 110 | process = psutil.Process(os.getpid()) | ||
860 | 111 | vsize, rss = process.get_memory_info() | ||
861 | 112 | utime, stime = process.get_cpu_times() | ||
862 | 113 | cpu_percent = process.get_cpu_percent() | ||
863 | 114 | memory_percent = process.get_memory_percent() | ||
864 | 115 | |||
865 | 116 | mock = self.mocker.mock() | ||
866 | 117 | self.expect(mock.get_memory_info()).result((vsize, rss)) | ||
867 | 118 | self.expect(mock.get_cpu_times()).result((utime, stime)) | ||
868 | 119 | self.expect(mock.get_cpu_percent()).result(cpu_percent) | ||
869 | 120 | self.expect(mock.get_memory_percent()).result(memory_percent) | ||
870 | 121 | self.expect(mock.get_num_threads).result(None) | ||
871 | 122 | self.mocker.replay() | ||
872 | 123 | |||
873 | 124 | result = report_process_memory_and_cpu(process=mock) | ||
874 | 125 | self.assertEqual(utime, result["proc.cpu.user"]) | ||
875 | 126 | self.assertEqual(stime, result["proc.cpu.system"]) | ||
876 | 127 | self.assertEqual(cpu_percent, result["proc.cpu.percent"]) | ||
877 | 128 | self.assertEqual(vsize, result["proc.memory.vsize"]) | ||
878 | 129 | self.assertEqual(rss, result["proc.memory.rss"]) | ||
879 | 130 | self.assertEqual(memory_percent, result["proc.memory.percent"]) | ||
880 | 131 | self.failIf("proc.threads" in result) | ||
881 | 132 | |||
882 | 133 | def test_self_statinfo_with_num_threads(self): | ||
883 | 134 | """ | ||
884 | 135 | Process stat info is collected through psutil. | ||
885 | 136 | |||
886 | 137 | If the L{Process} implementation contains C{get_num_threads} then the | ||
887 | 138 | number of threads will be included in the output. | ||
888 | 139 | |||
889 | 140 | """ | ||
890 | 141 | process = psutil.Process(os.getpid()) | ||
891 | 142 | vsize, rss = process.get_memory_info() | ||
892 | 143 | utime, stime = process.get_cpu_times() | ||
893 | 144 | cpu_percent = process.get_cpu_percent() | ||
894 | 145 | memory_percent = process.get_memory_percent() | ||
895 | 146 | |||
896 | 147 | mock = self.mocker.mock() | ||
897 | 148 | self.expect(mock.get_memory_info()).result((vsize, rss)) | ||
898 | 149 | self.expect(mock.get_cpu_times()).result((utime, stime)) | ||
899 | 150 | self.expect(mock.get_cpu_percent()).result(cpu_percent) | ||
900 | 151 | self.expect(mock.get_memory_percent()).result(memory_percent) | ||
901 | 152 | self.expect(mock.get_num_threads()).result(1) | ||
902 | 153 | self.mocker.replay() | ||
903 | 154 | |||
904 | 155 | result = report_process_memory_and_cpu(process=mock) | ||
905 | 156 | self.assertEqual(utime, result["proc.cpu.user"]) | ||
906 | 157 | self.assertEqual(stime, result["proc.cpu.system"]) | ||
907 | 158 | self.assertEqual(cpu_percent, result["proc.cpu.percent"]) | ||
908 | 159 | self.assertEqual(vsize, result["proc.memory.vsize"]) | ||
909 | 160 | self.assertEqual(rss, result["proc.memory.rss"]) | ||
910 | 161 | self.assertEqual(memory_percent, result["proc.memory.percent"]) | ||
911 | 162 | self.assertEqual(1, result["proc.threads"]) | ||
912 | 163 | |||
913 | 164 | def test_ioinfo(self): | ||
914 | 165 | """Process IO info is collected through psutil.""" | ||
915 | 166 | mock = self.mocker.mock() | ||
916 | 167 | self.expect(mock.get_io_counters).result(None) | ||
917 | 168 | self.mocker.replay() | ||
918 | 169 | |||
919 | 170 | # If the version of psutil doesn't have the C{get_io_counters}, | ||
920 | 171 | # then io stats are not included in the output. | ||
921 | 172 | result = report_process_io_counters(process=mock) | ||
922 | 173 | self.failIf("proc.io.read.count" in result) | ||
923 | 174 | self.failIf("proc.io.write.count" in result) | ||
924 | 175 | self.failIf("proc.io.read.bytes" in result) | ||
925 | 176 | self.failIf("proc.io.write.bytes" in result) | ||
926 | 177 | |||
927 | 178 | def test_ioinfo_with_get_io_counters(self): | ||
928 | 179 | """ | ||
929 | 180 | Process IO info is collected through psutil. | ||
930 | 181 | |||
931 | 182 | If C{get_io_counters} is implemented by the L{Process} object, | ||
932 | 183 | then io information will be returned with the process information. | ||
933 | 184 | """ | ||
934 | 185 | io_counters = (10, 42, 125, 16) | ||
935 | 186 | |||
936 | 187 | mock = self.mocker.mock() | ||
937 | 188 | self.expect(mock.get_io_counters).result(mock) | ||
938 | 189 | self.expect(mock.get_io_counters()).result(io_counters) | ||
939 | 190 | self.mocker.replay() | ||
940 | 191 | |||
941 | 192 | result = report_process_io_counters(process=mock) | ||
942 | 193 | self.assertEqual(10, result["proc.io.read.count"]) | ||
943 | 194 | self.assertEqual(42, result["proc.io.write.count"]) | ||
944 | 195 | self.assertEqual(125, result["proc.io.read.bytes"]) | ||
945 | 196 | self.assertEqual(16, result["proc.io.write.bytes"]) | ||
946 | 197 | |||
947 | 198 | def test_netinfo_no_get_connections(self): | ||
948 | 199 | """ | ||
949 | 200 | Process connection info is collected through psutil. | ||
950 | 201 | |||
951 | 202 | If the version of psutil doesn't implement C{get_connections} for | ||
952 | 203 | L{Process}, then no information is returned. | ||
953 | 204 | """ | ||
954 | 205 | mock = self.mocker.mock() | ||
955 | 206 | self.expect(mock.get_connections).result(None) | ||
956 | 207 | self.mocker.replay() | ||
957 | 208 | |||
958 | 209 | # If the version of psutil doesn't have the C{get_io_counters}, | ||
959 | 210 | # then io stats are not included in the output. | ||
960 | 211 | result = report_process_net_stats(process=mock) | ||
961 | 212 | self.failIf("proc.net.status.established" in result) | ||
962 | 213 | |||
963 | 214 | def test_netinfo_with_get_connections(self): | ||
964 | 215 | """ | ||
965 | 216 | Process connection info is collected through psutil. | ||
966 | 217 | |||
967 | 218 | If the version of psutil implements C{get_connections} for L{Process}, | ||
968 | 219 | then a count of connections in each state is returned. | ||
969 | 220 | """ | ||
970 | 221 | connections = [ | ||
971 | 222 | (115, 2, 1, ("10.0.0.1", 48776), | ||
972 | 223 | ("93.186.135.91", 80), "ESTABLISHED"), | ||
973 | 224 | (117, 2, 1, ("10.0.0.1", 43761), | ||
974 | 225 | ("72.14.234.100", 80), "CLOSING"), | ||
975 | 226 | (119, 2, 1, ("10.0.0.1", 60759), | ||
976 | 227 | ("72.14.234.104", 80), "ESTABLISHED"), | ||
977 | 228 | (123, 2, 1, ("10.0.0.1", 51314), | ||
978 | 229 | ("72.14.234.83", 443), "SYN_SENT") | ||
979 | 230 | ] | ||
980 | 231 | |||
981 | 232 | mock = self.mocker.mock() | ||
982 | 233 | self.expect(mock.get_connections).result(mock) | ||
983 | 234 | self.expect(mock.get_connections()).result(connections) | ||
984 | 235 | self.mocker.replay() | ||
985 | 236 | |||
986 | 237 | result = report_process_net_stats(process=mock) | ||
987 | 238 | self.assertEqual(2, result["proc.net.status.established"]) | ||
988 | 239 | self.assertEqual(1, result["proc.net.status.closing"]) | ||
989 | 240 | self.assertEqual(1, result["proc.net.status.syn_sent"]) | ||
990 | 241 | |||
991 | 242 | def test_reactor_stats(self): | ||
992 | 243 | """Given a twisted reactor, pull out some stats from it.""" | ||
993 | 244 | mock = self.mocker.mock() | ||
994 | 245 | self.expect(mock.getReaders()).result([None, None, None]) | ||
995 | 246 | self.expect(mock.getWriters()).result([None, None]) | ||
996 | 247 | self.mocker.replay() | ||
997 | 248 | |||
998 | 249 | result = report_reactor_stats(mock)() | ||
999 | 250 | self.assertEqual(3, result["reactor.readers"]) | ||
1000 | 251 | self.assertEqual(2, result["reactor.writers"]) | ||
1001 | 252 | |||
1002 | 253 | def test_threadpool_stats(self): | ||
1003 | 254 | """Given a twisted threadpool, pull out some stats from it.""" | ||
1004 | 255 | mock = self.mocker.mock() | ||
1005 | 256 | self.expect(mock.q.qsize()).result(42) | ||
1006 | 257 | self.expect(mock.threads).result(6 * [None]) | ||
1007 | 258 | self.expect(mock.waiters).result(2 * [None]) | ||
1008 | 259 | self.expect(mock.working).result(4 * [None]) | ||
1009 | 260 | self.mocker.replay() | ||
1010 | 261 | |||
1011 | 262 | result = report_threadpool_stats(mock)() | ||
1012 | 263 | self.assertEqual(42, result["threadpool.queue"]) | ||
1013 | 264 | self.assertEqual(6, result["threadpool.threads"]) | ||
1014 | 265 | self.assertEqual(2, result["threadpool.waiters"]) | ||
1015 | 266 | self.assertEqual(4, result["threadpool.working"]) | ||
1016 | 0 | 267 | ||
1017 | === modified file 'txstatsd/tests/test_service.py' | |||
1018 | --- txstatsd/tests/test_service.py 2011-07-05 05:27:22 +0000 | |||
1019 | +++ txstatsd/tests/test_service.py 2011-07-12 14:22:56 +0000 | |||
1020 | @@ -6,6 +6,7 @@ | |||
1021 | 6 | 6 | ||
1022 | 7 | 7 | ||
1023 | 8 | class GlueOptionsTestCase(TestCase): | 8 | class GlueOptionsTestCase(TestCase): |
1024 | 9 | |||
1025 | 9 | def test_defaults(self): | 10 | def test_defaults(self): |
1026 | 10 | """ | 11 | """ |
1027 | 11 | Defaults get passed over to the instance. | 12 | Defaults get passed over to the instance. |
1028 | @@ -94,6 +95,7 @@ | |||
1029 | 94 | 95 | ||
1030 | 95 | 96 | ||
1031 | 96 | class ServiceTests(TestCase): | 97 | class ServiceTests(TestCase): |
1032 | 98 | |||
1033 | 97 | def test_service(self): | 99 | def test_service(self): |
1034 | 98 | """ | 100 | """ |
1035 | 99 | The StatsD service can be created. | 101 | The StatsD service can be created. |
python-psutil needs to be added as a dependency.