1 #! /usr/bin/perl -w 2 ##--------------------------------------------------------------------## 3 ##--- Control supervision of applications run with callgrind ---## 4 ##--- callgrind_control ---## 5 ##--------------------------------------------------------------------## 6 7 # This file is part of Callgrind, a cache-simulator and call graph 8 # tracer built on Valgrind. 9 # 10 # Copyright (C) 2003-2011 Josef Weidendorfer <Josef.Weidendorfer (at] gmx.de> 11 # 12 # This program is free software; you can redistribute it and/or 13 # modify it under the terms of the GNU General Public License as 14 # published by the Free Software Foundation; either version 2 of the 15 # License, or (at your option) any later version. 16 # 17 # This program is distributed in the hope that it will be useful, but 18 # WITHOUT ANY WARRANTY; without even the implied warranty of 19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 # General Public License for more details. 21 # 22 # You should have received a copy of the GNU General Public License 23 # along with this program; if not, write to the Free Software 24 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 25 # 02111-1307, USA. 26 27 sub getCallgrindPids { 28 29 @pids = (); 30 open LIST, "vgdb -l|"; 31 while(<LIST>) { 32 if (/^use --pid=(\d+) for \S*?valgrind\s+(.*?)\s*$/) { 33 $pid = $1; 34 $cmd = $2; 35 if (!($cmd =~ /--tool=callgrind/)) { next; } 36 while($cmd =~ s/^-+\S+\s+//) {} 37 $cmdline{$pid} = $cmd; 38 $cmd =~ s/^(\S*).*/$1/; 39 $cmd{$pid} = $cmd; 40 #print "Found PID $pid, cmd '$cmd{$pid}', cmdline '$cmdline{$pid}'.\n"; 41 push(@pids, $pid); 42 } 43 } 44 close LIST; 45 } 46 47 sub printHeader { 48 if ($headerPrinted) { return; } 49 $headerPrinted = 1; 50 51 print "Observe the status and control currently active callgrind runs.\n"; 52 print "(C) 2003-2011, Josef Weidendorfer (Josef.Weidendorfer\@gmx.de)\n\n"; 53 } 54 55 sub printVersion { 56 print "callgrind_control-@VERSION@\n"; 57 exit; 58 } 59 60 sub shortHelp { 61 print "See '$0 -h' for help.\n"; 62 exit; 63 } 64 65 sub printHelp { 66 printHeader; 67 68 print "Usage: callgrind_control [options] [pid|program-name...]\n\n"; 69 print "If no pids/names are given, an action is applied to all currently\n"; 70 print "active Callgrind runs. Default action is printing short information.\n\n"; 71 print "Options:\n"; 72 print " -h --help Show this help text\n"; 73 print " --version Show version\n"; 74 print " -s --stat Show statistics\n"; 75 print " -b --back Show stack/back trace\n"; 76 print " -e [<A>,...] Show event counters for <A>,... (default: all)\n"; 77 print " --dump[=<s>] Request a dump optionally using <s> as description\n"; 78 print " -z --zero Zero all event counters\n"; 79 print " -k --kill Kill\n"; 80 print " -i --instr=on|off Switch instrumentation state on/off\n"; 81 print "\n"; 82 exit; 83 } 84 85 86 # 87 # Parts more or less copied from cg_annotate (author: Nicholas Nethercote) 88 # 89 90 sub prepareEvents { 91 92 @events = split(/\s+/, $events); 93 %events = (); 94 $n = 0; 95 foreach $event (@events) { 96 $events{$event} = $n; 97 $n++; 98 } 99 if (@show_events) { 100 foreach my $show_event (@show_events) { 101 (defined $events{$show_event}) or 102 print "Warning: Event `$show_event' is not being collected\n"; 103 } 104 } else { 105 @show_events = @events; 106 } 107 @show_order = (); 108 foreach my $show_event (@show_events) { 109 push(@show_order, $events{$show_event}); 110 } 111 } 112 113 sub max ($$) 114 { 115 my ($x, $y) = @_; 116 return ($x > $y ? $x : $y); 117 } 118 119 sub line_to_CC ($) 120 { 121 my @CC = (split /\s+/, $_[0]); 122 (@CC <= @events) or die("Line $.: too many event counts\n"); 123 return \@CC; 124 } 125 126 sub commify ($) { 127 my ($val) = @_; 128 1 while ($val =~ s/^(\d+)(\d{3})/$1,$2/); 129 return $val; 130 } 131 132 sub compute_CC_col_widths (@) 133 { 134 my @CCs = @_; 135 my $CC_col_widths = []; 136 137 # Initialise with minimum widths (from event names) 138 foreach my $event (@events) { 139 push(@$CC_col_widths, length($event)); 140 } 141 142 # Find maximum width count for each column. @CC_col_width positions 143 # correspond to @CC positions. 144 foreach my $CC (@CCs) { 145 foreach my $i (0 .. scalar(@$CC)-1) { 146 if (defined $CC->[$i]) { 147 # Find length, accounting for commas that will be added 148 my $length = length $CC->[$i]; 149 my $clength = $length + int(($length - 1) / 3); 150 $CC_col_widths->[$i] = max($CC_col_widths->[$i], $clength); 151 } 152 } 153 } 154 return $CC_col_widths; 155 } 156 157 # Print the CC with each column's size dictated by $CC_col_widths. 158 sub print_CC ($$) 159 { 160 my ($CC, $CC_col_widths) = @_; 161 162 foreach my $i (@show_order) { 163 my $count = (defined $CC->[$i] ? commify($CC->[$i]) : "."); 164 my $space = ' ' x ($CC_col_widths->[$i] - length($count)); 165 print("$space$count "); 166 } 167 } 168 169 sub print_events ($) 170 { 171 my ($CC_col_widths) = @_; 172 173 foreach my $i (@show_order) { 174 my $event = $events[$i]; 175 my $event_width = length($event); 176 my $col_width = $CC_col_widths->[$i]; 177 my $space = ' ' x ($col_width - $event_width); 178 print("$space$event "); 179 } 180 } 181 182 183 184 # 185 # Main 186 # 187 188 getCallgrindPids; 189 190 $requestEvents = 0; 191 $requestDump = 0; 192 $switchInstr = 0; 193 $headerPrinted = 0; 194 $dumpHint = ""; 195 $verbose = 0; 196 197 %spids = (); 198 foreach $arg (@ARGV) { 199 if ($arg =~ /^-/) { 200 if ($requestDump == 1) { $requestDump = 2; } 201 if ($requestEvents == 1) { $requestEvents = 2; } 202 203 if ($arg =~ /^(-h|--help)$/) { 204 printHelp; 205 } 206 elsif ($arg =~ /^--version$/) { 207 printVersion; 208 } 209 elsif ($arg =~ /^-v$/) { 210 $verbose++; 211 next; 212 } 213 elsif ($arg =~ /^(-s|--stat)$/) { 214 $printStatus = 1; 215 next; 216 } 217 elsif ($arg =~ /^(-b|--back)$/) { 218 $printBacktrace = 1; 219 next; 220 } 221 elsif ($arg =~ /^-e$/) { 222 $requestEvents = 1; 223 next; 224 } 225 elsif ($arg =~ /^(-d|--dump)(|=.*)$/) { 226 if ($2 ne "") { 227 $requestDump = 2; 228 $dumpHint = substr($2,1); 229 } 230 else { 231 # take next argument as dump hint 232 $requestDump = 1; 233 } 234 next; 235 } 236 elsif ($arg =~ /^(-z|--zero)$/) { 237 $requestZero = 1; 238 next; 239 } 240 elsif ($arg =~ /^(-k|--kill)$/) { 241 $requestKill = 1; 242 next; 243 } 244 elsif ($arg =~ /^(-i|--instr)(|=on|=off)$/) { 245 $switchInstr = 2; 246 if ($2 eq "=on") { 247 $switchInstrMode = "on"; 248 } 249 elsif ($2 eq "=off") { 250 $switchInstrMode = "off"; 251 } 252 else { 253 # check next argument for "on" or "off" 254 $switchInstr = 1; 255 } 256 next; 257 } 258 else { 259 print "Error: unknown command line option '$arg'.\n"; 260 shortHelp; 261 } 262 } 263 264 if ($arg =~ /^[A-Za-z_]/) { 265 # arguments of -d/-e/-i are non-numeric 266 if ($requestDump == 1) { 267 $requestDump = 2; 268 $dumpHint = $arg; 269 next; 270 } 271 272 if ($requestEvents == 1) { 273 $requestEvents = 2; 274 @show_events = split(/,/, $arg); 275 next; 276 } 277 278 if ($switchInstr == 1) { 279 $switchInstr = 2; 280 if ($arg eq "on") { 281 $switchInstrMode = "on"; 282 } 283 elsif ($arg eq "off") { 284 $switchInstrMode = "off"; 285 } 286 else { 287 print "Error: need to specify 'on' or 'off' after '-i'.\n"; 288 shortHelp; 289 } 290 next; 291 } 292 } 293 294 if (defined $cmd{$arg}) { $spids{$arg} = 1; next; } 295 $nameFound = 0; 296 foreach $p (@pids) { 297 if ($cmd{$p} =~ /$arg$/) { 298 $nameFound = 1; 299 $spids{$p} = 1; 300 } 301 } 302 if ($nameFound) { next; } 303 304 print "Error: Callgrind task with PID/name '$arg' not detected.\n"; 305 shortHelp; 306 } 307 308 309 if ($switchInstr == 1) { 310 print "Error: need to specify 'on' or 'off' after '-i'.\n"; 311 shortHelp; 312 } 313 314 if (scalar @pids == 0) { 315 print "No active callgrind runs detected.\n"; 316 exit; 317 } 318 319 @spids = keys %spids; 320 if (scalar @spids >0) { @pids = @spids; } 321 322 $vgdbCommand = ""; 323 $waitForAnswer = 0; 324 if ($requestDump) { 325 $vgdbCommand = "dump"; 326 if ($dumpHint ne "") { $vgdbCommand .= " ".$dumpHint; } 327 } 328 if ($requestZero) { $vgdbCommand = "zero"; } 329 if ($requestKill) { $vgdbCommand = "v.kill"; } 330 if ($switchInstr) { $vgdbCommand = "instrumentation ".$switchInstrMode; } 331 if ($printStatus || $printBacktrace || $requestEvents) { 332 $vgdbCommand = "status internal"; 333 $waitForAnswer = 1; 334 } 335 336 foreach $pid (@pids) { 337 $pidstr = "PID $pid: "; 338 if ($pid >0) { print $pidstr.$cmdline{$pid}; } 339 340 if ($vgdbCommand eq "") { 341 print "\n"; 342 next; 343 } 344 if ($verbose>0) { 345 print " [requesting '$vgdbCommand']\n"; 346 } else { 347 print "\n"; 348 } 349 open RESULT, "vgdb --pid=$pid $vgdbCommand|"; 350 351 @tids = (); 352 $ctid = 0; 353 %fcount = (); 354 %func = (); 355 %calls = (); 356 %events = (); 357 @events = (); 358 @threads = (); 359 %totals = (); 360 361 $exec_bbs = 0; 362 $dist_bbs = 0; 363 $exec_calls = 0; 364 $dist_calls = 0; 365 $dist_ctxs = 0; 366 $dist_funcs = 0; 367 $threads = ""; 368 $events = ""; 369 370 while(<RESULT>) { 371 if (/function-(\d+)-(\d+): (.+)$/) { 372 if ($ctid != $1) { 373 $ctid = $1; 374 push(@tids, $ctid); 375 $fcount{$ctid} = 0; 376 } 377 $fcount{$ctid}++; 378 $func{$ctid,$fcount{$ctid}} = $3; 379 } 380 elsif (/calls-(\d+)-(\d+): (.+)$/) { 381 if ($ctid != $1) { next; } 382 $calls{$ctid,$fcount{$ctid}} = $3; 383 } 384 elsif (/events-(\d+)-(\d+): (.+)$/) { 385 if ($ctid != $1) { next; } 386 $events{$ctid,$fcount{$ctid}} = line_to_CC($3); 387 } 388 elsif (/events-(\d+): (.+)$/) { 389 if (scalar @events == 0) { next; } 390 $totals{$1} = line_to_CC($2); 391 } 392 elsif (/executed-bbs: (\d+)/) { $exec_bbs = $1; } 393 elsif (/distinct-bbs: (\d+)/) { $dist_bbs = $1; } 394 elsif (/executed-calls: (\d+)/) { $exec_calls = $1; } 395 elsif (/distinct-calls: (\d+)/) { $dist_calls = $1; } 396 elsif (/distinct-functions: (\d+)/) { $dist_funcs = $1; } 397 elsif (/distinct-contexts: (\d+)/) { $dist_ctxs = $1; } 398 elsif (/events: (.+)$/) { $events = $1; prepareEvents; } 399 elsif (/threads: (.+)$/) { $threads = $1; @threads = split " ", $threads; } 400 elsif (/instrumentation: (\w+)$/) { $instrumentation = $1; } 401 } 402 403 #if ($? ne "0") { print " Got Error $?\n"; } 404 if (!$waitForAnswer) { print " OK.\n"; next; } 405 406 if ($instrumentation eq "off") { 407 print " No information available as instrumentation is switched off.\n\n"; 408 exit; 409 } 410 411 if ($printStatus) { 412 if ($requestEvents <1) { 413 print " Number of running threads: " .($#threads+1). ", thread IDs: $threads\n"; 414 print " Events collected: $events\n"; 415 } 416 417 print " Functions: ".commify($dist_funcs); 418 print " (executed ".commify($exec_calls); 419 print ", contexts ".commify($dist_ctxs).")\n"; 420 421 print " Basic blocks: ".commify($dist_bbs); 422 print " (executed ".commify($exec_bbs); 423 print ", call sites ".commify($dist_calls).")\n"; 424 } 425 426 if ($requestEvents >0) { 427 $totals_width = compute_CC_col_widths(values %totals); 428 print "\n Totals:"; 429 print_events($totals_width); 430 print("\n"); 431 foreach $tid (@tids) { 432 print " Th".substr(" ".$tid,-2)." "; 433 print_CC($totals{$tid}, $totals_width); 434 print("\n"); 435 } 436 } 437 438 if ($printBacktrace) { 439 440 if ($requestEvents >0) { 441 $totals_width = compute_CC_col_widths(values %events); 442 } 443 444 foreach $tid (@tids) { 445 print "\n Frame: "; 446 if ($requestEvents >0) { 447 print_events($totals_width); 448 } 449 print "Backtrace for Thread $tid\n"; 450 451 $i = $fcount{$tid}; 452 $c = 0; 453 while($i>0 && $c<100) { 454 $fc = substr(" $c",-2); 455 print " [$fc] "; 456 if ($requestEvents >0) { 457 print_CC($events{$tid,$i-1}, $totals_width); 458 } 459 print $func{$tid,$i}; 460 if ($i > 1) { 461 print " (".$calls{$tid,$i-1}." x)"; 462 } 463 print "\n"; 464 $i--; 465 $c++; 466 } 467 print "\n"; 468 } 469 } 470 print "\n"; 471 } 472 473