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