Index: trunk/udpprofile/web/config.py |
— | — | @@ -0,0 +1,7 @@ |
| 2 | +#!/usr/bin/python |
| 3 | + |
| 4 | +password="thepassword" |
| 5 | +host="127.0.0.1" |
| 6 | +port=3811 |
| 7 | +db="phase3" |
| 8 | + |
Index: trunk/udpprofile/web/admin.py |
— | — | @@ -0,0 +1,53 @@ |
| 2 | +#!/usr/bin/python |
| 3 | + |
| 4 | +import shelve |
| 5 | +import cgi |
| 6 | +import cgitb; cgitb.enable() |
| 7 | + |
| 8 | +import datetime |
| 9 | +import sys |
| 10 | +import config |
| 11 | + |
| 12 | +password=config.password |
| 13 | + |
| 14 | +print "Content-type: text/html\n" |
| 15 | + |
| 16 | +form=cgi.SvFormContentDict() |
| 17 | + |
| 18 | +if 'password' in form: |
| 19 | + if form['password'] != "" and form['password'] != password: |
| 20 | + print "access denied!!!!!!!!!1111oneoneeleven" |
| 21 | + sys.exit() |
| 22 | + else: |
| 23 | + authed=True |
| 24 | +else: |
| 25 | + authed=False |
| 26 | + |
| 27 | +print """ |
| 28 | + <script>function deletesample(sample) {form=document.forms['actions'];form['sample'].value=sample;form.submit();}</script> |
| 29 | + <form name='actions' method='POST' action='admin.py'> |
| 30 | + <input type='submit' name='action' value='take'> |
| 31 | + <input type='submit' name='action' value='clear'> |
| 32 | + <input type='hidden' name='sample' value=''> |
| 33 | + <input type='%s' name='password' value='%s' |
| 34 | + </form>""" % ((authed and 'hidden' or 'password'),(authed and password or '')) |
| 35 | + |
| 36 | +store = shelve.open('baselines') |
| 37 | + |
| 38 | +if 'action' not in form: |
| 39 | + if 'sample' in form: |
| 40 | + del store[form['sample']] |
| 41 | +elif form['action'] == 'clear' and authed: |
| 42 | + from socket import * |
| 43 | + sock = socket(AF_INET,SOCK_DGRAM) |
| 44 | + sock.sendto('-truncate',(config.host,config.port)) |
| 45 | +elif form['action'] == 'take' and authed: |
| 46 | + from extractprofile import SocketProfile |
| 47 | + store[str(datetime.datetime.now()).replace(" ","-")] = SocketProfile(config.host,config.port).extract() |
| 48 | +elif 'sample' in form and authed: |
| 49 | + del store[form['sample']] |
| 50 | + |
| 51 | +for entry in store.keys(): |
| 52 | + print """<div><a href='report.py?sample=%s'>%s</a> <a href='javascript:deletesample("%s")'>delete</a></div>""" % (entry,entry,entry) |
| 53 | + |
| 54 | +print "<div><a href='report.py'>current</a></div>" |
\ No newline at end of file |
Index: trunk/udpprofile/web/extractprofile.py |
— | — | @@ -7,12 +7,14 @@ |
8 | 8 | |
9 | 9 | import sys |
10 | 10 | import os |
| 11 | +import socket |
11 | 12 | |
12 | 13 | import xml.sax |
13 | 14 | from xml.sax import saxutils |
14 | 15 | from xml.sax import make_parser |
15 | 16 | from xml.sax.handler import feature_namespaces |
16 | 17 | |
| 18 | +# XML SAX parser class, puts stuff into some array! |
17 | 19 | class ExtractProfile(xml.sax.handler.ContentHandler): |
18 | 20 | def __init__(self): |
19 | 21 | self.parser=make_parser() |
— | — | @@ -60,6 +62,20 @@ |
61 | 63 | self.inContent=0 |
62 | 64 | self.parser.parse(file) |
63 | 65 | return self.profile |
| 66 | + |
| 67 | +class SocketProfile: |
| 68 | + def __init__(self,host='localhost',port=3811): |
| 69 | + self.sock=SocketSource() |
| 70 | + self.sock.connect((host,port)) |
| 71 | + |
| 72 | + def extract(self): |
| 73 | + return ExtractProfile().extract(self.sock) |
| 74 | + |
| 75 | +class SocketSource (socket.socket): |
| 76 | + """Stub class for extending socket object to support file source mechanics""" |
| 77 | + def read(self,what): |
| 78 | + """Alias recv to read, missing in socket.socket""" |
| 79 | + return self.recv(what,0) |
64 | 80 | |
65 | 81 | if __name__ == '__main__': |
66 | 82 | print "\nNot a valid entry point" |
Index: trunk/udpprofile/web/report.py |
— | — | @@ -2,25 +2,27 @@ |
3 | 3 | |
4 | 4 | # Configuration and defaults |
5 | 5 | |
6 | | -profilehost = "localhost" |
7 | | -profileport = 3811 |
| 6 | +import config |
8 | 7 | |
9 | | -db="enwiki" |
| 8 | +db=config.db |
| 9 | + |
10 | 10 | sort="real" |
11 | 11 | limit=50 |
12 | 12 | |
13 | 13 | |
14 | | -from extractprofile import ExtractProfile |
| 14 | +from extractprofile import SocketProfile |
15 | 15 | |
16 | 16 | import cgi |
17 | 17 | import cgitb; cgitb.enable() |
18 | 18 | |
19 | 19 | import socket |
| 20 | +import shelve |
20 | 21 | |
21 | 22 | print "Content-type: text/html"; |
22 | 23 | print "\n" |
23 | 24 | |
24 | 25 | form=cgi.SvFormContentDict() |
| 26 | +store = shelve.open('baselines') |
25 | 27 | |
26 | 28 | if "db" in form: |
27 | 29 | db=form["db"] |
— | — | @@ -30,24 +32,31 @@ |
31 | 33 | |
32 | 34 | if "limit" in form: limit=int(form["limit"]) |
33 | 35 | |
34 | | -compare="none" |
35 | | -if "compare" in form: compare=form["compare"] |
| 36 | +if "compare" in form: |
| 37 | + compare=form["compare"] |
| 38 | + compared=store[compare] |
| 39 | +else: |
| 40 | + compare="" |
| 41 | + compared=None |
36 | 42 | |
37 | | -class SocketSource (socket.socket): |
38 | | - def read(self,what): |
39 | | - return self.recv(what,0) |
40 | | -sock=SocketSource() |
41 | | -sock.connect((profilehost,profileport)) |
| 43 | +if 'sample' not in form: |
| 44 | + fullprofile=SocketProfile(config.host,config.port).extract() |
| 45 | + sample="" |
| 46 | +else: |
| 47 | + fullprofile=store[form['sample']] |
| 48 | + sample=form['sample'] |
42 | 49 | |
43 | | -cache={} |
44 | | -fullprofile=ExtractProfile().extract(sock) |
| 50 | + |
| 51 | + |
45 | 52 | events=fullprofile[db]["-"].items() |
46 | | -cache[db]=events |
47 | | -cache["_dbs"]=fullprofile.keys() |
48 | | -dbs=cache["_dbs"] |
49 | | - |
| 53 | +dbs=fullprofile.keys() |
50 | 54 | total=fullprofile[db]["-"]["-total"] |
51 | 55 | |
| 56 | +# Limit the scope |
| 57 | +if compare: |
| 58 | + compared=compared[db]["-"] |
| 59 | + oldtotal=compared["-total"] |
| 60 | + |
52 | 61 | #cache.close() |
53 | 62 | if sort=="name": |
54 | 63 | events.sort(lambda x,y: cmp(x[0],y[0])) |
— | — | @@ -55,54 +64,145 @@ |
56 | 65 | events.sort(lambda y,x: cmp(x[1][sort],y[1][sort])) |
57 | 66 | |
58 | 67 | def surl(stype,stext=None,limit=50): |
| 68 | + """ Simple URL formatter for headers """ |
59 | 69 | if(stext==None): stext=stype |
60 | | - if stype==sort: return """<td>%s</td>"""%stext |
61 | | - return """<td><a href='report.py?db=%s&sort=%s&limit=%d'>%s</a></td>"""%(db,stype,limit,stext) |
| 70 | + if stype==sort: return """<td><b>%s</b></td>"""%stext |
| 71 | + return """<td><a href='report.py?db=%s&sort=%s&limit=%d&sample=%s&compare=%s'>%s</a></td>"""%(db,stype,limit,sample,compare,stext) |
62 | 72 | |
63 | 73 | print """ |
64 | 74 | <style> |
65 | | -table { width: 100%; } |
| 75 | +table { width: 100%; font-size: 9pt; } |
66 | 76 | td { cell-padding: 1px; |
67 | 77 | text-align: right; |
68 | 78 | vertical-align: top; |
69 | 79 | argin: 2px; |
70 | 80 | border: 1px silver dotted; |
| 81 | + white-space: nowrap; |
71 | 82 | background-color: #eeeeee; |
72 | 83 | } |
73 | | -td.name { text-align: left; width: 100%;} |
| 84 | +td.name { text-align: left; width: 100%; white-space: normal;} |
74 | 85 | tr.head td { text-align: center; } |
75 | 86 | </style>""" |
76 | 87 | |
| 88 | +if sample: |
| 89 | + print "<div>Using old sample: %s, <a href='report.py?db=%s'>reset to current</a></div>" % (sample,db) |
| 90 | + |
| 91 | +# Top list of databases |
77 | 92 | for dbname in dbs: |
78 | 93 | if db == dbname: print " [%s] "%dbname |
79 | 94 | else: print " [<a href='report.py?db=%s'>%s</a>] "%(dbname,dbname) |
80 | 95 | |
81 | 96 | if limit==50: |
82 | | - print " [ showing %d events, <a href='report.py?db=%s&sort=%s&limit=5000'>show more</a> ] " % (limit,db,sort) |
| 97 | + print " [ showing %d events, <a href='report.py?db=%s&sort=%s&sample=%s&compare=%s&limit=5000'>show more</a> ] " % (limit,db,sort,sample,compare) |
83 | 98 | else: |
84 | | - print " [ showing %d events, <a href='report.py?db=%s&sort=%s&limit=50'>show less</a> ] " % (limit,db,sort) |
| 99 | + print " [ showing %d events, <a href='report.py?db=%s&sort=%s&sample=%s&compare=%s&limit=50'>show less</a> ] " % (limit,db,sort,sample,compare) |
85 | 100 | |
| 101 | +print " [ <a href='admin.py'>admin</a> ]</div>" |
86 | 102 | |
| 103 | +print """<form> |
| 104 | +<input type='hidden' name='db' value='%s'> |
| 105 | +<input type='hidden' name='sort' value='%s'> |
| 106 | +<input type='hidden' name='limit' value='%d'> |
| 107 | +<input type='hidden' name='sample' value='%s'> |
| 108 | +<select name='compare'><option></option>""" %(db,sort,limit,sample) |
| 109 | + |
| 110 | +samples=store.keys() |
| 111 | +samples.sort() |
| 112 | +for baseline in samples: |
| 113 | + print "<option%s>%s</option>" % ((compare==baseline and " SELECTED" or ""),baseline) |
| 114 | +print "</select><input type='submit' value='compare'></form>" |
| 115 | + |
| 116 | + |
| 117 | +# This is header! |
87 | 118 | print """ |
88 | 119 | <table> |
89 | 120 | <tr class="head">""" |
90 | 121 | print surl("name") |
91 | 122 | print surl("count") |
| 123 | +print "<td>count%</td>" |
| 124 | +print "<td>change</td>" |
92 | 125 | print surl("cpu","cpu%") |
| 126 | +print "<td>change</td>" |
93 | 127 | print surl("onecpu","cpu/c") |
| 128 | +print "<td>change</td>" |
94 | 129 | print surl("real","real%") |
| 130 | +print "<td>change</td>" |
95 | 131 | print surl("onereal","real/c") |
| 132 | +print "<td>change</td>" |
96 | 133 | print "</tr>" |
97 | 134 | |
98 | | -rowformat="""<tr class="data"><td class="name">%s</td><td>%d</td> |
99 | | - <td>%.2f</td><td>%.1f</td><td>%.2f</td><td>%.1f</td></tr>""" |
| 135 | +rowformat=""" |
| 136 | +<tr class="data"><td class="name">%s</td><td>%d</td> |
| 137 | +<td>%.2f</td><td>%.1f</td><td>%.2f</td><td>%.1f</td></tr>""" |
100 | 138 | |
| 139 | + |
| 140 | +# name |
| 141 | +# counts: total relative compared |
| 142 | +# cputime: total compared percall compared |
| 143 | +# realtime: total compared percall compared |
| 144 | + |
| 145 | +comparedformat=""" |
| 146 | +<tr class="data"><td class="name">%s</td> |
| 147 | +<td>%d</td><td>%.2f</td><td>%.1f</td> |
| 148 | +<td>%.2f</td><td>%.1f</td><td>%.1f</td><td>%.1f</td> |
| 149 | +<td>%.2f</td><td>%.1f</td><td>%.1f</td><td>%.1f</td> |
| 150 | +</tr> |
| 151 | +""" |
| 152 | + |
| 153 | +# This is really really hacky way of reporting percentages |
| 154 | + |
| 155 | +# And this is output of results. |
101 | 156 | for event in events: |
102 | | - if event[0]=="close": continue |
| 157 | + (name,event)=event |
| 158 | + if name=="close": continue |
| 159 | + if compared and name in compared: old=compared[name] |
| 160 | + else: old=None |
| 161 | + |
103 | 162 | limit-=1 |
104 | 163 | if limit<0: break |
105 | | - print rowformat % \ |
106 | | - (event[0].replace(",",", "),event[1]["count"],event[1]["cpu"]/total["cpu"]*100,event[1]["onecpu"]*1000,\ |
107 | | - event[1]["real"]/total["real"]*100,event[1]["onereal"]*1000) |
| 164 | + |
| 165 | + callcount=float(event["count"])/total["count"] |
| 166 | + cpupct=event["cpu"]/total["cpu"] |
| 167 | + onecpu=event["onecpu"] |
| 168 | + realpct=event["real"]/total["real"] |
| 169 | + onereal=event["onereal"] |
| 170 | + |
| 171 | + if old: |
| 172 | + oldcount=float(old["count"])/oldtotal["count"] |
| 173 | + countdiff = (callcount-oldcount)/oldcount |
| 174 | + |
| 175 | + oldcpupct = old["cpu"]/oldtotal["cpu"] |
| 176 | + cpupctdiff = (cpupct-oldcpupct)/oldcpupct |
| 177 | + |
| 178 | + onecpudiff = ( onecpu - old["onecpu"] ) / old["onecpu"] |
| 179 | + |
| 180 | + oldrealpct = old["real"]/oldtotal["real"] |
| 181 | + realpctdiff = (realpct-oldrealpct)/oldrealpct |
| 182 | + |
| 183 | + o=old["onereal"] |
| 184 | + onerealdiff = ( onereal - old["onereal"] ) / old["onereal"] |
| 185 | + |
| 186 | + else: |
| 187 | + countdiff=0 |
| 188 | + cpupctdiff=0 |
| 189 | + onecpudiff=0 |
| 190 | + realpctdiff=0 |
| 191 | + onerealdiff=0 |
| 192 | + |
| 193 | + dbg=0 |
| 194 | + |
| 195 | + if dbg and name=="wfMsgReal": |
| 196 | + print old |
| 197 | + print oldtotal |
| 198 | + print event |
| 199 | + print total |
| 200 | + if not dbg: print comparedformat % ( |
| 201 | + name.replace(",",", "), |
| 202 | + event["count"], callcount,countdiff*100, |
| 203 | + cpupct*100,cpupctdiff*100, |
| 204 | + onecpu*1000,onecpudiff*100, |
| 205 | + realpct*100,realpctdiff*100, |
| 206 | + onereal*1000,onerealdiff*100 |
| 207 | + ) |
108 | 208 | |
109 | 209 | print "</table>" |
Index: trunk/udpprofile/web/VERSION |
— | — | @@ -0,0 +1 @@ |
| 2 | +0.0.2 |