File Coverage

blib/lib/Net/Graphite.pm
Criterion Covered Total %
statement 63 91 69.2
branch 22 50 44.0
condition 7 18 38.8
subroutine 12 16 75.0
pod 7 9 77.7
total 111 184 60.3


line stmt bran cond sub pod time code
1             package Net::Graphite;
2 4     4   251669 use strict;
  4         54  
  4         102  
3 4     4   18 use warnings;
  4         5  
  4         108  
4 4     4   1968 use Errno qw(EINTR);
  4         4895  
  4         315  
5 4     4   25 use Carp qw/confess/;
  4         7  
  4         142  
6 4     4   1693 use IO::Socket::INET;
  4         71124  
  4         22  
7 4     4   1448 use Scalar::Util qw/reftype/;
  4         7  
  4         3690  
8              
9             $Net::Graphite::VERSION = '0.18';
10              
11             our $TEST = 0; # if true, don't send anything to graphite
12              
13             sub new {
14 6     6 0 1805 my $class = shift;
15 6 100 66     38 my %args = @_ == 1 && ref $_[0] eq 'HASH' ? %{$_[0]} : @_;
  1         4  
16              
17 6         78 return bless {
18             host => '127.0.0.1',
19             port => 2003,
20             fire_and_forget => 0,
21             return_connect_error => 0,
22             proto => 'tcp',
23             timeout => 1,
24             # flush_limit
25             # path
26             # transformer
27             %args,
28              
29             # private
30             _flush_buffer => [],
31             # _socket
32             }, $class;
33             }
34              
35             sub send {
36 6     6 1 3441 my $self = shift;
37 6         7 my $value;
38 6 100       19 $value = shift if @_ % 2; # single value passed in
39 6         16 my %args = @_;
40              
41 6 100       14 if ($args{data}) {
42 3   33     15 my $xform = $args{transformer} || $self->transformer;
43 3 50       8 if ($xform) {
44             # FIXME
45 0         0 $self->{_flush_buffer} = [ $xform->($args{data}) ];
46             }
47             else {
48 3 100       10 if (ref $args{data}) {
49 2         10 my $reftype = reftype $args{data};
50              
51             # default transformers
52 2 50       5 if ($reftype eq 'HASH') {
53             # hash structure from Yves
54 2 100       16 my $start_path = $args{path} ? $args{path} : $self->path;
55 2         3 foreach my $epoch (sort {$a <=> $b} keys %{ $args{data} }) {
  2         8  
  2         10  
56 4         11 $self->_fill_lines_for_epoch($epoch, $args{data}{$epoch}, $start_path);
57             }
58             }
59             # TODO - not sure what structure is most useful;
60             # an aref of [$path, $value, $epoch] seems a bit trivial?
61             # elsif ($reftype eq 'ARRAY') {
62             #
63             # }
64             # TODO
65             # elsif ($reftype eq 'CODE') {
66             # my $iter = $args{data};
67             # while (my $text = $iter->()) {
68             # $plaintext .= $text;
69             # }
70             # }
71             # how about sth of DBI? XML? maybe not
72             else {
73 0         0 confess "Arg 'data' passed to send method is a ref but has no transformer";
74             }
75             }
76             else {
77             # passed plaintext without a transformer
78             # FIXME
79 1         3 $self->{_flush_buffer} = [ $args{data} ];
80             }
81             }
82             }
83             else {
84 3 100       8 $value = $args{value} unless defined $value;
85 3   66     12 my $path = $args{path} || $self->path;
86 3   66     8 my $time = $args{time} || time;
87              
88 3         10 $self->{_flush_buffer} = [ "$path $value $time\n" ];
89             }
90              
91 6         20 $self->flush();
92              
93 6         9 return join('', @{ $self->{_flush_buffer} }); # FIXME
  6         42  
94             }
95              
96             sub flush {
97 6     6 1 25 my ($self) = @_;
98             return unless
99 6 50       16 my $flush_buffer = $self->{_flush_buffer};
100 6 50       25 return unless @$flush_buffer;
101              
102 6 50       15 $self->trace($flush_buffer) if $self->{trace};
103              
104 6 50       23 unless ($Net::Graphite::TEST) {
105             # FIXME: need to deal with incompletely-sent metrics
106 0 0       0 if ($self->connect()) {
107 0         0 foreach my $buf (@$flush_buffer) {
108 0         0 while (length($buf)) {
109 0         0 my $res = $self->{_socket}->send($buf);
110 0 0       0 if (not defined $res) {
111 0 0       0 next if $! == EINTR;
112 0         0 last; # not sure what to do here
113             }
114              
115 0         0 substr($buf, 0, $res, '');
116             }
117 0 0 0     0 if (length($buf) && not $self->{fire_and_forget}) {
118 0         0 confess "Error sending data";
119             }
120             }
121             }
122             # I didn't close the socket!
123             }
124             }
125              
126             sub _fill_lines_for_epoch {
127 28     28   42 my ($self, $epoch, $hash, $path) = @_;
128              
129             # still in the "branches"
130 28 100       38 if (ref $hash) {
131 12         32 foreach my $key (sort keys %$hash) {
132 24         33 my $value = $hash->{$key};
133 24         45 $self->_fill_lines_for_epoch($epoch, $value, "$path.$key");
134             }
135             }
136             # reached the "leaf" value
137             else {
138 16         16 push @{ $self->{_flush_buffer} }, "$path $hash $epoch\n";
  16         51  
139             }
140             }
141              
142             sub connect {
143 0     0 1 0 my $self = shift;
144             return $self->{_socket}
145 0 0 0     0 if $self->{_socket} && $self->{_socket}->connected;
146              
147             $self->{_socket} = IO::Socket::INET->new(
148             PeerHost => $self->{host},
149             PeerPort => $self->{port},
150             Proto => $self->{proto},
151             Timeout => $self->{timeout},
152 0         0 );
153              
154 0 0       0 unless ($self->{_socket}) {
155 0 0       0 if ($self->{return_connect_error}) {
    0          
156             # This is probably only used if you call $graphite->connect before ->send
157             # in order to check if there is a connection;
158             # otherwise, it'll just "forget" (without even "firing").
159 0         0 return;
160             }
161             elsif (not $self->{fire_and_forget}) {
162 0         0 confess "Error creating socket: $!";
163             }
164             }
165 0         0 return $self->{_socket};
166             }
167              
168             # if you need to close/flush for some reason
169             sub close {
170 0     0 1 0 my $self = shift;
171 0 0       0 return unless my $socket = delete $self->{_socket};
172 0         0 $socket->close();
173             }
174              
175             sub trace {
176 0     0 0 0 my (undef, $val_line) = @_;
177 0         0 print STDERR $val_line;
178             }
179              
180             ### mutators
181             sub flush_limit {
182 0     0 1 0 my ($self, $limit) = @_;
183 0 0       0 $self->{flush_limit} = $limit if defined $limit;
184 0         0 return $self->{flush_limit};
185             }
186             sub path {
187 3     3 1 15 my ($self, $path) = @_;
188 3 50       9 $self->{path} = $path if defined $path;
189 3         9 return $self->{path};
190             }
191             sub transformer {
192 3     3 1 7 my ($self, $xform) = @_;
193 3 50       7 $self->{transformer} = $xform if defined $xform;
194 3         14 return $self->{transformer};
195             }
196              
197             1;
198             __END__