Mirror of strace – the linux syscall tracer
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

strace-graph 7.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. #!/usr/bin/perl
  2. # This script processes strace -f output. It displays a graph of invoked
  3. # subprocesses, and is useful for finding out what complex commands do.
  4. # You will probably want to invoke strace with -q as well, and with
  5. # -s 100 to get complete filenames.
  6. # The script can also handle the output with strace -t, -tt, or -ttt.
  7. # It will add elapsed time for each process in that case.
  8. # Copyright (c) 1998 by Richard Braakman <dark@xs4all.nl>.
  9. # Copyright (c) 1998-2018 The strace developers.
  10. # SPDX-License-Identifier: LGPL-2.1-or-later
  11. use strict;
  12. use warnings;
  13. my %unfinished;
  14. my $floatform;
  15. # Scales for strace slowdown. Make configurable!
  16. my $scale_factor = 3.5;
  17. my %running_fqname;
  18. while (<>) {
  19. my ($pid, $call, $args, $result, $time, $time_spent);
  20. chop;
  21. $floatform = 0;
  22. s/^(\d+)\s+//;
  23. $pid = $1;
  24. if (s/^(\d\d):(\d\d):(\d\d)(?:\.(\d\d\d\d\d\d))? //) {
  25. $time = $1 * 3600 + $2 * 60 + $3;
  26. if (defined $4) {
  27. $time = $time + $4 / 1000000;
  28. $floatform = 1;
  29. }
  30. } elsif (s/^(\d+)\.(\d\d\d\d\d\d) //) {
  31. $time = $1 + ($2 / 1000000);
  32. $floatform = 1;
  33. }
  34. if (s/ <unfinished ...>$//) {
  35. $unfinished{$pid} = $_;
  36. next;
  37. }
  38. if (s/^<... \S+ resumed> //) {
  39. unless (exists $unfinished{$pid}) {
  40. print STDERR "$0: $ARGV: cannot find start of resumed call on line $.";
  41. next;
  42. }
  43. $_ = $unfinished{$pid} . $_;
  44. delete $unfinished{$pid};
  45. }
  46. if (/^--- SIG(\S+) (.*) ---$/) {
  47. # $pid received signal $1
  48. # currently we don't do anything with this
  49. next;
  50. }
  51. if (/^\+\+\+ killed by SIG(\S+) \+\+\+$/) {
  52. # $pid received signal $1
  53. handle_killed($pid, $time);
  54. next;
  55. }
  56. if (/^\+\+\+ exited with (\d+) \+\+\+$/) {
  57. # $pid exited $1
  58. # currently we don't do anything with this
  59. next;
  60. }
  61. ($call, $args, $result) = /(\S+)\((.*)\)\s+= (.*)$/;
  62. if ($result =~ /^(.*) <([0-9.]*)>$/) {
  63. ($result, $time_spent) = ($1, $2);
  64. }
  65. unless (defined $result) {
  66. print STDERR "$0: $ARGV: $.: cannot parse line.\n";
  67. next;
  68. }
  69. handle_trace($pid, $call, $args, $result, $time);
  70. }
  71. display_trace();
  72. exit 0;
  73. sub parse_str {
  74. my ($in) = @_;
  75. my $result = "";
  76. while (1) {
  77. if ($in =~ s/^\\(.)//) {
  78. $result .= $1;
  79. } elsif ($in =~ s/^\"//) {
  80. if ($in =~ s/^\.\.\.//) {
  81. return ("$result...", $in);
  82. }
  83. return ($result, $in);
  84. } elsif ($in =~ s/([^\\\"]*)//) {
  85. $result .= $1;
  86. } else {
  87. return (undef, $in);
  88. }
  89. }
  90. }
  91. sub parse_one {
  92. my ($in) = @_;
  93. if ($in =~ s/^\"//) {
  94. my $tmp;
  95. ($tmp, $in) = parse_str($in);
  96. if (not defined $tmp) {
  97. print STDERR "$0: $ARGV: $.: cannot parse string.\n";
  98. return (undef, $in);
  99. }
  100. return ($tmp, $in);
  101. } elsif ($in =~ s/^0x([[:xdigit:]]+)//) {
  102. return (hex $1, $in);
  103. } elsif ($in =~ s/^(\d+)//) {
  104. return (int $1, $in);
  105. } else {
  106. print STDERR "$0: $ARGV: $.: unrecognized element.\n";
  107. return (undef, $in);
  108. }
  109. }
  110. sub parseargs {
  111. my ($in) = @_;
  112. my @args = ();
  113. my $tmp;
  114. while (length $in) {
  115. if ($in =~ s/^\[//) {
  116. my @subarr = ();
  117. if ($in =~ s,^/\* (\d+) vars \*/\],,) {
  118. push @args, $1;
  119. } else {
  120. while ($in !~ s/^\]//) {
  121. ($tmp, $in) = parse_one($in);
  122. defined $tmp or return undef;
  123. push @subarr, $tmp;
  124. unless ($in =~ /^\]/ or $in =~ s/^, //) {
  125. print STDERR "$0: $ARGV: $.: missing comma in array.\n";
  126. return undef;
  127. }
  128. if ($in =~ s/^\.\.\.//) {
  129. push @subarr, "...";
  130. }
  131. }
  132. push @args, \@subarr;
  133. }
  134. } elsif ($in =~ s/^\{//) {
  135. my %subhash = ();
  136. while ($in !~ s/^\}//) {
  137. my $key;
  138. unless ($in =~ s/^(\w+)=//) {
  139. print STDERR "$0: $ARGV: $.: struct field expected.\n";
  140. return undef;
  141. }
  142. $key = $1;
  143. ($tmp, $in) = parse_one($in);
  144. defined $tmp or return undef;
  145. $subhash{$key} = $tmp;
  146. unless ($in =~ s/, //) {
  147. print STDERR "$0: $ARGV: $.: missing comma in struct.\n";
  148. return undef;
  149. }
  150. }
  151. push @args, \%subhash;
  152. } else {
  153. ($tmp, $in) = parse_one($in);
  154. defined $tmp or return undef;
  155. push @args, $tmp;
  156. }
  157. unless (length($in) == 0 or $in =~ s/^, //) {
  158. print STDERR "$0: $ARGV: $.: missing comma.\n";
  159. return undef;
  160. }
  161. }
  162. return @args;
  163. }
  164. my $depth = "";
  165. # process info, indexed by pid.
  166. # fields:
  167. # parent pid number
  168. # seq clones, forks and execs for this pid, in sequence (array)
  169. # filename and argv (from latest exec)
  170. # basename (derived from filename)
  171. # argv[0] is modified to add the basename if it differs from the 0th argument.
  172. my %pr;
  173. sub handle_trace {
  174. my ($pid, $call, $args, $result, $time) = @_;
  175. my $pid_fqname = $pid . "-" . $time;
  176. if (defined $time and not defined $running_fqname{$pid}) {
  177. $pr{$pid_fqname}{start} = $time;
  178. $running_fqname{$pid} = $pid_fqname;
  179. }
  180. $pid_fqname = $running_fqname{$pid};
  181. if ($call eq 'execve') {
  182. return if $result ne '0';
  183. my ($filename, $argv) = parseargs($args);
  184. my ($basename) = $filename =~ m/([^\/]*)$/;
  185. if ($basename ne $$argv[0]) {
  186. $$argv[0] = "$basename($$argv[0])";
  187. }
  188. my $seq = $pr{$pid_fqname}{seq};
  189. $seq = [] if not defined $seq;
  190. push @$seq, ['EXEC', $filename, $argv];
  191. $pr{$pid_fqname}{seq} = $seq;
  192. } elsif ($call eq 'fork' || $call eq 'clone' || $call eq 'vfork') {
  193. return if $result == 0;
  194. my $seq = $pr{$pid_fqname}{seq};
  195. my $result_fqname= $result . "-" . $time;
  196. $seq = [] if not defined $seq;
  197. push @$seq, ['FORK', $result_fqname];
  198. $pr{$pid_fqname}{seq} = $seq;
  199. $pr{$result_fqname}{start} = $time;
  200. $pr{$result_fqname}{parent} = $pid_fqname;
  201. $pr{$result_fqname}{seq} = [];
  202. $running_fqname{$result} = $result_fqname;
  203. } elsif ($call eq '_exit' || $call eq 'exit_group') {
  204. $pr{$running_fqname{$pid}}{end} = $time if defined $time and not defined $pr{$running_fqname{$pid}}{end};
  205. delete $running_fqname{$pid};
  206. }
  207. }
  208. sub handle_killed {
  209. my ($pid, $time) = @_;
  210. $pr{$pid}{end} = $time if defined $time and not defined $pr{$pid}{end};
  211. }
  212. sub straight_seq {
  213. my ($pid) = @_;
  214. my $seq = $pr{$pid}{seq};
  215. for my $elem (@$seq) {
  216. if ($$elem[0] eq 'EXEC') {
  217. my $argv = $$elem[2];
  218. print "$$elem[0] $$elem[1] @$argv\n";
  219. } elsif ($$elem[0] eq 'FORK') {
  220. print "$$elem[0] $$elem[1]\n";
  221. } else {
  222. print "$$elem[0]\n";
  223. }
  224. }
  225. }
  226. sub first_exec {
  227. my ($pid) = @_;
  228. my $seq = $pr{$pid}{seq};
  229. for my $elem (@$seq) {
  230. if ($$elem[0] eq 'EXEC') {
  231. return $elem;
  232. }
  233. }
  234. return undef;
  235. }
  236. sub display_pid_trace {
  237. my ($pid, $lead) = @_;
  238. my $i = 0;
  239. my @seq = @{$pr{$pid}{seq}};
  240. my $elapsed;
  241. if (not defined first_exec($pid)) {
  242. unshift @seq, ['EXEC', '', ['(anon)'] ];
  243. }
  244. if (defined $pr{$pid}{start} and defined $pr{$pid}{end}) {
  245. $elapsed = $pr{$pid}{end} - $pr{$pid}{start};
  246. $elapsed /= $scale_factor;
  247. if ($floatform) {
  248. $elapsed = sprintf("%0.02f", $elapsed);
  249. } else {
  250. $elapsed = int $elapsed;
  251. }
  252. }
  253. for my $elem (@seq) {
  254. $i++;
  255. if ($$elem[0] eq 'EXEC') {
  256. my $argv = $$elem[2];
  257. if (defined $elapsed) {
  258. print "$lead [$elapsed] $pid @$argv\n";
  259. undef $elapsed;
  260. } else {
  261. print "$lead $pid @$argv\n";
  262. }
  263. } elsif ($$elem[0] eq 'FORK') {
  264. if ($i == 1) {
  265. if ($lead =~ /-$/) {
  266. display_pid_trace($$elem[1], "$lead--+--");
  267. } else {
  268. display_pid_trace($$elem[1], "$lead +--");
  269. }
  270. } elsif ($i == @seq) {
  271. display_pid_trace($$elem[1], "$lead `--");
  272. } else {
  273. display_pid_trace($$elem[1], "$lead +--");
  274. }
  275. }
  276. if ($i == 1) {
  277. $lead =~ s/\`--/ /g;
  278. $lead =~ s/-/ /g;
  279. $lead =~ s/\+/|/g;
  280. }
  281. }
  282. }
  283. sub display_trace {
  284. my ($startpid) = @_;
  285. $startpid = (keys %pr)[0];
  286. while ($pr{$startpid}{parent}) {
  287. $startpid = $pr{$startpid}{parent};
  288. }
  289. display_pid_trace($startpid, "");
  290. }