1
# Copyright 2005-2011 Canonical Ltd. All rights reserved.
3
# This program is free software: you can redistribute it and/or modify
4
# it under the terms of the GNU Affero General Public License as published by
5
# the Free Software Foundation, either version 3 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU Affero General Public License for more details.
13
# You should have received a copy of the GNU Affero General Public License
14
# along with this program. If not, see <http://www.gnu.org/licenses/>.
20
from django.conf import settings
22
HARD_TIMEOUT_EXCEPTIONS = ['RequestExpired',
23
'RequestStatementTimedOut',
25
'LaunchpadTimeoutError']
26
SOFT_TIMEOUT_EXCEPTIONS = ['SoftRequestTimeout']
27
TIMEOUT_EXCEPTIONS = HARD_TIMEOUT_EXCEPTIONS + SOFT_TIMEOUT_EXCEPTIONS
30
def get_safe_pageid(oops):
31
"""Always return a useful Page ID or, if Unknown, the URL."""
32
if oops.pageid == "Unknown":
37
class ErrorSummarySection:
38
"""A section in the error summary."""
42
def __init__(self, title):
45
self.section_id = title.lower().replace(" ", "-")
47
def addOops(self, oops):
48
etype = oops.exception_type
49
evalue = oops.normalized_exception_value
51
oops_group = self.oops_groups.setdefault((etype, evalue),
52
oopsgroup.OopsGroup(etype, evalue))
53
oops_group.addOops(oops)
55
def renderHeadlineTXT(self, fp):
56
"""Render this section stats header in plain text."""
57
fp.write(' * %d %s\n' % (
58
self.errorCount(), self.title))
60
def renderHeadlineHTML(self, fp):
61
"""Render this section stats header in HTML."""
62
fp.write('<li><a href="#%s">%d %s</a></li>\n' %
63
(self.section_id, self.errorCount(), self.title))
66
#XXX missing test and docstring and better name
68
group.count for group in self.oops_groups.itervalues())
70
def renderTXT(self, fp):
71
"""Render this section in plain text."""
72
groups = sorted(self.oops_groups.itervalues(),
73
key=lambda group: (group.count, group.etype,
78
if self.max_count >= 0 and total > self.max_count:
79
fp.write('=== Top %d %s (total of %s unique items) ===\n\n' % (
80
self.max_count, self.title, total))
81
groups = groups[:self.max_count]
83
fp.write('=== All %s ===\n\n' % self.title)
89
def renderHTML(self, fp):
90
"""Render this section in HTML."""
91
fp.write('<div id="%s">' % self.section_id)
92
fp.write('<h2>All %s</h2>\n' % self.title)
93
groups = sorted(self.oops_groups.itervalues(),
94
key=lambda group: (group.count, group.etype,
102
class ExceptionsSection(ErrorSummarySection):
103
"""A section in the error summary."""
107
class TimeOutSection(ErrorSummarySection):
108
"""Timeout section in the error summary."""
110
def addOops(self, oops):
111
etype = oops.most_expensive_statement
113
oops_group = self.oops_groups.setdefault((etype, evalue),
114
oopsgroup.OopsGroup(etype, evalue))
115
oops_group.addOops(oops)
118
class TimeOutCountSection(ErrorSummarySection):
119
"""The timeout counts by page id section."""
122
def __init__(self, title):
123
ErrorSummarySection.__init__(self, title)
124
self.soft_timeouts = {}
125
self.hard_timeouts = {}
126
self.cached_timeouts_by_pageid = []
128
def addOops(self, oops):
129
etype = oops.exception_type
130
assert etype in TIMEOUT_EXCEPTIONS
131
pageid = get_safe_pageid(oops)
132
if etype in SOFT_TIMEOUT_EXCEPTIONS:
133
timeouts = self.soft_timeouts
134
elif etype in HARD_TIMEOUT_EXCEPTIONS:
135
timeouts = self.hard_timeouts
137
raise AssertionError(
138
"%s is %s, not a timeout" % (oops.oopsid, etype))
139
timeouts.setdefault(pageid, 0)
140
timeouts[pageid] += 1
142
def renderHeadlineTXT(self, fp):
145
def renderHeadlineHTML(self, fp):
147
'<li><a href="#%s">%s</a></li>\n' % (self.section_id, self.title))
149
def renderTXT(self, fp):
150
fp.write('=== Top %d %s ===\n\n' % (self.max_count, self.title))
151
fp.write(' Hard / Soft Page ID\n')
152
for ht, st, pageid in self._calculateTimeOutCounts():
153
fp.write('%9s / %4s %s\n' % (ht, st, pageid))
156
def renderHTML(self, fp):
157
fp.write('<div id="%s">' % self.section_id)
158
fp.write('<h2>%s</h2>\n' % self.title)
159
fp.write('<table class="top-value-table">\n')
161
fp.write('<th>Hard</th>\n')
162
fp.write('<th>Soft</th>\n')
163
fp.write('<th>Page ID</th>\n')
165
for ht, st, pageid in self._calculateTimeOutCounts():
167
fp.write('<td>%s</td>\n<td>%s</td>\n<td>%s</td>\n' %
173
def _calculateTimeOutCounts(self):
174
if self.cached_timeouts_by_pageid:
175
return self.cached_timeouts_by_pageid[:self.max_count]
177
# First, collect all the pageids that presented timeouts.
178
pageid_set = set(self.soft_timeouts.keys())
179
pageid_set = pageid_set.union(self.hard_timeouts.keys())
180
# Then pick out the counts and display the section
181
for pageid in pageid_set:
182
st = self.soft_timeouts.get(pageid, 0)
183
ht = self.hard_timeouts.get(pageid, 0)
184
self.cached_timeouts_by_pageid.append((ht, st, pageid))
185
self.cached_timeouts_by_pageid.sort(reverse=True)
186
return self.cached_timeouts_by_pageid[:self.max_count]
189
class TopValueSection(ErrorSummarySection):
190
"""A base section for displaying top N sets of OOPSes."""
193
def __init__(self, title):
194
ErrorSummarySection.__init__(self, title)
197
def addOops(self, oops):
198
pageid = get_safe_pageid(oops)
199
value = self.getValue(oops)
200
if (self.pageids.has_key(pageid) and
201
value <= self.pageids[pageid][0]):
203
self.pageids[pageid] = (value, oops.oopsid, pageid)
205
def renderHeadlineTXT(self, fp):
208
def renderHeadlineHTML(self, fp):
210
'<li><a href="#%s">%s</a></li>\n' % (self.section_id, self.title))
212
def formatValues(self, value, oopsid, pageid):
213
formatted_value = self.formatValue(value)
214
return '%s %-14s %s\n' % (formatted_value, oopsid, pageid)
216
def renderHTML(self, fp):
217
fp.write('<div id="%s">' % self.section_id)
218
fp.write('<h2>%s</h2>\n' % self.title)
219
fp.write('<table class="top-value-table">\n')
221
fp.write('<th>%s</th>\n' % self.value_name)
222
fp.write('<th>Oops ID</th>\n')
223
fp.write('<th>Page</th>\n')
225
for value, oopsid, pageid in self.getValues():
227
fp.write('<td>%s</td>\n<td><a href="%s/oops/?oopsid=%s">%s</a></td>'
228
'\n<td>%s</td>\n' % (
229
self.formatValue(value), settings.ROOT_URL,
230
oopsid, oopsid, pageid))
235
def renderTXT(self, fp):
236
fp.write('=== Top %d %s ===\n\n' % (self.max_count, self.title))
237
for duration, oopsid, pageid in self.getValues():
238
fp.write(self.formatValues(duration, oopsid, pageid))
242
"""Yield all values in this class sorted by highest value."""
243
for value, oopsid, pageid in sorted(self.pageids.values(),
244
reverse=True)[:self.max_count]:
245
yield (value, oopsid, pageid)
247
def getValue(self, oops):
248
"""Return the value for which this OOPS will be evaluted.
250
This class attempts to collect the OOPSes with the highest
251
values; this method allows you to specify which attribute of
252
the OOPS you are considering.
254
raise NotImplementedError
256
def formatValue(self, value):
257
"""Format the value obtained for rendering.
259
In normal situations the value should be formatted with 9
262
raise NotImplementedError
265
class StatementCountSection(TopValueSection):
266
"""The top statement counts section."""
269
def getValue(self, oops):
270
return oops.statements_count
272
def formatValue(self, value):
276
class TopDurationSection(TopValueSection):
277
"""The top page IDs by duration."""
278
value_name = "Duration"
280
def getValue(self, oops):
283
def formatValue(self, value):
284
return '%9.2fs' % value