Merge ~cjwatson/launchpad:number-cruncher-librarian into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: f0051c20b4ba7bcc28d7a6acca972c4b69942589
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:number-cruncher-librarian
Merge into: launchpad:master
Diff against target: 205 lines (+112/-17)
2 files modified
lib/lp/services/statsd/numbercruncher.py (+49/-17)
lib/lp/services/statsd/tests/test_numbercruncher.py (+63/-0)
Reviewer Review Type Date Requested Status
Tom Wardill (community) Approve
Review via email: mp+393616@code.launchpad.net

Commit message

number-cruncher: Add librarian metrics

Description of the change

From lp:tuolumne-lp-configs. This query is fairly heavyweight (it takes a couple of minutes to run on staging), so only run it once an hour.

To post a comment you must log in.
Revision history for this message
Tom Wardill (twom) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/services/statsd/numbercruncher.py b/lib/lp/services/statsd/numbercruncher.py
2index 6a43c36..27c3a30 100644
3--- a/lib/lp/services/statsd/numbercruncher.py
4+++ b/lib/lp/services/statsd/numbercruncher.py
5@@ -10,6 +10,10 @@ __all__ = ['NumberCruncher']
6
7 import logging
8
9+from storm.expr import (
10+ Count,
11+ Sum,
12+ )
13 import transaction
14 from twisted.application import service
15 from twisted.internet import (
16@@ -26,6 +30,8 @@ from lp.buildmaster.enums import (
17 )
18 from lp.buildmaster.interfaces.builder import IBuilderSet
19 from lp.buildmaster.manager import PrefetchedBuilderFactory
20+from lp.services.database.interfaces import IStore
21+from lp.services.librarian.model import LibraryFileContent
22 from lp.services.statsd.interfaces.statsd_client import IStatsdClient
23
24 NUMBER_CRUNCHER_LOG_NAME = "number-cruncher"
25@@ -36,6 +42,7 @@ class NumberCruncher(service.Service):
26
27 QUEUE_INTERVAL = 60
28 BUILDER_INTERVAL = 60
29+ LIBRARIAN_INTERVAL = 3600
30
31 def __init__(self, clock=None, builder_factory=None):
32 if clock is None:
33@@ -68,6 +75,10 @@ class NumberCruncher(service.Service):
34 stopping_deferred = loop.start(interval)
35 return loop, stopping_deferred
36
37+ def _sendGauge(self, gauge_name, value):
38+ self.logger.debug("{}: {}".format(gauge_name, value))
39+ self.statsd_client.gauge(gauge_name, value)
40+
41 def updateBuilderQueues(self):
42 """Update statsd with the build queue lengths.
43
44@@ -82,8 +93,7 @@ class NumberCruncher(service.Service):
45 gauge_name = (
46 "buildqueue,virtualized={},arch={},env={}".format(
47 virt, arch, self.statsd_client.lp_environment))
48- self.logger.debug("{}: {}".format(gauge_name, value[0]))
49- self.statsd_client.gauge(gauge_name, value[0])
50+ self._sendGauge(gauge_name, value[0])
51 self.logger.debug("Build queue stats update complete.")
52 except Exception:
53 self.logger.exception("Failure while updating build queue stats:")
54@@ -118,8 +128,7 @@ class NumberCruncher(service.Service):
55 for count_name, count_value in counts.items():
56 gauge_name = "builders,status={},arch={},env={}".format(
57 count_name, processor, self.statsd_client.lp_environment)
58- self.logger.debug("{}: {}".format(gauge_name, count_value))
59- self.statsd_client.gauge(gauge_name, count_value)
60+ self._sendGauge(gauge_name, count_value)
61 self.logger.debug("Builder stats update complete.")
62
63 def updateBuilderStats(self):
64@@ -134,20 +143,43 @@ class NumberCruncher(service.Service):
65 self.logger.exception("Failure while updating builder stats:")
66 transaction.abort()
67
68+ def updateLibrarianStats(self):
69+ """Update librarian statistics.
70+
71+ This aborts the current transaction before returning.
72+ """
73+ try:
74+ self.logger.debug("Updating librarian stats.")
75+ store = IStore(LibraryFileContent)
76+ total_files, total_filesize = store.find(
77+ (Count(), Sum(LibraryFileContent.filesize))).one()
78+ self._sendGauge(
79+ "librarian.total_files,env={}".format(
80+ self.statsd_client.lp_environment),
81+ total_files)
82+ self._sendGauge(
83+ "librarian.total_filesize,env={}".format(
84+ self.statsd_client.lp_environment),
85+ total_filesize)
86+ self.logger.debug("Librarian stats update complete.")
87+ except Exception:
88+ self.logger.exception("Failure while updating librarian stats:")
89+ transaction.abort()
90+
91 def startService(self):
92 self.logger.info("Starting number-cruncher service.")
93- self.update_queues_loop, self.update_queues_deferred = (
94- self._startLoop(self.QUEUE_INTERVAL, self.updateBuilderQueues))
95- self.update_builder_loop, self.update_builder_deferred = (
96- self._startLoop(self.BUILDER_INTERVAL, self.updateBuilderStats))
97+ self.loops = []
98+ self.stopping_deferreds = []
99+ for interval, callback in (
100+ (self.QUEUE_INTERVAL, self.updateBuilderQueues),
101+ (self.BUILDER_INTERVAL, self.updateBuilderStats),
102+ (self.LIBRARIAN_INTERVAL, self.updateLibrarianStats),
103+ ):
104+ loop, stopping_deferred = self._startLoop(interval, callback)
105+ self.loops.append(loop)
106+ self.stopping_deferreds.append(stopping_deferred)
107
108 def stopService(self):
109- deferreds = []
110- deferreds.append(self.update_queues_deferred)
111- deferreds.append(self.update_builder_deferred)
112-
113- self.update_queues_loop.stop()
114- self.update_builder_loop.stop()
115-
116- d = defer.DeferredList(deferreds, consumeErrors=True)
117- return d
118+ for loop in self.loops:
119+ loop.stop()
120+ return defer.DeferredList(self.stopping_deferreds, consumeErrors=True)
121diff --git a/lib/lp/services/statsd/tests/test_numbercruncher.py b/lib/lp/services/statsd/tests/test_numbercruncher.py
122index 020ef76..0dff3a0 100644
123--- a/lib/lp/services/statsd/tests/test_numbercruncher.py
124+++ b/lib/lp/services/statsd/tests/test_numbercruncher.py
125@@ -10,6 +10,7 @@ __metaclass__ = type
126 from testtools.matchers import (
127 Equals,
128 MatchesListwise,
129+ Not,
130 )
131 from testtools.twistedsupport import AsynchronousDeferredRunTest
132 import transaction
133@@ -153,6 +154,57 @@ class TestNumberCruncher(StatsMixin, TestCaseWithFactory):
134 "Failure while updating build queue stats:",
135 cruncher.logger.getLogBuffer())
136
137+ def test_updateLibrarianStats(self):
138+ clock = task.Clock()
139+ cruncher = NumberCruncher(clock=clock)
140+ cruncher.updateLibrarianStats()
141+
142+ self.assertFalse(is_transaction_in_progress())
143+ self.assertEqual(2, self.stats_client.gauge.call_count)
144+ self.assertThat(
145+ [x[0] for x in self.stats_client.gauge.call_args_list],
146+ MatchesListwise([
147+ MatchesListwise([
148+ Equals('librarian.total_files,env=test'),
149+ Not(Equals(0)),
150+ ]),
151+ MatchesListwise([
152+ Equals('librarian.total_filesize,env=test'),
153+ Not(Equals(0)),
154+ ]),
155+ ]))
156+ total_files = self.stats_client.gauge.call_args_list[0][0][1]
157+ total_filesize = self.stats_client.gauge.call_args_list[1][0][1]
158+
159+ self.factory.makeLibraryFileAlias(content=b'x' * 1000, db_only=True)
160+ self.factory.makeLibraryFileAlias(content=b'x' * 2000, db_only=True)
161+ self.stats_client.gauge.reset_mock()
162+ cruncher.updateLibrarianStats()
163+
164+ self.assertFalse(is_transaction_in_progress())
165+ self.assertEqual(2, self.stats_client.gauge.call_count)
166+ self.assertThat(
167+ [x[0] for x in self.stats_client.gauge.call_args_list],
168+ MatchesListwise([
169+ Equals(('librarian.total_files,env=test', total_files + 2)),
170+ Equals((
171+ 'librarian.total_filesize,env=test',
172+ total_filesize + 3000,
173+ )),
174+ ]))
175+
176+ def test_updateLibrarianStats_error(self):
177+ clock = task.Clock()
178+ cruncher = NumberCruncher(clock=clock)
179+ cruncher.logger = BufferLogger()
180+ with DatabaseBlockedPolicy():
181+ cruncher.updateLibrarianStats()
182+
183+ self.assertFalse(is_transaction_in_progress())
184+ self.assertIn(
185+ "Failure while updating librarian stats:",
186+ cruncher.logger.getLogBuffer())
187+
188 def test_startService_starts_update_queues_loop(self):
189 clock = task.Clock()
190 cruncher = NumberCruncher(clock=clock)
191@@ -174,3 +226,14 @@ class TestNumberCruncher(StatsMixin, TestCaseWithFactory):
192 advance = NumberCruncher.BUILDER_INTERVAL + 1
193 clock.advance(advance)
194 self.assertNotEqual(0, cruncher.updateBuilderStats.call_count)
195+
196+ def test_startService_starts_update_librarian_loop(self):
197+ clock = task.Clock()
198+ cruncher = NumberCruncher(clock=clock)
199+
200+ cruncher.updateLibrarianStats = FakeMethod()
201+
202+ cruncher.startService()
203+ advance = NumberCruncher.LIBRARIAN_INTERVAL + 1
204+ clock.advance(advance)
205+ self.assertNotEqual(0, cruncher.updateLibrarianStats.call_count)

Subscribers

People subscribed via source and target branches

to status/vote changes: