1#!/usr/bin/perl -w
2#
3# handlerviz.pl
4# ~~~~~~~~~~~~~
5#
6# A visualisation tool for post-processing the debug output generated by
7# Asio-based programs. Programs write this output to the standard error stream
8# when compiled with the define `BOOST_ASIO_ENABLE_HANDLER_TRACKING'.
9#
10# This tool generates output intended for use with the GraphViz tool `dot'. For
11# example, to convert output to a PNG image, use:
12#
13#   perl handlerviz.pl < output.txt | dot -Tpng > output.png
14#
15# To convert to a PDF file, use:
16#
17#   perl handlerviz.pl < output.txt | dot -Tpdf > output.pdf
18#
19# Copyright (c) 2003-2020 Christopher M. Kohlhoff (chris at kohlhoff dot com)
20#
21# Distributed under the Boost Software License, Version 1.0. (See accompanying
22# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
23#
24
25use strict;
26
27my %nodes = ();
28my @edges = ();
29my %locations = ();
30my %anon_nodes = ();
31my $anon_id = 0;
32my %all_nodes = ();
33my %pending_nodes = ();
34
35#-------------------------------------------------------------------------------
36# Parse the debugging output and populate the nodes and edges.
37
38sub parse_debug_output()
39{
40  while (my $line = <>)
41  {
42    chomp($line);
43
44    if ($line =~ /\@asio\|([^|]*)\|([^|]*)\|(.*)$/)
45    {
46      my $timestamp = $1;
47      my $action = $2;
48      my $description = $3;
49
50      # Handler creation.
51      if ($action =~ /^([0-9]+)\*([0-9]+)$/)
52      {
53        my $begin = $1;
54        my $end = $2;
55        my $label = $description;
56        $label =~ s/\./\\n/g;
57
58        if ($begin eq "0")
59        {
60          $begin = "a" . $anon_id++;
61          $anon_nodes{$begin} = $timestamp;
62          $all_nodes{"$timestamp-$begin"} = $begin;
63        }
64
65        my %edge = ( begin=>$begin, end=>$end, label=>$label );
66        push(@edges, \%edge);
67
68        $pending_nodes{$end} = 1;
69      }
70
71      # Handler location.
72      elsif ($action =~ /^([0-9]+)\^([0-9]+)$/)
73      {
74        if ($1 ne "0")
75        {
76          if (not exists($locations{($1,$2)}))
77          {
78            $locations{($1,$2)} = ();
79          }
80          push(@{$locations{($1,$2)}}, $description);
81        }
82      }
83
84      # Begin handler invocation.
85      elsif ($action =~ /^>([0-9]+)$/)
86      {
87        my %new_node = ( label=>$description, entry=>$timestamp );
88        $new_node{content} = ();
89        $nodes{$1} = \%new_node;
90        $all_nodes{"$timestamp-$1"} = $1;
91        delete($pending_nodes{$1});
92      }
93
94      # End handler invocation.
95      elsif ($action =~ /^<([0-9]+)$/)
96      {
97        $nodes{$1}->{exit} = $timestamp;
98      }
99
100      # Handler threw exception.
101      elsif ($action =~ /^!([0-9]+)$/)
102      {
103        push(@{$nodes{$1}->{content}}, "exception");
104      }
105
106      # Handler was destroyed without being invoked.
107      elsif ($action =~ /^~([0-9]+)$/)
108      {
109        my %new_node = ( label=>"$timestamp destroyed" );
110        $new_node{content} = ();
111        $nodes{$1} = \%new_node;
112        $all_nodes{"$timestamp-$1"} = $1;
113        delete($pending_nodes{$1});
114      }
115
116      # Handler performed some operation.
117      elsif ($action =~ /^([0-9]+)$/)
118      {
119        if ($1 eq "0")
120        {
121          my $id = "a" . $anon_id++;
122          $anon_nodes{$id} = "$timestamp\\l$description";
123          $all_nodes{"$timestamp-$id"} = $id;
124        }
125        else
126        {
127          push(@{$nodes{$1}->{content}}, "$description");
128        }
129      }
130    }
131  }
132}
133
134#-------------------------------------------------------------------------------
135# Helper function to convert a string to escaped HTML text.
136
137sub escape($)
138{
139  my $text = shift;
140  $text =~ s/&/\&amp\;/g;
141  $text =~ s/</\&lt\;/g;
142  $text =~ s/>/\&gt\;/g;
143  $text =~ s/\t/    /g;
144  return $text;
145}
146
147#-------------------------------------------------------------------------------
148# Templates for dot output.
149
150my $graph_header = <<"EOF";
151/* Generated by handlerviz.pl */
152digraph "handlerviz output"
153{
154graph [ nodesep="1" ];
155node [ shape="box", fontsize="9" ];
156edge [ arrowtail="dot", fontsize="9" ];
157EOF
158
159my $graph_footer = <<"EOF";
160}
161EOF
162
163my $node_header = <<"EOF";
164"%name%"
165[
166label=<<table border="0" cellspacing="0">
167<tr><td align="left" bgcolor="gray" border="0">%label%</td></tr>
168EOF
169
170my $node_footer = <<"EOF";
171</table>>
172]
173EOF
174
175my $node_content = <<"EOF";
176<tr><td align="left" bgcolor="white" border="0">
177<font face="mono" point-size="9">%content%</font>
178</td></tr>
179EOF
180
181my $anon_nodes_header = <<"EOF";
182{
183node [ shape="record" ];
184EOF
185
186my $anon_nodes_footer = <<"EOF";
187}
188EOF
189
190my $anon_node = <<"EOF";
191"%name%" [ label="%label%", color="gray" ];
192EOF
193
194my $pending_nodes_header = <<"EOF";
195{
196node [ shape="record", color="red" ];
197rank = "max";
198EOF
199
200my $pending_nodes_footer = <<"EOF";
201}
202EOF
203
204my $pending_node = <<"EOF";
205"%name%";
206EOF
207
208my $edges_header = <<"EOF";
209{
210edge [ style="dashed", arrowhead="open" ];
211EOF
212
213my $edges_footer = <<"EOF";
214}
215EOF
216
217my $edge = <<"EOF";
218"%begin%" -> "%end%" [ label="%label%", labeltooltip="%tooltip%" ]
219EOF
220
221my $node_order_header = <<"EOF";
222{
223node [ style="invisible" ];
224edge [ style="invis", weight="100" ];
225EOF
226
227my $node_order_footer = <<"EOF";
228}
229EOF
230
231my $node_order = <<"EOF";
232{
233rank="same"
234"%begin%";
235"o%begin%";
236}
237"o%begin%" -> "o%end%";
238EOF
239
240#-------------------------------------------------------------------------------
241# Generate dot output from the nodes and edges.
242
243sub print_nodes()
244{
245  foreach my $name (sort keys %nodes)
246  {
247    my $node = $nodes{$name};
248    my $entry = $node->{entry};
249    my $exit = $node->{exit};
250    my $label = escape($node->{label});
251    my $header = $node_header;
252    $header =~ s/%name%/$name/g;
253    $header =~ s/%label%/$label/g;
254    print($header);
255
256    if (defined($exit) and defined($entry))
257    {
258      my $line = $node_content;
259      my $content = $entry . " + " . sprintf("%.6f", $exit - $entry) . "s";
260      $line =~ s/%content%/$content/g;
261      print($line);
262    }
263
264    foreach my $content (@{$node->{content}})
265    {
266      $content = escape($content);
267      $content = " " if length($content) == 0;
268      my $line = $node_content;
269      $line =~ s/%content%/$content/g;
270      print($line);
271    }
272
273    print($node_footer);
274  }
275}
276
277sub print_anon_nodes()
278{
279  print($anon_nodes_header);
280  foreach my $name (sort keys %anon_nodes)
281  {
282    my $label = $anon_nodes{$name};
283    my $line = $anon_node;
284    $line =~ s/%name%/$name/g;
285    $line =~ s/%label%/$label/g;
286    print($line);
287  }
288  print($anon_nodes_footer);
289}
290
291sub print_pending_nodes()
292{
293  print($pending_nodes_header);
294  foreach my $name (sort keys %pending_nodes)
295  {
296    my $line = $pending_node;
297    $line =~ s/%name%/$name/g;
298    print($line);
299  }
300  print($pending_nodes_footer);
301}
302
303sub print_edges()
304{
305  print($edges_header);
306  foreach my $e (@edges)
307  {
308    my $begin = $e->{begin};
309    my $end = $e->{end};
310    my $label = $e->{label};
311    my $tooltip = "";
312    if (exists($locations{($begin,$end)}))
313    {
314      for my $line (@{$locations{($begin,$end)}})
315      {
316        $tooltip = $tooltip . escape($line) . "\n";
317      }
318    }
319    my $line = $edge;
320    $line =~ s/%begin%/$begin/g;
321    $line =~ s/%end%/$end/g;
322    $line =~ s/%label%/$label/g;
323    $line =~ s/%tooltip%/$tooltip/g;
324    print($line);
325  }
326  print($edges_footer);
327}
328
329sub print_node_order()
330{
331  my $prev = "";
332  print($node_order_header);
333  foreach my $name (sort keys %all_nodes)
334  {
335    if ($prev ne "")
336    {
337      my $begin = $prev;
338      my $end = $all_nodes{$name};
339      my $line = $node_order;
340      $line =~ s/%begin%/$begin/g;
341      $line =~ s/%end%/$end/g;
342      print($line);
343    }
344    $prev = $all_nodes{$name};
345  }
346  foreach my $name (sort keys %pending_nodes)
347  {
348    if ($prev ne "")
349    {
350      my $begin = $prev;
351      my $line = $node_order;
352      $line =~ s/%begin%/$begin/g;
353      $line =~ s/%end%/$name/g;
354      print($line);
355    }
356    last;
357  }
358  print($node_order_footer);
359}
360
361sub generate_dot()
362{
363  print($graph_header);
364  print_nodes();
365  print_anon_nodes();
366  print_pending_nodes();
367  print_edges();
368  print_node_order();
369  print($graph_footer);
370}
371
372#-------------------------------------------------------------------------------
373
374parse_debug_output();
375generate_dot();
376