File Coverage

blib/lib/Net/Statsd/Lite.pm
Criterion Covered Total %
statement 86 86 100.0
branch 14 16 87.5
condition n/a
subroutine 20 20 100.0
pod 4 6 66.6
total 124 128 96.8


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   1360681139 use v5.14;
  27         620  
6              
7 27     27   686 use Moo 1.000000;
  27         3220  
  27         1172  
8              
9 27     27   35766 use Devel::StrictMode;
  27         411  
  27         8459  
10 27     27   439 use IO::Socket 1.18 ();
  27         2116  
  27         1516  
11 27     27   17385 use MooX::TypeTiny;
  27         15049  
  27         356  
12 27     27   35643 use Ref::Util qw/ is_plain_hashref /;
  27         182  
  27         3705  
13 27     27   323 use Scalar::Util qw/ refaddr /;
  27         167  
  27         4738  
14 27     27   301 use Sub::Quote qw/ quote_sub /;
  27         220  
  27         5748  
15 27     27   245 use Sub::Util 1.40 qw/ set_subname /;
  27         1160  
  27         4954  
16 27         315 use Types::Common 2.000000 qw/ Bool Enum InstanceOf Int IntRange NonEmptySimpleStr
17             NumRange PositiveInt PositiveOrZeroInt PositiveOrZeroNum SimpleStr StrMatch
18 27     27   16727 /;
  27         7057983  
19              
20 27     27   142356 use namespace::autoclean;
  27         78  
  27         1050  
21              
22             # RECOMMEND PREREQ: Ref::Util::XS
23             # RECOMMEND PREREQ: Type::Tiny::XS
24              
25             our $VERSION = 'v0.7.0';
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 21     21   530 my ($self) = shift;
76 21 50       1091 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 21         19664 return $sock;
82             },
83             handles => { _send => 'send' },
84             );
85              
86              
87             BEGIN {
88 27     27   158 my $class = __PACKAGE__;
89              
90 27         736 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         126345 foreach my $name ( keys %PROTOCOL ) {
100              
101 162         110449 my $type = $PROTOCOL{$name}[1];
102 162         330 my $rate = $PROTOCOL{$name}[2];
103              
104 162         314 my $code = q{ my ($self, $metric, $value, $opts) = @_; };
105              
106 162 100       447 if (defined $rate) {
107 81         260 $code .= q[ $opts = { rate => $opts } unless is_plain_hashref($opts); ] .
108             q[ my $rate = $opts->{rate} // 1; ]
109             }
110             else {
111 81         222 $code .= q[ $opts //= {}; ];
112             }
113              
114 162         256 if (STRICT) {
115              
116 162         568 $code .= $type->inline_assert('$value');
117              
118 162 100       28226 if (defined $rate) {
119 81         423 my $range = NumRange[0,1];
120 81         87922 $code .= $range->inline_assert('$rate') . ';';
121             }
122             }
123              
124 162         24525 my $tmpl = $PROTOCOL{$name}[0];
125              
126 162 100       420 if ( defined $rate ) {
127              
128 81         228 $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         187 $code .= q{$self->record_metric( $tmpl, $metric, $value, $opts );};
137              
138             }
139              
140 162         1045 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   18156 no strict 'refs'; ## no critic (ProhibitNoStrict)
  27         99  
  27         3660  
  27         19401  
150              
151 27         256 *{"${class}::update"} = set_subname "update" => \&counter;
  27         152  
152 27         167 *{"${class}::timing_ms"} = set_subname "timing_ms" => \&timing;
  27         267  
153              
154             }
155              
156             }
157              
158             sub increment {
159 5     5 1 5001963 my ( $self, $metric, $opts ) = @_;
160 5         632 $self->counter( $metric, 1, $opts );
161             }
162              
163             sub decrement {
164 2     2 1 2000863 my ( $self, $metric, $opts ) = @_;
165 2         220 $self->counter( $metric, -1, $opts );
166             }
167              
168              
169             sub record_metric {
170 25     25 1 16009573 my ( $self, $suffix, $metric, $value ) = @_;
171              
172 25         509 my $data = $self->prefix . $metric . ':' . $value . $suffix . "\n";
173              
174 25 100       319 if ( $self->autoflush ) {
175 19         695 send( $self->_socket, $data, 0 );
176 19         2675 return;
177             }
178              
179 6         33 my $index = refaddr $self;
180 6         54 my $avail = $self->max_buffer_size - length( $Buffers{$index} );
181              
182 6 100       35 $self->flush if length($data) > $avail;
183              
184 6         60 $Buffers{$index} .= $data;
185              
186             }
187              
188              
189             sub flush {
190 29     29 1 157 my ($self) = @_;
191              
192 29         184 my $index = refaddr $self;
193 29 100       224 if ( $Buffers{$index} ne '' ) {
194 3         133 send( $self->_socket, $Buffers{$index}, 0 );
195 3         365 $Buffers{$index} = '';
196             }
197             }
198              
199             sub BUILD {
200 27     27 0 216591 my ($self) = @_;
201              
202 27         321 $Buffers{ refaddr $self } = '';
203             }
204              
205             sub DEMOLISH {
206 27     27 0 4060914 my ( $self, $is_global ) = @_;
207              
208 27 50       180 return if $is_global;
209              
210 27         250 $self->flush;
211              
212 27         815 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.7.0
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 SUPPORT FOR OLDER PERL VERSIONS
462              
463             Since v0.7.0, the this module requires Perl v5.14 or later.
464              
465             Future releases may only support Perl versions released in the last ten years.
466              
467             If you need this module on Perl v5.10, please use one of the v0.6.x
468             versions of this module. Significant bug or security fixes may be
469             backported to those versions.
470              
471             =head1 SEE ALSO
472              
473             This module was forked from L<Net::Statsd::Tiny>.
474              
475             L<https://github.com/statsd/statsd/blob/master/docs/metric_types.md>
476              
477             L<https://github.com/b/statsd_spec>
478              
479             =head1 SOURCE
480              
481             The development version is on github at L<https://github.com/robrwo/Net-Statsd-Lite>
482             and may be cloned from L<git://github.com/robrwo/Net-Statsd-Lite.git>
483              
484             =head1 BUGS
485              
486             Please report any bugs or feature requests on the bugtracker website
487             L<https://github.com/robrwo/Net-Statsd-Lite/issues>
488              
489             When submitting a bug or request, please include a test-file or a
490             patch to an existing test-file that illustrates the bug or desired
491             feature.
492              
493             =head1 AUTHOR
494              
495             Robert Rothenberg <rrwo@cpan.org>
496              
497             The initial development of this module was sponsored by Science Photo
498             Library L<https://www.sciencephoto.com>.
499              
500             =head1 CONTRIBUTORS
501              
502             =for stopwords Michael R. Davis Toby Inkster
503              
504             =over 4
505              
506             =item *
507              
508             Michael R. Davis <mrdvt@cpan.org>
509              
510             =item *
511              
512             Toby Inkster <tobyink@cpan.org>
513              
514             =back
515              
516             =head1 COPYRIGHT AND LICENSE
517              
518             This software is Copyright (c) 2018-2023 by Robert Rothenberg.
519              
520             This is free software, licensed under:
521              
522             The Artistic License 2.0 (GPL Compatible)
523              
524             =cut