Merge lp:~michael.nelson/launchpad/588684-builders-timeout into lp:launchpad
- 588684-builders-timeout
- Merge into devel
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | 10952 | ||||
Proposed branch: | lp:~michael.nelson/launchpad/588684-builders-timeout | ||||
Merge into: | lp:launchpad | ||||
Diff against target: |
277 lines (+74/-60) 5 files modified
lib/lp/buildmaster/doc/builder.txt (+23/-25) lib/lp/buildmaster/interfaces/builder.py (+14/-8) lib/lp/buildmaster/model/builder.py (+22/-21) lib/lp/soyuz/browser/builder.py (+12/-6) lib/lp/soyuz/browser/tests/builder-views.txt (+3/-0) |
||||
To merge this branch: | bzr merge lp:~michael.nelson/launchpad/588684-builders-timeout | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Abel Deuring (community) | code | Approve | |
Review via email: mp+26818@code.launchpad.net |
Commit message
Description of the change
This branch replaces many heavy queries (one per builder group... eg. 386 virtual) on the /builders page with a single query that gets the stats for all builder groups.
See bug 588684 for the analysis from stub.
To test:
bin/test -vv -t doc/builder.txt -t builder-views.txt
It will need to be cherry-picked to replace the cowboy that currently disables the build queue size information.
Michael Nelson (michael.nelson) wrote : | # |
On Fri, Jun 4, 2010 at 6:22 PM, Abel Deuring <email address hidden> wrote:
> Review: Approve code
> Nice work! Just one nitpick:
>
> === modified file 'lib/lp/
> --- lib/lp/
> +++ lib/lp/
>
> + BinaryPackageBu
> DistroArchSerie
> + # WHERE
>
> This "WHERE" looks a bit odd ;) I think you can remove it.
Done...(woops, left over from the SQL->Storm conversion). Thanks Abel.
Preview Diff
1 | === modified file 'lib/lp/buildmaster/doc/builder.txt' | |||
2 | --- lib/lp/buildmaster/doc/builder.txt 2010-04-12 05:52:01 +0000 | |||
3 | +++ lib/lp/buildmaster/doc/builder.txt 2010-06-05 06:54:27 +0000 | |||
4 | @@ -111,29 +111,22 @@ | |||
5 | 111 | bob | 111 | bob |
6 | 112 | frog | 112 | frog |
7 | 113 | 113 | ||
26 | 114 | 'getBuildQueueSizeForProcessor' returns the number of pending builds | 114 | 'getBuildQueueSizes' returns the number of pending builds for each |
27 | 115 | for a given Processor. The callsites can also control which build-farm | 115 | Processor/virtualization. |
28 | 116 | they are interested via the 'virtualized' argument. | 116 | |
29 | 117 | 117 | >>> queue_sizes = builderset.getBuildQueueSizes() | |
30 | 118 | >>> from lp.soyuz.model.processor import Processor | 118 | >>> queue_sizes['nonvirt']['386'] |
31 | 119 | >>> p386 = Processor.selectOneBy(name='386') | 119 | (1L, datetime.timedelta(0, 60)) |
32 | 120 | >>> amd64 = Processor.selectOneBy(name='amd64') | 120 | |
33 | 121 | 121 | There are no 'amd64' build queue entries. | |
34 | 122 | 'virtualized' defaults to False, so if not passed, it will return the | 122 | |
35 | 123 | size and estimated duration of the non-virtualized (trusted) build queue. | 123 | >>> queue_sizes['nonvirt'].keys() |
36 | 124 | 124 | [u'386'] | |
19 | 125 | >>> builderset.getBuildQueueSizeForProcessor(p386) | ||
20 | 126 | (1, datetime.timedelta(0, 60)) | ||
21 | 127 | |||
22 | 128 | The 'amd64' build queue is empty. | ||
23 | 129 | |||
24 | 130 | >>> builderset.getBuildQueueSizeForProcessor(amd64) | ||
25 | 131 | (0, None) | ||
37 | 132 | 125 | ||
38 | 133 | The virtualized build queue for 386 is also empty. | 126 | The virtualized build queue for 386 is also empty. |
39 | 134 | 127 | ||
42 | 135 | >>> builderset.getBuildQueueSizeForProcessor(p386, virtualized=True) | 128 | >>> queue_sizes['virt'].keys() |
43 | 136 | (0, None) | 129 | [] |
44 | 137 | 130 | ||
45 | 138 | The queue size is not affect by builds target to disabled | 131 | The queue size is not affect by builds target to disabled |
46 | 139 | archives. Builds for disabled archive are not dispatched as well, this | 132 | archives. Builds for disabled archive are not dispatched as well, this |
47 | @@ -145,23 +138,28 @@ | |||
48 | 145 | >>> from lp.registry.interfaces.distribution import IDistributionSet | 138 | >>> from lp.registry.interfaces.distribution import IDistributionSet |
49 | 146 | >>> ubuntu = getUtility(IDistributionSet).getByName('ubuntu') | 139 | >>> ubuntu = getUtility(IDistributionSet).getByName('ubuntu') |
50 | 147 | >>> ubuntu.main_archive.disable() | 140 | >>> ubuntu.main_archive.disable() |
51 | 141 | >>> import transaction | ||
52 | 142 | >>> transaction.commit() | ||
53 | 148 | >>> login(ANONYMOUS) | 143 | >>> login(ANONYMOUS) |
54 | 149 | 144 | ||
55 | 150 | That done, the non-virtualized queue for i386 becomes empty. | 145 | That done, the non-virtualized queue for i386 becomes empty. |
56 | 151 | 146 | ||
59 | 152 | >>> builderset.getBuildQueueSizeForProcessor(p386) | 147 | >>> queue_sizes = builderset.getBuildQueueSizes() |
60 | 153 | (0, None) | 148 | >>> queue_sizes['nonvirt'].keys() |
61 | 149 | [] | ||
62 | 154 | 150 | ||
63 | 155 | Let's re-enable the ubuntu primary archive. | 151 | Let's re-enable the ubuntu primary archive. |
64 | 156 | 152 | ||
65 | 157 | >>> login('foo.bar@canonical.com') | 153 | >>> login('foo.bar@canonical.com') |
66 | 158 | >>> ubuntu.main_archive.enable() | 154 | >>> ubuntu.main_archive.enable() |
67 | 155 | >>> transaction.commit() | ||
68 | 159 | >>> login(ANONYMOUS) | 156 | >>> login(ANONYMOUS) |
69 | 160 | 157 | ||
70 | 161 | The build for the ubuntu primary archive shows up again. | 158 | The build for the ubuntu primary archive shows up again. |
71 | 162 | 159 | ||
74 | 163 | >>> builderset.getBuildQueueSizeForProcessor(p386) | 160 | >>> queue_sizes = builderset.getBuildQueueSizes() |
75 | 164 | (1, datetime.timedelta(0, 60)) | 161 | >>> queue_sizes['nonvirt']['386'] |
76 | 162 | (1L, datetime.timedelta(0, 60)) | ||
77 | 165 | 163 | ||
78 | 166 | 164 | ||
79 | 167 | Resuming buildd slaves | 165 | Resuming buildd slaves |
80 | @@ -306,6 +304,6 @@ | |||
81 | 306 | instead simply cleaned, ready for the next build. | 304 | instead simply cleaned, ready for the next build. |
82 | 307 | 305 | ||
83 | 308 | >>> rescue_slave_if_lost(WaitingSlave(build_id='bad')) | 306 | >>> rescue_slave_if_lost(WaitingSlave(build_id='bad')) |
85 | 309 | Cleaning slave | 307 | Cleaning slave |
86 | 310 | WARNING:root:Builder 'mock' rescued from 'bad': 'Bad value' | 308 | WARNING:root:Builder 'mock' rescued from 'bad': 'Bad value' |
87 | 311 | 309 | ||
88 | 312 | 310 | ||
89 | === modified file 'lib/lp/buildmaster/interfaces/builder.py' | |||
90 | --- lib/lp/buildmaster/interfaces/builder.py 2010-04-12 05:52:01 +0000 | |||
91 | +++ lib/lp/buildmaster/interfaces/builder.py 2010-06-05 06:54:27 +0000 | |||
92 | @@ -327,14 +327,20 @@ | |||
93 | 327 | def getBuildersByArch(arch): | 327 | def getBuildersByArch(arch): |
94 | 328 | """Return all configured builders for a given DistroArchSeries.""" | 328 | """Return all configured builders for a given DistroArchSeries.""" |
95 | 329 | 329 | ||
104 | 330 | def getBuildQueueSizeForProcessor(processor, virtualized=False): | 330 | def getBuildQueueSizes(): |
105 | 331 | """Return the number of pending builds for a given processor. | 331 | """Return the number of pending builds for each processor. |
106 | 332 | 332 | ||
107 | 333 | :param processor: IProcessor; | 333 | :return: a dict of tuples with the queue size and duration for |
108 | 334 | :param virtualized: boolean, controls which queue to check, | 334 | each processor and virtualisation. For example: |
109 | 335 | 'virtualized' means PPA. | 335 | { |
110 | 336 | 336 | 'virt': { | |
111 | 337 | :return: a tuple containing the size of the queue, as an integer, | 337 | '386': (1, datetime.timedelta(0, 60)), |
112 | 338 | 'amd64': (2, datetime.timedelta(0, 30)), | ||
113 | 339 | }, | ||
114 | 340 | 'nonvirt':... | ||
115 | 341 | } | ||
116 | 342 | |||
117 | 343 | The tuple contains the size of the queue, as an integer, | ||
118 | 338 | and the sum of the jobs 'estimated_duration' in queue, | 344 | and the sum of the jobs 'estimated_duration' in queue, |
119 | 339 | as a timedelta or None for empty queues. | 345 | as a timedelta or None for empty queues. |
120 | 340 | """ | 346 | """ |
121 | 341 | 347 | ||
122 | === modified file 'lib/lp/buildmaster/model/builder.py' | |||
123 | --- lib/lp/buildmaster/model/builder.py 2010-05-18 08:54:36 +0000 | |||
124 | +++ lib/lp/buildmaster/model/builder.py 2010-06-05 06:54:27 +0000 | |||
125 | @@ -24,6 +24,7 @@ | |||
126 | 24 | 24 | ||
127 | 25 | from sqlobject import ( | 25 | from sqlobject import ( |
128 | 26 | BoolCol, ForeignKey, IntCol, SQLObjectNotFound, StringCol) | 26 | BoolCol, ForeignKey, IntCol, SQLObjectNotFound, StringCol) |
129 | 27 | from storm.expr import Count, Sum | ||
130 | 27 | from storm.store import Store | 28 | from storm.store import Store |
131 | 28 | from zope.component import getUtility | 29 | from zope.component import getUtility |
132 | 29 | from zope.interface import implements | 30 | from zope.interface import implements |
133 | @@ -36,7 +37,7 @@ | |||
134 | 36 | from canonical.launchpad.webapp import urlappend | 37 | from canonical.launchpad.webapp import urlappend |
135 | 37 | from canonical.launchpad.webapp.interfaces import NotFoundError | 38 | from canonical.launchpad.webapp.interfaces import NotFoundError |
136 | 38 | from canonical.launchpad.webapp.interfaces import ( | 39 | from canonical.launchpad.webapp.interfaces import ( |
138 | 39 | IStoreSelector, MAIN_STORE, DEFAULT_FLAVOR) | 40 | IStoreSelector, MAIN_STORE, DEFAULT_FLAVOR, SLAVE_FLAVOR) |
139 | 40 | from canonical.lazr.utils import safe_hasattr | 41 | from canonical.lazr.utils import safe_hasattr |
140 | 41 | from canonical.librarian.utils import copy_and_close | 42 | from canonical.librarian.utils import copy_and_close |
141 | 42 | from lp.buildmaster.interfaces.buildbase import BuildStatus | 43 | from lp.buildmaster.interfaces.buildbase import BuildStatus |
142 | @@ -693,7 +694,7 @@ | |||
143 | 693 | % arch.processorfamily.id, | 694 | % arch.processorfamily.id, |
144 | 694 | clauseTables=("Processor",)) | 695 | clauseTables=("Processor",)) |
145 | 695 | 696 | ||
147 | 696 | def getBuildQueueSizeForProcessor(self, processor, virtualized=False): | 697 | def getBuildQueueSizes(self): |
148 | 697 | """See `IBuilderSet`.""" | 698 | """See `IBuilderSet`.""" |
149 | 698 | # Avoiding circular imports. | 699 | # Avoiding circular imports. |
150 | 699 | from lp.soyuz.model.archive import Archive | 700 | from lp.soyuz.model.archive import Archive |
151 | @@ -702,33 +703,33 @@ | |||
152 | 702 | DistroArchSeries) | 703 | DistroArchSeries) |
153 | 703 | from lp.soyuz.model.processor import Processor | 704 | from lp.soyuz.model.processor import Processor |
154 | 704 | 705 | ||
164 | 705 | store = Store.of(processor) | 706 | find_spec = ( |
165 | 706 | origin = ( | 707 | Count(), |
166 | 707 | Archive, | 708 | Sum(BuildQueue.estimated_duration), |
158 | 708 | BinaryPackageBuild, | ||
159 | 709 | PackageBuild, | ||
160 | 710 | BuildFarmJob, | ||
161 | 711 | BuildPackageJob, | ||
162 | 712 | BuildQueue, | ||
163 | 713 | DistroArchSeries, | ||
167 | 714 | Processor, | 709 | Processor, |
168 | 710 | Archive.require_virtualized, | ||
169 | 715 | ) | 711 | ) |
172 | 716 | queue = store.using(*origin).find( | 712 | store = getUtility(IStoreSelector).get(MAIN_STORE, SLAVE_FLAVOR) |
173 | 717 | BuildQueue, | 713 | results = store.find( |
174 | 714 | find_spec, | ||
175 | 718 | BuildPackageJob.job == BuildQueue.jobID, | 715 | BuildPackageJob.job == BuildQueue.jobID, |
176 | 719 | BuildPackageJob.build == BinaryPackageBuild.id, | 716 | BuildPackageJob.build == BinaryPackageBuild.id, |
177 | 720 | BinaryPackageBuild.distro_arch_series == DistroArchSeries.id, | ||
178 | 721 | BinaryPackageBuild.package_build == PackageBuild.id, | 717 | BinaryPackageBuild.package_build == PackageBuild.id, |
179 | 718 | PackageBuild.build_farm_job == BuildFarmJob.id, | ||
180 | 722 | PackageBuild.archive == Archive.id, | 719 | PackageBuild.archive == Archive.id, |
182 | 723 | PackageBuild.build_farm_job == BuildFarmJob.id, | 720 | BinaryPackageBuild.distro_arch_series == DistroArchSeries.id, |
183 | 724 | DistroArchSeries.processorfamilyID == Processor.familyID, | 721 | DistroArchSeries.processorfamilyID == Processor.familyID, |
184 | 725 | BuildFarmJob.status == BuildStatus.NEEDSBUILD, | 722 | BuildFarmJob.status == BuildStatus.NEEDSBUILD, |
191 | 726 | Archive._enabled == True, | 723 | Archive._enabled == True).group_by( |
192 | 727 | Processor.id == processor.id, | 724 | Processor, Archive.require_virtualized) |
193 | 728 | Archive.require_virtualized == virtualized, | 725 | |
194 | 729 | ) | 726 | result_dict = {'virt': {}, 'nonvirt': {}} |
195 | 730 | 727 | for size, duration, processor, virtualized in results: | |
196 | 731 | return (queue.count(), queue.sum(BuildQueue.estimated_duration)) | 728 | virt_str = 'virt' if virtualized else 'nonvirt' |
197 | 729 | result_dict[virt_str][processor.name] = ( | ||
198 | 730 | size, duration) | ||
199 | 731 | |||
200 | 732 | return result_dict | ||
201 | 732 | 733 | ||
202 | 733 | def pollBuilders(self, logger, txn): | 734 | def pollBuilders(self, logger, txn): |
203 | 734 | """See IBuilderSet.""" | 735 | """See IBuilderSet.""" |
204 | 735 | 736 | ||
205 | === modified file 'lib/lp/soyuz/browser/builder.py' | |||
206 | --- lib/lp/soyuz/browser/builder.py 2010-05-26 09:12:34 +0000 | |||
207 | +++ lib/lp/soyuz/browser/builder.py 2010-06-05 06:54:27 +0000 | |||
208 | @@ -149,12 +149,18 @@ | |||
209 | 149 | def number_of_building_builders(self): | 149 | def number_of_building_builders(self): |
210 | 150 | return len([b for b in self.builders if b.currentjob is not None]) | 150 | return len([b for b in self.builders if b.currentjob is not None]) |
211 | 151 | 151 | ||
212 | 152 | @cachedproperty | ||
213 | 153 | def build_queue_sizes(self): | ||
214 | 154 | """Return the build queue sizes for all processors.""" | ||
215 | 155 | builderset = getUtility(IBuilderSet) | ||
216 | 156 | return builderset.getBuildQueueSizes() | ||
217 | 157 | |||
218 | 152 | @property | 158 | @property |
219 | 153 | def ppa_builders(self): | 159 | def ppa_builders(self): |
220 | 154 | """Return a BuilderCategory object for PPA builders.""" | 160 | """Return a BuilderCategory object for PPA builders.""" |
221 | 155 | builder_category = BuilderCategory( | 161 | builder_category = BuilderCategory( |
222 | 156 | 'PPA build status', virtualized=True) | 162 | 'PPA build status', virtualized=True) |
224 | 157 | builder_category.groupBuilders(self.builders) | 163 | builder_category.groupBuilders(self.builders, self.build_queue_sizes) |
225 | 158 | return builder_category | 164 | return builder_category |
226 | 159 | 165 | ||
227 | 160 | @property | 166 | @property |
228 | @@ -162,7 +168,7 @@ | |||
229 | 162 | """Return a BuilderCategory object for PPA builders.""" | 168 | """Return a BuilderCategory object for PPA builders.""" |
230 | 163 | builder_category = BuilderCategory( | 169 | builder_category = BuilderCategory( |
231 | 164 | 'Official distributions build status', virtualized=False) | 170 | 'Official distributions build status', virtualized=False) |
233 | 165 | builder_category.groupBuilders(self.builders) | 171 | builder_category.groupBuilders(self.builders, self.build_queue_sizes) |
234 | 166 | return builder_category | 172 | return builder_category |
235 | 167 | 173 | ||
236 | 168 | 174 | ||
237 | @@ -199,7 +205,7 @@ | |||
238 | 199 | return sorted(self._builder_groups, | 205 | return sorted(self._builder_groups, |
239 | 200 | key=operator.attrgetter('processor_name')) | 206 | key=operator.attrgetter('processor_name')) |
240 | 201 | 207 | ||
242 | 202 | def groupBuilders(self, all_builders): | 208 | def groupBuilders(self, all_builders, build_queue_sizes): |
243 | 203 | """Group the given builders as a collection of Buildergroups. | 209 | """Group the given builders as a collection of Buildergroups. |
244 | 204 | 210 | ||
245 | 205 | A BuilderGroup will be initialized for each processor. | 211 | A BuilderGroup will be initialized for each processor. |
246 | @@ -214,10 +220,10 @@ | |||
247 | 214 | else: | 220 | else: |
248 | 215 | grouped_builders[builder.processor] = [builder] | 221 | grouped_builders[builder.processor] = [builder] |
249 | 216 | 222 | ||
250 | 217 | builderset = getUtility(IBuilderSet) | ||
251 | 218 | for processor, builders in grouped_builders.iteritems(): | 223 | for processor, builders in grouped_builders.iteritems(): |
254 | 219 | queue_size, duration = builderset.getBuildQueueSizeForProcessor( | 224 | virt_str = 'virt' if self.virtualized else 'nonvirt' |
255 | 220 | processor, virtualized=self.virtualized) | 225 | queue_size, duration = build_queue_sizes[virt_str].get( |
256 | 226 | processor.name, (0, None)) | ||
257 | 221 | builder_group = BuilderGroup( | 227 | builder_group = BuilderGroup( |
258 | 222 | processor.name, queue_size, duration, | 228 | processor.name, queue_size, duration, |
259 | 223 | sorted(builders, key=operator.attrgetter('title'))) | 229 | sorted(builders, key=operator.attrgetter('title'))) |
260 | 224 | 230 | ||
261 | === modified file 'lib/lp/soyuz/browser/tests/builder-views.txt' | |||
262 | --- lib/lp/soyuz/browser/tests/builder-views.txt 2010-04-09 15:46:09 +0000 | |||
263 | +++ lib/lp/soyuz/browser/tests/builder-views.txt 2010-06-05 06:54:27 +0000 | |||
264 | @@ -390,10 +390,13 @@ | |||
265 | 390 | >>> any_failed_build.retry() | 390 | >>> any_failed_build.retry() |
266 | 391 | >>> removeSecurityProxy( | 391 | >>> removeSecurityProxy( |
267 | 392 | ... any_failed_build.buildqueue_record).estimated_duration = one_minute | 392 | ... any_failed_build.buildqueue_record).estimated_duration = one_minute |
268 | 393 | >>> transaction.commit() | ||
269 | 393 | >>> login(ANONYMOUS) | 394 | >>> login(ANONYMOUS) |
270 | 394 | 395 | ||
271 | 395 | Now the pending build is included in the right category and group. | 396 | Now the pending build is included in the right category and group. |
272 | 396 | 397 | ||
273 | 398 | >>> builderset_view = getMultiAdapter( | ||
274 | 399 | ... (builderset, empty_request), name="+index") | ||
275 | 397 | >>> builder_category = builderset_view.ppa_builders | 400 | >>> builder_category = builderset_view.ppa_builders |
276 | 398 | >>> print_category(builder_category) | 401 | >>> print_category(builder_category) |
277 | 399 | 386 2 2 0:01:00 | 402 | 386 2 2 0:01:00 |
Nice work! Just one nitpick:
=== modified file 'lib/lp/ buildmaster/ model/builder. py' buildmaster/ model/builder. py 2010-05-18 08:54:36 +0000 buildmaster/ model/builder. py 2010-06-04 15:28:29 +0000
--- lib/lp/
+++ lib/lp/
[...]
@@ -702,33 +703,34 @@
DistroArc hSeries) model.processor import Processor
from lp.soyuz.
- store = Store.of(processor) estimated_ duration) ,
Processor , require_ virtualized, *origin) .find( IStoreSelector) .get(MAIN_ STORE, SLAVE_FLAVOR)
BuildPack ageJob. job == BuildQueue.jobID,
BuildPack ageJob. build == BinaryPackageBu ild.id, ild.distro_ arch_series == DistroArchSerie s.id,
BinaryPac kageBuild. package_ build == PackageBuild.id, build_farm_ job == BuildFarmJob.id,
PackageBu ild.archive == Archive.id, build_farm_ job == BuildFarmJob.id, ild.distro_ arch_series == DistroArchSerie s.id,
DistroArc hSeries. processorfamily ID == Processor.familyID,
- origin = (
- Archive,
- BinaryPackageBuild,
- PackageBuild,
- BuildFarmJob,
- BuildPackageJob,
- BuildQueue,
- DistroArchSeries,
+ find_spec = (
+ Count(),
+ Sum(BuildQueue.
+ Archive.
)
- queue = store.using(
- BuildQueue,
+ store = getUtility(
+ results = store.find(
+ find_spec,
- BinaryPackageBu
+ PackageBuild.
- PackageBuild.
+ BinaryPackageBu
+ # WHERE
This "WHERE" looks a bit odd ;) I think you can remove it.