File Coverage

blib/lib/Sys/Trace/Impl/Ktrace.pm
Criterion Covered Total %
statement 15 68 22.0
branch 0 32 0.0
condition 0 12 0.0
subroutine 5 12 41.6
pod 0 6 0.0
total 20 130 15.3


line stmt bran cond sub pod time code
1             package Sys::Trace::Impl::Ktrace;
2 1     1   3350 use strict;
  1         16  
  1         71  
3              
4 1     1   7 use Cwd ();
  1         1  
  1         17  
5 1     1   5 use File::Spec ();
  1         1  
  1         30  
6 1     1   7 use File::Temp ();
  1         3  
  1         27  
7 1     1   5 use POSIX ();
  1         2  
  1         999  
8              
9             =head1 NAME
10              
11             Sys::Trace::Impl::Ktrace - Sys::Trace implementation for ktrace(1)
12              
13             =head1 DESCRIPTION
14              
15             This should not normally be used directly, instead use L which will
16             pick a suitable interface for your platform.
17              
18             =cut
19              
20             sub usable {
21 0     0 0   system q{ktrace 2>/dev/null};
22 0   0       return POSIX::WIFEXITED($?) && POSIX::WEXITSTATUS($?) == 1;
23             }
24              
25             sub new {
26 0     0 0   my($class, %args) = @_;
27 0           my $self = bless {}, $class;
28              
29 0           my @run = "ktrace";
30              
31 0 0         if($args{follow_forks}) {
32 0           push @run, "-d";
33             }
34              
35             # TODO: Support saving this elsewhere for offline processing?
36 0           $self->{temp} = File::Temp->new;
37 0           push @run, "-f", $self->{temp};
38              
39 0 0         if($args{exec}) {
    0          
40 0           push @run, ref $args{exec}
41 0 0         ? @{$args{exec}}
42             : (qw(sh -c), $args{exec});
43             } elsif($args{pid}) {
44 0           push @run, "-p", $args{pid};
45             }
46              
47 0           $self->{run} = \@run;
48              
49 0           return $self;
50             }
51              
52             sub call {
53 0     0 0   my($self, @calls) = @_;
54              
55             # Ktrace doesn't have an similar thing to strace's -e option. We filter out
56             # afterwards instead.
57              
58 0           push @{$self->{only}}, @calls;
  0            
59             }
60              
61             sub run {
62 0     0 0   my($self) = @_;
63 0           $self->{cwd} = Cwd::getcwd;
64 0 0         exec @{$self->{run}} or die "Unable to exec: $!";
  0            
65             }
66              
67             sub pid {
68 0     0 0   my($self, $pid) = @_;
69 0 0         $self->{pid} = $pid if defined $pid;
70 0           $self->{pid};
71             }
72              
73             my $line_re = qr{^\s
74             ([0-9]+)\s+ # pid
75             (\w+)\s+ # program
76             ([0-9.]+)\s+ # time
77             (\w+)\s+ # type
78             (.*) # args
79             $}x;
80             my @line_names = qw(pid program time type args);
81              
82             my $call_re = qr{^(\w+)(?:\((.*)\))?$};
83             my @call_names = qw(call args);
84              
85             my $ret_re = qr{^\w+ ([0-9]+)(?: errno ([0-9]+) (.*))?};
86             my @ret_names = qw(return errno sterror);
87              
88             sub parse {
89 0     0 0   my($self, $out_fh) = @_;
90              
91 0 0         if(!$out_fh) {
92 0 0         open $out_fh, "-|", "kdump", "-f", $self->{temp}, "-T" or die $!;
93             }
94              
95             # List of calls to filter on
96 0           my %only;
97 0 0         $only{@{$self->{only}}} = () if $self->{only};
  0            
98              
99 0           my @calls;
100             my %cur;
101 0           while(<$out_fh>) {
102 0           my %call;
103 0           @call{@line_names} = $_ =~ $line_re;
104              
105 0 0         if($call{pid}) {
106 0 0 0       if($call{type} eq 'CALL') {
    0          
    0          
107             # Reset %cur, first call
108 0           %cur = %call;
109 0           delete $cur{type}; # Meaningless once parsed
110              
111             # Add additional info
112 0           @cur{@call_names} = $call{args} =~ $call_re;
113 0           $cur{args} = [split /,/, $cur{args}];
114            
115             } elsif($call{type} eq 'NAMI') {
116             # Name for something
117 0           $cur{name} = _parse_str($call{args});
118              
119 0 0         if($cur{name} !~ m{^/}) {
120             # Resolve realtive paths
121 0           $cur{name} = File::Spec->rel2abs($cur{name}, $self->{cwd});
122             }
123              
124             } elsif($call{type} eq 'RET' && %cur) {
125             # Return
126 0           @call{@ret_names} = $call{args} =~ $ret_re;
127              
128 0           $cur{systime} = $call{time} - $cur{time};
129 0           $cur{walltime} = delete $cur{time};
130              
131 0 0 0       if($cur{call} eq 'chdir' && $cur{return} == 0) {
132 0           $self->{cwd} = $cur{name};
133             }
134              
135 0 0 0       push @calls, {%cur} if !%only || exists $only{$cur{call}};
136             }
137             } else {
138             # Probably GIO output, ignore for now
139             }
140             }
141              
142 0           return \@calls;
143             }
144              
145             sub _parse_str {
146 0     0     my($str) = @_;
147 0           return ($str =~ /^"(.*)"$/)[0];
148             }
149              
150             1;