Home | History | Annotate | Download | only in strace
      1 #!/usr/bin/perl
      2 
      3 # This script processes strace -f output.  It displays a graph of invoked
      4 # subprocesses, and is useful for finding out what complex commands do.
      5 
      6 # You will probably want to invoke strace with -q as well, and with
      7 # -s 100 to get complete filenames.
      8 
      9 # The script can also handle the output with strace -t, -tt, or -ttt.
     10 # It will add elapsed time for each process in that case.
     11 
     12 # This script is Copyright (C) 1998 by Richard Braakman <dark (at] xs4all.nl>.
     13 
     14 # Redistribution and use in source and binary forms, with or without
     15 # modification, are permitted provided that the following conditions
     16 # are met:
     17 # 1. Redistributions of source code must retain the above copyright
     18 #    notice, this list of conditions and the following disclaimer.
     19 # 2. Redistributions in binary form must reproduce the above copyright
     20 #    notice, this list of conditions and the following disclaimer in the
     21 #    documentation and/or other materials provided with the distribution.
     22 # 3. The name of the author may not be used to endorse or promote products
     23 #    derived from this software without specific prior written permission.
     24 #
     25 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
     26 # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     27 # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     28 # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
     29 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
     30 # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     31 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     32 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     33 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     34 # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     35 #
     36 #	$Id$
     37 
     38 my %unfinished;
     39 
     40 # Scales for strace slowdown.  Make configurable!
     41 my $scale_factor = 3.5;
     42 
     43 while (<>) {
     44     my ($pid, $call, $args, $result, $time);
     45     chop;
     46 
     47     s/^(\d+)\s+//;
     48     $pid = $1;
     49 
     50     if (s/^(\d\d):(\d\d):(\d\d)(?:\.(\d\d\d\d\d\d))? //) {
     51 	$time = $1 * 3600 + $2 * 60 + $3;
     52 	if (defined $4) {
     53 	    $time = $time + $4 / 1000000;
     54 	    $floatform = 1;
     55 	}
     56     } elsif (s/^(\d+)\.(\d\d\d\d\d\d) //) {
     57 	$time = $1 + ($2 / 1000000);
     58 	$floatform = 1;
     59     }
     60 
     61     if (s/ <unfinished ...>$//) {
     62 	$unfinished{$pid} = $_;
     63 	next;
     64     }
     65 
     66     if (s/^<... \S+ resumed> //) {
     67 	unless (exists $unfinished{$pid}) {
     68 	    print STDERR "$0: $ARGV: cannot find start of resumed call on line $.";
     69 	    next;
     70 	}
     71 	$_ = $unfinished{$pid} . $_;
     72 	delete $unfinished{$pid};
     73     }
     74 
     75     if (/^--- SIG(\S+) \(.*\) ---$/) {
     76 	# $pid received signal $1
     77 	# currently we don't do anything with this
     78 	next;
     79     }
     80 
     81     if (/^\+\+\+ killed by SIG(\S+) \+\+\+$/) {
     82 	# $pid received signal $1
     83 	handle_killed($pid, $time);
     84 	next;
     85     }
     86 
     87     ($call, $args, $result) = /(\S+)\((.*)\)\s+= (.*)$/;
     88     unless (defined $result) {
     89 	print STDERR "$0: $ARGV: $.: cannot parse line.\n";
     90 	next;
     91     }
     92 
     93     handle_trace($pid, $call, $args, $result, $time);
     94 }
     95 
     96 display_trace();
     97 
     98 exit 0;
     99 
    100 sub parse_str {
    101     my ($in) = @_;
    102     my $result = "";
    103 
    104     while (1) {
    105 	if ($in =~ s/^\\(.)//) {
    106 	    $result .= $1;
    107 	} elsif ($in =~ s/^\"//) {
    108 	    if ($in =~ s/^\.\.\.//) {
    109 		return ("$result...", $in);
    110 	    }
    111 	    return ($result, $in);
    112 	} elsif ($in =~ s/([^\\\"]*)//) {
    113 	    $result .= $1;
    114 	} else {
    115 	    return (undef, $in);
    116 	}
    117     }
    118 }
    119 
    120 sub parse_one {
    121     my ($in) = @_;
    122 
    123     if ($in =~ s/^\"//) {
    124 	($tmp, $in) = parse_str($in);
    125 	if (not defined $tmp) {
    126 	    print STDERR "$0: $ARGV: $.: cannot parse string.\n";
    127 	    return (undef, $in);
    128 	}
    129 	return ($tmp, $in);
    130     } elsif ($in =~ s/^0x(\x+)//) {
    131 	return (hex $1, $in);
    132     } elsif ($in =~ s/^(\d+)//) {
    133 	return (int $1, $in);
    134     } else {
    135 	print STDERR "$0: $ARGV: $.: unrecognized element.\n";
    136 	return (undef, $in);
    137     }
    138 }
    139 
    140 sub parseargs {
    141     my ($in) = @_;
    142     my @args = ();
    143     my $tmp;
    144 
    145     while (length $in) {
    146 	if ($in =~ s/^\[//) {
    147 	    my @subarr = ();
    148 	    if ($in =~ s,^/\* (\d+) vars \*/\],,) {
    149 		push @args, $1;
    150 	    } else {
    151 		while ($in !~ s/^\]//) {
    152 		    ($tmp, $in) = parse_one($in);
    153 		    defined $tmp or return undef;
    154 		    push @subarr, $tmp;
    155 		    unless ($in =~ /^\]/ or $in =~ s/^, //) {
    156 			print STDERR "$0: $ARGV: $.: missing comma in array.\n";
    157 			return undef;
    158 		    }
    159 		    if ($in =~ s/^\.\.\.//) {
    160 			push @subarr, "...";
    161 		    }
    162 		}
    163 		push @args, \@subarr;
    164 	    }
    165 	} elsif ($in =~ s/^\{//) {
    166 	    my %subhash = ();
    167 	    while ($in !~ s/^\}//) {
    168 		my $key;
    169 		unless ($in =~ s/^(\w+)=//) {
    170 		    print STDERR "$0: $ARGV: $.: struct field expected.\n";
    171 		    return undef;
    172 		}
    173 		$key = $1;
    174 		($tmp, $in) = parse_one($in);
    175 		defined $tmp or return undef;
    176 		$subhash{$key} = $tmp;
    177 		unless ($in =~ s/, //) {
    178 		    print STDERR "$0: $ARGV: $.: missing comma in struct.\n";
    179 		    return undef;
    180 		}
    181 	    }
    182 	    push @args, \%subhash;
    183 	} else {
    184 	    ($tmp, $in) = parse_one($in);
    185 	    defined $tmp or return undef;
    186 	    push @args, $tmp;
    187 	}
    188 	unless (length($in) == 0 or $in =~ s/^, //) {
    189 	    print STDERR "$0: $ARGV: $.: missing comma.\n";
    190 	    return undef;
    191 	}
    192     }
    193     return @args;
    194 }
    195 
    196 
    197 my $depth = "";
    198 
    199 # process info, indexed by pid.
    200 # fields:
    201 #    parent         pid number
    202 #    seq            forks and execs for this pid, in sequence  (array)
    203 
    204 #  filename and argv (from latest exec)
    205 #  basename (derived from filename)
    206 # argv[0] is modified to add the basename if it differs from the 0th argument.
    207 
    208 my %pr;
    209 
    210 sub handle_trace {
    211     my ($pid, $call, $args, $result, $time) = @_;
    212     my $p;
    213 
    214     if (defined $time and not defined $pr{$pid}{start}) {
    215 	$pr{$pid}{start} = $time;
    216     }
    217 
    218     if ($call eq 'execve') {
    219 	return if $result != 0;
    220 
    221 	my ($filename, $argv) = parseargs($args);
    222 	($basename) = $filename =~ m/([^\/]*)$/;
    223 	if ($basename ne $$argv[0]) {
    224 	    $$argv[0] = "$basename($$argv[0])";
    225         }
    226 	my $seq = $pr{$pid}{seq};
    227 	$seq = [] if not defined $seq;
    228 
    229 	push @$seq, ['EXEC', $filename, $argv];
    230 
    231 	$pr{$pid}{seq} = $seq;
    232     } elsif ($call eq 'fork' || $call eq 'clone' || $call eq 'vfork') {
    233 	return if $result == 0;
    234 
    235 	my $seq = $pr{$pid}{seq};
    236 	$seq = [] if not defined $seq;
    237 	push @$seq, ['FORK', $result];
    238 	$pr{$pid}{seq} = $seq;
    239 	$pr{$result}{parent} = $pid;
    240     } elsif ($call eq '_exit') {
    241 	$pr{$pid}{end} = $time if defined $time;
    242     }
    243 }
    244 
    245 sub handle_killed {
    246     my ($pid, $time) = @_;
    247     $pr{$pid}{end} = $time if defined $time;
    248 }
    249 
    250 sub straight_seq {
    251     my ($pid) = @_;
    252     my $seq = $pr{$pid}{seq};
    253 
    254     for $elem (@$seq) {
    255 	if ($$elem[0] eq 'EXEC') {
    256 	    my $argv = $$elem[2];
    257 	    print "$$elem[0] $$elem[1] @$argv\n";
    258 	} elsif ($$elem[0] eq 'FORK') {
    259 	    print "$$elem[0] $$elem[1]\n";
    260 	} else {
    261 	    print "$$elem[0]\n";
    262 	}
    263     }
    264 }
    265 
    266 sub first_exec {
    267     my ($pid) = @_;
    268     my $seq = $pr{$pid}{seq};
    269 
    270     for $elem (@$seq) {
    271 	if ($$elem[0] eq 'EXEC') {
    272 	    return $elem;
    273 	}
    274     }
    275     return undef;
    276 }
    277 
    278 sub display_pid_trace {
    279     my ($pid, $lead) = @_;
    280     my $i = 0;
    281     my @seq = @{$pr{$pid}{seq}};
    282     my $elapsed;
    283 
    284     if (not defined first_exec($pid)) {
    285 	unshift @seq, ['EXEC', '', ['(anon)'] ];
    286     }
    287 
    288     if (defined $pr{$pid}{start} and defined $pr{$pid}{end}) {
    289 	$elapsed = $pr{$pid}{end} - $pr{$pid}{start};
    290 	$elapsed /= $scale_factor;
    291 	if ($floatform) {
    292 	    $elapsed = sprintf("%0.02f", $elapsed);
    293 	} else {
    294 	    $elapsed = int $elapsed;
    295 	}
    296     }
    297 
    298     for $elem (@seq) {
    299 	$i++;
    300 	if ($$elem[0] eq 'EXEC') {
    301 	    my $argv = $$elem[2];
    302 	    if (defined $elapsed) {
    303 		print "$lead [$elapsed] @$argv\n";
    304 		undef $elapsed;
    305 	    } else {
    306 		print "$lead @$argv\n";
    307 	    }
    308 	} elsif ($$elem[0] eq 'FORK') {
    309 	    if ($i == 1) {
    310                 if ($lead =~ /-$/) {
    311 		    display_pid_trace($$elem[1], "$lead--+--");
    312                 } else {
    313 		    display_pid_trace($$elem[1], "$lead  +--");
    314                 }
    315 	    } elsif ($i == @seq) {
    316 		display_pid_trace($$elem[1], "$lead  `--");
    317 	    } else {
    318 		display_pid_trace($$elem[1], "$lead  +--");
    319 	    }
    320 	}
    321 	if ($i == 1) {
    322 	    $lead =~ s/\`--/   /g;
    323 	    $lead =~ s/-/ /g;
    324 	    $lead =~ s/\+/|/g;
    325 	}
    326     }
    327 }
    328 
    329 sub display_trace {
    330     my ($startpid) = @_;
    331 
    332     $startpid = (keys %pr)[0];
    333     while ($pr{$startpid}{parent}) {
    334 	$startpid = $pr{$startpid}{parent};
    335     }
    336 
    337     display_pid_trace($startpid, "");
    338 }
    339