]> granicus.if.org Git - strace/blob - strace-graph
strace-graph: handle strace -T
[strace] / strace-graph
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@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 use strict;
37 use warnings;
38
39 my %unfinished;
40 my $floatform;
41
42 # Scales for strace slowdown.  Make configurable!
43 my $scale_factor = 3.5;
44
45 while (<>) {
46     my ($pid, $call, $args, $result, $time, $time_spent);
47     chop;
48     $floatform = 0;
49
50     s/^(\d+)\s+//;
51     $pid = $1;
52
53     if (s/^(\d\d):(\d\d):(\d\d)(?:\.(\d\d\d\d\d\d))? //) {
54         $time = $1 * 3600 + $2 * 60 + $3;
55         if (defined $4) {
56             $time = $time + $4 / 1000000;
57             $floatform = 1;
58         }
59     } elsif (s/^(\d+)\.(\d\d\d\d\d\d) //) {
60         $time = $1 + ($2 / 1000000);
61         $floatform = 1;
62     }
63
64     if (s/ <unfinished ...>$//) {
65         $unfinished{$pid} = $_;
66         next;
67     }
68
69     if (s/^<... \S+ resumed> //) {
70         unless (exists $unfinished{$pid}) {
71             print STDERR "$0: $ARGV: cannot find start of resumed call on line $.";
72             next;
73         }
74         $_ = $unfinished{$pid} . $_;
75         delete $unfinished{$pid};
76     }
77
78     if (/^--- SIG(\S+) \(.*\) ---$/) {
79         # $pid received signal $1
80         # currently we don't do anything with this
81         next;
82     }
83
84     if (/^\+\+\+ killed by SIG(\S+) \+\+\+$/) {
85         # $pid received signal $1
86         handle_killed($pid, $time);
87         next;
88     }
89
90     ($call, $args, $result) = /(\S+)\((.*)\)\s+= (.*)$/;
91     if ($result =~ /^(.*) <([0-9.]*)>$/) {
92         ($result, $time_spent) = ($1, $2);
93     }
94     unless (defined $result) {
95         print STDERR "$0: $ARGV: $.: cannot parse line.\n";
96         next;
97     }
98
99     handle_trace($pid, $call, $args, $result, $time);
100 }
101
102 display_trace();
103
104 exit 0;
105
106 sub parse_str {
107     my ($in) = @_;
108     my $result = "";
109
110     while (1) {
111         if ($in =~ s/^\\(.)//) {
112             $result .= $1;
113         } elsif ($in =~ s/^\"//) {
114             if ($in =~ s/^\.\.\.//) {
115                 return ("$result...", $in);
116             }
117             return ($result, $in);
118         } elsif ($in =~ s/([^\\\"]*)//) {
119             $result .= $1;
120         } else {
121             return (undef, $in);
122         }
123     }
124 }
125
126 sub parse_one {
127     my ($in) = @_;
128
129     if ($in =~ s/^\"//) {
130         my $tmp;
131         ($tmp, $in) = parse_str($in);
132         if (not defined $tmp) {
133             print STDERR "$0: $ARGV: $.: cannot parse string.\n";
134             return (undef, $in);
135         }
136         return ($tmp, $in);
137     } elsif ($in =~ s/^0x([[:xdigit:]]+)//) {
138         return (hex $1, $in);
139     } elsif ($in =~ s/^(\d+)//) {
140         return (int $1, $in);
141     } else {
142         print STDERR "$0: $ARGV: $.: unrecognized element.\n";
143         return (undef, $in);
144     }
145 }
146
147 sub parseargs {
148     my ($in) = @_;
149     my @args = ();
150     my $tmp;
151
152     while (length $in) {
153         if ($in =~ s/^\[//) {
154             my @subarr = ();
155             if ($in =~ s,^/\* (\d+) vars \*/\],,) {
156                 push @args, $1;
157             } else {
158                 while ($in !~ s/^\]//) {
159                     ($tmp, $in) = parse_one($in);
160                     defined $tmp or return undef;
161                     push @subarr, $tmp;
162                     unless ($in =~ /^\]/ or $in =~ s/^, //) {
163                         print STDERR "$0: $ARGV: $.: missing comma in array.\n";
164                         return undef;
165                     }
166                     if ($in =~ s/^\.\.\.//) {
167                         push @subarr, "...";
168                     }
169                 }
170                 push @args, \@subarr;
171             }
172         } elsif ($in =~ s/^\{//) {
173             my %subhash = ();
174             while ($in !~ s/^\}//) {
175                 my $key;
176                 unless ($in =~ s/^(\w+)=//) {
177                     print STDERR "$0: $ARGV: $.: struct field expected.\n";
178                     return undef;
179                 }
180                 $key = $1;
181                 ($tmp, $in) = parse_one($in);
182                 defined $tmp or return undef;
183                 $subhash{$key} = $tmp;
184                 unless ($in =~ s/, //) {
185                     print STDERR "$0: $ARGV: $.: missing comma in struct.\n";
186                     return undef;
187                 }
188             }
189             push @args, \%subhash;
190         } else {
191             ($tmp, $in) = parse_one($in);
192             defined $tmp or return undef;
193             push @args, $tmp;
194         }
195         unless (length($in) == 0 or $in =~ s/^, //) {
196             print STDERR "$0: $ARGV: $.: missing comma.\n";
197             return undef;
198         }
199     }
200     return @args;
201 }
202
203
204 my $depth = "";
205
206 # process info, indexed by pid.
207 # fields:
208 #    parent         pid number
209 #    seq            forks and execs for this pid, in sequence  (array)
210
211 #  filename and argv (from latest exec)
212 #  basename (derived from filename)
213 # argv[0] is modified to add the basename if it differs from the 0th argument.
214
215 my %pr;
216
217 sub handle_trace {
218     my ($pid, $call, $args, $result, $time) = @_;
219     my $p;
220
221     if (defined $time and not defined $pr{$pid}{start}) {
222         $pr{$pid}{start} = $time;
223     }
224
225     if ($call eq 'execve') {
226         return if $result ne '0';
227
228         my ($filename, $argv) = parseargs($args);
229         my ($basename) = $filename =~ m/([^\/]*)$/;
230         if ($basename ne $$argv[0]) {
231             $$argv[0] = "$basename($$argv[0])";
232         }
233         my $seq = $pr{$pid}{seq};
234         $seq = [] if not defined $seq;
235
236         push @$seq, ['EXEC', $filename, $argv];
237
238         $pr{$pid}{seq} = $seq;
239     } elsif ($call eq 'fork' || $call eq 'clone' || $call eq 'vfork') {
240         return if $result == 0;
241
242         my $seq = $pr{$pid}{seq};
243         $seq = [] if not defined $seq;
244         push @$seq, ['FORK', $result];
245         $pr{$pid}{seq} = $seq;
246         $pr{$result}{parent} = $pid;
247     } elsif ($call eq '_exit') {
248         $pr{$pid}{end} = $time if defined $time;
249     }
250 }
251
252 sub handle_killed {
253     my ($pid, $time) = @_;
254     $pr{$pid}{end} = $time if defined $time;
255 }
256
257 sub straight_seq {
258     my ($pid) = @_;
259     my $seq = $pr{$pid}{seq};
260
261     for my $elem (@$seq) {
262         if ($$elem[0] eq 'EXEC') {
263             my $argv = $$elem[2];
264             print "$$elem[0] $$elem[1] @$argv\n";
265         } elsif ($$elem[0] eq 'FORK') {
266             print "$$elem[0] $$elem[1]\n";
267         } else {
268             print "$$elem[0]\n";
269         }
270     }
271 }
272
273 sub first_exec {
274     my ($pid) = @_;
275     my $seq = $pr{$pid}{seq};
276
277     for my $elem (@$seq) {
278         if ($$elem[0] eq 'EXEC') {
279             return $elem;
280         }
281     }
282     return undef;
283 }
284
285 sub display_pid_trace {
286     my ($pid, $lead) = @_;
287     my $i = 0;
288     my @seq = @{$pr{$pid}{seq}};
289     my $elapsed;
290
291     if (not defined first_exec($pid)) {
292         unshift @seq, ['EXEC', '', ['(anon)'] ];
293     }
294
295     if (defined $pr{$pid}{start} and defined $pr{$pid}{end}) {
296         $elapsed = $pr{$pid}{end} - $pr{$pid}{start};
297         $elapsed /= $scale_factor;
298         if ($floatform) {
299             $elapsed = sprintf("%0.02f", $elapsed);
300         } else {
301             $elapsed = int $elapsed;
302         }
303     }
304
305     for my $elem (@seq) {
306         $i++;
307         if ($$elem[0] eq 'EXEC') {
308             my $argv = $$elem[2];
309             if (defined $elapsed) {
310                 print "$lead [$elapsed] @$argv\n";
311                 undef $elapsed;
312             } else {
313                 print "$lead @$argv\n";
314             }
315         } elsif ($$elem[0] eq 'FORK') {
316             if ($i == 1) {
317                 if ($lead =~ /-$/) {
318                     display_pid_trace($$elem[1], "$lead--+--");
319                 } else {
320                     display_pid_trace($$elem[1], "$lead  +--");
321                 }
322             } elsif ($i == @seq) {
323                 display_pid_trace($$elem[1], "$lead  `--");
324             } else {
325                 display_pid_trace($$elem[1], "$lead  +--");
326             }
327         }
328         if ($i == 1) {
329             $lead =~ s/\`--/   /g;
330             $lead =~ s/-/ /g;
331             $lead =~ s/\+/|/g;
332         }
333     }
334 }
335
336 sub display_trace {
337     my ($startpid) = @_;
338
339     $startpid = (keys %pr)[0];
340     while ($pr{$startpid}{parent}) {
341         $startpid = $pr{$startpid}{parent};
342     }
343
344     display_pid_trace($startpid, "");
345 }