File Coverage

blib/lib/AWS/XRay.pm
Criterion Covered Total %
statement 115 127 90.5
branch 31 48 64.5
condition 11 19 57.8
subroutine 27 28 96.4
pod 8 11 72.7
total 192 233 82.4


line stmt bran cond sub pod time code
1             package AWS::XRay;
2              
3 10     10   161354 use 5.012000;
  10         90  
4 10     10   50 use strict;
  10         30  
  10         233  
5 10     10   57 use warnings;
  10         19  
  10         259  
6              
7 10     10   4445 use Crypt::URandom ();
  10         54654  
  10         271  
8 10     10   5009 use IO::Socket::INET;
  10         203552  
  10         55  
9 10     10   9206 use Module::Load;
  10         11130  
  10         61  
10 10     10   5310 use Time::HiRes ();
  10         12967  
  10         287  
11 10     10   4488 use Types::Serialiser;
  10         35126  
  10         345  
12 10     10   4145 use AWS::XRay::Segment;
  10         25  
  10         296  
13 10     10   3907 use AWS::XRay::Buffer;
  10         26  
  10         310  
14              
15 10     10   62 use Exporter 'import';
  10         17  
  10         12203  
16             our @EXPORT_OK = qw/ new_trace_id capture capture_from trace /;
17              
18             our $VERSION = "0.10";
19              
20             our $TRACE_ID;
21             our $SEGMENT_ID;
22             our $ENABLED;
23             our $SAMPLED;
24             our $SAMPLING_RATE = 1;
25             our $SAMPLER = sub { rand() < $SAMPLING_RATE };
26             our $AUTO_FLUSH = 1;
27              
28             our @PLUGINS;
29              
30             our $DAEMON_HOST = "127.0.0.1";
31             our $DAEMON_PORT = 2000;
32              
33             if ($ENV{"AWS_XRAY_DAEMON_ADDRESS"}) {
34             ($DAEMON_HOST, $DAEMON_PORT) = split /:/, $ENV{"AWS_XRAY_DAEMON_ADDRESS"};
35             }
36              
37             my $Sock;
38              
39             sub sampling_rate {
40 14     14 1 23246 my $class = shift;
41 14 50       49 if (@_) {
42 14         25 $SAMPLING_RATE = shift;
43             }
44 14         29 $SAMPLING_RATE;
45             }
46              
47             sub sampler {
48 4     4 1 14231 my $class = shift;
49 4 50       15 if (@_) {
50 4         28 $SAMPLER = shift;
51             }
52 4         10 $SAMPLER;
53             }
54              
55             sub plugins {
56 1     1 0 89 my $class = shift;
57 1 50       4 if (@_) {
58 1         4 @PLUGINS = @_;
59 1         7 Module::Load::load $_ for @PLUGINS;
60             }
61 1         18 @PLUGINS;
62             }
63              
64             sub auto_flush {
65 0     0 1 0 my $class = shift;
66 0 0       0 if (@_) {
67 0         0 my $auto_flush = shift;
68 0 0       0 if ($auto_flush != $AUTO_FLUSH) {
69 0 0 0     0 $Sock->close if $Sock && $Sock->can("close");
70 0         0 undef $Sock; # regenerate
71             }
72 0         0 $AUTO_FLUSH = $auto_flush;
73             }
74 0         0 $AUTO_FLUSH;
75             }
76              
77             sub sock {
78 3   50 3 0 21 $Sock //= AWS::XRay::Buffer->new(
      50        
      66        
79             IO::Socket::INET->new(
80             PeerAddr => $DAEMON_HOST || "127.0.0.1",
81             PeerPort => $DAEMON_PORT || 2000,
82             Proto => "udp",
83             ),
84             $AUTO_FLUSH,
85             );
86             }
87              
88             sub new_trace_id {
89 1511     1511 1 3508 sprintf(
90             "1-%x-%s",
91             CORE::time(),
92             unpack("H*", Crypt::URandom::urandom(12)),
93             );
94             }
95              
96             sub new_id {
97 2222     2222 0 8482 unpack("H*", Crypt::URandom::urandom(8))
98             }
99              
100             # alias for backward compatibility
101             *trace = \&capture;
102              
103             sub capture {
104 4433     4433 1 423735 my ($name, $code) = @_;
105 4433         5775 my $wantarray = wantarray;
106              
107 4433         5351 my $enabled;
108 4433         5591 my $sampled = $SAMPLED;
109 4433 100       7677 if (defined $ENABLED) {
    50          
110 2412 100       3945 $enabled = $ENABLED ? 1 : 0; # fix true or false (not undef)
111             } elsif ($TRACE_ID) {
112 0         0 $enabled = 0; # called from parent capture
113             } else {
114             # root capture try sampling
115 2021 100       3215 $sampled = $SAMPLER->() ? 1 : 0;
116 2021 100       6434 $enabled = $sampled ? 1 : 0;
117             }
118 4433         5702 local $ENABLED = $enabled;
119 4433         5601 local $SAMPLED = $sampled;
120              
121 4433 100       11642 return $code->(AWS::XRay::Segment->new) if !$enabled;
122              
123 2221   66     4846 local $TRACE_ID = $TRACE_ID // new_trace_id();
124              
125 2221         90908 my $segment = AWS::XRay::Segment->new({ name => $name });
126 2221 100 66     7889 unless (defined $segment->{type} && $segment->{type} eq "subsegment") {
127 1510         3018 $_->apply_plugin($segment) for @PLUGINS;
128             }
129              
130 2221         3762 local $SEGMENT_ID = $segment->{id};
131              
132 2221         2797 my @ret;
133 2221         2891 eval {
134 2221 100       4317 if ($wantarray) {
    100          
135 1         3 @ret = $code->($segment);
136             }
137             elsif (defined $wantarray) {
138 3         10 $ret[0] = $code->($segment);
139             }
140             else {
141 2217         4672 $code->($segment);
142             }
143             };
144 2221         407689 my $error = $@;
145 2221 50       3798 if ($error) {
146 0         0 $segment->{error} = Types::Serialiser::true;
147             $segment->{cause} = {
148 0         0 exceptions => [
149             {
150             id => new_id(),
151             message => "$error",
152             remote => Types::Serialiser::true,
153             },
154             ],
155             };
156             }
157 2221         2683 eval {
158 2221         4463 $segment->close();
159             };
160 2221 50       446917 if ($@) {
161 0         0 warn $@;
162             }
163 2221 50       3978 die $error if $error;
164 2221 100       11198 return $wantarray ? @ret : $ret[0];
165             }
166              
167             sub capture_from {
168 1004     1004 1 8430 my ($header, $name, $code) = @_;
169 1004         1569 my ($trace_id, $segment_id, $sampled) = parse_trace_header($header);
170              
171 1004   100     2461 local $AWS::XRay::SAMPLED = $sampled // $SAMPLER->();
172 1004         4866 local $AWS::XRay::ENABLED = $AWS::XRay::SAMPLED;
173 1004         1602 local($AWS::XRay::TRACE_ID, $AWS::XRay::SEGMENT_ID) = ($trace_id, $segment_id);
174 1004         1621 capture($name, $code);
175             }
176              
177             sub parse_trace_header {
178 1004 100   1004 1 2462 my $header = shift or return;
179              
180 3         7 my ($trace_id, $segment_id, $sampled);
181 3 50       21 if ($header =~ /Root=([0-9a-fA-F-]+)/) {
182 3         12 $trace_id = $1;
183             }
184 3 50       13 if ($header =~ /Parent=([0-9a-fA-F]+)/) {
185 3         7 $segment_id = $1;
186             }
187 3 50       15 if ($header =~ /Sampled=([^;]+)/) {
188 3         8 $sampled = $1;
189             }
190 3         11 return ($trace_id, $segment_id, $sampled);
191             }
192              
193             sub add_capture {
194 2     2 1 102 my ($class, $package, @methods) = @_;
195 10     10   205 no warnings 'redefine';
  10         24  
  10         514  
196 10     10   146 no strict 'refs';
  10         26  
  10         2635  
197 2         7 for my $method (@methods) {
198 2 50       28 my $orig = $package->can($method) or next;
199 2         16 *{"${package}::${method}"} = sub {
200 2     2   9 my @args = @_;
201             capture(
202             $package,
203             sub {
204 2     2   6 my $segment = shift;
205 2         9 $segment->{metadata}->{method} = $method;
206 2         6 $segment->{metadata}->{package} = $package;
207 2         13 $orig->(@args);
208             },
209 2         11 );
210 2         14 };
211             }
212             }
213              
214             if ($ENV{LAMBDA_TASK_ROOT}) {
215             # AWS::XRay is loaded in AWS Lambda worker.
216             # notify the Lambda Runtime that initialization is complete.
217             mkdir '/tmp/.aws-xray' or warn "failed to make directory: $!";
218             open my $fh, '>', '/tmp/.aws-xray/initialized' or warn "failed to create file: $!";
219             close $fh;
220             utime undef, undef, '/tmp/.aws-xray/initialized' or warn "failed to touch file: $!";
221              
222             # patch the capture
223 10     10   74 no warnings 'redefine';
  10         18  
  10         360  
224 10     10   63 no strict 'refs';
  10         28  
  10         1864  
225             my $org = \&capture;
226             *capture = sub {
227             my ($trace_id, $segment_id, $sampled) = parse_trace_header($ENV{_X_AMZN_TRACE_ID});
228             local $AWS::XRay::SAMPLED = $sampled // $SAMPLER->();
229             local $AWS::XRay::ENABLED = $AWS::XRay::SAMPLED;
230             local($AWS::XRay::TRACE_ID, $AWS::XRay::SEGMENT_ID) = ($trace_id, $segment_id);
231             local *capture = $org;
232             local *trace = $org;
233             capture(@_);
234             };
235             *trace = \&capture;
236             }
237              
238             1;
239             __END__