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