launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #17948
[Merge] lp:~cjwatson/launchpad/recipe-timeouts into lp:launchpad
Colin Watson has proposed merging lp:~cjwatson/launchpad/recipe-timeouts into lp:launchpad.
Commit message:
Preload data needed for recipe listings.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #1425080 in Launchpad itself: "Person:+recipes timeouts"
https://bugs.launchpad.net/launchpad/+bug/1425080
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/recipe-timeouts/+merge/250750
Preload data needed for recipe listings.
I removed the load_related(Branch, spr_datas, ['base_branch_id']) from SourcePackageRecipe.preLoadDataForSourcePackageRecipes since SourcePackageRecipeData.preLoadReferencedBranches already does the exact same thing. The changes to SourcePackageRecipeData.preLoadReferencedBranches are based on BranchMergeProposal.preloadDataForBMPs.
--
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/recipe-timeouts into lp:launchpad.
=== modified file 'lib/lp/code/browser/sourcepackagerecipelisting.py'
--- lib/lp/code/browser/sourcepackagerecipelisting.py 2011-12-24 17:49:30 +0000
+++ lib/lp/code/browser/sourcepackagerecipelisting.py 2015-02-24 13:55:06 +0000
@@ -1,4 +1,4 @@
-# Copyright 2010-2011 Canonical Ltd. This software is licensed under the
+# Copyright 2010-2015 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Base class view for sourcepackagerecipe listings."""
@@ -13,6 +13,7 @@
]
+from lp.code.browser.decorations import DecoratedBranch
from lp.services.feeds.browser import FeedsMixin
from lp.services.webapp import (
canonical_url,
@@ -57,6 +58,13 @@
branch_enabled = False
+ def initialize(self):
+ super(BranchRecipeListingView, self).initialize()
+ # Replace our context with a decorated branch, if it is not already
+ # decorated.
+ if not isinstance(self.context, DecoratedBranch):
+ self.context = DecoratedBranch(self.context)
+
class PersonRecipeListingView(RecipeListingView):
=== modified file 'lib/lp/code/browser/tests/test_sourcepackagerecipelisting.py'
--- lib/lp/code/browser/tests/test_sourcepackagerecipelisting.py 2014-06-10 16:13:03 +0000
+++ lib/lp/code/browser/tests/test_sourcepackagerecipelisting.py 2015-02-24 13:55:06 +0000
@@ -1,4 +1,4 @@
-# Copyright 2010-2014 Canonical Ltd. This software is licensed under the
+# Copyright 2010-2015 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Tests for sourcepackagerecipe listings."""
@@ -6,8 +6,15 @@
__metaclass__ = type
-from lp.testing import BrowserTestCase
+from testtools.matchers import Equals
+
+from lp.testing import (
+ BrowserTestCase,
+ person_logged_in,
+ record_two_runs,
+ )
from lp.testing.layers import DatabaseFunctionalLayer
+from lp.testing.matchers import HasQueryCount
class TestSourcePackageRecipeListing(BrowserTestCase):
@@ -15,7 +22,7 @@
layer = DatabaseFunctionalLayer
def test_project_branch_recipe_listing(self):
- # We can see recipes for the product. We need to create two, since
+ # We can see recipes for the project. We need to create two, since
# only one will redirect to that recipe.
branch = self.factory.makeProductBranch()
recipe = self.factory.makeSourcePackageRecipe(branches=[branch])
@@ -37,3 +44,49 @@
Source Package Recipes for lp:.*
Name Owner Registered
spr-name.* Person-name""", text)
+
+ def test_branch_query_count(self):
+ # The number of queries required to render the list of all recipes
+ # for a branch is constant in the number of owners and recipes.
+ person = self.factory.makePerson()
+ branch = self.factory.makeProductBranch(owner=person)
+
+ def create_recipe():
+ with person_logged_in(person):
+ self.factory.makeSourcePackageRecipe(branches=[branch])
+
+ recorder1, recorder2 = record_two_runs(
+ lambda: self.getMainText(branch, "+recipes"), create_recipe, 5)
+ self.assertThat(recorder2, HasQueryCount(Equals(recorder1.count)))
+
+ def test_project_query_count(self):
+ # The number of queries required to render the list of all recipes
+ # for a project is constant in the number of owners, branches, and
+ # recipes.
+ person = self.factory.makePerson()
+ project = self.factory.makeProduct(owner=person)
+
+ def create_recipe():
+ with person_logged_in(person):
+ branch = self.factory.makeProductBranch(product=project)
+ self.factory.makeSourcePackageRecipe(branches=[branch])
+
+ recorder1, recorder2 = record_two_runs(
+ lambda: self.getMainText(project, "+recipes"), create_recipe, 5)
+ self.assertThat(recorder2, HasQueryCount(Equals(recorder1.count)))
+
+ def test_person_query_count(self):
+ # The number of queries required to render the list of all recipes
+ # for a person is constant in the number of projects, branches, and
+ # recipes.
+ person = self.factory.makePerson()
+
+ def create_recipe():
+ with person_logged_in(person):
+ branch = self.factory.makeProductBranch(owner=person)
+ self.factory.makeSourcePackageRecipe(
+ owner=person, branches=[branch])
+
+ recorder1, recorder2 = record_two_runs(
+ lambda: self.getMainText(person, "+recipes"), create_recipe, 5)
+ self.assertThat(recorder2, HasQueryCount(Equals(recorder1.count)))
=== modified file 'lib/lp/code/model/branch.py'
--- lib/lp/code/model/branch.py 2015-02-16 13:01:34 +0000
+++ lib/lp/code/model/branch.py 2015-02-24 13:55:06 +0000
@@ -1406,9 +1406,13 @@
@property
def recipes(self):
"""See `IHasRecipes`."""
+ from lp.code.model.sourcepackagerecipe import SourcePackageRecipe
from lp.code.model.sourcepackagerecipedata import (
- SourcePackageRecipeData)
- return SourcePackageRecipeData.findRecipes(self)
+ SourcePackageRecipeData,
+ )
+ hook = SourcePackageRecipe.preLoadDataForSourcePackageRecipes
+ return DecoratedResultSet(
+ SourcePackageRecipeData.findRecipes(self), pre_iter_hook=hook)
merge_queue_id = Int(name='merge_queue', allow_none=True)
merge_queue = Reference(merge_queue_id, 'BranchMergeQueue.id')
=== modified file 'lib/lp/code/model/sourcepackagerecipe.py'
--- lib/lp/code/model/sourcepackagerecipe.py 2014-07-09 03:08:57 +0000
+++ lib/lp/code/model/sourcepackagerecipe.py 2015-02-24 13:55:06 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Implementation of the `SourcePackageRecipe` content type."""
@@ -12,6 +12,7 @@
datetime,
timedelta,
)
+from operator import attrgetter
from lazr.delegates import delegates
from pytz import utc
@@ -49,16 +50,13 @@
from lp.code.interfaces.sourcepackagerecipebuild import (
ISourcePackageRecipeBuildSource,
)
-from lp.code.model.branch import Branch
from lp.code.model.sourcepackagerecipebuild import SourcePackageRecipeBuild
from lp.code.model.sourcepackagerecipedata import SourcePackageRecipeData
from lp.code.vocabularies.sourcepackagerecipe import BuildableDistroSeries
+from lp.registry.interfaces.person import IPersonSet
from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.registry.model.distroseries import DistroSeries
-from lp.services.database.bulk import (
- load_referencing,
- load_related,
- )
+from lp.services.database.bulk import load_referencing
from lp.services.database.constants import (
DEFAULT,
UTC_NOW,
@@ -166,14 +164,15 @@
spr_datas = load_referencing(
SourcePackageRecipeData,
sourcepackagerecipes, ['sourcepackage_recipe_id'])
- # Load the related branches.
- load_related(Branch, spr_datas, ['base_branch_id'])
# Store the SourcePackageRecipeData in the sourcepackagerecipes
# objects.
for spr_data in spr_datas:
cache = get_property_cache(spr_data.sourcepackage_recipe)
cache._recipe_data = spr_data
SourcePackageRecipeData.preLoadReferencedBranches(spr_datas)
+ owner_ids = set(map(attrgetter('owner_id'), sourcepackagerecipes))
+ list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
+ owner_ids, need_validity=True))
def setRecipeText(self, recipe_text):
parsed = SourcePackageRecipeData.getParsedRecipe(recipe_text)
=== modified file 'lib/lp/code/model/sourcepackagerecipedata.py'
--- lib/lp/code/model/sourcepackagerecipedata.py 2013-06-20 05:50:00 +0000
+++ lib/lp/code/model/sourcepackagerecipedata.py 2015-02-24 13:55:06 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Implementation of the recipe storage.
@@ -46,6 +46,8 @@
)
from lp.code.interfaces.branchlookup import IBranchLookup
from lp.code.model.branch import Branch
+from lp.registry.model.distroseries import DistroSeries
+from lp.registry.model.sourcepackagename import SourcePackageName
from lp.services.database.bulk import (
load_referencing,
load_related,
@@ -323,14 +325,23 @@
@staticmethod
def preLoadReferencedBranches(sourcepackagerecipedatas):
+ # Circular imports.
+ from lp.code.model.branchcollection import GenericBranchCollection
+ from lp.registry.model.product import Product
# Load the related Branch, _SourcePackageRecipeDataInstruction.
- load_related(
+ base_branches = load_related(
Branch, sourcepackagerecipedatas, ['base_branch_id'])
sprd_instructions = load_referencing(
_SourcePackageRecipeDataInstruction,
sourcepackagerecipedatas, ['recipe_data_id'])
sub_branches = load_related(
Branch, sprd_instructions, ['branch_id'])
+ all_branches = base_branches + sub_branches
+ # Pre-load branches' data.
+ load_related(SourcePackageName, all_branches, ['sourcepackagenameID'])
+ load_related(DistroSeries, all_branches, ['distroseriesID'])
+ load_related(Product, all_branches, ['productID'])
+ GenericBranchCollection.preloadDataForBranches(all_branches)
# Store the pre-fetched objects on the sourcepackagerecipedatas
# objects.
branch_to_recipe_data = dict([
=== modified file 'lib/lp/registry/model/person.py'
--- lib/lp/registry/model/person.py 2015-02-06 15:29:45 +0000
+++ lib/lp/registry/model/person.py 2015-02-24 13:55:06 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2014 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Implementation classes for a Person."""
@@ -3158,10 +3158,11 @@
def recipes(self):
"""See `IHasRecipes`."""
from lp.code.model.sourcepackagerecipe import SourcePackageRecipe
- store = Store.of(self)
- return store.find(
+ recipes = Store.of(self).find(
SourcePackageRecipe,
SourcePackageRecipe.owner == self)
+ hook = SourcePackageRecipe.preLoadDataForSourcePackageRecipes
+ return DecoratedResultSet(recipes, pre_iter_hook=hook)
def canAccess(self, obj, attribute):
"""See `IPerson.`"""
=== modified file 'lib/lp/registry/model/product.py'
--- lib/lp/registry/model/product.py 2015-02-13 17:16:22 +0000
+++ lib/lp/registry/model/product.py 2015-02-24 13:55:06 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Database classes including and related to Product."""
@@ -1520,12 +1520,14 @@
@property
def recipes(self):
"""See `IHasRecipes`."""
- return Store.of(self).find(
+ recipes = Store.of(self).find(
SourcePackageRecipe,
SourcePackageRecipe.id ==
SourcePackageRecipeData.sourcepackage_recipe_id,
SourcePackageRecipeData.base_branch == Branch.id,
Branch.product == self)
+ hook = SourcePackageRecipe.preLoadDataForSourcePackageRecipes
+ return DecoratedResultSet(recipes, pre_iter_hook=hook)
def getBugTaskWeightFunction(self):
"""Provide a weight function to determine optimal bug task.
Follow ups