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