Contact: fumanchu@aminus.org

Log in as guest/misc to create tickets

root/pyconqwww.py

Revision 140 (checked in by fumanchu, 3 years ago)

Better tracebacks for pyconquer.

Line 
1 """CherryPy browser for PyConquer logs.
2
3 Use PyConquer to gether logs, then use the serve() function to browse the
4 results in a web browser. If you run this module from the command line,
5 it will call serve() for you.
6 """
7
8 import cgi
9 import os
10 import re
11 import sys
12
13 try:
14     import cStringIO as StringIO
15 except ImportError:
16     import StringIO
17
18 import urllib
19
20 import cherrypy
21
22
23 TEMPLATE_INDEX = """<html>
24 <head><title>PyConquer Logs</title></head>
25 <frameset cols='250, 1*'>
26     <frame src='menu' />
27     <frame name='main' src='' />
28 </frameset>
29 </html>
30 """
31
32 TEMPLATE_MENU = """<html>
33 <head>
34     <title>PyConquer Menu</title>
35     <style>
36         body {font: 9pt Arial, serif;}
37     </style>
38 </head>
39 <body>
40     <h2>PyConquer Logs</h2>
41     <p>Click on one of the runs below to see pyconquer data.</p>
42     %s
43 </body>
44 </html>
45 """
46
47 TEMPLATE_REPORT = r"""<html>
48 <head>
49     <title>%(filename)s</title>
50     <style>
51         h2 { margin-bottom: .25em; }
52         div {
53             font: 10pt Courier, monotype;
54             margin: 0 0 0 2em;
55             background-color: #CCCCCC;
56             }
57         div.calltime { float: right; }
58         a { text-decoration: none; }
59     </style>
60
61 <script type='text/javascript'>
62 <!--
63
64 function http() {
65     var h;
66     if (typeof(XMLHttpRequest) != "undefined") {
67         h = new XMLHttpRequest();
68     } else {
69         try { h = new ActiveXObject("Msxml2.XMLHTTP"); }
70         catch (e) {
71             try { h = new ActiveXObject("Microsoft.XMLHTTP"); }
72             catch (E) { alert("Your browser is not supported."); }
73         }
74     }
75     return h
76 }
77
78 function http_action(callback) {
79     var h = http();
80     h.onreadystatechange = function() {
81         if (h.readyState == 4) {
82             if (h.status != 200 && h.status != 204
83                 // Internet Explorer may return 1223 for 204
84                 && h.status != 1223) {
85                 alert("Error. Status = " + h.status + "\n" + h.responseText);
86             } else {
87                 var result = "";
88                 var ct = h.getResponseHeader("Content-Type");
89                 if (ct.split(";")[0] == "text/xml") {
90                     try {
91                         result = h.responseXML;
92                         if (xmldoc == null) throw "No XML in response";
93                     } catch (e) {
94                         result = h.responseText;
95                         alert(h.responseText);
96                         return;
97                     }
98                 } else {
99                     result = h.responseText;
100                 }
101                 if (callback != undefined) callback(result);
102             }
103         }
104     }
105     return h;
106 }
107
108 function show(elem, style) {
109     if (style == undefined) style = 'inline';
110     if (navigator.appName == "Microsoft Internet Explorer") {
111         elem.style.display = style;
112     } else {
113         if (elem.tagName && (elem.tagName.toLowerCase() == 'tr')) {
114             elem.style.visibility = 'visible';
115         } else {
116             elem.style.display = style;
117         }
118     }
119 }
120
121 function hide(elem) {
122     if (navigator.appName == "Microsoft Internet Explorer") {
123         elem.style.display = 'none';
124     } else {
125         if (elem.tagName && (elem.tagName.toLowerCase() == 'tr')) {
126             elem.style.visibility = 'collapse';
127         } else {
128             elem.style.display = 'none';
129         }
130     }
131 }
132
133 function toggle_branch(link) {
134     var contents = link.parentNode.nextSibling;
135     // Firefox has an extra #text node in-between the two DIV nodes
136     if (contents.tagName == undefined) contents = contents.nextSibling;
137     if (contents.innerHTML == "") {
138         function insert_branch(xmldoc) {
139             contents.innerHTML = xmldoc;
140         }
141         var h = http_action(insert_branch);
142         h.open("GET", "callblock?filename=%(filename)s&pos=" + link.id, true);
143         h.send(null);
144     } else {
145         if (contents.style.display == 'none') {
146             show(contents, 'block');
147         } else {
148             hide(contents);
149         }
150     }
151 }
152
153 // -->
154 </script>
155 </head>
156 <body>
157     <h2>%(filename)s</h2>
158     <div>
159 %(content)s
160     </div>
161 </body>
162 </html>
163 """
164
165 TEMPLATE_LINE = """<div class="%s">
166     <div class="calltime">%s</div>
167     %s %s (%s:%s)
168 </div>
169 """
170
171 TEMPLATE_CALL = """<div class="%s">
172     <div class="calltime">%s</div>
173     %s <a href='javascript:void(false)' id='%s' onclick='toggle_branch(this)'>%s</a> (%s:%s)
174 </div>
175 <div class="call_contents"></div>
176 """
177
178
179 class Event(object):
180    
181     def __init__(self, line, pos):
182         self.pos = pos
183         self.result = None
184         self.time = None
185        
186         m = re.match(r"(?P<indent>-+)(?P<msgtype>.) import "
187                      r"(?P<filename>.*)( \(\d lines\))?", line)
188         if m is not None:
189             indent, msgtype, module, _ = m.groups()
190             self.func = "import"
191             self.lineno = 0
192         else:
193             m = re.match(r"(?P<indent>-+)(?P<msgtype>.) (?P<func>.*)"
194                          r" \((?P<module>.*):(?P<lineno>\d+)\)(?P<result>.*)", line)
195             indent, msgtype, func, module, lineno, result = m.groups()
196             if result.endswith("ms"):
197                 result, time = result.rsplit(" ", 1)
198                 self.time = float(time[:-2])
199             self.func = func
200             self.lineno = lineno
201             self.result = result or None
202        
203         self.indent = len(indent)
204         self.msgtype = msgtype
205         self.module = module
206
207
208 class LogBrowser(object):
209    
210     def __init__(self, path):
211         self.path = os.path.abspath(path)
212    
213     def index(self):
214         return TEMPLATE_INDEX
215     index.exposed = True
216    
217     def menu(self):
218         runs = [f for f in os.listdir(self.path)
219                 if f.startswith("pyconquer") and f.endswith(".log")]
220         runs.sort()
221         return TEMPLATE_MENU % '\n'.join([
222             "<a href='report?filename=%s' target='main'>%s</a><br />" % (i, i)
223             for i in runs])
224     menu.exposed = True
225    
226     def call_content(self, filename, pos):
227         """Return all lines one indent depth inside the given call.
228         
229         pos: the 0-based index for the line where the call block begins
230             (i.e. the line of the call itself).
231         """
232         pos = int(pos)
233         lines = open(filename, 'rb').readlines()[pos:]
234         if not lines:
235             return []
236        
237         content = []
238         call_line = lines.pop(0).strip()
239         if call_line.startswith('--'):
240             indent0 = Event(call_line, 0).indent
241         else:
242             indent0 = 0
243        
244         for line in lines:
245             pos += 1
246             line = line.strip()
247             if line.startswith('--'):
248                 e = Event(line, pos)
249                 if e.indent <= indent0:
250                     break
251                 else:
252                     content.append(e)
253        
254         if not content:
255             return []
256        
257         min_depth = min([event.indent for event in content])
258         return [event for event in content if event.indent == min_depth]
259    
260     def callblock(self, filename, pos):
261         fullpath = os.path.abspath(os.path.join(self.path, filename))
262         if not fullpath.startswith(self.path):
263             raise cherrypy.HTTPError(403)
264        
265         results = []
266         for e in self.call_content(fullpath, pos):
267             typename = {'>': 'call', '<': 'return',
268                        '[': 'ccall', ']': 'creturn',
269                        'E': 'exception', 'e': 'cexception',
270                        '.': 'line', '=': 'watch',
271                        'X': 'threadexit', '*': 'threadstart'}[e.msgtype]
272            
273             if e.time is None:
274                 time = ''
275             else:
276                 time = "%0.3f" % e.time
277             if typename in ('exception', 'cexception', 'call', 'ccall'):
278                 results.append(TEMPLATE_CALL %
279                                (typename, time, e.msgtype, e.pos,
280                                 e.func, e.module, e.lineno))
281             else:
282                 results.append(TEMPLATE_LINE %
283                                (typename, time, e.msgtype,
284                                 e.func, e.module, e.lineno))
285        
286         return ''.join(results)
287     callblock.exposed = True
288    
289     def report(self, filename):
290         return TEMPLATE_REPORT % {'filename': filename,
291                                   'content': self.callblock(filename, 0)}
292     report.exposed = True
293
294
295 def serve(port=8080, path=os.getcwd()):
296     import cherrypy
297     cherrypy.config.update({'server.socket_port': int(port),
298                             'server.thread_pool': 10,
299                             })
300     cherrypy.quickstart(LogBrowser(path))
301
302 if __name__ == "__main__":
303     serve(*tuple(sys.argv[1:]))
304
Note: See TracBrowser for help on using the browser.