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