~james-w/python-oops-tools/prod-deploy

« back to all changes in this revision

Viewing changes to src/oopstools/oops/sections.py

  • Committer: Robert Collins
  • Date: 2011-10-13 20:18:51 UTC
  • Revision ID: robertc@robertcollins.net-20111013201851-ym8jmdhoeol3p83s
Export of cruft-deleted tree.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2005-2011 Canonical Ltd.  All rights reserved.
 
2
#
 
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.
 
7
#
 
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.
 
12
#
 
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/>.
 
15
 
 
16
 
 
17
 
 
18
import oopsgroup
 
19
 
 
20
from django.conf import settings
 
21
 
 
22
HARD_TIMEOUT_EXCEPTIONS = ['RequestExpired',
 
23
                           'RequestStatementTimedOut',
 
24
                           'TimeoutError',
 
25
                           'LaunchpadTimeoutError']
 
26
SOFT_TIMEOUT_EXCEPTIONS = ['SoftRequestTimeout']
 
27
TIMEOUT_EXCEPTIONS = HARD_TIMEOUT_EXCEPTIONS + SOFT_TIMEOUT_EXCEPTIONS
 
28
 
 
29
 
 
30
def get_safe_pageid(oops):
 
31
    """Always return a useful Page ID or, if Unknown, the URL."""
 
32
    if oops.pageid == "Unknown":
 
33
        return oops.url
 
34
    return oops.pageid
 
35
 
 
36
 
 
37
class ErrorSummarySection:
 
38
    """A section in the error summary."""
 
39
 
 
40
    max_count = 15
 
41
 
 
42
    def __init__(self, title):
 
43
        self.oops_groups = {}
 
44
        self.title = title
 
45
        self.section_id = title.lower().replace(" ", "-")
 
46
 
 
47
    def addOops(self, oops):
 
48
        etype = oops.exception_type
 
49
        evalue = oops.normalized_exception_value
 
50
 
 
51
        oops_group = self.oops_groups.setdefault((etype, evalue),
 
52
                                    oopsgroup.OopsGroup(etype, evalue))
 
53
        oops_group.addOops(oops)
 
54
 
 
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))
 
59
 
 
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))
 
64
 
 
65
    def errorCount(self):
 
66
      #XXX missing test and docstring and better name
 
67
       return sum(
 
68
           group.count for group in self.oops_groups.itervalues())
 
69
 
 
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,
 
74
                                           group.evalue),
 
75
                        reverse=True)
 
76
 
 
77
        total = len(groups)
 
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]
 
82
        else:
 
83
            fp.write('=== All %s ===\n\n' % self.title)
 
84
 
 
85
        for group in groups:
 
86
            group.renderTXT(fp)
 
87
        fp.write('\n')
 
88
 
 
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,
 
95
                                           group.evalue),
 
96
                        reverse=True)
 
97
        for group in groups:
 
98
            group.renderHTML(fp)
 
99
        fp.write('</div>')
 
100
 
 
101
 
 
102
class ExceptionsSection(ErrorSummarySection):
 
103
    """A section in the error summary."""
 
104
    max_count = 50
 
105
 
 
106
 
 
107
class TimeOutSection(ErrorSummarySection):
 
108
    """Timeout section in the error summary."""
 
109
 
 
110
    def addOops(self, oops):
 
111
        etype = oops.most_expensive_statement
 
112
        evalue = ''
 
113
        oops_group = self.oops_groups.setdefault((etype, evalue),
 
114
                                    oopsgroup.OopsGroup(etype, evalue))
 
115
        oops_group.addOops(oops)
 
116
 
 
117
 
 
118
class TimeOutCountSection(ErrorSummarySection):
 
119
    """The timeout counts by page id section."""
 
120
    max_count = 10
 
121
 
 
122
    def __init__(self, title):
 
123
        ErrorSummarySection.__init__(self, title)
 
124
        self.soft_timeouts = {}
 
125
        self.hard_timeouts = {}
 
126
        self.cached_timeouts_by_pageid = []
 
127
 
 
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
 
136
        else:
 
137
            raise AssertionError(
 
138
                "%s is %s, not a timeout" % (oops.oopsid, etype))
 
139
        timeouts.setdefault(pageid, 0)
 
140
        timeouts[pageid] += 1
 
141
 
 
142
    def renderHeadlineTXT(self, fp):
 
143
        return
 
144
 
 
145
    def renderHeadlineHTML(self, fp):
 
146
        fp.write(
 
147
            '<li><a href="#%s">%s</a></li>\n' % (self.section_id, self.title))
 
148
 
 
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))
 
154
        fp.write('\n\n')
 
155
 
 
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')
 
160
        fp.write('<tr>\n')
 
161
        fp.write('<th>Hard</th>\n')
 
162
        fp.write('<th>Soft</th>\n')
 
163
        fp.write('<th>Page ID</th>\n')
 
164
        fp.write('</tr>\n')
 
165
        for ht, st, pageid in self._calculateTimeOutCounts():
 
166
            fp.write('<tr>\n')
 
167
            fp.write('<td>%s</td>\n<td>%s</td>\n<td>%s</td>\n' %
 
168
                (ht, st, pageid))
 
169
            fp.write('</tr>\n')
 
170
        fp.write('</table>')
 
171
        fp.write('</div>')
 
172
 
 
173
    def _calculateTimeOutCounts(self):
 
174
        if self.cached_timeouts_by_pageid:
 
175
            return self.cached_timeouts_by_pageid[:self.max_count]
 
176
 
 
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]
 
187
 
 
188
 
 
189
class TopValueSection(ErrorSummarySection):
 
190
    """A base section for displaying top N sets of OOPSes."""
 
191
    max_count = 10
 
192
 
 
193
    def __init__(self, title):
 
194
        ErrorSummarySection.__init__(self, title)
 
195
        self.pageids = {}
 
196
 
 
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]):
 
202
                return
 
203
        self.pageids[pageid] = (value, oops.oopsid, pageid)
 
204
 
 
205
    def renderHeadlineTXT(self, fp):
 
206
        return
 
207
 
 
208
    def renderHeadlineHTML(self, fp):
 
209
        fp.write(
 
210
            '<li><a href="#%s">%s</a></li>\n' % (self.section_id, self.title))
 
211
 
 
212
    def formatValues(self, value, oopsid, pageid):
 
213
        formatted_value = self.formatValue(value)
 
214
        return '%s  %-14s  %s\n' % (formatted_value, oopsid, pageid)
 
215
 
 
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')
 
220
        fp.write('<tr>\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')
 
224
        fp.write('</tr>\n')
 
225
        for value, oopsid, pageid in self.getValues():
 
226
            fp.write('<tr>\n')
 
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))
 
231
            fp.write('</tr>\n')
 
232
        fp.write('</table>')
 
233
        fp.write('</div>')
 
234
 
 
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))
 
239
        fp.write('\n\n')
 
240
 
 
241
    def getValues(self):
 
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)
 
246
 
 
247
    def getValue(self, oops):
 
248
        """Return the value for which this OOPS will be evaluted.
 
249
 
 
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.
 
253
        """
 
254
        raise NotImplementedError
 
255
 
 
256
    def formatValue(self, value):
 
257
        """Format the value obtained for rendering.
 
258
 
 
259
        In normal situations the value should be formatted with 9
 
260
        spaces.
 
261
        """
 
262
        raise NotImplementedError
 
263
 
 
264
 
 
265
class StatementCountSection(TopValueSection):
 
266
    """The top statement counts section."""
 
267
    value_name = "Count"
 
268
 
 
269
    def getValue(self, oops):
 
270
        return oops.statements_count
 
271
 
 
272
    def formatValue(self, value):
 
273
        return '%9d' % value
 
274
 
 
275
 
 
276
class TopDurationSection(TopValueSection):
 
277
    """The top page IDs by duration."""
 
278
    value_name = "Duration"
 
279
 
 
280
    def getValue(self, oops):
 
281
        return oops.duration
 
282
 
 
283
    def formatValue(self, value):
 
284
        return '%9.2fs' % value
 
285