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.
6 # You will probably want to invoke strace with -q as well, and with
7 # -s 100 to get complete filenames.
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.
12 # Copyright (c) 1998 by Richard Braakman <dark@xs4all.nl>.
13 # Copyright (c) 1998-2018 The strace developers.
15 # SPDX-License-Identifier: LGPL-2.1-or-later
23 # Scales for strace slowdown. Make configurable!
24 my $scale_factor = 3.5;
28 my ($pid, $call, $args, $result, $time, $time_spent);
35 if (s/^(\d\d):(\d\d):(\d\d)(?:\.(\d\d\d\d\d\d))? //) {
36 $time = $1 * 3600 + $2 * 60 + $3;
38 $time = $time + $4 / 1000000;
41 } elsif (s/^(\d+)\.(\d\d\d\d\d\d) //) {
42 $time = $1 + ($2 / 1000000);
46 if (s/ <unfinished ...>$//) {
47 $unfinished{$pid} = $_;
51 if (s/^<... \S+ resumed> //) {
52 unless (exists $unfinished{$pid}) {
53 print STDERR "$0: $ARGV: cannot find start of resumed call on line $.";
56 $_ = $unfinished{$pid} . $_;
57 delete $unfinished{$pid};
60 if (/^--- SIG(\S+) (.*) ---$/) {
61 # $pid received signal $1
62 # currently we don't do anything with this
66 if (/^\+\+\+ killed by SIG(\S+) \+\+\+$/) {
67 # $pid received signal $1
68 handle_killed($pid, $time);
72 if (/^\+\+\+ exited with (\d+) \+\+\+$/) {
74 # currently we don't do anything with this
78 ($call, $args, $result) = /(\S+)\((.*)\)\s+= (.*)$/;
79 if ($result =~ /^(.*) <([0-9.]*)>$/) {
80 ($result, $time_spent) = ($1, $2);
82 unless (defined $result) {
83 print STDERR "$0: $ARGV: $.: cannot parse line.\n";
87 handle_trace($pid, $call, $args, $result, $time);
99 if ($in =~ s/^\\(.)//) {
101 } elsif ($in =~ s/^\"//) {
102 if ($in =~ s/^\.\.\.//) {
103 return ("$result...", $in);
105 return ($result, $in);
106 } elsif ($in =~ s/([^\\\"]*)//) {
117 if ($in =~ s/^\"//) {
119 ($tmp, $in) = parse_str($in);
120 if (not defined $tmp) {
121 print STDERR "$0: $ARGV: $.: cannot parse string.\n";
125 } elsif ($in =~ s/^0x([[:xdigit:]]+)//) {
126 return (hex $1, $in);
127 } elsif ($in =~ s/^(\d+)//) {
128 return (int $1, $in);
130 print STDERR "$0: $ARGV: $.: unrecognized element.\n";
141 if ($in =~ s/^\[//) {
143 if ($in =~ s,^/\* (\d+) vars \*/\],,) {
146 while ($in !~ s/^\]//) {
147 ($tmp, $in) = parse_one($in);
148 defined $tmp or return undef;
150 unless ($in =~ /^\]/ or $in =~ s/^, //) {
151 print STDERR "$0: $ARGV: $.: missing comma in array.\n";
154 if ($in =~ s/^\.\.\.//) {
158 push @args, \@subarr;
160 } elsif ($in =~ s/^\{//) {
162 while ($in !~ s/^\}//) {
164 unless ($in =~ s/^(\w+)=//) {
165 print STDERR "$0: $ARGV: $.: struct field expected.\n";
169 ($tmp, $in) = parse_one($in);
170 defined $tmp or return undef;
171 $subhash{$key} = $tmp;
172 unless ($in =~ s/, //) {
173 print STDERR "$0: $ARGV: $.: missing comma in struct.\n";
177 push @args, \%subhash;
179 ($tmp, $in) = parse_one($in);
180 defined $tmp or return undef;
183 unless (length($in) == 0 or $in =~ s/^, //) {
184 print STDERR "$0: $ARGV: $.: missing comma.\n";
194 # process info, indexed by pid.
197 # seq clones, forks and execs for this pid, in sequence (array)
199 # filename and argv (from latest exec)
200 # basename (derived from filename)
201 # argv[0] is modified to add the basename if it differs from the 0th argument.
206 my ($pid, $call, $args, $result, $time) = @_;
207 my $pid_fqname = $pid . "-" . $time;
209 if (defined $time and not defined $running_fqname{$pid}) {
210 $pr{$pid_fqname}{start} = $time;
211 $running_fqname{$pid} = $pid_fqname;
214 $pid_fqname = $running_fqname{$pid};
216 if ($call eq 'execve') {
217 return if $result ne '0';
219 my ($filename, $argv) = parseargs($args);
220 my ($basename) = $filename =~ m/([^\/]*)$/;
221 if ($basename ne $$argv[0]) {
222 $$argv[0] = "$basename($$argv[0])";
224 my $seq = $pr{$pid_fqname}{seq};
225 $seq = [] if not defined $seq;
227 push @$seq, ['EXEC', $filename, $argv];
229 $pr{$pid_fqname}{seq} = $seq;
230 } elsif ($call eq 'fork' || $call eq 'clone' || $call eq 'vfork') {
231 return if $result == 0;
233 my $seq = $pr{$pid_fqname}{seq};
234 my $result_fqname= $result . "-" . $time;
235 $seq = [] if not defined $seq;
236 push @$seq, ['FORK', $result_fqname];
237 $pr{$pid_fqname}{seq} = $seq;
238 $pr{$result_fqname}{start} = $time;
239 $pr{$result_fqname}{parent} = $pid_fqname;
240 $pr{$result_fqname}{seq} = [];
241 $running_fqname{$result} = $result_fqname;
242 } elsif ($call eq '_exit' || $call eq 'exit_group') {
243 $pr{$running_fqname{$pid}}{end} = $time if defined $time and not defined $pr{$running_fqname{$pid}}{end};
244 delete $running_fqname{$pid};
249 my ($pid, $time) = @_;
250 $pr{$pid}{end} = $time if defined $time and not defined $pr{$pid}{end};
255 my $seq = $pr{$pid}{seq};
257 for my $elem (@$seq) {
258 if ($$elem[0] eq 'EXEC') {
259 my $argv = $$elem[2];
260 print "$$elem[0] $$elem[1] @$argv\n";
261 } elsif ($$elem[0] eq 'FORK') {
262 print "$$elem[0] $$elem[1]\n";
271 my $seq = $pr{$pid}{seq};
273 for my $elem (@$seq) {
274 if ($$elem[0] eq 'EXEC') {
281 sub display_pid_trace {
282 my ($pid, $lead) = @_;
284 my @seq = @{$pr{$pid}{seq}};
287 if (not defined first_exec($pid)) {
288 unshift @seq, ['EXEC', '', ['(anon)'] ];
291 if (defined $pr{$pid}{start} and defined $pr{$pid}{end}) {
292 $elapsed = $pr{$pid}{end} - $pr{$pid}{start};
293 $elapsed /= $scale_factor;
295 $elapsed = sprintf("%0.02f", $elapsed);
297 $elapsed = int $elapsed;
301 for my $elem (@seq) {
303 if ($$elem[0] eq 'EXEC') {
304 my $argv = $$elem[2];
305 if (defined $elapsed) {
306 print "$lead [$elapsed] $pid @$argv\n";
309 print "$lead $pid @$argv\n";
311 } elsif ($$elem[0] eq 'FORK') {
314 display_pid_trace($$elem[1], "$lead--+--");
316 display_pid_trace($$elem[1], "$lead +--");
318 } elsif ($i == @seq) {
319 display_pid_trace($$elem[1], "$lead `--");
321 display_pid_trace($$elem[1], "$lead +--");
335 $startpid = (keys %pr)[0];
336 while ($pr{$startpid}{parent}) {
337 $startpid = $pr{$startpid}{parent};
340 display_pid_trace($startpid, "");