Merge ~twom/launchpad:stats-in-buildd-manager into launchpad:master
- Git
- lp:~twom/launchpad
- stats-in-buildd-manager
- Merge into master
Proposed by
Tom Wardill
Status: | Merged |
---|---|
Approved by: | Tom Wardill |
Approved revision: | aef55cbb6e7e9d12113a9995466204785ac577ff |
Merge reported by: | Otto Co-Pilot |
Merged at revision: | not available |
Proposed branch: | ~twom/launchpad:stats-in-buildd-manager |
Merge into: | launchpad:master |
Diff against target: |
392 lines (+218/-5) 10 files modified
constraints.txt (+1/-0) lib/lp/buildmaster/interactor.py (+2/-2) lib/lp/buildmaster/manager.py (+40/-0) lib/lp/buildmaster/tests/mock_slaves.py (+3/-1) lib/lp/buildmaster/tests/test_manager.py (+90/-1) lib/lp/services/config/schema-lazr.conf (+6/-1) lib/lp/services/statsd/__init__.py (+35/-0) lib/lp/services/statsd/tests/__init__.py (+0/-0) lib/lp/services/statsd/tests/test_lp_statsd.py (+40/-0) setup.py (+1/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Colin Watson (community) | Approve | ||
Ioana Lasc (community) | Approve | ||
Review via email: mp+389011@code.launchpad.net |
Commit message
Add statsd totals for builder stats by processor
Description of the change
It'd be useful to graph the builder states over time, by architecture. Create a statsd client that is configured from settings (with a dummy one to prevent network traffic if not).
Use this in builddmanager in an extra (tunable) loop to prevent impingement on other manager work.
To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) : | # |
review:
Approve
- bf1b6d3... by Tom Wardill
-
Remove unnecessary empty config
Revision history for this message
Tom Wardill (twom) : | # |
- 9eea3aa... by Tom Wardill
-
Add startService test, fix client method name
- 5f433b1... by Tom Wardill
-
Actually include the value, not the name
- eeba945... by Tom Wardill
-
Change to use labels/tags for gauge names
- aef55cb... by Tom Wardill
-
Change tag format, include more attributes as tags
Revision history for this message
Colin Watson (cjwatson) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/constraints.txt b/constraints.txt | |||
2 | index 8a9077d..79ed49e 100644 | |||
3 | --- a/constraints.txt | |||
4 | +++ b/constraints.txt | |||
5 | @@ -297,6 +297,7 @@ SimpleTAL==4.3; python_version < "3" | |||
6 | 297 | SimpleTAL==5.2; python_version >= "3" | 297 | SimpleTAL==5.2; python_version >= "3" |
7 | 298 | soupmatchers==0.4 | 298 | soupmatchers==0.4 |
8 | 299 | soupsieve==1.9 | 299 | soupsieve==1.9 |
9 | 300 | statsd==3.3.0 | ||
10 | 300 | # lp:~launchpad-committers/storm/lp | 301 | # lp:~launchpad-committers/storm/lp |
11 | 301 | storm==0.24+lp416 | 302 | storm==0.24+lp416 |
12 | 302 | subprocess32==3.2.6 | 303 | subprocess32==3.2.6 |
13 | diff --git a/lib/lp/buildmaster/interactor.py b/lib/lp/buildmaster/interactor.py | |||
14 | index 7450a0b..b1d9ccd 100644 | |||
15 | --- a/lib/lp/buildmaster/interactor.py | |||
16 | +++ b/lib/lp/buildmaster/interactor.py | |||
17 | @@ -321,7 +321,7 @@ BuilderVitals = namedtuple( | |||
18 | 321 | 'BuilderVitals', | 321 | 'BuilderVitals', |
19 | 322 | ('name', 'url', 'processor_names', 'virtualized', 'vm_host', | 322 | ('name', 'url', 'processor_names', 'virtualized', 'vm_host', |
20 | 323 | 'vm_reset_protocol', 'builderok', 'manual', 'build_queue', 'version', | 323 | 'vm_reset_protocol', 'builderok', 'manual', 'build_queue', 'version', |
22 | 324 | 'clean_status')) | 324 | 'clean_status', 'active')) |
23 | 325 | 325 | ||
24 | 326 | _BQ_UNSPECIFIED = object() | 326 | _BQ_UNSPECIFIED = object() |
25 | 327 | 327 | ||
26 | @@ -334,7 +334,7 @@ def extract_vitals_from_db(builder, build_queue=_BQ_UNSPECIFIED): | |||
27 | 334 | [processor.name for processor in builder.processors], | 334 | [processor.name for processor in builder.processors], |
28 | 335 | builder.virtualized, builder.vm_host, builder.vm_reset_protocol, | 335 | builder.virtualized, builder.vm_host, builder.vm_reset_protocol, |
29 | 336 | builder.builderok, builder.manual, build_queue, builder.version, | 336 | builder.builderok, builder.manual, build_queue, builder.version, |
31 | 337 | builder.clean_status) | 337 | builder.clean_status, builder.active) |
32 | 338 | 338 | ||
33 | 339 | 339 | ||
34 | 340 | class BuilderInteractor(object): | 340 | class BuilderInteractor(object): |
35 | diff --git a/lib/lp/buildmaster/manager.py b/lib/lp/buildmaster/manager.py | |||
36 | index a4b96e3..3ace0d8 100644 | |||
37 | --- a/lib/lp/buildmaster/manager.py | |||
38 | +++ b/lib/lp/buildmaster/manager.py | |||
39 | @@ -61,6 +61,7 @@ from lp.services.database.stormexpr import ( | |||
40 | 61 | Values, | 61 | Values, |
41 | 62 | ) | 62 | ) |
42 | 63 | from lp.services.propertycache import get_property_cache | 63 | from lp.services.propertycache import get_property_cache |
43 | 64 | from lp.services.statsd import get_statsd_client | ||
44 | 64 | 65 | ||
45 | 65 | 66 | ||
46 | 66 | BUILDD_MANAGER_LOG_NAME = "slave-scanner" | 67 | BUILDD_MANAGER_LOG_NAME = "slave-scanner" |
47 | @@ -691,6 +692,9 @@ class BuilddManager(service.Service): | |||
48 | 691 | # How often to flush logtail updates, in seconds. | 692 | # How often to flush logtail updates, in seconds. |
49 | 692 | FLUSH_LOGTAILS_INTERVAL = 15 | 693 | FLUSH_LOGTAILS_INTERVAL = 15 |
50 | 693 | 694 | ||
51 | 695 | # How often to update stats, in seconds | ||
52 | 696 | UPDATE_STATS_INTERVAL = 60 | ||
53 | 697 | |||
54 | 694 | def __init__(self, clock=None, builder_factory=None): | 698 | def __init__(self, clock=None, builder_factory=None): |
55 | 695 | # Use the clock if provided, it's so that tests can | 699 | # Use the clock if provided, it's so that tests can |
56 | 696 | # advance it. Use the reactor by default. | 700 | # advance it. Use the reactor by default. |
57 | @@ -702,6 +706,7 @@ class BuilddManager(service.Service): | |||
58 | 702 | self.logger = self._setupLogger() | 706 | self.logger = self._setupLogger() |
59 | 703 | self.current_builders = [] | 707 | self.current_builders = [] |
60 | 704 | self.pending_logtails = {} | 708 | self.pending_logtails = {} |
61 | 709 | self.statsd_client = get_statsd_client() | ||
62 | 705 | 710 | ||
63 | 706 | def _setupLogger(self): | 711 | def _setupLogger(self): |
64 | 707 | """Set up a 'slave-scanner' logger that redirects to twisted. | 712 | """Set up a 'slave-scanner' logger that redirects to twisted. |
65 | @@ -721,6 +726,36 @@ class BuilddManager(service.Service): | |||
66 | 721 | logger.setLevel(level) | 726 | logger.setLevel(level) |
67 | 722 | return logger | 727 | return logger |
68 | 723 | 728 | ||
69 | 729 | def updateStats(self): | ||
70 | 730 | """Update statsd with the builder statuses.""" | ||
71 | 731 | self.logger.debug("Updating builder stats.") | ||
72 | 732 | counts_by_processor = {} | ||
73 | 733 | for builder in self.builder_factory.iterVitals(): | ||
74 | 734 | if not builder.active: | ||
75 | 735 | continue | ||
76 | 736 | for processor_name in builder.processor_names: | ||
77 | 737 | counts = counts_by_processor.setdefault( | ||
78 | 738 | "{},virtualized={}".format( | ||
79 | 739 | processor_name, | ||
80 | 740 | builder.virtualized), | ||
81 | 741 | {'cleaning': 0, 'idle': 0, 'disabled': 0, 'building': 0}) | ||
82 | 742 | if not builder.builderok: | ||
83 | 743 | counts['disabled'] += 1 | ||
84 | 744 | elif builder.clean_status == BuilderCleanStatus.CLEANING: | ||
85 | 745 | counts['cleaning'] += 1 | ||
86 | 746 | elif (builder.build_queue and | ||
87 | 747 | builder.build_queue.status == BuildQueueStatus.RUNNING): | ||
88 | 748 | counts['building'] += 1 | ||
89 | 749 | elif builder.clean_status == BuilderCleanStatus.CLEAN: | ||
90 | 750 | counts['idle'] += 1 | ||
91 | 751 | for processor, counts in counts_by_processor.items(): | ||
92 | 752 | for count_name, count_value in counts.items(): | ||
93 | 753 | gauge_name = "builders.{},arch={}".format( | ||
94 | 754 | count_name, processor) | ||
95 | 755 | self.logger.debug("{}: {}".format(gauge_name, count_value)) | ||
96 | 756 | self.statsd_client.gauge(gauge_name, count_value) | ||
97 | 757 | self.logger.debug("Builder stats update complete.") | ||
98 | 758 | |||
99 | 724 | def checkForNewBuilders(self): | 759 | def checkForNewBuilders(self): |
100 | 725 | """Add and return any new builders.""" | 760 | """Add and return any new builders.""" |
101 | 726 | new_builders = set( | 761 | new_builders = set( |
102 | @@ -790,6 +825,9 @@ class BuilddManager(service.Service): | |||
103 | 790 | # Schedule bulk flushes for build queue logtail updates. | 825 | # Schedule bulk flushes for build queue logtail updates. |
104 | 791 | self.flush_logtails_loop, self.flush_logtails_deferred = ( | 826 | self.flush_logtails_loop, self.flush_logtails_deferred = ( |
105 | 792 | self._startLoop(self.FLUSH_LOGTAILS_INTERVAL, self.flushLogTails)) | 827 | self._startLoop(self.FLUSH_LOGTAILS_INTERVAL, self.flushLogTails)) |
106 | 828 | # Schedule stats updates. | ||
107 | 829 | self.stats_update_loop, self.stats_update_deferred = ( | ||
108 | 830 | self._startLoop(self.UPDATE_STATS_INTERVAL, self.updateStats)) | ||
109 | 793 | 831 | ||
110 | 794 | def stopService(self): | 832 | def stopService(self): |
111 | 795 | """Callback for when we need to shut down.""" | 833 | """Callback for when we need to shut down.""" |
112 | @@ -798,9 +836,11 @@ class BuilddManager(service.Service): | |||
113 | 798 | deferreds = [slave.stopping_deferred for slave in self.builder_slaves] | 836 | deferreds = [slave.stopping_deferred for slave in self.builder_slaves] |
114 | 799 | deferreds.append(self.scan_builders_deferred) | 837 | deferreds.append(self.scan_builders_deferred) |
115 | 800 | deferreds.append(self.flush_logtails_deferred) | 838 | deferreds.append(self.flush_logtails_deferred) |
116 | 839 | deferreds.append(self.stats_update_deferred) | ||
117 | 801 | 840 | ||
118 | 802 | self.flush_logtails_loop.stop() | 841 | self.flush_logtails_loop.stop() |
119 | 803 | self.scan_builders_loop.stop() | 842 | self.scan_builders_loop.stop() |
120 | 843 | self.stats_update_loop.stop() | ||
121 | 804 | for slave in self.builder_slaves: | 844 | for slave in self.builder_slaves: |
122 | 805 | slave.stopCycle() | 845 | slave.stopCycle() |
123 | 806 | 846 | ||
124 | diff --git a/lib/lp/buildmaster/tests/mock_slaves.py b/lib/lp/buildmaster/tests/mock_slaves.py | |||
125 | index 2794137..2e935e4 100644 | |||
126 | --- a/lib/lp/buildmaster/tests/mock_slaves.py | |||
127 | +++ b/lib/lp/buildmaster/tests/mock_slaves.py | |||
128 | @@ -58,7 +58,8 @@ class MockBuilder: | |||
129 | 58 | processors=None, virtualized=True, vm_host=None, | 58 | processors=None, virtualized=True, vm_host=None, |
130 | 59 | url='http://fake:0000', version=None, | 59 | url='http://fake:0000', version=None, |
131 | 60 | clean_status=BuilderCleanStatus.DIRTY, | 60 | clean_status=BuilderCleanStatus.DIRTY, |
133 | 61 | vm_reset_protocol=BuilderResetProtocol.PROTO_1_1): | 61 | vm_reset_protocol=BuilderResetProtocol.PROTO_1_1, |
134 | 62 | active=True): | ||
135 | 62 | self.currentjob = None | 63 | self.currentjob = None |
136 | 63 | self.builderok = builderok | 64 | self.builderok = builderok |
137 | 64 | self.manual = manual | 65 | self.manual = manual |
138 | @@ -71,6 +72,7 @@ class MockBuilder: | |||
139 | 71 | self.failnotes = None | 72 | self.failnotes = None |
140 | 72 | self.version = version | 73 | self.version = version |
141 | 73 | self.clean_status = clean_status | 74 | self.clean_status = clean_status |
142 | 75 | self.active = active | ||
143 | 74 | 76 | ||
144 | 75 | def setCleanStatus(self, clean_status): | 77 | def setCleanStatus(self, clean_status): |
145 | 76 | self.clean_status = clean_status | 78 | self.clean_status = clean_status |
146 | diff --git a/lib/lp/buildmaster/tests/test_manager.py b/lib/lp/buildmaster/tests/test_manager.py | |||
147 | index b0d56ed..06b4e2d 100644 | |||
148 | --- a/lib/lp/buildmaster/tests/test_manager.py | |||
149 | +++ b/lib/lp/buildmaster/tests/test_manager.py | |||
150 | @@ -13,8 +13,12 @@ import os | |||
151 | 13 | import signal | 13 | import signal |
152 | 14 | import time | 14 | import time |
153 | 15 | 15 | ||
154 | 16 | from fixtures import MockPatch | ||
155 | 16 | from six.moves import xmlrpc_client | 17 | from six.moves import xmlrpc_client |
157 | 17 | from testtools.matchers import Equals | 18 | from testtools.matchers import ( |
158 | 19 | Equals, | ||
159 | 20 | MatchesListwise, | ||
160 | 21 | ) | ||
161 | 18 | from testtools.testcase import ExpectedException | 22 | from testtools.testcase import ExpectedException |
162 | 19 | from testtools.twistedsupport import AsynchronousDeferredRunTest | 23 | from testtools.twistedsupport import AsynchronousDeferredRunTest |
163 | 20 | import transaction | 24 | import transaction |
164 | @@ -45,6 +49,7 @@ from lp.buildmaster.interfaces.builder import ( | |||
165 | 45 | IBuilderSet, | 49 | IBuilderSet, |
166 | 46 | ) | 50 | ) |
167 | 47 | from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet | 51 | from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet |
168 | 52 | from lp.buildmaster.interfaces.processor import IProcessorSet | ||
169 | 48 | from lp.buildmaster.manager import ( | 53 | from lp.buildmaster.manager import ( |
170 | 49 | BuilddManager, | 54 | BuilddManager, |
171 | 50 | BUILDER_FAILURE_THRESHOLD, | 55 | BUILDER_FAILURE_THRESHOLD, |
172 | @@ -1207,6 +1212,22 @@ class TestBuilddManager(TestCase): | |||
173 | 1207 | clock.advance(advance) | 1212 | clock.advance(advance) |
174 | 1208 | self.assertNotEqual(0, manager.flushLogTails.call_count) | 1213 | self.assertNotEqual(0, manager.flushLogTails.call_count) |
175 | 1209 | 1214 | ||
176 | 1215 | def test_startService_adds_updateStats_loop(self): | ||
177 | 1216 | # When startService is called, the manager will start up a | ||
178 | 1217 | # updateStats loop. | ||
179 | 1218 | self._stub_out_scheduleNextScanCycle() | ||
180 | 1219 | clock = task.Clock() | ||
181 | 1220 | manager = BuilddManager(clock=clock) | ||
182 | 1221 | |||
183 | 1222 | # Replace updateStats() with FakeMethod so we can see if it was | ||
184 | 1223 | # called. | ||
185 | 1224 | manager.updateStats = FakeMethod() | ||
186 | 1225 | |||
187 | 1226 | manager.startService() | ||
188 | 1227 | advance = BuilddManager.UPDATE_STATS_INTERVAL + 1 | ||
189 | 1228 | clock.advance(advance) | ||
190 | 1229 | self.assertNotEqual(0, manager.updateStats.call_count) | ||
191 | 1230 | |||
192 | 1210 | 1231 | ||
193 | 1211 | class TestFailureAssessments(TestCaseWithFactory): | 1232 | class TestFailureAssessments(TestCaseWithFactory): |
194 | 1212 | 1233 | ||
195 | @@ -1596,3 +1617,71 @@ class TestBuilddManagerScript(TestCaseWithFactory): | |||
196 | 1596 | self.assertFalse( | 1617 | self.assertFalse( |
197 | 1597 | os.access(rotated_logfilepath, os.F_OK), | 1618 | os.access(rotated_logfilepath, os.F_OK), |
198 | 1598 | "Twistd's log file was rotated by twistd.") | 1619 | "Twistd's log file was rotated by twistd.") |
199 | 1620 | |||
200 | 1621 | |||
201 | 1622 | class TestStats(TestCaseWithFactory): | ||
202 | 1623 | |||
203 | 1624 | layer = ZopelessDatabaseLayer | ||
204 | 1625 | run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=20) | ||
205 | 1626 | |||
206 | 1627 | def setUp(self): | ||
207 | 1628 | super(TestStats, self).setUp() | ||
208 | 1629 | self.stats_client = self.useFixture( | ||
209 | 1630 | MockPatch( | ||
210 | 1631 | 'lp.buildmaster.manager.get_statsd_client' | ||
211 | 1632 | )).mock() | ||
212 | 1633 | |||
213 | 1634 | def test_single_processor(self): | ||
214 | 1635 | builder = self.factory.makeBuilder() | ||
215 | 1636 | builder.setCleanStatus(BuilderCleanStatus.CLEAN) | ||
216 | 1637 | self.patch(BuilderSlave, 'makeBuilderSlave', FakeMethod(OkSlave())) | ||
217 | 1638 | transaction.commit() | ||
218 | 1639 | clock = task.Clock() | ||
219 | 1640 | manager = BuilddManager(clock=clock) | ||
220 | 1641 | manager.builder_factory.update() | ||
221 | 1642 | manager.updateStats() | ||
222 | 1643 | |||
223 | 1644 | self.assertEqual(8, self.stats_client.gauge.call_count) | ||
224 | 1645 | for call in self.stats_client.mock.gauge.call_args_list: | ||
225 | 1646 | self.assertIn('386', call[0][0]) | ||
226 | 1647 | |||
227 | 1648 | def test_multiple_processor(self): | ||
228 | 1649 | builder = self.factory.makeBuilder( | ||
229 | 1650 | processors=[getUtility(IProcessorSet).getByName('amd64')]) | ||
230 | 1651 | builder.setCleanStatus(BuilderCleanStatus.CLEAN) | ||
231 | 1652 | self.patch(BuilderSlave, 'makeBuilderSlave', FakeMethod(OkSlave())) | ||
232 | 1653 | transaction.commit() | ||
233 | 1654 | clock = task.Clock() | ||
234 | 1655 | manager = BuilddManager(clock=clock) | ||
235 | 1656 | manager.builder_factory.update() | ||
236 | 1657 | manager.updateStats() | ||
237 | 1658 | |||
238 | 1659 | self.assertEqual(12, self.stats_client.gauge.call_count) | ||
239 | 1660 | i386_calls = [c for c in self.stats_client.gauge.call_args_list | ||
240 | 1661 | if '386' in c[0][0]] | ||
241 | 1662 | amd64_calls = [c for c in self.stats_client.gauge.call_args_list | ||
242 | 1663 | if 'amd64' in c[0][0]] | ||
243 | 1664 | self.assertEqual(8, len(i386_calls)) | ||
244 | 1665 | self.assertEqual(4, len(amd64_calls)) | ||
245 | 1666 | |||
246 | 1667 | def test_correct_values(self): | ||
247 | 1668 | builder = self.factory.makeBuilder( | ||
248 | 1669 | processors=[getUtility(IProcessorSet).getByName('amd64')]) | ||
249 | 1670 | builder.setCleanStatus(BuilderCleanStatus.CLEANING) | ||
250 | 1671 | self.patch(BuilderSlave, 'makeBuilderSlave', FakeMethod(OkSlave())) | ||
251 | 1672 | transaction.commit() | ||
252 | 1673 | clock = task.Clock() | ||
253 | 1674 | manager = BuilddManager(clock=clock) | ||
254 | 1675 | manager.builder_factory.update() | ||
255 | 1676 | manager.updateStats() | ||
256 | 1677 | |||
257 | 1678 | self.assertEqual(12, self.stats_client.gauge.call_count) | ||
258 | 1679 | calls = [c[0] for c in self.stats_client.gauge.call_args_list | ||
259 | 1680 | if 'amd64' in c[0][0]] | ||
260 | 1681 | self.assertThat( | ||
261 | 1682 | calls, MatchesListwise( | ||
262 | 1683 | [Equals(('builders.disabled,arch=amd64,virtualized=True', 0)), | ||
263 | 1684 | Equals(('builders.building,arch=amd64,virtualized=True', 0)), | ||
264 | 1685 | Equals(('builders.idle,arch=amd64,virtualized=True', 0)), | ||
265 | 1686 | Equals(('builders.cleaning,arch=amd64,virtualized=True', 1)) | ||
266 | 1687 | ])) | ||
267 | diff --git a/lib/lp/services/config/schema-lazr.conf b/lib/lp/services/config/schema-lazr.conf | |||
268 | index 80bc7e2..bb8afcd 100644 | |||
269 | --- a/lib/lp/services/config/schema-lazr.conf | |||
270 | +++ b/lib/lp/services/config/schema-lazr.conf | |||
271 | @@ -113,7 +113,6 @@ authentication_endpoint: none | |||
272 | 113 | # authserver. | 113 | # authserver. |
273 | 114 | authentication_timeout: 15 | 114 | authentication_timeout: 15 |
274 | 115 | 115 | ||
275 | 116 | |||
276 | 117 | [canonical] | 116 | [canonical] |
277 | 118 | # datatype: boolean | 117 | # datatype: boolean |
278 | 119 | show_tracebacks: False | 118 | show_tracebacks: False |
279 | @@ -2022,3 +2021,9 @@ concurrency: 1 | |||
280 | 2022 | timeout: 86400 | 2021 | timeout: 86400 |
281 | 2023 | fallback_queue: | 2022 | fallback_queue: |
282 | 2024 | concurrency: 1 | 2023 | concurrency: 1 |
283 | 2024 | |||
284 | 2025 | # Statsd connection details | ||
285 | 2026 | [statsd] | ||
286 | 2027 | host: none | ||
287 | 2028 | port: none | ||
288 | 2029 | prefix: none | ||
289 | diff --git a/lib/lp/services/statsd/__init__.py b/lib/lp/services/statsd/__init__.py | |||
290 | 2025 | new file mode 100644 | 2030 | new file mode 100644 |
291 | index 0000000..22c839f | |||
292 | --- /dev/null | |||
293 | +++ b/lib/lp/services/statsd/__init__.py | |||
294 | @@ -0,0 +1,35 @@ | |||
295 | 1 | # Copyright 2020 Canonical Ltd. This software is licensed under the | ||
296 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
297 | 3 | |||
298 | 4 | """Statsd client wrapper with Launchpad configuration""" | ||
299 | 5 | |||
300 | 6 | from __future__ import absolute_import, print_function, unicode_literals | ||
301 | 7 | |||
302 | 8 | __metaclass__ = type | ||
303 | 9 | __all__ = ['get_statsd_client'] | ||
304 | 10 | |||
305 | 11 | |||
306 | 12 | from statsd import StatsClient | ||
307 | 13 | |||
308 | 14 | from lp.services.config import config | ||
309 | 15 | |||
310 | 16 | |||
311 | 17 | class UnconfiguredStatsdClient: | ||
312 | 18 | """Dummy client for if statsd is not configured in the environment. | ||
313 | 19 | |||
314 | 20 | This client will be used if the statsd settings are not available to | ||
315 | 21 | Launchpad. Prevents unnecessary network traffic. | ||
316 | 22 | """ | ||
317 | 23 | |||
318 | 24 | def __getattr__(self, name): | ||
319 | 25 | return lambda *args, **kwargs: None | ||
320 | 26 | |||
321 | 27 | |||
322 | 28 | def get_statsd_client(): | ||
323 | 29 | if config.statsd.host: | ||
324 | 30 | return StatsClient( | ||
325 | 31 | host=config.statsd.host, | ||
326 | 32 | port=config.statsd.port, | ||
327 | 33 | prefix=config.statsd.prefix) | ||
328 | 34 | else: | ||
329 | 35 | return UnconfiguredStatsdClient() | ||
330 | diff --git a/lib/lp/services/statsd/tests/__init__.py b/lib/lp/services/statsd/tests/__init__.py | |||
331 | 0 | new file mode 100644 | 36 | new file mode 100644 |
332 | index 0000000..e69de29 | |||
333 | --- /dev/null | |||
334 | +++ b/lib/lp/services/statsd/tests/__init__.py | |||
335 | diff --git a/lib/lp/services/statsd/tests/test_lp_statsd.py b/lib/lp/services/statsd/tests/test_lp_statsd.py | |||
336 | 1 | new file mode 100644 | 37 | new file mode 100644 |
337 | index 0000000..691e93d | |||
338 | --- /dev/null | |||
339 | +++ b/lib/lp/services/statsd/tests/test_lp_statsd.py | |||
340 | @@ -0,0 +1,40 @@ | |||
341 | 1 | # Copyright 2020 Canonical Ltd. This software is licensed under the | ||
342 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
343 | 3 | |||
344 | 4 | """Tests for the Launchpad statsd client""" | ||
345 | 5 | |||
346 | 6 | from __future__ import absolute_import, print_function, unicode_literals | ||
347 | 7 | |||
348 | 8 | __metaclass__ = type | ||
349 | 9 | |||
350 | 10 | from statsd import StatsClient | ||
351 | 11 | |||
352 | 12 | from lp.services.config import config | ||
353 | 13 | from lp.services.statsd import ( | ||
354 | 14 | get_statsd_client, | ||
355 | 15 | UnconfiguredStatsdClient, | ||
356 | 16 | ) | ||
357 | 17 | from lp.testing import TestCase | ||
358 | 18 | from lp.testing.layers import BaseLayer | ||
359 | 19 | |||
360 | 20 | |||
361 | 21 | class TestClientConfiguration(TestCase): | ||
362 | 22 | |||
363 | 23 | layer = BaseLayer | ||
364 | 24 | |||
365 | 25 | def test_get_correct_instance_unconfigured(self): | ||
366 | 26 | """Test that we get the correct client, depending on config values.""" | ||
367 | 27 | config.push( | ||
368 | 28 | 'statsd_test', | ||
369 | 29 | "[statsd]\nhost:") | ||
370 | 30 | client = get_statsd_client() | ||
371 | 31 | self.assertEqual( | ||
372 | 32 | type(client), UnconfiguredStatsdClient) | ||
373 | 33 | |||
374 | 34 | def test_get_correct_instance_configured(self): | ||
375 | 35 | config.push( | ||
376 | 36 | 'statsd_test', | ||
377 | 37 | "[statsd]\nhost: 127.0.01\nport: 9999\nprefix: test\n") | ||
378 | 38 | client = get_statsd_client() | ||
379 | 39 | self.assertEqual( | ||
380 | 40 | type(client), StatsClient) | ||
381 | diff --git a/setup.py b/setup.py | |||
382 | index db7963e..0f15f7f 100644 | |||
383 | --- a/setup.py | |||
384 | +++ b/setup.py | |||
385 | @@ -232,6 +232,7 @@ setup( | |||
386 | 232 | 'six', | 232 | 'six', |
387 | 233 | 'soupmatchers', | 233 | 'soupmatchers', |
388 | 234 | 'Sphinx', | 234 | 'Sphinx', |
389 | 235 | 'statsd', | ||
390 | 235 | 'storm', | 236 | 'storm', |
391 | 236 | # XXX cjwatson 2020-08-07: Temporarily dropped on Python 3 until | 237 | # XXX cjwatson 2020-08-07: Temporarily dropped on Python 3 until |
392 | 237 | # codeimport can be ported to Breezy. | 238 | # codeimport can be ported to Breezy. |
looks good to me but i'd get a review from Colin as well.