Merge lp:~beuno/python-oops-tools/show-newest-oopses into lp:python-oops-tools

Proposed by Martin Albisetti
Status: Needs review
Proposed branch: lp:~beuno/python-oops-tools/show-newest-oopses
Merge into: lp:python-oops-tools
Diff against target: 309 lines (+85/-18)
9 files modified
src/oopstools/oops/dboopsloader.py (+4/-4)
src/oopstools/oops/dbsummaries.py (+5/-2)
src/oopstools/oops/helpers.py (+6/-4)
src/oopstools/oops/oopsstore.py (+15/-4)
src/oopstools/oops/templates/newest.html (+12/-0)
src/oopstools/oops/test/oopsstore.txt (+32/-0)
src/oopstools/oops/test/test_runner.py (+2/-1)
src/oopstools/oops/views.py (+7/-2)
src/oopstools/urls.py (+2/-1)
To merge this branch: bzr merge lp:~beuno/python-oops-tools/show-newest-oopses
Reviewer Review Type Date Requested Status
Benji York (community) code Approve
Review via email: mp+129752@code.launchpad.net

Commit message

Add a view to see the newest oopses.

Description of the change

Add an option to see the newest oopses by project.

To post a comment you must log in.
Revision history for this message
Benji York (benji) wrote :

The whitespace fixes are appreciated. One class of whitespace change
looks like a search/replace error though: lines 8 and 103 of the diff
have one-element lists that included a trailing comma. That was
somewhat odd, but the added space after the comma is even oddder. I
suggest removing both the comma and the space.

I don't know much about the on-disk layout for OOPS storage, but I would
be a little concerned that non-oops files and directories would confuse
the "newest" method. If this trait is common to the rest of the
OOPS-tools code, then my concern can be ignored.

The test for the new "newest" function (line 239 of the diff) contains a
dependency on the order of elements returned from a call the "values"
method of a dict. That order can vary, so the returned list needs to be
sorted in the doctest in order to avoid spurious test failures.

The above applies to the test on line 246 of the diff as well.

Once these small issues are fixed, the code is good to land.

review: Approve (code)
52. By Martin Albisetti

Review comments

Revision history for this message
Martin Albisetti (beuno) wrote :

> The whitespace fixes are appreciated. One class of whitespace change
> looks like a search/replace error though: lines 8 and 103 of the diff
> have one-element lists that included a trailing comma. That was
> somewhat odd, but the added space after the comma is even oddder. I
> suggest removing both the comma and the space.

Done.

> I don't know much about the on-disk layout for OOPS storage, but I would
> be a little concerned that non-oops files and directories would confuse
> the "newest" method. If this trait is common to the rest of the
> OOPS-tools code, then my concern can be ignored.

It is. In this case, the worst that can happen is linking to something that isn't an oops.

> The test for the new "newest" function (line 239 of the diff) contains a
> dependency on the order of elements returned from a call the "values"
> method of a dict. That order can vary, so the returned list needs to be
> sorted in the doctest in order to avoid spurious test failures.
>
> The above applies to the test on line 246 of the diff as well.

Done!

53. By Martin Albisetti

Start talking to the DB instead

Unmerged revisions

53. By Martin Albisetti

Start talking to the DB instead

52. By Martin Albisetti

Review comments

51. By Martin Albisetti

Add test

50. By Martin Albisetti

Clean up things a bit

49. By Martin Albisetti

Working, quick and dirty

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/oopstools/oops/dboopsloader.py'
--- src/oopstools/oops/dboopsloader.py 2011-10-21 13:54:29 +0000
+++ src/oopstools/oops/dboopsloader.py 2012-10-17 22:06:22 +0000
@@ -87,7 +87,7 @@
87 # XXX matsubara: remove the if clause when refactoring this code87 # XXX matsubara: remove the if clause when refactoring this code
88 # to use a single oops directory.88 # to use a single oops directory.
89 if type(oopsdir) == str:89 if type(oopsdir) == str:
90 oopsdir = [oopsdir,]90 oopsdir = [oopsdir, ]
9191
92 # Walk through all subdirectories containing OOPSes and ensure there92 # Walk through all subdirectories containing OOPSes and ensure there
93 # is a matching database structure for them.93 # is a matching database structure for them.
@@ -141,7 +141,7 @@
141 before_date = date - datetime.timedelta(days=1)141 before_date = date - datetime.timedelta(days=1)
142 after_date = date + datetime.timedelta(days=2)142 after_date = date + datetime.timedelta(days=2)
143 query = Oops.objects.filter(143 query = Oops.objects.filter(
144 date__range=(before_date,after_date),144 date__range=(before_date, after_date),
145 pathname__startswith=prefix).values_list(145 pathname__startswith=prefix).values_list(
146 'pathname', flat=True)146 'pathname', flat=True)
147 oops_reports = set(147 oops_reports = set(
@@ -151,7 +151,7 @@
151 oops = self._load_oops(datedir, filename)151 oops = self._load_oops(datedir, filename)
152 if oops is not None:152 if oops is not None:
153 yield oops153 yield oops
154 # We update the last_date only when oops 154 # We update the last_date only when oops
155 # has the date different to what we already have155 # has the date different to what we already have
156 # This speeds up the loading process156 # This speeds up the loading process
157 if entry.last_date != date:157 if entry.last_date != date:
@@ -177,7 +177,7 @@
177 # condition.177 # condition.
178 logger.error(178 logger.error(
179 "OOPS %s/%s already in the DB.", datedir, filename)179 "OOPS %s/%s already in the DB.", datedir, filename)
180 transaction.rollback() # Continue to next one.180 transaction.rollback() # Continue to next one.
181 else:181 else:
182 logger.error(182 logger.error(
183 "%s raised with datedir: %s filename: %s ",183 "%s raised with datedir: %s filename: %s ",
184184
=== modified file 'src/oopstools/oops/dbsummaries.py'
--- src/oopstools/oops/dbsummaries.py 2012-09-11 02:08:56 +0000
+++ src/oopstools/oops/dbsummaries.py 2012-10-17 22:06:22 +0000
@@ -32,6 +32,7 @@
32#############################################################################32#############################################################################
33# Groups33# Groups
3434
35
35def _format_http_method_count(data):36def _format_http_method_count(data):
36 tmp = []37 tmp = []
37 for method in TRACKED_HTTP_METHODS + ('Other',):38 for method in TRACKED_HTTP_METHODS + ('Other',):
@@ -40,6 +41,7 @@
40 tmp.append("%s: %s" % (method, count))41 tmp.append("%s: %s" % (method, count))
41 return ' '.join(tmp)42 return ' '.join(tmp)
4243
44
43def _escape(value):45def _escape(value):
44 if value is not None:46 if value is not None:
45 if not isinstance(value, unicode):47 if not isinstance(value, unicode):
@@ -333,7 +335,7 @@
333 'oopsinfestation__exception_value')335 'oopsinfestation__exception_value')
334 )336 )
335 super(ErrorSection, self).__init__(337 super(ErrorSection, self).__init__(
336 title, errors, max_count, _all_groups = all_groups)338 title, errors, max_count, _all_groups=all_groups)
337339
338340
339class NotFoundSection(ErrorSection):341class NotFoundSection(ErrorSection):
@@ -362,7 +364,7 @@
362 '-count', 'most_expensive_statement')364 '-count', 'most_expensive_statement')
363 )365 )
364 super(TimeOutSection, self).__init__(366 super(TimeOutSection, self).__init__(
365 title, errors, max_count, _all_groups = all_groups)367 title, errors, max_count, _all_groups=all_groups)
366368
367# We perform the top value queries outside of the ORM for performance.369# We perform the top value queries outside of the ORM for performance.
368#370#
@@ -772,6 +774,7 @@
772 ErrorSection, dict(title='Exceptions', max_count=50), section_set)774 ErrorSection, dict(title='Exceptions', max_count=50), section_set)
773 self.addSections(*section_set)775 self.addSections(*section_set)
774776
777
775class ISDErrorSummary(GenericErrorSummary):778class ISDErrorSummary(GenericErrorSummary):
776 """Summarize ISD error reports (placeholder)"""779 """Summarize ISD error reports (placeholder)"""
777780
778781
=== modified file 'src/oopstools/oops/helpers.py'
--- src/oopstools/oops/helpers.py 2011-10-13 20:18:51 +0000
+++ src/oopstools/oops/helpers.py 2012-10-17 22:06:22 +0000
@@ -101,7 +101,7 @@
101def parsedate(date_string):101def parsedate(date_string):
102 """Return a naive date time object for the given string.102 """Return a naive date time object for the given string.
103103
104 This function ignores subsecond accuracy and the timezone. 104 This function ignores subsecond accuracy and the timezone.
105 May be used to parse a ISO 8601 date string.105 May be used to parse a ISO 8601 date string.
106106
107 >>> ds = '2009-01-02'107 >>> ds = '2009-01-02'
@@ -132,7 +132,7 @@
132 """Generate a summary with the given prefix and dates."""132 """Generate a summary with the given prefix and dates."""
133 script_cmd = '%s/analyse_error_reports' % settings.BIN_DIR133 script_cmd = '%s/analyse_error_reports' % settings.BIN_DIR
134134
135 args = [script_cmd,]135 args = [script_cmd]
136 for prefix in prefixes:136 for prefix in prefixes:
137 args.append("-p%s" % prefix)137 args.append("-p%s" % prefix)
138 for d in (from_date, to_date):138 for d in (from_date, to_date):
@@ -161,7 +161,7 @@
161 pos = text.find(pattern)161 pos = text.find(pattern)
162 if pos < 0:162 if pos < 0:
163 return "Pattern: '%s' not found in %s" % (pattern, text)163 return "Pattern: '%s' not found in %s" % (pattern, text)
164 return text[pos+len(pattern):].split("===")[0].strip()164 return text[pos + len(pattern):].split("===")[0].strip()
165165
166166
167def reset_database():167def reset_database():
@@ -209,6 +209,7 @@
209209
210_cached_launchpadlib = None210_cached_launchpadlib = None
211211
212
212def get_launchpadlib():213def get_launchpadlib():
213 """Return a launchpadlib instance from the cache.214 """Return a launchpadlib instance from the cache.
214215
@@ -225,6 +226,7 @@
225226
226_today = None227_today = None
227228
229
228def today():230def today():
229 """Return a date time object from the cache.231 """Return a date time object from the cache.
230232
@@ -238,6 +240,6 @@
238240
239def load_prefixes(report_name):241def load_prefixes(report_name):
240 """Return a list of Prefix objects grouped by Report."""242 """Return a list of Prefix objects grouped by Report."""
241 from oopstools.oops.models import Report 243 from oopstools.oops.models import Report
242 report = Report.objects.get(name=report_name)244 report = Report.objects.get(name=report_name)
243 return [prefix.value for prefix in report.prefixes.all()]245 return [prefix.value for prefix in report.prefixes.all()]
244246
=== modified file 'src/oopstools/oops/oopsstore.py'
--- src/oopstools/oops/oopsstore.py 2011-10-13 20:18:51 +0000
+++ src/oopstools/oops/oopsstore.py 2012-10-17 22:06:22 +0000
@@ -21,7 +21,7 @@
21import datetime21import datetime
22import itertools22import itertools
2323
24from oopstools.oops.models import Oops, OopsReadError, oops_re24from oopstools.oops.models import Oops, OopsReadError, oops_re, Prefix
2525
2626
27# the section of the OOPS ID before the instance identifier is the27# the section of the OOPS ID before the instance identifier is the
@@ -77,7 +77,7 @@
77 # XXX matsubara: remove the if clause when refactoring this code77 # XXX matsubara: remove the if clause when refactoring this code
78 # to use a single oops directory.78 # to use a single oops directory.
79 if type(oopsdir) == str:79 if type(oopsdir) == str:
80 oopsdir = [oopsdir,]80 oopsdir = [oopsdir]
81 # XXX matsubara: find_dirs changes directories to do its job and81 # XXX matsubara: find_dirs changes directories to do its job and
82 # before calling it, record the original directory the script is82 # before calling it, record the original directory the script is
83 # in and after it finishes building83 # in and after it finishes building
@@ -123,7 +123,7 @@
123 dse = match.group('dse')123 dse = match.group('dse')
124 oops = match.group('oopsprefix') + match.group('id')124 oops = match.group('oopsprefix') + match.group('id')
125 if date:125 if date:
126 pass # got enough data126 pass # got enough data
127 elif dse:127 elif dse:
128 # dse is the number of days since the epoch128 # dse is the number of days since the epoch
129 day = epoch + datetime.timedelta(days=int(dse) - 1)129 day = epoch + datetime.timedelta(days=int(dse) - 1)
@@ -145,7 +145,7 @@
145 # between the OOPS filename and the OOPS id, that's why145 # between the OOPS filename and the OOPS id, that's why
146 # there're two different regex to identify those.146 # there're two different regex to identify those.
147 oops_prefix = filename.split(".")[1]147 oops_prefix = filename.split(".")[1]
148 oops_prefix = re.sub("\d+$", "", oops_prefix)148 oops_prefix = re.sub("\d+$", "", oops_prefix)
149 # Normalize oops prefix149 # Normalize oops prefix
150 return oops_prefix.upper()150 return oops_prefix.upper()
151151
@@ -183,3 +183,14 @@
183 return itertools.ifilter(183 return itertools.ifilter(
184 lambda oops: startdatetime <= oops.date <= enddatetime,184 lambda oops: startdatetime <= oops.date <= enddatetime,
185 oops_results)185 oops_results)
186
187 def newest(self, prefix, limit=50):
188 """Get the newest oopses."""
189 oops_list = list()
190
191 prefix = Prefix.objects.get(value__exact=prefix)
192 query = Oops.objects.filter(
193 prefix__exact=prefix).order_by('date')[:limit]
194 for oops in query:
195 oops_list.append(oops)
196 return oops_list
186197
=== added file 'src/oopstools/oops/templates/newest.html'
--- src/oopstools/oops/templates/newest.html 1970-01-01 00:00:00 +0000
+++ src/oopstools/oops/templates/newest.html 2012-10-17 22:06:22 +0000
@@ -0,0 +1,12 @@
1{% extends "base.html" %}
2
3{% block title %}Newest OOPS Reports{% endblock %}
4
5{% block summaries %}
6
7<ul>
8{% for oops in latest_oopses %}
9 <li><a href="/oops/?oopsid={{ oops }}">{{ oops }}</a></li>
10{% endfor %}
11</ul>
12{% endblock %}
013
=== modified file 'src/oopstools/oops/test/oopsstore.txt'
--- src/oopstools/oops/test/oopsstore.txt 2011-10-13 20:18:51 +0000
+++ src/oopstools/oops/test/oopsstore.txt 2012-10-17 22:06:22 +0000
@@ -119,3 +119,35 @@
119 >>> [oops.oopsid for oops in119 >>> [oops.oopsid for oops in
120 ... store.search(start_date, end_date, prefixes=['CCW'])]120 ... store.search(start_date, end_date, prefixes=['CCW'])]
121 []121 []
122
123Show the newest oopses for each directory.
124
125 >>> newest_oopses = store.newest()
126 >>> len(newest_oopses)
127 2
128
129 >>> sorted(newest_oopses.values())
130 [['01149-1925canistelubuntu0.oops'], ['61295.SMPM25']]
131
132newest() can optionally limit the amount of oopses it returns.
133
134 >>> from datetime import date
135 >>> from django.conf import settings
136 >>> from oopstools.oops.helpers import create_fake_oops
137 >>> oops_dir_one = os.path.join(
138 ... settings.OOPSDIR[0], 'dir1', '2008-03-18')
139 >>> fake_oops_one = open(oops_dir_one + "/" + "68350.X1", "w")
140 >>> contents = create_fake_oops(date(2008, 3, 18))
141 >>> fake_oops_one.write(contents)
142 >>> fake_oops_one.close()
143 >>> newest_oopses = store.newest()
144 >>> sorted(newest_oopses.values())
145 [['01149-1925canistelubuntu0.oops'], ['68350.X1', '61295.SMPM25']]
146
147When limiting to 1, we only get 1 oops from dir1
148
149 >>> newest_oopses = store.newest(limit=1)
150 >>> sorted(newest_oopses.values())
151 [['01149-1925canistelubuntu0.oops'], ['68350.X1']]
152
153 >>> os.remove(fake_oops_one.name)
122154
=== modified file 'src/oopstools/oops/test/test_runner.py'
--- src/oopstools/oops/test/test_runner.py 2011-10-13 20:18:51 +0000
+++ src/oopstools/oops/test/test_runner.py 2012-10-17 22:06:22 +0000
@@ -24,7 +24,8 @@
24from django.test.simple import DjangoTestSuiteRunner24from django.test.simple import DjangoTestSuiteRunner
2525
26DEFAULT_OPTIONS = (26DEFAULT_OPTIONS = (
27 doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS |doctest.REPORT_NDIFF)27 doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS | doctest.REPORT_NDIFF)
28
2829
29class CustomTestRunner(DjangoTestSuiteRunner):30class CustomTestRunner(DjangoTestSuiteRunner):
3031
3132
=== modified file 'src/oopstools/oops/views.py'
--- src/oopstools/oops/views.py 2011-11-03 00:08:18 +0000
+++ src/oopstools/oops/views.py 2012-10-17 22:06:22 +0000
@@ -44,7 +44,7 @@
44 oopsids.add(query_str.upper())44 oopsids.add(query_str.upper())
45 oopsids.add(query_str.lower())45 oopsids.add(query_str.lower())
46 if not query_str.upper().startswith('OOPS-'):46 if not query_str.upper().startswith('OOPS-'):
47 oopsids.update(map(lambda x:'OOPS-'+x, oopsids))47 oopsids.update(map(lambda x: 'OOPS-' + x, oopsids))
4848
49 # Check each ID in both the database and filesystem49 # Check each ID in both the database and filesystem
50 # (should maybe have an API option for this, instead of doing it manually?)50 # (should maybe have an API option for this, instead of doing it manually?)
@@ -151,4 +151,9 @@
151 return render_to_response("summary.html", dictionary=data)151 return render_to_response("summary.html", dictionary=data)
152152
153153
154154def newest(request):
155 """Show the newest oopses for all projects."""
156 store = OopsStore(settings.OOPSDIR)
157 latest_oopses = store.newest(prefix=request.GET.get('prefix'))
158 data = {'latest_oopses': latest_oopses}
159 return render_to_response("newest.html", dictionary=data)
155160
=== modified file 'src/oopstools/urls.py'
--- src/oopstools/urls.py 2011-10-13 20:18:51 +0000
+++ src/oopstools/urls.py 2012-10-17 22:06:22 +0000
@@ -24,7 +24,7 @@
24 # Example:24 # Example:
25 # (r'^oopstools/', include('oopstools.foo.urls')),25 # (r'^oopstools/', include('oopstools.foo.urls')),
2626
27 # Uncomment the admin/doc line below and add 'django.contrib.admindocs' 27 # Uncomment the admin/doc line below and add 'django.contrib.admindocs'
28 # to INSTALLED_APPS to enable admin documentation:28 # to INSTALLED_APPS to enable admin documentation:
29 # (r'^admin/doc/', include('django.contrib.admindocs.urls')),29 # (r'^admin/doc/', include('django.contrib.admindocs.urls')),
3030
@@ -39,4 +39,5 @@
39 (r'^oops[.py]*/meta$', 'oopstools.oops.views.meta'),39 (r'^oops[.py]*/meta$', 'oopstools.oops.views.meta'),
40 (r'^oops/static/(?P<path>.*)$', 'django.views.static.serve',40 (r'^oops/static/(?P<path>.*)$', 'django.views.static.serve',
41 {'document_root': settings.STATIC_DOC_ROOT}),41 {'document_root': settings.STATIC_DOC_ROOT}),
42 (r'^newest/$', 'oopstools.oops.views.newest')
42)43)

Subscribers

People subscribed via source and target branches

to all changes: