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

Proposed by Robert Collins
Status: Merged
Approved by: Robert Collins
Approved revision: 19
Merged at revision: 18
Proposed branch: lp:~lifeless/python-oops-tools/polish
Merge into: lp:python-oops-tools
Diff against target: 183 lines (+29/-20)
6 files modified
src/oopstools/NEWS.txt (+5/-0)
src/oopstools/oops/models.py (+10/-10)
src/oopstools/oops/templates/oops.html (+3/-1)
src/oopstools/oops/test/oops.txt (+1/-1)
src/oopstools/oops/test/pagetest.txt (+2/-1)
src/oopstools/oops/test/test_dboopsloader.py (+8/-7)
To merge this branch: bzr merge lp:~lifeless/python-oops-tools/polish
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) Approve
Review via email: mp+82365@code.launchpad.net

Commit message

Show backtraces in OOPS report timelines.

Description of the change

Update the oops-tools model to show the new backtrace column in OOPS report timelines.

This is a bit crude - I'm making a somewhat horrid table more horrid (by making it wider). It also makes simple queries have a big gap between them.

OTOH there is a bit of a steep curve to get a js library in here due to our preferred tech not being the default in Django. I think we'll have a net benefit from this change without nice JS hiding, and I'm going to investigate JS hiding separately - e.g. Talk to francis about ROI, preferred choice etc etc.

This change can be easily backed out (its about 5 minutes to go from intent to deployed on our oops-tools instance a the moment) if we decide its a problem, so I'd like to get it up there and see.

Most of the changes have test coverage (in that tests broke when the change was made) except the actual template change itself. I've added a smoke test there (that the heading exists, which implies something about the body of the table), but its not truely satisfactory. OTOH I don't know what would be really satisfactory, given we're dealing with a pagetest here.

Thanks for your consideration :)

To post a comment you must log in.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

Is there no HTML test that would covers this at all? I do have a few concerns:

 * Out of interest, any idea how costly it is to record, and later, render tracebacks for every event?

 * What about HTML-escaping? What if the traceback contained a line like:

        123, "</pre>", (a < b))

—wouldn't that upset the rendered HTML? Not likely to be a big problem for us, I suppose, but if we want this code to be reusable…

review: Needs Information
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

I keep forgetting that <pre> doesn't change tag parsing. Which means that the traceback and all other text items going out are almost certainly escaped just fine.

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

Thanks for the review. I started a thread about the overheads on launchpad-dev - I measured 0.2seconds for recording, stringifying, and shoving in a list, 1000 tracebacks in a 60 deep stack frame.For a well behaved page with < 100 callouts (SQL, memcache etc) thats an overhead of 20ms, which isn't free, but isn't huge either. For a great page doing 20-30 callouts, thats 4-7ms, totally tolerable. So bad pages may be noticable, on good pages will be fine. This app doesn't do the recording of course; thats done by the oops producer, which doesn't do template rendering - it just puts the capture strings into a bson document if an OOPS is recorded.

On the html/pre side, I'm not a django expert. I'll do a quick manual test to make sure that the django template engine does handle a malicious backtrace.

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

Django escapes the content in the {{}} so </pre> in a traceback becomes &lt;/pre&gt; etc. All is good.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/oopstools/NEWS.txt'
2--- src/oopstools/NEWS.txt 2011-11-16 02:22:14 +0000
3+++ src/oopstools/NEWS.txt 2011-11-16 08:16:24 +0000
4@@ -22,6 +22,11 @@
5 a bit better.
6 (Robert Collins, William Grant, Roman Yepishev, #885416, #884265)
7
8+* Report timelines now include a backtrace for each event, making it easier to
9+ determine the origin of the event (e.g. why something is looked up).
10+ Timelines without this information will show 'unknown' in the relevant
11+ column on the OOPS page. (Robert Collins, #890976)
12+
13 * The req_vars variable in OOPS reports may now be a dict.
14 (Robert Collins, #888866)
15
16
17=== modified file 'src/oopstools/oops/models.py'
18--- src/oopstools/oops/models.py 2011-11-16 07:25:56 +0000
19+++ src/oopstools/oops/models.py 2011-11-16 08:16:24 +0000
20@@ -350,11 +350,11 @@
21 # Sanity check/coerce the statements into what we expect.
22 # Must be a list:
23 if not isinstance(statements, list):
24- statements = [(0, 0, 'badtimeline', str(statements))]
25+ statements = [(0, 0, 'badtimeline', str(statements), 'unknown')]
26 else:
27- filler = (0, 0, 'unknown', 'unknown')
28+ filler = (0, 0, 'unknown', 'unknown', 'unknown')
29 for row, action in enumerate(statements):
30- statements[row] = tuple(action[:4]) + filler[len(action):]
31+ statements[row] = tuple(action[:5]) + filler[len(action):]
32 duration = oops.get('duration')
33 if duration is not None:
34 total_time = int(duration * 1000)
35@@ -388,7 +388,7 @@
36 most_expensive_statement = None
37 else:
38 costs = {}
39- for (start, end, db_id, statement) in statements:
40+ for (start, end, db_id, statement, tb) in statements:
41 statement = replace_variables(statement)
42 cost = end - start
43 if statement in costs:
44@@ -566,8 +566,8 @@
45 #XXX need docstring and test
46 if not self.statements:
47 return []
48- return [(count+1, start, stop, db_id, stop - start, stmt) for
49- (count, (start, stop, db_id, stmt)) in
50+ return [(count+1, start, stop, db_id, stop - start, stmt, tb) for
51+ (count, (start, stop, db_id, stmt, tb)) in
52 enumerate(self.statements)]
53
54 def repeated_statements(self):
55@@ -623,7 +623,7 @@
56 if not self.statements:
57 return 0
58 accumulated_time = 0
59- for (start, stop, db_id, stmt) in self.statements:
60+ for (start, stop, db_id, stmt, trace) in self.statements:
61 length = stop - start
62 accumulated_time += length
63 return accumulated_time
64@@ -636,13 +636,13 @@
65 def _group_queries(self, unsorted_statements):
66 """Group SQL log queries.
67
68- unsorted_statements is a list of (start, stop, db_id, statement).
69+ unsorted_statements is a list of (start, stop, db_id, statement, tb).
70 Return a list of (total_time_ms, count, mean_length, saving, db_id,
71 statement).
72 """
73 sorted_statements = sorted(
74 unsorted_statements,
75- key=lambda (start, stop, db_id, statement): statement
76+ key=lambda row: row[3]
77 )
78 groups = []
79 def append_to_group(gtime, gcount, db_id, statement):
80@@ -653,7 +653,7 @@
81
82 group_statement = None
83 for row in sorted_statements:
84- start, stop, db_id, statement = row
85+ start, stop, db_id, statement, tb = row
86 statement = replace_variables(statement)
87 length = stop - start
88 if statement != group_statement:
89
90=== modified file 'src/oopstools/oops/templates/oops.html'
91--- src/oopstools/oops/templates/oops.html 2011-10-13 20:18:51 +0000
92+++ src/oopstools/oops/templates/oops.html 2011-11-16 08:16:24 +0000
93@@ -168,16 +168,18 @@
94 <th>Length</th>
95 <th>Database id</th>
96 <th>Statement</th>
97+ <th>Traceback</th>
98 </tr>
99 </thead>
100 <tbody>
101- {% for num, start, stop, db_id, length, statement in oops.formatted_statements %}
102+ {% for num, start, stop, db_id, length, statement, tb in oops.formatted_statements %}
103 <tr>
104 <td>{{num}}.</td>
105 <td>{{start}}</td>
106 <td>{{length}}ms</td>
107 <td>{{db_id}}</td>
108 <td><pre>{{statement|format_sql}}</pre></td>
109+ <td><pre>{{tb}}</pre></td>
110 </tr>
111 {% endfor %}
112 </tbody>
113
114=== modified file 'src/oopstools/oops/test/oops.txt'
115--- src/oopstools/oops/test/oops.txt 2011-11-02 21:29:36 +0000
116+++ src/oopstools/oops/test/oops.txt 2011-11-16 08:16:24 +0000
117@@ -263,7 +263,7 @@
118 recorded together with the SQL statement.
119
120 >>> oops_with_db_id = Oops.objects.get(oopsid__exact="OOPS-1308x1")
121- >>> for start, stop, db_id, statement in oops_with_db_id.statements:
122+ >>> for start, stop, db_id, statement, tb in oops_with_db_id.statements:
123 ... print start, stop, db_id, statement
124 4 5 session UPDATE SessionData SET last_accessed ...
125 6 7 session SELECT key, pickle FROM SessionPkgData WHERE ...
126
127=== modified file 'src/oopstools/oops/test/pagetest.txt'
128--- src/oopstools/oops/test/pagetest.txt 2011-11-02 23:26:40 +0000
129+++ src/oopstools/oops/test/pagetest.txt 2011-11-16 08:16:24 +0000
130@@ -38,7 +38,8 @@
131 ...<div id="request_variables">...
132 ...
133 ...<li>CHANNEL_CREATION_TIME: 1...</li>...
134- ...
135+ ...SQL Statement Log...
136+ ...Start...Length...Database id...Statement...Traceback...
137
138 It also accepts the OOPS id without the "OOPS-" part.
139
140
141=== modified file 'src/oopstools/oops/test/test_dboopsloader.py'
142--- src/oopstools/oops/test/test_dboopsloader.py 2011-11-16 02:22:14 +0000
143+++ src/oopstools/oops/test/test_dboopsloader.py 2011-11-16 08:16:24 +0000
144@@ -196,7 +196,8 @@
145 'timeline': 'a'
146 }
147 oops = parsed_oops_to_model_oops(report, 'bug-890001-1')
148- self.assertEqual([(0, 0, 'badtimeline', 'a')], oops.statements)
149+ self.assertEqual(
150+ [(0, 0, 'badtimeline', 'a', 'unknown')], oops.statements)
151
152 def test_short_timeline_row(self):
153 # A timeline row that is short is padded with 'unknown' or 0's.
154@@ -211,10 +212,10 @@
155 }
156 oops = parsed_oops_to_model_oops(report, 'bug-890001-2')
157 self.assertEqual([
158- (0, 0, 'unknown', 'unknown'),
159- (1, 0, 'unknown', 'unknown'),
160- (1, 2, 'unknown', 'unknown'),
161- (1, 2, 'foo', 'unknown'),
162+ (0, 0, 'unknown', 'unknown', 'unknown'),
163+ (1, 0, 'unknown', 'unknown', 'unknown'),
164+ (1, 2, 'unknown', 'unknown', 'unknown'),
165+ (1, 2, 'foo', 'unknown', 'unknown'),
166 ], oops.statements)
167
168 def test_long_timeline_row(self):
169@@ -222,12 +223,12 @@
170 report = {
171 'id': 'bug-890001-3',
172 'timeline': [
173- (1, 2, 'foo', 'bar', 'baz'),
174+ (1, 2, 'foo', 'bar', 'baz', 'quux'),
175 ],
176 }
177 oops = parsed_oops_to_model_oops(report, 'bug-890001-3')
178 self.assertEqual([
179- (1, 2, 'foo', 'bar'),
180+ (1, 2, 'foo', 'bar', 'baz'),
181 ], oops.statements)
182
183 def test_empty_report_long_id_uses_unknown_bug_889982(self):

Subscribers

People subscribed via source and target branches

to all changes: