Merge lp:~lifeless/launchpad/bug-717394 into lp:launchpad

Proposed by Robert Collins
Status: Merged
Approved by: Robert Collins
Approved revision: no longer in the source branch.
Merged at revision: 12427
Proposed branch: lp:~lifeless/launchpad/bug-717394
Merge into: lp:launchpad
Diff against target: 127 lines (+34/-19)
4 files modified
lib/lp/bugs/browser/bugtarget.py (+27/-14)
lib/lp/bugs/browser/bugtask.py (+3/-4)
lib/lp/bugs/interfaces/bugtask.py (+3/-0)
lib/lp/bugs/model/bugtask.py (+1/-1)
To merge this branch: bzr merge lp:~lifeless/launchpad/bug-717394
Reviewer Review Type Date Requested Status
Stuart Bishop (community) Approve
Review via email: mp+50541@code.launchpad.net

Commit message

[r=stub][bug=516050,717394] Use an aggregate function rather than python iteration to summaries milestone bug counts.

Description of the change

Another step on bug search performance, this branch uses the relatively new counting API for bugs to get aggregates for milestones rather than querying once per milestone. This should save nearly a second on bug searches in the Ubuntu context and help make bug searches in smaller contexts just that little bit snappier.

To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote :

I should note that as I was just refactoring I've again not added explicit tests; all the behaviour is tested, with one caveat - I improved the strictness of the aggregate function, and there isn't a test that it was doing the wrong thing before - but it should be pretty obvious to anyone coming along later that it was incorrect before.

Revision history for this message
Stuart Bishop (stub) wrote :

The following two lines leave me scratching my head looking for side effects:

reduce(lambda _, series:milestones.extend(series.milestones),
self.series_list, [])

Better written as:

for series in self.series_list:
    milestones.extend(series.milestones)

Does the bug search api allow you to group by BugTask.milestone instead of BugTask.milestoneID? I think the former would be clearer.

It would be nice if countBugs() in lp/bugs/model/bugtask.py retained its Storm syntax, but I don't know how to spell the COUNT(DISTINCT...) construct either.

I notice that lp/bugs/interfaces/bugtask.py is growing a pretty complex class. This seems wrong - IBugTaskSearchParams can be declared in this file, but the implementation should exist elsewhere. Your 'Yay circular deps' is just a symptom. Worth opening a Bug and adding a referencing XXX: to this class for the next time someone extends or refactors this?

review: Approve
Revision history for this message
Robert Collins (lifeless) wrote :

The reduce goes in the next branch or two anyhow.

BugTask.milestone is a storm reference column, they compile poorly - so no.

I tried a storm version for the distinct and it compiled to select count((distinct BugTask.bug)) which is not valid sql.

The params is in the interface class so it can be imported around the import fascist which model code can't AIUI - so I'm not sure there is an option to move it substantially. I don't think XXX 'this is a good obvious idea' bugs are worth their time: they generally just add up.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/browser/bugtarget.py'
2--- lib/lp/bugs/browser/bugtarget.py 2011-02-17 09:14:19 +0000
3+++ lib/lp/bugs/browser/bugtarget.py 2011-02-21 23:28:14 +0000
4@@ -24,7 +24,10 @@
5 import cgi
6 from cStringIO import StringIO
7 from datetime import datetime
8-from operator import itemgetter
9+from operator import (
10+ attrgetter,
11+ itemgetter,
12+ )
13 import urllib
14
15 from lazr.restful.interface import copy_field
16@@ -1188,9 +1191,11 @@
17 """
18 series_buglistings = []
19 bug_task_set = getUtility(IBugTaskSet)
20- series = any(*self.series_list)
21+ series_list = self.series_list
22+ if not series_list:
23+ return series_buglistings
24 open_bugs = bug_task_set.open_bugtask_search
25- open_bugs.setTarget(series)
26+ open_bugs.setTarget(any(*series_list))
27 # This would be better as delegation not a case statement.
28 if IDistribution(self.context, None):
29 backlink = BugTask.distroseriesID
30@@ -1203,7 +1208,7 @@
31 else:
32 raise AssertionError("illegal context %r" % self.context)
33 counts = bug_task_set.countBugs(open_bugs, (backlink,))
34- for series in self.series_list:
35+ for series in series_list:
36 series_bug_count = counts.get((series.id,), 0)
37 if series_bug_count > 0:
38 series_buglistings.append(
39@@ -1218,16 +1223,24 @@
40 def milestone_buglistings(self):
41 """Return a buglisting for each milestone."""
42 milestone_buglistings = []
43- for series in self.series_list:
44- for milestone in series.milestones:
45- milestone_bug_count = milestone.open_bugtasks.count()
46- if milestone_bug_count > 0:
47- milestone_buglistings.append(
48- dict(
49- title=milestone.name,
50- url=canonical_url(milestone),
51- count=milestone_bug_count,
52- ))
53+ bug_task_set = getUtility(IBugTaskSet)
54+ milestones = []
55+ reduce(lambda _, series:milestones.extend(series.milestones),
56+ self.series_list, [])
57+ if not milestones:
58+ return milestone_buglistings
59+ open_bugs = bug_task_set.open_bugtask_search
60+ open_bugs.setTarget(any(*milestones))
61+ counts = bug_task_set.countBugs(open_bugs, (BugTask.milestoneID,))
62+ for milestone in milestones:
63+ milestone_bug_count = counts.get((milestone.id,), 0)
64+ if milestone_bug_count > 0:
65+ milestone_buglistings.append(
66+ dict(
67+ title=milestone.name,
68+ url=canonical_url(milestone),
69+ count=milestone_bug_count,
70+ ))
71 return milestone_buglistings
72
73
74
75=== modified file 'lib/lp/bugs/browser/bugtask.py'
76--- lib/lp/bugs/browser/bugtask.py 2011-02-18 02:58:59 +0000
77+++ lib/lp/bugs/browser/bugtask.py 2011-02-21 23:28:14 +0000
78@@ -3078,12 +3078,11 @@
79
80 # XXX flacoste 2008/04/24 This should be moved to a
81 # BugTaskSearchParams.setTarget().
82- if IDistroSeries.providedBy(self.context):
83- search_params.setDistroSeries(self.context)
84+ if (IDistroSeries.providedBy(self.context) or
85+ IProductSeries.providedBy(self.context)):
86+ search_params.setTarget(self.context)
87 elif IDistribution.providedBy(self.context):
88 search_params.setDistribution(self.context)
89- elif IProductSeries.providedBy(self.context):
90- search_params.setProductSeries(self.context)
91 elif IProduct.providedBy(self.context):
92 search_params.setProduct(self.context)
93 elif IProjectGroup.providedBy(self.context):
94
95=== modified file 'lib/lp/bugs/interfaces/bugtask.py'
96--- lib/lp/bugs/interfaces/bugtask.py 2011-02-17 09:14:19 +0000
97+++ lib/lp/bugs/interfaces/bugtask.py 2011-02-21 23:28:14 +0000
98@@ -1281,6 +1281,7 @@
99 # Yay circular deps.
100 from lp.registry.interfaces.distroseries import IDistroSeries
101 from lp.registry.interfaces.productseries import IProductSeries
102+ from lp.registry.interfaces.milestone import IMilestone
103 if isinstance(target, (any, all)):
104 assert len(target.query_values), \
105 'cannot determine target with no targets'
106@@ -1291,6 +1292,8 @@
107 self.setDistroSeries(target)
108 elif IProductSeries.providedBy(instance):
109 self.setProductSeries(target)
110+ elif IMilestone.providedBy(instance):
111+ self.milestone = target
112 else:
113 raise AssertionError("unknown target type %r" % target)
114
115
116=== modified file 'lib/lp/bugs/model/bugtask.py'
117--- lib/lp/bugs/model/bugtask.py 2011-02-17 04:40:55 +0000
118+++ lib/lp/bugs/model/bugtask.py 2011-02-21 23:28:14 +0000
119@@ -2502,7 +2502,7 @@
120 def countBugs(self, params, group_on):
121 """See `IBugTaskSet`."""
122 resultset = self._search(
123- group_on + (Count(BugTask.bugID),), [], None, params).result_set
124+ group_on + (SQL("COUNT(Distinct BugTask.bug)"),), [], None, params).result_set
125 # We group on the related field:
126 resultset.group_by(*group_on)
127 resultset.order_by()