Merge lp:~rvb/launchpad/builders-timeout-903827-2 into lp:launchpad

Proposed by Raphaël Badin
Status: Merged
Approved by: Raphaël Badin
Approved revision: no longer in the source branch.
Merged at revision: 14654
Proposed branch: lp:~rvb/launchpad/builders-timeout-903827-2
Merge into: lp:launchpad
Prerequisite: lp:~rvb/launchpad/builder-history-lfa
Diff against target: 312 lines (+64/-26)
7 files modified
lib/lp/buildmaster/doc/buildqueue.txt (+3/-0)
lib/lp/buildmaster/manager.py (+7/-2)
lib/lp/buildmaster/model/builder.py (+7/-1)
lib/lp/buildmaster/model/buildqueue.py (+16/-2)
lib/lp/code/model/sourcepackagerecipebuild.py (+5/-3)
lib/lp/soyuz/browser/builder.py (+12/-3)
lib/lp/soyuz/browser/tests/test_builder.py (+14/-15)
To merge this branch: bzr merge lp:~rvb/launchpad/builders-timeout-903827-2
Reviewer Review Type Date Requested Status
j.c.sackett (community) Approve
Review via email: mp+87620@code.launchpad.net

Commit message

[r=jcsackett][bug=903827] Make buildqueue.specific_job and builder.currentjob cached properties.

Description of the change

This branch makes buildqueue.specific_job and builder.currentjob cached properties. builder.currentjob is eager loaded in lib/lp/soyuz/browser/builder and buildqueue.specific_job (which should not change over time) is loaded in preloadSpecificJobData.

= Tests =
(modified)
test_builder test_builders_binary_package_build_query_count
test_builder test_builders_recipe_build_query_count
test_builder test_builders_translation_template_build_query_count

= Q/A =
The number of repeated statements of https://launchpad.net/builders should diminish a great deal. It's currently around in [300 - 600] depending on the type of builds building. Note that the repeated statement issued to fetch buildqueue records will still be there.

To post a comment you must log in.
Revision history for this message
j.c.sackett (jcsackett) wrote :

This looks like a good improvement.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/buildmaster/doc/buildqueue.txt'
--- lib/lp/buildmaster/doc/buildqueue.txt 2011-12-30 06:14:56 +0000
+++ lib/lp/buildmaster/doc/buildqueue.txt 2012-01-06 15:32:25 +0000
@@ -13,6 +13,7 @@
13collected) the BuildQueue record representing it is removed.13collected) the BuildQueue record representing it is removed.
1414
15 >>> from lp.services.webapp.testing import verifyObject15 >>> from lp.services.webapp.testing import verifyObject
16 >>> from lp.services.propertycache import get_property_cache
16 >>> from lp.buildmaster.interfaces.buildqueue import (17 >>> from lp.buildmaster.interfaces.buildqueue import (
17 ... IBuildQueue, IBuildQueueSet)18 ... IBuildQueue, IBuildQueueSet)
1819
@@ -135,6 +136,7 @@
135136
136 >>> job.reset()137 >>> job.reset()
137138
139 >>> del get_property_cache(bob).currentjob
138 >>> print bob.currentjob140 >>> print bob.currentjob
139 None141 None
140142
@@ -154,6 +156,7 @@
154156
155 >>> job.markAsBuilding(bob)157 >>> job.markAsBuilding(bob)
156158
159 >>> del get_property_cache(bob).currentjob
157 >>> bob.currentjob == job160 >>> bob.currentjob == job
158 True161 True
159162
160163
=== modified file 'lib/lp/buildmaster/manager.py'
--- lib/lp/buildmaster/manager.py 2012-01-03 12:17:29 +0000
+++ lib/lp/buildmaster/manager.py 2012-01-06 15:32:25 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2011 Canonical Ltd. This software is licensed under the1# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Soyuz buildd slave manager logic."""4"""Soyuz buildd slave manager logic."""
@@ -34,6 +34,7 @@
34 BuildBehaviorMismatch,34 BuildBehaviorMismatch,
35 )35 )
36from lp.buildmaster.model.builder import Builder36from lp.buildmaster.model.builder import Builder
37from lp.services.propertycache import get_property_cache
37from lp.services.database.transaction_policy import DatabaseTransactionPolicy38from lp.services.database.transaction_policy import DatabaseTransactionPolicy
3839
3940
@@ -50,7 +51,9 @@
50def assessFailureCounts(builder, fail_notes):51def assessFailureCounts(builder, fail_notes):
51 """View builder/job failure_count and work out which needs to die. """52 """View builder/job failure_count and work out which needs to die. """
52 # builder.currentjob hides a complicated query, don't run it twice.53 # builder.currentjob hides a complicated query, don't run it twice.
53 # See bug 623281.54 # See bug 623281 (Note that currentjob is a cachedproperty).
55
56 del get_property_cache(builder).currentjob
54 current_job = builder.currentjob57 current_job = builder.currentjob
55 if current_job is None:58 if current_job is None:
56 job_failure_count = 059 job_failure_count = 0
@@ -64,6 +67,7 @@
64 # we can do is try them both again, and hope that the job67 # we can do is try them both again, and hope that the job
65 # runs against a different builder.68 # runs against a different builder.
66 current_job.reset()69 current_job.reset()
70 del get_property_cache(builder).currentjob
67 return71 return
6872
69 if builder.failure_count > job_failure_count:73 if builder.failure_count > job_failure_count:
@@ -96,6 +100,7 @@
96 # but that would cause us to query the slave for its status100 # but that would cause us to query the slave for its status
97 # again, and if the slave is non-responsive it holds up the101 # again, and if the slave is non-responsive it holds up the
98 # next buildd scan.102 # next buildd scan.
103 del get_property_cache(builder).currentjob
99104
100105
101class SlaveScanner:106class SlaveScanner:
102107
=== modified file 'lib/lp/buildmaster/model/builder.py'
--- lib/lp/buildmaster/model/builder.py 2012-01-03 12:11:57 +0000
+++ lib/lp/buildmaster/model/builder.py 2012-01-06 15:32:25 +0000
@@ -431,6 +431,7 @@
431431
432 def _getCurrentBuildBehavior(self):432 def _getCurrentBuildBehavior(self):
433 """Return the current build behavior."""433 """Return the current build behavior."""
434 self._clean_currentjob_cache()
434 if not safe_hasattr(self, '_current_build_behavior'):435 if not safe_hasattr(self, '_current_build_behavior'):
435 self._current_build_behavior = None436 self._current_build_behavior = None
436437
@@ -479,10 +480,12 @@
479 def gotFailure(self):480 def gotFailure(self):
480 """See `IBuilder`."""481 """See `IBuilder`."""
481 self.failure_count += 1482 self.failure_count += 1
483 self._clean_currentjob_cache()
482484
483 def resetFailureCount(self):485 def resetFailureCount(self):
484 """See `IBuilder`."""486 """See `IBuilder`."""
485 self.failure_count = 0487 self.failure_count = 0
488 self._clean_currentjob_cache()
486489
487 def rescueIfLost(self, logger=None):490 def rescueIfLost(self, logger=None):
488 """See `IBuilder`."""491 """See `IBuilder`."""
@@ -498,11 +501,14 @@
498501
499 # XXX 2010-08-24 Julian bug=623281502 # XXX 2010-08-24 Julian bug=623281
500 # This should not be a property! It's masking a complicated query.503 # This should not be a property! It's masking a complicated query.
501 @property504 @cachedproperty
502 def currentjob(self):505 def currentjob(self):
503 """See IBuilder"""506 """See IBuilder"""
504 return getUtility(IBuildQueueSet).getByBuilder(self)507 return getUtility(IBuildQueueSet).getByBuilder(self)
505508
509 def _clean_currentjob_cache(self):
510 del get_property_cache(self).currentjob
511
506 def requestAbort(self):512 def requestAbort(self):
507 """See IBuilder."""513 """See IBuilder."""
508 return self.slave.abort()514 return self.slave.abort()
509515
=== modified file 'lib/lp/buildmaster/model/buildqueue.py'
--- lib/lp/buildmaster/model/buildqueue.py 2012-01-02 11:21:14 +0000
+++ lib/lp/buildmaster/model/buildqueue.py 2012-01-06 15:32:25 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2011 Canonical Ltd. This software is licensed under the1# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4# pylint: disable-msg=E0611,W02124# pylint: disable-msg=E0611,W0212
@@ -53,6 +53,10 @@
53 )53 )
54from lp.services.job.interfaces.job import JobStatus54from lp.services.job.interfaces.job import JobStatus
55from lp.services.job.model.job import Job55from lp.services.job.model.job import Job
56from lp.services.propertycache import (
57 cachedproperty,
58 get_property_cache,
59 )
56from lp.services.webapp.interfaces import (60from lp.services.webapp.interfaces import (
57 DEFAULT_FLAVOR,61 DEFAULT_FLAVOR,
58 IStoreSelector,62 IStoreSelector,
@@ -138,12 +142,15 @@
138 """See `IBuildQueue`."""142 """See `IBuildQueue`."""
139 return IBuildFarmJobBehavior(self.specific_job)143 return IBuildFarmJobBehavior(self.specific_job)
140144
141 @property145 @cachedproperty
142 def specific_job(self):146 def specific_job(self):
143 """See `IBuildQueue`."""147 """See `IBuildQueue`."""
144 specific_class = specific_job_classes()[self.job_type]148 specific_class = specific_job_classes()[self.job_type]
145 return specific_class.getByJob(self.job)149 return specific_class.getByJob(self.job)
146150
151 def _clear_specific_job_cache(self):
152 del get_property_cache(self).specific_job
153
147 @staticmethod154 @staticmethod
148 def preloadSpecificJobData(queues):155 def preloadSpecificJobData(queues):
149 key = attrgetter('job_type')156 key = attrgetter('job_type')
@@ -158,6 +165,12 @@
158 if len(list(specific_jobs)) == 0:165 if len(list(specific_jobs)) == 0:
159 continue166 continue
160 specific_class.preloadJobsData(specific_jobs)167 specific_class.preloadJobsData(specific_jobs)
168 specific_jobs_dict = dict(
169 (specific_job.job, specific_job)
170 for specific_job in specific_jobs)
171 for queue in queue_subset:
172 cache = get_property_cache(queue)
173 cache.specific_job = specific_jobs_dict[queue.job]
161174
162 @property175 @property
163 def date_started(self):176 def date_started(self):
@@ -180,6 +193,7 @@
180 SQLBase.destroySelf(self)193 SQLBase.destroySelf(self)
181 specific_job.cleanUp()194 specific_job.cleanUp()
182 job.destroySelf()195 job.destroySelf()
196 self._clear_specific_job_cache()
183197
184 def manualScore(self, value):198 def manualScore(self, value):
185 """See `IBuildQueue`."""199 """See `IBuildQueue`."""
186200
=== modified file 'lib/lp/code/model/sourcepackagerecipebuild.py'
--- lib/lp/code/model/sourcepackagerecipebuild.py 2012-01-06 15:32:25 +0000
+++ lib/lp/code/model/sourcepackagerecipebuild.py 2012-01-06 15:32:25 +0000
@@ -1,4 +1,4 @@
1# Copyright 2010-2011 Canonical Ltd. This software is licensed under the1# Copyright 2010-2012 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4# pylint: disable-msg=F0401,E10024# pylint: disable-msg=F0401,E1002
@@ -291,10 +291,12 @@
291 # Circular imports.291 # Circular imports.
292 from lp.code.model.sourcepackagerecipe import SourcePackageRecipe292 from lp.code.model.sourcepackagerecipe import SourcePackageRecipe
293 from lp.services.librarian.model import LibraryFileAlias293 from lp.services.librarian.model import LibraryFileAlias
294 from lp.buildmaster.model.buildfarmjob import BuildFarmJob
294 package_builds = load_related(295 package_builds = load_related(
295 PackageBuild, builds, ['package_build_id'])296 PackageBuild, builds, ['package_build_id'])
296 build_farm_jobs = [297 build_farm_jobs = load_related(
297 build.build_farm_job for build in builds]298 BuildFarmJob, [build.package_build for build in builds],
299 ['build_farm_job_id'])
298 load_related(LibraryFileAlias, build_farm_jobs, ['log_id'])300 load_related(LibraryFileAlias, build_farm_jobs, ['log_id'])
299 archives = load_related(Archive, package_builds, ['archive_id'])301 archives = load_related(Archive, package_builds, ['archive_id'])
300 load_related(Person, archives, ['ownerID'])302 load_related(Person, archives, ['ownerID'])
301303
=== modified file 'lib/lp/soyuz/browser/builder.py'
--- lib/lp/soyuz/browser/builder.py 2012-01-02 11:21:14 +0000
+++ lib/lp/soyuz/browser/builder.py 2012-01-06 15:32:25 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2011 Canonical Ltd. This software is licensed under the1# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Browser views for builders."""4"""Browser views for builders."""
@@ -44,7 +44,10 @@
44from lp.buildmaster.model.buildqueue import BuildQueue44from lp.buildmaster.model.buildqueue import BuildQueue
45from lp.services.database.decoratedresultset import DecoratedResultSet45from lp.services.database.decoratedresultset import DecoratedResultSet
46from lp.services.database.lpstorm import IStore46from lp.services.database.lpstorm import IStore
47from lp.services.propertycache import cachedproperty47from lp.services.propertycache import (
48 cachedproperty,
49 get_property_cache,
50 )
48from lp.services.webapp import (51from lp.services.webapp import (
49 ApplicationMenu,52 ApplicationMenu,
50 canonical_url,53 canonical_url,
@@ -148,11 +151,17 @@
148 def builders(self):151 def builders(self):
149 """All active builders"""152 """All active builders"""
150 def do_eager_load(builders):153 def do_eager_load(builders):
151 # Prefetch the jobs' data.154 # Populate builders' currentjob cachedproperty.
152 queues = IStore(BuildQueue).find(155 queues = IStore(BuildQueue).find(
153 BuildQueue,156 BuildQueue,
154 BuildQueue.builderID.is_in(157 BuildQueue.builderID.is_in(
155 builder.id for builder in builders))158 builder.id for builder in builders))
159 queue_builders = dict(
160 (queue.builderID, queue) for queue in queues)
161 for builder in builders:
162 cache = get_property_cache(builder)
163 cache.currentjob = queue_builders.get(builder.id, None)
164 # Prefetch the jobs' data.
156 BuildQueue.preloadSpecificJobData(queues)165 BuildQueue.preloadSpecificJobData(queues)
157166
158 return list(DecoratedResultSet(167 return list(DecoratedResultSet(
159168
=== modified file 'lib/lp/soyuz/browser/tests/test_builder.py'
--- lib/lp/soyuz/browser/tests/test_builder.py 2012-01-06 15:32:25 +0000
+++ lib/lp/soyuz/browser/tests/test_builder.py 2012-01-06 15:32:25 +0000
@@ -1,4 +1,4 @@
1# Copyright 2010-2011 Canonical Ltd. This software is licensed under the1# Copyright 2010-2012 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Tests for the lp.soyuz.browser.builder module."""4"""Tests for the lp.soyuz.browser.builder module."""
@@ -67,14 +67,10 @@
6767
68 layer = LaunchpadFunctionalLayer68 layer = LaunchpadFunctionalLayer
6969
70 # XXX rvb: the 3 additional queries per build are the result of the calls70 # XXX rvb: the query issued per build is the result of the call to
71 # to:71 # build.buildqueue_record. It was decided not to make it a cachedproperty
72 # - builder.currentjob72 # because the code relies on the fact that this property always returns
73 # - buildqueue.specific_job73 # the current record.
74 # These could be converted into cachedproperty and pre-populated in
75 # bulk but several tests assert that the value returned by these
76 # these properties are up to date. Since they are not really expensive
77 # to compute I'll leave them as regular properties for now.
7874
79 def test_builders_binary_package_build_query_count(self):75 def test_builders_binary_package_build_query_count(self):
80 def create_build():76 def create_build():
@@ -82,12 +78,13 @@
82 queue = build.queueBuild()78 queue = build.queueBuild()
83 queue.markAsBuilding(build.builder)79 queue.markAsBuilding(build.builder)
8480
81 nb_objects = 2
85 recorder1, recorder2 = record_two_runs(82 recorder1, recorder2 = record_two_runs(
86 builders_homepage_render, create_build, 2)83 builders_homepage_render, create_build, nb_objects)
8784
88 self.assertThat(85 self.assertThat(
89 recorder2,86 recorder2,
90 HasQueryCount(LessThan(recorder1.count + 3 * 2 + 1)))87 HasQueryCount(LessThan(recorder1.count + 1 * nb_objects + 1)))
9188
92 def test_builders_recipe_build_query_count(self):89 def test_builders_recipe_build_query_count(self):
93 def create_build():90 def create_build():
@@ -95,12 +92,13 @@
95 queue = build.queueBuild()92 queue = build.queueBuild()
96 queue.markAsBuilding(build.builder)93 queue.markAsBuilding(build.builder)
9794
95 nb_objects = 2
98 recorder1, recorder2 = record_two_runs(96 recorder1, recorder2 = record_two_runs(
99 builders_homepage_render, create_build, 2)97 builders_homepage_render, create_build, nb_objects)
10098
101 self.assertThat(99 self.assertThat(
102 recorder2,100 recorder2,
103 HasQueryCount(LessThan(recorder1.count + 4 * 2 + 1)))101 HasQueryCount(LessThan(recorder1.count + 1 * nb_objects + 1)))
104102
105 def test_builders_translation_template_build_query_count(self):103 def test_builders_translation_template_build_query_count(self):
106 def create_build():104 def create_build():
@@ -114,9 +112,10 @@
114 queue = queueset.get(job_id)112 queue = queueset.get(job_id)
115 queue.markAsBuilding(self.factory.makeBuilder())113 queue.markAsBuilding(self.factory.makeBuilder())
116114
115 nb_objects = 2
117 recorder1, recorder2 = record_two_runs(116 recorder1, recorder2 = record_two_runs(
118 builders_homepage_render, create_build, 2)117 builders_homepage_render, create_build, nb_objects)
119118
120 self.assertThat(119 self.assertThat(
121 recorder2,120 recorder2,
122 HasQueryCount(LessThan(recorder1.count + 3 * 2 + 1)))121 HasQueryCount(LessThan(recorder1.count + 1 * nb_objects + 1)))