• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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">&lt;&lt;</button>
201          &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
202        <button id="forward" type="button">&gt;&gt;</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