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-2021 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/&/\&\;/g; 141 $text =~ s/</\<\;/g; 142 $text =~ s/>/\>\;/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