Merge lp:~lifeless/python-oops-tools/prune into lp:python-oops-tools

Proposed by Robert Collins
Status: Merged
Approved by: Robert Collins
Approved revision: no longer in the source branch.
Merged at revision: 23
Proposed branch: lp:~lifeless/python-oops-tools/prune
Merge into: lp:python-oops-tools
Diff against target: 250 lines (+188/-1)
7 files modified
.bzrignore (+1/-0)
setup.py (+1/-0)
src/oopstools/NEWS.txt (+3/-0)
src/oopstools/oops/migrations/0020_add_oops_prune.py (+40/-0)
src/oopstools/oops/models.py (+19/-0)
src/oopstools/scripts/prune.py (+123/-0)
versions.cfg (+1/-1)
To merge this branch: bzr merge lp:~lifeless/python-oops-tools/prune
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+82840@code.launchpad.net

Commit message

Permit pruning of OOPS from the DB that are not referenced by a bug report after a week.

Description of the change

This implements oops pruning building on the pruning present in datedir-repo. The custom code for python-oops-tools, that isn't part of the CLI, is uhm trivial. I've got room to add tests, but they seemed of vanishing utility here so I'm going to throw myself on the mercy of my reviewer.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) wrote :

+ oldest_oops = Oops.objects.order_by('id')[0]

id isn't used as a criterion anywhere else. Perhaps sort by date?

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

we want the oldest oops in the system - there has been no other cause for that particular query to date.

lp:~lifeless/python-oops-tools/prune updated
21. By Robert Collins

Print out the path to the OOPS when amqp2disk fails to import it.

22. By Robert Collins

Permit wider prefix and appinstance fields.

23. By Robert Collins

Permit pruning of OOPS from the DB that are not referenced by a bug report after a week.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2011-10-13 20:18:51 +0000
3+++ .bzrignore 2011-11-21 04:15:28 +0000
4@@ -14,3 +14,4 @@
5 apache/*
6 src/oopstools/settings.py
7 ./dist
8+prune.log
9
10=== modified file 'setup.py'
11--- setup.py 2011-10-16 22:35:01 +0000
12+++ setup.py 2011-11-21 04:15:28 +0000
13@@ -83,6 +83,7 @@
14 'amqp2disk = oopstools.scripts.amqp2disk:main',
15 'analyse_error_reports = oopstools.scripts.analyse_error_reports:main',
16 'load_sample_data = oopstools.scripts.load_sample_data:main',
17+ 'prune = oopstools.scripts.prune:main',
18 'update_db = oopstools.scripts.update_db:main',
19 'dir_finder = oopstools.scripts.dir_finder:main',
20 'report = oopstools.scripts.report:main',
21
22=== modified file 'src/oopstools/NEWS.txt'
23--- src/oopstools/NEWS.txt 2011-11-16 23:21:38 +0000
24+++ src/oopstools/NEWS.txt 2011-11-21 04:15:28 +0000
25@@ -21,6 +21,9 @@
26 * Mixed case OOPS reports can now be looked up in the web UI without their
27 OOPS-prefix (if they had one). (Robert Collins, #884571)
28
29+* Old OOPS reports can be cleaned out using the new script bin/prune.
30+ (Robert Collins)
31+
32 * OOPS reports that don't meet the normal rules for req_vars are handled
33 a bit better.
34 (Robert Collins, William Grant, Roman Yepishev, #885416, #884265)
35
36=== added file 'src/oopstools/oops/migrations/0020_add_oops_prune.py'
37--- src/oopstools/oops/migrations/0020_add_oops_prune.py 1970-01-01 00:00:00 +0000
38+++ src/oopstools/oops/migrations/0020_add_oops_prune.py 2011-11-21 04:15:28 +0000
39@@ -0,0 +1,40 @@
40+# Copyright 2011 Canonical Ltd. All rights reserved.
41+#
42+# This program is free software: you can redistribute it and/or modify
43+# it under the terms of the GNU Affero General Public License as published by
44+# the Free Software Foundation, either version 3 of the License, or
45+# (at your option) any later version.
46+#
47+# This program is distributed in the hope that it will be useful,
48+# but WITHOUT ANY WARRANTY; without even the implied warranty of
49+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
50+# GNU Affero General Public License for more details.
51+#
52+# You should have received a copy of the GNU Affero General Public License
53+# along with this program. If not, see <http://www.gnu.org/licenses/>.
54+
55+from south.v2 import DataMigration
56+from south.db import db
57+
58+
59+class Migration(DataMigration):
60+
61+ def forwards(self, orm):
62+ db.create_table('oops_pruneinfo', (
63+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
64+ ('pruned_until', self.gf('django.db.models.fields.DateTimeField')())
65+ ))
66+ db.send_create_signal('oops', ['PruneInfo'])
67+
68+ def backwards(self, orm):
69+ db.delete_table('oops_pruneinfo')
70+
71+ models = {
72+ 'oops.prune': {
73+ 'Meta': {'object_name': 'PruneInfo'},
74+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
75+ 'pruned_until': ('django.db.models.fields.DateTimeField', [], {}),
76+ },
77+ }
78+
79+ complete_apps = ['oops']
80
81=== modified file 'src/oopstools/oops/models.py'
82--- src/oopstools/oops/models.py 2011-11-17 16:11:12 +0000
83+++ src/oopstools/oops/models.py 2011-11-21 04:15:28 +0000
84@@ -120,6 +120,25 @@
85 return unicode(self.value)
86
87
88+class PruneInfo(models.Model):
89+ """Information about oops pruning."""
90+
91+ pruned_until = models.DateTimeField()
92+
93+ @classmethod
94+ def prune_unreferenced(klass, prune_from, prune_until, references):
95+ # XXX: This trusts date more than we may want to - should consider
96+ # having a date_received separate to the date generated.
97+ to_delete = set(Oops.objects.filter(
98+ date__gte=prune_from, date__lte=prune_until).exclude(
99+ oopsid__in=references))
100+ # deleting 1 at a time is a lot of commits, but leaves the DB lively
101+ # for other transactions. May need to batch this in future if its
102+ # too slow.
103+ for oopsid in to_delete:
104+ Oops.objects.filter(oopsid__exact=oopsid).delete()
105+
106+
107 class Report(models.Model):
108 """A summary report for OOPSes of a set of prefixes."""
109
110
111=== added file 'src/oopstools/scripts/prune.py'
112--- src/oopstools/scripts/prune.py 1970-01-01 00:00:00 +0000
113+++ src/oopstools/scripts/prune.py 2011-11-21 04:15:28 +0000
114@@ -0,0 +1,123 @@
115+# Copyright 2011 Canonical Ltd. All rights reserved.
116+#
117+# This program is free software: you can redistribute it and/or modify
118+# it under the terms of the GNU Affero General Public License as published by
119+# the Free Software Foundation, either version 3 of the License, or
120+# (at your option) any later version.
121+#
122+# This program is distributed in the hope that it will be useful,
123+# but WITHOUT ANY WARRANTY; without even the implied warranty of
124+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
125+# GNU Affero General Public License for more details.
126+#
127+# You should have received a copy of the GNU Affero General Public License
128+# along with this program. If not, see <http://www.gnu.org/licenses/>.
129+
130+# Delete DB records of OOPSes that have no bug reports for them.
131+
132+__metaclass__ = type
133+
134+import datetime
135+import logging
136+import optparse
137+import sys
138+from textwrap import dedent
139+
140+from oops_datedir_repo.prune import LaunchpadTracker
141+from pytz import utc
142+
143+from oopstools.oops.models import (
144+ Oops,
145+ PruneInfo,
146+ )
147+
148+
149+def main(argv=None, tracker=LaunchpadTracker, logging=logging):
150+ """Console script entry point."""
151+ if argv is None:
152+ argv = sys.argv
153+ usage = dedent("""\
154+ %prog [options]
155+
156+ The following options must be supplied:
157+ Either
158+ --project
159+ or
160+ --projectgroup
161+
162+ e.g.
163+ %prog --projectgroup launchpad-project
164+
165+ Will process every member project of launchpad-project.
166+
167+ When run this program will ask Launchpad for OOPS references made since
168+ the last date it pruned up to, with an upper limit of one week from
169+ today. It then looks in the database for all oopses created during that
170+ date range, and if they are not in the set returned by Launchpad,
171+ deletes them. If the database has never been pruned before, it will
172+ pick the earliest date present in the repository as the start date.
173+
174+ This does not delete OOPS files that are on disk (e.g. created by
175+ amqp2disk) - a separate pruner is available in oops-datedir-repo that
176+ should be used to prune them.
177+ """)
178+ description = \
179+ "Delete OOPS reports that are not referenced in a bug tracker."
180+ parser = optparse.OptionParser(
181+ description=description, usage=usage)
182+ parser.add_option('--project',
183+ help="Launchpad project to find references in.")
184+ parser.add_option('--projectgroup',
185+ help="Launchpad project group to find references in.")
186+ parser.add_option(
187+ '--lpinstance', help="Launchpad instance to use", default="production")
188+ options, args = parser.parse_args(argv[1:])
189+ def needed(*optnames):
190+ present = set()
191+ for optname in optnames:
192+ if getattr(options, optname, None) is not None:
193+ present.add(optname)
194+ if not present:
195+ if len(optnames) == 1:
196+ raise ValueError('Option "%s" must be supplied' % optname)
197+ else:
198+ raise ValueError(
199+ 'One of options %s must be supplied' % (optnames,))
200+ elif len(present) != 1:
201+ raise ValueError(
202+ 'Only one of options %s can be supplied' % (optnames,))
203+ needed('project', 'projectgroup')
204+ logging.basicConfig(
205+ filename='prune.log', filemode='w', level=logging.DEBUG)
206+ one_week = datetime.timedelta(weeks=1)
207+ one_day = datetime.timedelta(days=1)
208+ # Only prune OOPS reports more than one week old.
209+ prune_until = datetime.datetime.now(utc) - one_week
210+ # Ignore OOPS reports we already found references for - older than the last
211+ # prune date.
212+ try:
213+ info = PruneInfo.objects.all()[0]
214+ except IndexError:
215+ # Never been pruned.
216+ try:
217+ oldest_oops = Oops.objects.order_by('id')[0]
218+ except IndexError:
219+ # And has no oopses
220+ return 0
221+ info = PruneInfo(pruned_until=oldest_oops.date-one_day)
222+ info.save()
223+ prune_from = info.pruned_until
224+ if prune_from.tzinfo is None:
225+ # Workaround django tz handling bug:
226+ # https://code.djangoproject.com/ticket/17062
227+ prune_from = prune_from.replace(tzinfo=utc)
228+ # The tracker finds all the references for the selected dates.
229+ finder = tracker(options)
230+ references = finder.find_oops_references(
231+ prune_from, prune_until, options.project, options.projectgroup)
232+ # Then we can delete the unreferenced oopses.
233+ PruneInfo.prune_unreferenced(prune_from, prune_until, references)
234+ # And finally save the fact we have scanned up to the selected date.
235+ info.pruned_until = prune_until
236+ info.save()
237+ return 0
238
239=== modified file 'versions.cfg'
240--- versions.cfg 2011-11-16 07:25:56 +0000
241+++ versions.cfg 2011-11-21 04:15:28 +0000
242@@ -21,7 +21,7 @@
243 mechanize = 0.1.11
244 oops = 0.0.10
245 oops-amqp = 0.0.4
246-oops-datedir-repo = 0.0.12
247+oops-datedir-repo = 0.0.14
248 setuptools = 0.6c11
249 z3c.recipe.filetemplate = 2.0.3
250 z3c.recipe.sphinxdoc = 0.0.8

Subscribers

People subscribed via source and target branches

to all changes: