1#!/usr/bin/env python 2 3""" 4This program parse the output from pcap_compile() to visualize the CFG after 5each optimize phase. 6 7Usage guide: 81. Enable optimizier debugging code when configure libpcap, 9 and build libpcap & the test programs 10 ./configure --enable-optimizer-dbg 11 make 12 make testprogs 132. Run filtertest to compile BPF expression and produce the CFG as a 14 DOT graph, save to output a.txt 15 testprogs/filtertest -g EN10MB host 192.168.1.1 > a.txt 163. Send a.txt to this program's standard input 17 cat a.txt | testprogs/visopts.py 184. Step 2&3 can be merged: 19 testprogs/filtertest -g EN10MB host 192.168.1.1 | testprogs/visopts.py 205. The standard output is something like this: 21 generated files under directory: /tmp/visopts-W9ekBw 22 the directory will be removed when this programs finished. 23 open this link: http://localhost:39062/expr1.html 246. Using open link at the 3rd line `http://localhost:39062/expr1.html' 25 26Note: 271. The CFG is translated to SVG an document, expr1.html embeded them as external 28 document. If you open expr1.html as local file using file:// protocol, some 29 browsers will deny such requests so the web pages will not shown properly. 30 For chrome, you can run it using following command to avoid this: 31 chromium --disable-web-security 32 That's why this program start a localhost http server. 332. expr1.html use jquery from http://ajax.googleapis.com, so you need internet 34 access to show the web page. 35""" 36 37import sys, os 38import string 39import subprocess 40import json 41 42html_template = string.Template(""" 43<html> 44 <head> 45 <title>BPF compiler optimization phases for $expr </title> 46 <style type="text/css"> 47 .hc { 48 /* half width container */ 49 display: inline-block; 50 float: left; 51 width: 50%; 52 } 53 </style> 54 55 <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"/></script> 56 <!--script type="text/javascript" src="./jquery.min.js"/></script--> 57 <script type="text/javascript"> 58 var expr = '$expr'; 59 var exprid = 1; 60 var gcount = $gcount; 61 var logs = JSON.parse('$logs'); 62 logs[gcount] = ""; 63 64 var leftsvg = null; 65 var rightsvg = null; 66 67 function gurl(index) { 68 index += 1; 69 if (index < 10) 70 s = "00" + index; 71 else if (index < 100) 72 s = "0" + index; 73 else 74 s = "" + index; 75 return "./expr" + exprid + "_g" + s + ".svg" 76 } 77 78 function annotate_svgs() { 79 if (!leftsvg || !rightsvg) return; 80 81 $$.each([$$(leftsvg), $$(rightsvg)], function() { 82 $$(this).find("[id|='block'][opacity]").each(function() { 83 $$(this).removeAttr('opacity'); 84 }); 85 }); 86 87 $$(leftsvg).find("[id|='block']").each(function() { 88 var has = $$(rightsvg).find("#" + this.id).length != 0; 89 if (!has) $$(this).attr("opacity", "0.4"); 90 else { 91 $$(this).click(function() { 92 var target = $$(rightsvg).find("#" + this.id); 93 var offset = $$("#rightsvgc").offset().top + target.position().top; 94 window.scrollTo(0, offset); 95 target.focus(); 96 }); 97 } 98 }); 99 $$(rightsvg).find("[id|='block']").each(function() { 100 var has = $$(leftsvg).find("#" + this.id).length != 0; 101 if (!has) $$(this).attr("opacity", "0.4"); 102 else { 103 $$(this).click(function() { 104 var target = $$(leftsvg).find("#" + this.id); 105 var offset = $$("#leftsvgc").offset().top + target.position().top; 106 window.scrollTo(0, offset); 107 target.focus(); 108 }); 109 } 110 }); 111 } 112 113 function init_svgroot(svg) { 114 svg.setAttribute("width", "100%"); 115 svg.setAttribute("height", "100%"); 116 } 117 function wait_leftsvg() { 118 if (leftsvg) return; 119 var doc = document.getElementById("leftsvgc").getSVGDocument(); 120 if (doc == null) { 121 setTimeout(wait_leftsvg, 500); 122 return; 123 } 124 leftsvg = doc.documentElement; 125 //console.log(leftsvg); 126 // initialize it 127 init_svgroot(leftsvg); 128 annotate_svgs(); 129 } 130 function wait_rightsvg() { 131 if (rightsvg) return; 132 var doc = document.getElementById("rightsvgc").getSVGDocument(); 133 if (doc == null) { 134 setTimeout(wait_rightsvg, 500); 135 return; 136 } 137 rightsvg = doc.documentElement; 138 //console.log(rightsvg); 139 // initialize it 140 init_svgroot(rightsvg); 141 annotate_svgs(); 142 } 143 function load_left(index) { 144 var url = gurl(index); 145 var frag = "<embed id='leftsvgc' type='image/svg+xml' pluginspage='http://www.adobe.com/svg/viewer/install/' src='" + url + "'/>"; 146 $$("#lsvg").html(frag); 147 $$("#lcomment").html(logs[index]); 148 $$("#lsvglink").attr("href", url); 149 leftsvg = null; 150 wait_leftsvg(); 151 } 152 function load_right(index) { 153 var url = gurl(index); 154 var frag = "<embed id='rightsvgc' type='image/svg+xml' pluginspage='http://www.adobe.com/svg/viewer/install/' src='" + url + "'/>"; 155 $$("#rsvg").html(frag); 156 $$("#rcomment").html(logs[index]); 157 $$("#rsvglink").attr("href", url); 158 rightsvg = null; 159 wait_rightsvg(); 160 } 161 162 $$(document).ready(function() { 163 for (var i = 0; i < gcount; i++) { 164 var opt = "<option value='" + i + "'>loop" + i + " -- " + logs[i] + "</option>"; 165 $$("#lselect").append(opt); 166 $$("#rselect").append(opt); 167 } 168 var on_selected = function() { 169 var index = parseInt($$(this).children("option:selected").val()); 170 if (this.id == "lselect") 171 load_left(index); 172 else 173 load_right(index); 174 } 175 $$("#lselect").change(on_selected); 176 $$("#rselect").change(on_selected); 177 178 $$("#backward").click(function() { 179 var index = parseInt($$("#lselect option:selected").val()); 180 if (index <= 0) return; 181 $$("#lselect").val(index - 1).change(); 182 $$("#rselect").val(index).change(); 183 }); 184 $$("#forward").click(function() { 185 var index = parseInt($$("#rselect option:selected").val()); 186 if (index >= gcount - 1) return; 187 $$("#lselect").val(index).change(); 188 $$("#rselect").val(index + 1).change(); 189 }); 190 191 if (gcount >= 1) $$("#lselect").val(0).change(); 192 if (gcount >= 2) $$("#rselect").val(1).change(); 193 }); 194 </script> 195 </head> 196 <body style="width: 96%"> 197 <div> 198 <h1>$expr</h1> 199 <div style="text-align: center;"> 200 <button id="backward" type="button"><<</button> 201 202 <button id="forward" type="button">>></button> 203 </div> 204 </div> 205 <br/> 206 <div style="clear: both;"> 207 <div class="hc lc"> 208 <select id="lselect"></select> 209 <a id="lsvglink" target="_blank">open this svg in browser</a> 210 <p id="lcomment"></p> 211 </div> 212 <div class="hc rc"> 213 <select id="rselect"></select> 214 <a id="rsvglink" target="_blank">open this svg in browser</a> 215 <p id="rcomment"></p> 216 </div> 217 </div> 218 <br/> 219 <div style="clear: both;"> 220 <div id="lsvg" class="hc lc"></div> 221 <div id="rsvg" class="hc rc"></div> 222 </div> 223 </body> 224</html> 225""") 226 227def write_html(expr, gcount, logs): 228 logs = map(lambda s: s.strip().replace("\n", "<br/>"), logs) 229 230 global html_template 231 html = html_template.safe_substitute(expr=expr.encode("string-escape"), gcount=gcount, logs=json.dumps(logs).encode("string-escape")) 232 with file("expr1.html", "wt") as f: 233 f.write(html) 234 235def render_on_html(infile): 236 expr = None 237 gid = 1 238 log = "" 239 dot = "" 240 indot = 0 241 logs = [] 242 243 for line in infile: 244 if line.startswith("machine codes for filter:"): 245 expr = line[len("machine codes for filter:"):].strip() 246 break 247 elif line.startswith("digraph BPF {"): 248 indot = 1 249 dot = line 250 elif indot: 251 dot += line 252 if line.startswith("}"): 253 indot = 2 254 else: 255 log += line 256 257 if indot == 2: 258 p = subprocess.Popen(['dot', '-Tsvg'], stdin=subprocess.PIPE, stdout=subprocess.PIPE) 259 svg = p.communicate(dot)[0] 260 with file("expr1_g%03d.svg" % (gid), "wt") as f: 261 f.write(svg) 262 263 logs.append(log) 264 gid += 1 265 log = "" 266 dot = "" 267 indot = 0 268 269 if indot != 0: 270 #unterminated dot graph for expression 271 return False 272 if expr is None: 273 # BPF parser encounter error(s) 274 return False 275 write_html(expr, gid - 1, logs) 276 return True 277 278def run_httpd(): 279 import SimpleHTTPServer 280 import SocketServer 281 282 class MySocketServer(SocketServer.TCPServer): 283 allow_reuse_address = True 284 Handler = SimpleHTTPServer.SimpleHTTPRequestHandler 285 httpd = MySocketServer(("localhost", 0), Handler) 286 print "open this link: http://localhost:%d/expr1.html" % (httpd.server_address[1]) 287 try: 288 httpd.serve_forever() 289 except KeyboardInterrupt as e: 290 pass 291 292def main(): 293 import tempfile 294 import atexit 295 import shutil 296 os.chdir(tempfile.mkdtemp(prefix="visopts-")) 297 atexit.register(shutil.rmtree, os.getcwd()) 298 print "generated files under directory: %s" % os.getcwd() 299 print " the directory will be removed when this programs finished." 300 301 if not render_on_html(sys.stdin): 302 return 1 303 run_httpd() 304 return 0 305 306if __name__ == "__main__": 307 if '-h' in sys.argv or '--help' in sys.argv: 308 print __doc__ 309 exit(0) 310 exit(main()) 311