File Coverage

lib/InfluxDB/LineProtocol.pm
Criterion Covered Total %
statement 198 203 97.5
branch 47 60 78.3
condition 10 12 83.3
subroutine 23 23 100.0
pod 2 8 25.0
total 280 306 91.5


line stmt bran cond sub pod time code
1             package InfluxDB::LineProtocol;
2 4     4   287805 use strict;
  4         25  
  4         97  
3 4     4   13 use warnings;
  4         6  
  4         196  
4              
5             # ABSTRACT: Write and read InfluxDB LineProtocol
6              
7             our $VERSION = '1.015'; # VERSION
8              
9 4     4   19 use Carp qw(croak);
  4         5  
  4         184  
10 4     4   416 use Time::HiRes qw(gettimeofday);
  4         1121  
  4         22  
11              
12             my %versions = (
13             'v0.9.2' => '_0_9_2',
14             );
15              
16             sub import {
17 10     10   2357 my $class = shift;
18 10         28 my $caller = caller();
19              
20 10         149 my @to_export;
21             my $version;
22 10         11 my $precision = 'ns';
23 10         15 foreach my $param (@_) {
24 20 100 100     63 if ($param eq 'data2line' || $param eq 'line2data') {
25 13         18 push(@to_export,$param);
26             }
27 20 100       47 if ($param =~ /^precision=(\w+)$/) {
28 6         11 $precision = $1;
29             }
30 20 50 66     43 if ($param =~ /^v[\d\.]+$/ && $versions{$param}) {
31 1         2 $version = $versions{$param};
32             }
33             }
34              
35 10         14 foreach my $function (@to_export) {
36 13         11 my $target = $function;
37 13 100       20 $function = '_'.$function.$version if $version;
38              
39             {
40 4     4   1071 no strict 'refs';
  4         4  
  4         269  
  13         12  
41 13         20 *{"$caller\::$target"} = \&$function;
  13         48  
42             }
43             }
44              
45             # set up ts_$precision
46             {
47 4     4   25 no strict 'refs';
  4         5  
  4         7960  
  10         11  
48 10         12 my $selected = 'ts_'.$precision;
49 10         13 *{"$caller\::get_ts"} = \&$selected;
  10         151  
50             }
51              
52             }
53              
54             sub _format_key {
55 40     40   36 my $k = shift;
56              
57 40         45 $k =~ s/([, ])/\\$1/g;
58              
59 40         45 return $k;
60             }
61              
62             sub _format_measurement {
63 49     49   73 my $measurement = shift;
64              
65 49         161 $measurement =~ s/([, ])/\\$1/g;
66 49         81 return $measurement;
67             }
68              
69             sub _format_tag_key {
70 18     18   20 my $key = shift;
71              
72 18         43 $key =~ s/([, =])/\\$1/g;
73 18         25 return $key;
74             }
75              
76             sub _format_tag_value {
77 18     18   23 my $value = shift;
78              
79 18         50 $value =~ s/([, =])/\\$1/g;
80 18         23 return $value;
81             }
82              
83             sub _format_field_key {
84 56     56   71 my $k = shift;
85              
86 56         85 $k =~ s/([, =])/\\$1/g;
87              
88 56         66 return $k;
89             }
90              
91             sub _format_field_value {
92 56     56   53 my $v = shift;
93              
94 56 100       317 if ( $v =~ /^(-?\d+)(?:i?)$/ ) {
    100          
    100          
    100          
95 32         74 $v = $1 . 'i';
96             }
97             elsif ( $v =~ /^[Ff](?:ALSE|alse)?$/ ) {
98 3         4 $v = 'FALSE';
99             }
100             elsif ( $v =~ /^[Tt](?:RUE|rue)?$/ ) {
101 2         5 $v = 'TRUE';
102             }
103             elsif ( $v =~ /^-?\d+(?:\.\d+)?(?:e(?:-|\+)?\d+)?$/ ) {
104             # pass it on, no mod
105             }
106             else {
107             # string actually, but this should be quoted differently?
108 13         40 $v =~ s/(["\\])/\\$1/g;
109 13         26 $v = '"' . $v . '"';
110             }
111              
112 56         87 return $v;
113             }
114              
115             sub data2line {
116 49     49 1 100876 my ( $measurement, $values, $tags, $timestamp ) = @_;
117 49         181 my $caller = caller();
118              
119 49 50       763 if ( @_ == 1 ) {
120             # no $fields, so assume we already got a line
121 0         0 return $measurement;
122             }
123              
124 49         95 my $key = _format_measurement($measurement);
125              
126             # $tags has to be a hashref, if it's not, we don't have tags, so it's the timestamp
127 49 100       126 if ( defined $tags ) {
128 16 100       46 if ( ref($tags) eq 'HASH' ) {
    50          
129 13         17 my @tags;
130 13         45 foreach my $k ( sort keys %$tags )
131             { # Influx wants the tags presorted
132             # TODO check if sorting algorithm matches
133             # http://golang.org/pkg/bytes/#Compare
134 18         26 my $v = $tags->{$k};
135 18 50       32 next unless defined $v;
136              
137 18         27 my $esc_k = _format_tag_key($k);
138 18         62 my $esc_v = _format_tag_value($v);
139              
140 18         48 push( @tags, $esc_k . '=' . $esc_v );
141             }
142 13 50       49 $key .= join( ',', '', @tags ) if @tags;
143             }
144             elsif ( !ref($tags) ) {
145 3         5 $timestamp = $tags;
146             }
147             }
148              
149 49   66     187 $timestamp ||= $caller->get_ts();
150 49 50       549 croak("$timestamp does not look like an epoch timestamp")
151             unless $timestamp =~ /^\d+$/;
152              
153             # If values is not a hashref, convert it into one
154 49 100       147 $values = { value => $values } if (not ref($values));
155              
156 49         60 my @fields;
157 49         130 foreach my $k ( sort keys %$values ) {
158 56         83 my $v = $values->{$k};
159              
160 56         97 my $esc_k = _format_field_key($k);
161 56         88 my $esc_v = _format_field_value($v);
162              
163 56         129 push( @fields, $esc_k . '=' . $esc_v );
164             }
165 49         113 my $fields = join( ',', @fields );
166              
167 49         275 return sprintf( "%s %s %s", $key, $fields, $timestamp );
168             }
169              
170             sub ts_h {
171 2     2 0 2 my $now = time();
172 2         8 return int $now / 3600;
173             }
174              
175             sub ts_m {
176 2     2 0 4 my $now = time();
177 2         8 return int $now / 60;
178             }
179              
180             sub ts_s {
181 2     2 0 5 return scalar time();
182             }
183              
184             sub ts_ms {
185 2     2 0 393 my ($s,$us) = gettimeofday();
186 2         21 return sprintf("%s%03d", $s,substr($us,0,3));
187             }
188              
189             sub ts_us {
190 2     2 0 661 return sprintf("%s%06d", gettimeofday());
191             }
192              
193             sub ts_ns {
194 39     39 0 164 return sprintf("%s%06d000", gettimeofday());
195             }
196              
197             sub line2data {
198 42     42 1 73 my $line = shift;
199 42         61 chomp($line);
200              
201 42         93 $line =~ s/\\ /ESCAPEDSPACE/g;
202 42         63 $line =~ s/\\,/ESCAPEDCOMMA/g;
203 42         68 $line =~ s/\\"/ESCAPEDDBLQUOTE/g;
204 42         52 $line =~ s/\\\\/ESCAPEDBACKSLASH/g;
205 42         52 $line =~ s/\\=/ESCAPEDEQUALS/g;
206              
207 42         187 $line=~/^(.*?) (.*) (.*)$/;
208 42         149 my ($key, $fields, $timestamp) = ( $1, $2, $3);
209              
210 42         208 my ( $measurement, @taglist ) = split( /,/, $key );
211 42         64 $measurement =~ s/ESCAPEDSPACE/ /g;
212 42         53 $measurement =~ s/ESCAPEDCOMMA/,/g;
213              
214 42         39 my $tags;
215 42         64 foreach my $tagset (@taglist) {
216 18         26 $tagset =~ s/ESCAPEDSPACE/ /g;
217 18         20 $tagset =~ s/ESCAPEDCOMMA/,/g;
218 18         36 my ( $k, $v ) = split( /=/, $tagset );
219 18         20 $k =~ s/ESCAPEDEQUALS/=/g;
220 18         24 $v =~ s/ESCAPEDEQUALS/=/g;
221 18         41 $tags->{$k} = $v;
222             }
223              
224 42         45 my $values;
225             my @strings;
226 42 100       98 if ($fields =~ /"/) {
227 11         13 my $cnt=0;
228 11         46 $fields=~s/"(.*?)"/push(@strings, $1); 'ESCAPEDSTRING_'.$cnt++;/ge;
  13         26  
  13         40  
229             }
230 42         127 foreach my $valset ( split( /,/, $fields ) ) {
231 49         63 $valset =~ s/ESCAPEDSPACE/ /g;
232 49         60 $valset =~ s/ESCAPEDCOMMA/,/g;
233 49         100 my ( $k, $v ) = split( /=/, $valset );
234 49         84 $v =~ s/ESCAPEDSTRING_(\d+)/$strings[$1]/ge;
  13         25  
235 49         66 $v =~ s/ESCAPEDDBLQUOTE/"/g;
236 49         70 $v =~ s/ESCAPEDBACKSLASH/\\/g;
237 49         111 $v =~ s/^(-?\d+)i$/$1/;
238 49         51 $k =~ s/ESCAPEDBACKSLASH/\\\\/g;
239 49         49 $k =~ s/ESCAPEDEQUALS/=/g;
240 49         112 $values->{$k} = $v;
241             }
242              
243 42         140 return ( $measurement, $values, $tags, $timestamp );
244             }
245              
246             sub _data2line_0_9_2 {
247 34     34   75391 my ( $measurement, $values, $tags, $timestamp ) = @_;
248              
249 34 50       73 if ( @_ == 1 ) {
250             # no $fields, so assume we already got a line
251 0         0 return $measurement;
252             }
253              
254 34         33 my $key = $measurement;
255 34         84 $key =~ s/([, ])/\\$1/g;
256              
257             # $tags has to be a hashref, if it's not, we don't have tags, so it's the timestamp
258 34 100       54 if ( defined $tags ) {
259 12 100       26 if ( ref($tags) eq 'HASH' ) {
    50          
260 9         10 my @tags;
261 9         23 foreach my $k ( sort keys %$tags )
262             { # Influx wants the tags presorted
263             # TODO check if sorting algorithm matches
264             # http://golang.org/pkg/bytes/#Compare
265 13         15 my $v = $tags->{$k};
266 13 50       17 next unless defined $v;
267 13         23 $k =~ s/([, ])/\\$1/g;
268 13         20 $v =~ s/([, ])/\\$1/g;
269 13         28 push( @tags, $k . '=' . $v );
270             }
271 9 50       25 $key .= join( ',', '', @tags ) if @tags;
272             }
273             elsif ( !ref($tags) ) {
274 3         4 $timestamp = $tags;
275             }
276             }
277              
278 34 100       44 if ($timestamp) {
279 6 50       23 croak("$timestamp does not look like an epoch timestamp")
280             unless $timestamp =~ /^\d+$/;
281 6 50       10 if ( length($timestamp) < 19 ) {
282 0         0 my $missing = 19 - length($timestamp);
283 0         0 my $zeros = 0 x $missing;
284 0         0 $timestamp .= $zeros;
285             }
286             }
287             else {
288 28         41 $timestamp = join( '', gettimeofday(), '000' );
289 28 50       113 $timestamp .= '0' if length($timestamp) < 19;
290             }
291              
292             # If values is not a hashref, convert it into one
293 34 100       64 $values = { value => $values } if (not ref($values));
294              
295 34         32 my @fields;
296 34         76 foreach my $k ( sort keys %$values ) {
297 40         43 my $v = $values->{$k};
298 40         45 my $esc_k = _format_key($k);
299              
300 40 100 100     191 if (
301             # positive & negativ ints, exponentials, use Regexp::Common?
302             $v !~ /^-?\d+(?:\.\d+)?(?:e-?\d+)?$/
303             &&
304             # perl 5.12 Regexp::Assemble->new->add(qw(t T true TRUE f F false FALSE))->re;
305             $v !~ /^(?:F(?:ALSE)?|f(?:alse)?|T(?:RUE)?|t(?:rue)?)$/
306             )
307             {
308 13         21 $v =~ s/"/\\"/g;
309 13         20 $v = '"' . $v . '"';
310             }
311 40         76 push( @fields, $esc_k . '=' . $v );
312             }
313 34         49 my $fields = join( ',', @fields );
314              
315 34         168 return sprintf( "%s %s %s", $key, $fields, $timestamp );
316             }
317              
318             sub _line2data_0_9_2 {
319 34     34   42 my $line = shift;
320 34         37 chomp($line);
321              
322 34         65 $line =~ s/\\ /ESCAPEDSPACE/g;
323 34         41 $line =~ s/\\,/ESCAPEDCOMMA/g;
324 34         35 $line =~ s/\\"/ESCAPEDDBLQUOTE/g;
325              
326 34         132 $line=~/^(.*?) (.*) (.*)$/;
327 34         89 my ($key, $fields, $timestamp) = ( $1, $2, $3);
328              
329 34         67 my ( $measurement, @taglist ) = split( /,/, $key );
330 34         52 $measurement =~ s/ESCAPEDSPACE/ /g;
331 34         31 $measurement =~ s/ESCAPEDCOMMA/,/g;
332              
333 34         27 my $tags;
334 34         39 foreach my $tagset (@taglist) {
335 13         15 $tagset =~ s/ESCAPEDSPACE/ /g;
336 13         16 $tagset =~ s/ESCAPEDCOMMA/,/g;
337 13         21 my ( $k, $v ) = split( /=/, $tagset );
338 13         26 $tags->{$k} = $v;
339             }
340              
341 34         34 my $values;
342             my @strings;
343 34 100       62 if ($fields =~ /"/) {
344 11         11 my $cnt=0;
345 11         35 $fields=~s/"(.*?)"/push(@strings, $1); 'ESCAPEDSTRING_'.$cnt++;/ge;
  13         22  
  13         34  
346             }
347 34         50 foreach my $valset ( split( /,/, $fields ) ) {
348 40         38 $valset =~ s/ESCAPEDSPACE/ /g;
349 40         32 $valset =~ s/ESCAPEDCOMMA/,/g;
350 40         56 my ( $k, $v ) = split( /=/, $valset );
351 40         58 $v =~ s/ESCAPEDSTRING_(\d+)/$strings[$1]/ge;
  13         28  
352 40         37 $v =~ s/ESCAPEDDBLQUOTE/"/g;
353 40         73 $values->{$k} = $v;
354             }
355              
356 34         100 return ( $measurement, $values, $tags, $timestamp );
357             }
358              
359             1;
360              
361             __END__