aboutsummaryrefslogtreecommitdiff
path: root/contrib/libpcap/testprogs/visopts.py
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/libpcap/testprogs/visopts.py')
-rwxr-xr-xcontrib/libpcap/testprogs/visopts.py317
1 files changed, 317 insertions, 0 deletions
diff --git a/contrib/libpcap/testprogs/visopts.py b/contrib/libpcap/testprogs/visopts.py
new file mode 100755
index 000000000000..97eafffff4f0
--- /dev/null
+++ b/contrib/libpcap/testprogs/visopts.py
@@ -0,0 +1,317 @@
+#!/usr/bin/env python
+
+"""
+This program parses the output from pcap_compile() to visualize the CFG after
+each optimize phase.
+
+Usage guide:
+1. Enable optimizer debugging code when configure libpcap,
+ and build libpcap & the test programs
+ ./configure --enable-optimizer-dbg
+ make
+ make testprogs
+2. Run filtertest to compile BPF expression and produce the CFG as a
+ DOT graph, save to output a.txt
+ testprogs/filtertest -g EN10MB host 192.168.1.1 > a.txt
+3. Send a.txt to this program's standard input
+ cat a.txt | testprogs/visopts.py
+ (Graphviz must be installed)
+4. Step 2&3 can be merged:
+ testprogs/filtertest -g EN10MB host 192.168.1.1 | testprogs/visopts.py
+5. The standard output is something like this:
+ generated files under directory: /tmp/visopts-W9ekBw
+ the directory will be removed when this programs finished.
+ open this link: http://localhost:39062/expr1.html
+6. Open the URL at the 3rd line in a browser.
+
+Note:
+1. The CFG is translated to SVG images, expr1.html embeds them as external
+ documents. If you open expr1.html as local file using file:// protocol, some
+ browsers will deny such requests so the web page will not work properly.
+ For Chrome, you can run it using the following command to avoid this:
+ chromium --disable-web-security
+ That's why this program starts a localhost HTTP server.
+2. expr1.html uses jQuery from https://ajax.googleapis.com, so it needs Internet
+ access to work.
+"""
+
+import sys, os
+import string
+import subprocess
+import json
+
+html_template = string.Template("""
+<html>
+ <head>
+ <title>BPF compiler optimization phases for $expr </title>
+ <style type="text/css">
+ .hc {
+ /* half width container */
+ display: inline-block;
+ float: left;
+ width: 50%;
+ }
+ </style>
+
+ <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"/></script>
+ <!--script type="text/javascript" src="./jquery.min.js"/></script-->
+ <script type="text/javascript">
+ var expr = '$expr';
+ var exprid = 1;
+ var gcount = $gcount;
+ var logs = JSON.parse('$logs');
+ logs[gcount] = "";
+
+ var leftsvg = null;
+ var rightsvg = null;
+
+ function gurl(index) {
+ index += 1;
+ if (index < 10)
+ s = "00" + index;
+ else if (index < 100)
+ s = "0" + index;
+ else
+ s = "" + index;
+ return "./expr" + exprid + "_g" + s + ".svg"
+ }
+
+ function annotate_svgs() {
+ if (!leftsvg || !rightsvg) return;
+
+ $$.each([$$(leftsvg), $$(rightsvg)], function() {
+ $$(this).find("[id|='block'][opacity]").each(function() {
+ $$(this).removeAttr('opacity');
+ });
+ });
+
+ $$(leftsvg).find("[id|='block']").each(function() {
+ var has = $$(rightsvg).find("#" + this.id).length != 0;
+ if (!has) $$(this).attr("opacity", "0.4");
+ else {
+ $$(this).click(function() {
+ var target = $$(rightsvg).find("#" + this.id);
+ var offset = $$("#rightsvgc").offset().top + target.position().top;
+ window.scrollTo(0, offset);
+ target.focus();
+ });
+ }
+ });
+ $$(rightsvg).find("[id|='block']").each(function() {
+ var has = $$(leftsvg).find("#" + this.id).length != 0;
+ if (!has) $$(this).attr("opacity", "0.4");
+ else {
+ $$(this).click(function() {
+ var target = $$(leftsvg).find("#" + this.id);
+ var offset = $$("#leftsvgc").offset().top + target.position().top;
+ window.scrollTo(0, offset);
+ target.focus();
+ });
+ }
+ });
+ }
+
+ function init_svgroot(svg) {
+ svg.setAttribute("width", "100%");
+ svg.setAttribute("height", "100%");
+ }
+ function wait_leftsvg() {
+ if (leftsvg) return;
+ var doc = document.getElementById("leftsvgc").getSVGDocument();
+ if (doc == null) {
+ setTimeout(wait_leftsvg, 500);
+ return;
+ }
+ leftsvg = doc.documentElement;
+ //console.log(leftsvg);
+ // initialize it
+ init_svgroot(leftsvg);
+ annotate_svgs();
+ }
+ function wait_rightsvg() {
+ if (rightsvg) return;
+ var doc = document.getElementById("rightsvgc").getSVGDocument();
+ if (doc == null) {
+ setTimeout(wait_rightsvg, 500);
+ return;
+ }
+ rightsvg = doc.documentElement;
+ //console.log(rightsvg);
+ // initialize it
+ init_svgroot(rightsvg);
+ annotate_svgs();
+ }
+ function load_left(index) {
+ var url = gurl(index);
+ var frag = "<embed id='leftsvgc' type='image/svg+xml' pluginspage='https://www.adobe.com/svg/viewer/install/' src='" + url + "'/>";
+ $$("#lsvg").html(frag);
+ $$("#lcomment").html(logs[index]);
+ $$("#lsvglink").attr("href", url);
+ leftsvg = null;
+ wait_leftsvg();
+ }
+ function load_right(index) {
+ var url = gurl(index);
+ var frag = "<embed id='rightsvgc' type='image/svg+xml' pluginspage='https://www.adobe.com/svg/viewer/install/' src='" + url + "'/>";
+ $$("#rsvg").html(frag);
+ $$("#rcomment").html(logs[index]);
+ $$("#rsvglink").attr("href", url);
+ rightsvg = null;
+ wait_rightsvg();
+ }
+
+ $$(document).ready(function() {
+ for (var i = 0; i < gcount; i++) {
+ var opt = "<option value='" + i + "'>loop" + i + " -- " + logs[i] + "</option>";
+ $$("#lselect").append(opt);
+ $$("#rselect").append(opt);
+ }
+ var on_selected = function() {
+ var index = parseInt($$(this).children("option:selected").val());
+ if (this.id == "lselect")
+ load_left(index);
+ else
+ load_right(index);
+ }
+ $$("#lselect").change(on_selected);
+ $$("#rselect").change(on_selected);
+
+ $$("#backward").click(function() {
+ var index = parseInt($$("#lselect option:selected").val());
+ if (index <= 0) return;
+ $$("#lselect").val(index - 1).change();
+ $$("#rselect").val(index).change();
+ });
+ $$("#forward").click(function() {
+ var index = parseInt($$("#rselect option:selected").val());
+ if (index >= gcount - 1) return;
+ $$("#lselect").val(index).change();
+ $$("#rselect").val(index + 1).change();
+ });
+
+ if (gcount >= 1) $$("#lselect").val(0).change();
+ if (gcount >= 2) $$("#rselect").val(1).change();
+ });
+ </script>
+ </head>
+ <body style="width: 96%">
+ <div>
+ <h1>$expr</h1>
+ <div style="text-align: center;">
+ <button id="backward" type="button">&lt;&lt;</button>
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+ <button id="forward" type="button">&gt;&gt;</button>
+ </div>
+ </div>
+ <br/>
+ <div style="clear: both;">
+ <div class="hc lc">
+ <select id="lselect"></select>
+ <a id="lsvglink" target="_blank">open this svg in browser</a>
+ <p id="lcomment"></p>
+ </div>
+ <div class="hc rc">
+ <select id="rselect"></select>
+ <a id="rsvglink" target="_blank">open this svg in browser</a>
+ <p id="rcomment"></p>
+ </div>
+ </div>
+ <br/>
+ <div style="clear: both;">
+ <div id="lsvg" class="hc lc"></div>
+ <div id="rsvg" class="hc rc"></div>
+ </div>
+ </body>
+</html>
+""")
+
+def write_html(expr, gcount, logs):
+ logs = map(lambda s: s.strip().replace("\n", "<br/>"), logs)
+
+ global html_template
+ html = html_template.safe_substitute(expr=expr.encode("string-escape"), gcount=gcount, logs=json.dumps(logs).encode("string-escape"))
+ with file("expr1.html", "wt") as f:
+ f.write(html)
+
+def render_on_html(infile):
+ expr = None
+ gid = 1
+ log = ""
+ dot = ""
+ indot = 0
+ logs = []
+
+ for line in infile:
+ if line.startswith("machine codes for filter:"):
+ expr = line[len("machine codes for filter:"):].strip()
+ break
+ elif line.startswith("digraph BPF {"):
+ indot = 1
+ dot = line
+ elif indot:
+ dot += line
+ if line.startswith("}"):
+ indot = 2
+ else:
+ log += line
+
+ if indot == 2:
+ try:
+ p = subprocess.Popen(['dot', '-Tsvg'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+ except OSError as ose:
+ print "Failed to run 'dot':", ose
+ print "(Is Graphviz installed?)"
+ exit(1)
+
+ svg = p.communicate(dot)[0]
+ with file("expr1_g%03d.svg" % (gid), "wt") as f:
+ f.write(svg)
+
+ logs.append(log)
+ gid += 1
+ log = ""
+ dot = ""
+ indot = 0
+
+ if indot != 0:
+ #unterminated dot graph for expression
+ return False
+ if expr is None:
+ # BPF parser encounter error(s)
+ return False
+ write_html(expr, gid - 1, logs)
+ return True
+
+def run_httpd():
+ import SimpleHTTPServer
+ import SocketServer
+
+ class MySocketServer(SocketServer.TCPServer):
+ allow_reuse_address = True
+ Handler = SimpleHTTPServer.SimpleHTTPRequestHandler
+ httpd = MySocketServer(("localhost", 0), Handler)
+ print "open this link: http://localhost:%d/expr1.html" % (httpd.server_address[1])
+ try:
+ httpd.serve_forever()
+ except KeyboardInterrupt as e:
+ pass
+
+def main():
+ import tempfile
+ import atexit
+ import shutil
+ os.chdir(tempfile.mkdtemp(prefix="visopts-"))
+ atexit.register(shutil.rmtree, os.getcwd())
+ print "generated files under directory: %s" % os.getcwd()
+ print " the directory will be removed when this program has finished."
+
+ if not render_on_html(sys.stdin):
+ return 1
+ run_httpd()
+ return 0
+
+if __name__ == "__main__":
+ if '-h' in sys.argv or '--help' in sys.argv:
+ print __doc__
+ exit(0)
+ exit(main())