File Coverage

blib/lib/Net/Statsd/Lite.pm
Criterion Covered Total %
statement 92 92 100.0
branch 14 16 87.5
condition n/a
subroutine 22 22 100.0
pod 4 6 66.6
total 132 136 97.0


line stmt bran cond sub pod time code
1              
2             # ABSTRACT: A lightweight StatsD client that supports multimetric packets
3              
4             use v5.10;
5 27     27   1385836562  
  27         406  
6             use Moo 1.000000;
7 27     27   477  
  27         2127  
  27         938  
8             use Devel::StrictMode;
9 27     27   24949 use IO::Socket 1.18 ();
  27         219  
  27         5828  
10 27     27   310 use MooX::TypeTiny;
  27         1461  
  27         955  
11 27     27   13790 use Ref::Util qw/ is_plain_hashref /;
  27         10849  
  27         378  
12 27     27   28575 use Scalar::Util qw/ refaddr /;
  27         107  
  27         2708  
13 27     27   214 use Sub::Quote qw/ quote_sub /;
  27         108  
  27         3406  
14 27     27   317 use Sub::Util 1.40 qw/ set_subname /;
  27         113  
  27         3866  
15 27     27   232 use Types::Common::Numeric 1.004000 qw/ IntRange NumRange PositiveInt PositiveOrZeroInt PositiveOrZeroNum /;
  27         986  
  27         3595  
16 27     27   14760 use Types::Common::String qw/ NonEmptySimpleStr SimpleStr /;
  27         2928265  
  27         298  
17 27     27   39590 use Types::Standard qw/ Bool Enum InstanceOf Int StrMatch /;
  27         1132825  
  27         422  
18 27     27   18198  
  27         65  
  27         169  
19             use namespace::autoclean;
20 27     27   28888  
  27         441  
  27         895  
21             # RECOMMEND PREREQ: Ref::Util::XS
22             # RECOMMEND PREREQ: Type::Tiny::XS
23              
24             our $VERSION = 'v0.6.3';
25              
26              
27             has host => (
28             is => 'ro',
29             isa => NonEmptySimpleStr,
30             default => '127.0.0.1',
31             );
32              
33              
34             has port => (
35             is => 'ro',
36             isa => IntRange[ 0, 65535 ],
37             default => 8125,
38             );
39              
40              
41             has proto => (
42             is => 'ro',
43             isa => Enum [qw/ tcp udp /],
44             default => 'udp',
45             );
46              
47              
48             has prefix => (
49             is => 'ro',
50             isa => SimpleStr,
51             default => '',
52             );
53              
54              
55             has autoflush => (
56             is => 'ro',
57             isa => Bool,
58             default => 1,
59             );
60              
61             my %Buffers;
62              
63              
64             has max_buffer_size => (
65             is => 'ro',
66             isa => PositiveInt,
67             default => 512,
68             );
69              
70             has _socket => (
71             is => 'lazy',
72             isa => InstanceOf ['IO::Socket::INET'],
73             builder => sub {
74             my ($self) = shift;
75 21     21   438 my $sock = IO::Socket::INET->new(
76 21 50       1126 PeerAddr => $self->host,
77             PeerPort => $self->port,
78             Proto => $self->proto,
79             ) or die "Failed to initialize socket: $!";
80             return $sock;
81 21         16167 },
82             handles => { _send => 'send' },
83             );
84              
85              
86             BEGIN {
87             my $class = __PACKAGE__;
88 27     27   98  
89             my %PROTOCOL = (
90 27         125 set_add => [ '|s', SimpleStr, ],
91             counter => [ '|c', Int, 1 ],
92             gauge => [ '|g', StrMatch[ qr{\A[\-\+]?[0-9]+\z} ] ],
93             histogram => [ '|h', PositiveOrZeroNum, 1 ],
94             meter => [ '|m', PositiveOrZeroNum ],
95             timing => [ '|ms', PositiveOrZeroNum, 1 ],
96             );
97              
98             foreach my $name ( keys %PROTOCOL ) {
99 27         105954  
100             my $type = $PROTOCOL{$name}[1];
101 162         87043 my $rate = $PROTOCOL{$name}[2];
102 162         304  
103             my $code = q{ my ($self, $metric, $value, $opts) = @_; };
104 162         264  
105             if (defined $rate) {
106 162 100       421 $code .= q[ $opts = { rate => $opts } unless is_plain_hashref($opts); ] .
107 81         220 q[ my $rate = $opts->{rate} // 1; ]
108             }
109             else {
110             $code .= q[ $opts //= {}; ];
111 81         206 }
112              
113             if (STRICT) {
114 162         245  
115             $code .= $type->inline_assert('$value');
116 162         521  
117             if (defined $rate) {
118 162 100       21891 my $range = NumRange[0,1];
119 81         401 $code .= $range->inline_assert('$rate') . ';';
120 81         74392 }
121             }
122              
123             my $tmpl = $PROTOCOL{$name}[0];
124 162         19606  
125             if ( defined $rate ) {
126 162 100       362  
127             $code .= q/ if ($rate<1) {
128 81         239 $self->record_metric( $tmpl . '|@' . $rate, $metric, $value, $opts )
129             if rand() <= $rate;
130             } else {
131             $self->record_metric( $tmpl, $metric, $value, $opts ); } /;
132             }
133             else {
134              
135             $code .= q{$self->record_metric( $tmpl, $metric, $value, $opts );};
136 81         169  
137             }
138              
139             quote_sub "${class}::${name}", $code,
140 162         992 { '$tmpl' => \$tmpl },
141             { no_defer => 1 };
142              
143             }
144              
145             # Alises for other Net::Statsd::Client or Etsy::StatsD
146              
147             {
148             no strict 'refs'; ## no critic (ProhibitNoStrict)
149 27     27   15572  
  27         58  
  27         2629  
  27         14574  
150             *{"${class}::update"} = set_subname "update" => \&counter;
151 27         256 *{"${class}::timing_ms"} = set_subname "timing_ms" => \&timing;
  27         134  
152 27         167  
  27         272  
153             }
154              
155             }
156              
157             my ( $self, $metric, $opts ) = @_;
158             $self->counter( $metric, 1, $opts );
159 5     5 1 5001546 }
160 5         449  
161             my ( $self, $metric, $opts ) = @_;
162             $self->counter( $metric, -1, $opts );
163             }
164 2     2 1 2000551  
165 2         159  
166             my ( $self, $suffix, $metric, $value ) = @_;
167              
168             my $data = $self->prefix . $metric . ':' . $value . $suffix . "\n";
169              
170 25     25 1 16008080 if ( $self->autoflush ) {
171             send( $self->_socket, $data, 0 );
172 25         400 return;
173             }
174 25 100       251  
175 19         547 my $index = refaddr $self;
176 19         2661 my $avail = $self->max_buffer_size - length( $Buffers{$index} );
177              
178             $self->flush if length($data) > $avail;
179 6         19  
180 6         21 $Buffers{$index} .= $data;
181              
182 6 100       23 }
183              
184 6         33  
185             my ($self) = @_;
186              
187             my $index = refaddr $self;
188             if ( $Buffers{$index} ne '' ) {
189             send( $self->_socket, $Buffers{$index}, 0 );
190 29     29 1 91 $Buffers{$index} = '';
191             }
192 29         263 }
193 29 100       183  
194 3         53 my ($self) = @_;
195 3         220  
196             $Buffers{ refaddr $self } = '';
197             }
198              
199             my ( $self, $is_global ) = @_;
200 27     27 0 184033  
201             return if $is_global;
202 27         292  
203             $self->flush;
204              
205             delete $Buffers{ refaddr $self };
206 27     27 0 4047958 }
207              
208 27 50       157  
209             1;
210 27         196  
211              
212 27         590 =pod
213              
214             =encoding UTF-8
215              
216             =head1 NAME
217              
218             Net::Statsd::Lite - A lightweight StatsD client that supports multimetric packets
219              
220             =head1 VERSION
221              
222             version v0.6.3
223              
224             =head1 SYNOPSIS
225              
226             use Net::Statsd::Lite;
227              
228             my $stats = Net::Statsd::Lite->new(
229             prefix => 'myapp.',
230             autoflush => 0,
231             max_buffer_size => 8192,
232             );
233              
234             ...
235              
236             $stats->increment('this.counter');
237              
238             $stats->set_add( $username ) if $username;
239              
240             $stats->timing( $run_time * 1000 );
241              
242             $stats->flush;
243              
244             =head1 DESCRIPTION
245              
246             This is a small StatsD client that supports the
247             L<StatsD Metrics Export Specification v0.1|https://github.com/b/statsd_spec>.
248              
249             It supports the following features:
250              
251             =over
252              
253             =item *
254              
255             Multiple metrics can be sent in a single UDP packet.
256              
257             =item *
258              
259             It supports the meter and histogram metric types.
260              
261             =item *
262              
263             It can extended to support extensions such as tagging.
264              
265             =back
266              
267             Note that the specification requires the measured values to be
268             integers no larger than 64-bits, but ideally 53-bits.
269              
270             The current implementation expects values to be integers, except where
271             specified. But it otherwise does not enforce maximum/minimum values.
272              
273             =head1 ATTRIBUTES
274              
275             =head2 C<host>
276              
277             The host of the statsd daemon. It defaults to C<127.0.0.1>.
278              
279             =head2 C<port>
280              
281             The port that the statsd daemon is listening on. It defaults to
282             C<8125>.
283              
284             =head2 C<proto>
285              
286             The network protocol that the statsd daemon is using. It defaults to
287             C<udp>.
288              
289             =head2 C<prefix>
290              
291             The prefix to prepend to metric names. It defaults to a blank string.
292              
293             =head2 C<autoflush>
294              
295             A flag indicating whether metrics will be send immediately. It
296             defaults to true.
297              
298             When it is false, metrics will be saved in a buffer and only sent when
299             the buffer is full, or when the L</flush> method is called.
300              
301             Note that when this is disabled, you will want to flush the buffer
302             regularly at the end of each task (e.g. a website request or job).
303              
304             Not all StatsD daemons support receiving multiple metrics in a single
305             packet.
306              
307             =head2 C<max_buffer_size>
308              
309             Specifies the maximum buffer size. It defaults to C<512>.
310              
311             =head1 METHODS
312              
313             =head2 C<counter>
314              
315             $stats->counter( $metric, $value, $opts );
316              
317             This adds the C<$value> to the counter specified by the C<$metric>
318             name.
319              
320             C<$opts> can be a hash reference with the C<rate> key, or a simple
321             scalar with the C<$rate>.
322              
323             If a C<$rate> is specified and less than 1, then a sampling rate will
324             be added. C<$rate> must be between 0 and 1.
325              
326             =head2 C<update>
327              
328             This is an alias for L</counter>, for compatability with
329             L<Etsy::StatsD> or L<Net::Statsd::Client>.
330              
331             =head2 C<increment>
332              
333             $stats->increment( $metric, $opts );
334              
335             This is an alias for
336              
337             $stats->counter( $metric, 1, $opts );
338              
339             =head2 C<decrement>
340              
341             $stats->decrement( $metric, $opts );
342              
343             This is an alias for
344              
345             $stats->counter( $metric, -1, $opts );
346              
347             =head2 C<meter>
348              
349             $stats->meter( $metric, $value, $opts );
350              
351             This is a counter that only accepts positive (increasing) values. It
352             is appropriate for counters that will never decrease (e.g. the number
353             of requests processed.) However, this metric type is not supported by
354             many StatsD daemons.
355              
356             =head2 C<gauge>
357              
358             $stats->gauge( $metric, $value, $opts );
359              
360             A gauge can be thought of as a counter that is maintained by the
361             client instead of the daemon, where C<$value> is a positive integer.
362              
363             However, this also supports gauge increment extensions. If the number
364             is prefixed by a "+", then the gauge is incremented by that amount,
365             and if the number is prefixed by a "-", then the gauge is decremented
366             by that amount.
367              
368             =head2 C<timing>
369              
370             $stats->timing( $metric, $value, $opts );
371              
372             This logs a "timing" in milliseconds, so that statistics about the
373             metric can be gathered. The C<$value> must be positive number,
374             although the specification recommends that integers be used.
375              
376             In actually, any values can be logged, and this is often used as a
377             generic histogram for non-timing values (especially since many StatsD
378             daemons do not support the L</histogram> metric type).
379              
380             C<$opts> can be a hash reference with a C<rate> key, or a simple
381             scalar with the C<$rate>.
382              
383             If a C<$rate> is specified and less than 1, then a sampling rate will
384             be added. C<$rate> must be between 0 and 1. Note that sampling
385             rates for timings may not be supported by all statsd servers.
386              
387             =head2 C<timing_ms>
388              
389             This is an alias for L</timing>, for compatability with
390             L<Net::Statsd::Client>.
391              
392             =head2 C<histogram>
393              
394             $stats->histogram( $metric, $value, $opts );
395              
396             This logs a value so that statistics about the metric can be
397             gathered. The C<$value> must be a positive number, although the
398             specification recommends that integers be used.
399              
400             This metric type is not supported by many StatsD daemons. You can use
401             L</timing> for the same effect.
402              
403             =head2 C<set_add>
404              
405             $stats->set_add( $metric, $string, $opts );
406              
407             This adds the the C<$string> to a set, for logging the number of
408             unique things, e.g. IP addresses or usernames.
409              
410             =head2 record_metric
411              
412             This is an internal method for sending the data to the server.
413              
414             $stats->record_metric( $suffix, $metric, $value, $opts );
415              
416             This was renamed and documented in v0.5.0 to to simplify subclassing
417             that supports extensions to statsd, such as tagging.
418              
419             See the discussion of tagging extensions below.
420              
421             =head2 C<flush>
422              
423             This sends the buffer to the L</host> and empties the buffer, if there
424             is any data in the buffer.
425              
426             =head1 STRICT MODE
427              
428             If this module is first loaded in C<STRICT> mode, then the values and
429             rate arguments will be checked that they are the correct type.
430              
431             See L<Devel::StrictMode> for more information.
432              
433             =head1 TAGGING EXTENSIONS
434              
435             This class does not support tagging out-of-the box. But tagging can be
436             added easily to a subclass, for example, L<DogStatsd|https://www.datadoghq.com/> or
437             L<CloudWatch|https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-Agent-custom-metrics-statsd.html>
438             tagging can be added using something like
439              
440             use Moo 1.000000;
441             extends 'Net::Statsd::Lite';
442              
443             around record_metric => sub {
444             my ( $next, $self, $suffix, $metric, $value, $opts ) = @_;
445              
446             if ( my $tags = $opts->{tags} ) {
447             $suffix .= "|#" . join ",", map { s/|//g; $_ } @$tags;
448             }
449              
450             $self->$next( $suffix, $metric, $value, $opts );
451             };
452              
453             =head1 SEE ALSO
454              
455             This module was forked from L<Net::Statsd::Tiny>.
456              
457             L<https://github.com/statsd/statsd/blob/master/docs/metric_types.md>
458              
459             L<https://github.com/b/statsd_spec>
460              
461             =head1 SOURCE
462              
463             The development version is on github at L<https://github.com/robrwo/Net-Statsd-Lite>
464             and may be cloned from L<git://github.com/robrwo/Net-Statsd-Lite.git>
465              
466             =head1 BUGS
467              
468             Please report any bugs or feature requests on the bugtracker website
469             L<https://github.com/robrwo/Net-Statsd-Lite/issues>
470              
471             When submitting a bug or request, please include a test-file or a
472             patch to an existing test-file that illustrates the bug or desired
473             feature.
474              
475             =head1 AUTHOR
476              
477             Robert Rothenberg <rrwo@cpan.org>
478              
479             The initial development of this module was sponsored by Science Photo
480             Library L<https://www.sciencephoto.com>.
481              
482             =head1 CONTRIBUTORS
483              
484             =for stopwords Michael R. Davis Toby Inkster
485              
486             =over 4
487              
488             =item *
489              
490             Michael R. Davis <mrdvt@cpan.org>
491              
492             =item *
493              
494             Toby Inkster <tobyink@cpan.org>
495              
496             =back
497              
498             =head1 COPYRIGHT AND LICENSE
499              
500             This software is Copyright (c) 2018-2022 by Robert Rothenberg.
501              
502             This is free software, licensed under:
503              
504             The Artistic License 2.0 (GPL Compatible)
505              
506             =cut