File Coverage

blib/lib/Metrics/Any/Collector.pm
Criterion Covered Total %
statement 135 148 91.2
branch 39 46 84.7
condition 23 30 76.6
subroutine 26 28 92.8
pod 13 16 81.2
total 236 268 88.0


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2020 -- leonerd@leonerd.org.uk
5              
6             package Metrics::Any::Collector 0.08;
7              
8 12     12   122 use v5.14;
  12         36  
9 12     12   55 use warnings;
  12         20  
  12         277  
10              
11 12     12   55 use Carp;
  12         25  
  12         669  
12              
13 12     12   3321 use Metrics::Any::Adapter;
  12         23  
  12         59  
14              
15 12     12   69 use List::Util 1.29 qw( pairkeys );
  12         213  
  12         11568  
16              
17             =head1 NAME
18              
19             C - module-side of the monitoring metrics reporting API
20              
21             =head1 SYNOPSIS
22              
23             use Metrics::Any '$metrics',
24             strict => 0,
25             name_prefix => [ 'my_module_name' ];
26              
27             sub do_thing {
28             $metrics->inc_counter( 'things_done' );
29             }
30              
31             =head1 DESCRIPTION
32              
33             Instances of this class provide an API for individual modules to declare
34             metadata about metrics they will report, and to report individual values or
35             observations on those metrics. An instance should be obtained for a reporting
36             module by the C statement.
37              
38             The collector acts primarily as a proxy for the application's configured
39             L instance. The proxy will lazily create an adapter
40             when required to first actually report a metric value, but until then any
41             metadata stored by any of the C methods will not create one. This lazy
42             deferral allows a certain amount of flexibility with module load order and
43             application startup. By carefully writing module code to not report any values
44             of metrics until the main activity has actually begin, it should be possible
45             to allow programs to configure the metric reporting in a flexible manner
46             during program startup.
47              
48             =head1 ENVIRONMENT
49              
50             =head2 METRICS_ANY_DISABLE
51              
52             I
53              
54             Provides a list of packages and namespaces in which to disable L
55             reporting entirely.
56              
57             This variable gives a comma-separated list of name patterns. Patterns may end
58             with C<::*>, where they will match any package whose name starts with that
59             prefix, or they may be literal package names. If any code in matching packages
60             attempts to use L to report metrics, that code will
61             be given a C adapter, and no metrics will be reported from here.
62              
63             For example, to disable the metrics that C itself
64             creates when exporting Prometheus metrics:
65              
66             $ METRICS_ANY_DISABLE=Net::Async::HTTP::Server ./program.pl
67              
68             =cut
69              
70             # Not public API; used by Metrics::Any::import_into
71             sub new
72             {
73 15     15 0 29 my $class = shift;
74 15         41 my ( $package, %args ) = @_;
75              
76             return bless {
77             package => $package,
78             adapter => undef,
79             deferred => [],
80             name_prefix => $args{name_prefix},
81             metrics => {},
82 15   100     390 strict => $args{strict} // 1,
83             }, $class;
84             }
85              
86             my %disable_for_package;
87             my %disable_for_namespace;
88             if( my $val = $ENV{METRICS_ANY_DISABLE} ) {
89             foreach my $pattern ( split m/,/, $val ) {
90             if( $pattern =~ s/\*$// ) {
91             $pattern =~ s/::$//;
92             $disable_for_namespace{$pattern} = 1;
93             }
94             else {
95             $disable_for_package{$pattern} = 1;
96             }
97             }
98              
99             require Metrics::Any::Adapter::Null;
100             }
101              
102             sub _enabled_for_package
103             {
104 14     14   35 my ( $pkg ) = @_;
105              
106 14 100       51 return 0 if $disable_for_package{$pkg};
107 13 100       104 return 1 unless %disable_for_namespace;
108              
109 3         4 do {
110 4 100       23 return 0 if $disable_for_namespace{$pkg};
111             } while( $pkg =~ s/::[^:]+// );
112              
113 1         6 return 1;
114             }
115              
116             sub adapter
117             {
118 74     74 0 1763 my $self = shift;
119 74 100       450 return $self->{adapter} if $self->{adapter};
120              
121             my $adapter = $self->{adapter} =
122 14 100       49 ( _enabled_for_package( $self->{package} ) ? Metrics::Any::Adapter->adapter
123             : Metrics::Any::Adapter::Null->new );
124 14         41 foreach my $call ( @{ $self->{deferred} } ) {
  14         47  
125 9         29 my ( $method, @args ) = @$call;
126 9         58 $adapter->$method( @args );
127             }
128 14         47 undef $self->{deferred};
129 14         68 return $adapter;
130             }
131              
132             sub _adapter_call
133             {
134 49     49   84 my $self = shift;
135 49         122 my ( $method, @args ) = @_;
136              
137 49 100       113 if( $self->{adapter} ) {
138 40         188 $self->{adapter}->$method( @args );
139             }
140             else {
141 9         13 push @{ $self->{deferred} }, [ $method, @args ];
  9         60  
142             }
143             }
144              
145             sub _metricname
146             {
147 53     53   76 my $self = shift;
148 53         82 my ( $suffix ) = @_;
149              
150 53 100       235 return $suffix unless defined $self->{name_prefix};
151 8         12 return [ @{ $self->{name_prefix} }, @$suffix ];
  8         24  
152             }
153              
154             sub _labelvalues
155             {
156 70     70   104 my $self = shift;
157 70         125 my ( $type, $handle, @args ) = @_;
158              
159 70         150 my $meta = $self->{$handle};
160 70 100       146 if( $meta ) {
    100          
161 56 50       137 $meta->[0] eq $type or croak "Metric '$handle' is not a $type";
162             }
163             elsif( !$self->{strict} ) {
164 10         14 my @labelnames;
165 10 100       56 if( !@args ) {
    50          
    0          
166             # no labels
167             }
168             elsif( ref $args[0] eq "ARRAY" ) {
169 2         3 @labelnames = pairkeys @{ $args[0] };
  2         9  
170             }
171             elsif( ref $args[0] eq "HASH" ) {
172             carp "Lazily creating a labelled metric with multiple labels using a HASH reference yields unreliable label order"
173 0 0       0 if keys %{ $args[0] } > 1;
  0         0  
174 0         0 @labelnames = keys %{ $args[0] };
  0         0  
175             }
176             else {
177 0         0 croak "Cannot lazily create a labelled metric from label values specified in a flat list";
178             }
179              
180 10         25 my $make_method = "make_$type";
181 10         35 $self->$make_method( $handle, labels => \@labelnames );
182              
183 10         24 $meta = $self->{$handle};
184             }
185             else {
186 4         325 croak "No such metric '$handle'";
187             }
188              
189 66         135 my ( undef, @labelnames ) = @$meta;
190              
191 66 100       155 if( !@args ) {
    100          
192 57         161 return;
193             }
194             elsif( ref $args[0] ) {
195 7 50       16 warn "Received additional arguments to metrics reporting function\n" if @args > 1;
196 7         12 my ( $arg ) = @args;
197 7 100       37 my %v = ( ref $arg eq "ARRAY" ) ? @$arg : %$arg;
198              
199 7         11 my @labelvalues;
200             ( defined $v{$_} or croak "Missing value for label '$_'" ) and push @labelvalues, delete $v{$_}
201 7   33     39 for @labelnames;
      33        
202              
203             # Warn but don't complain about extra values
204 7         16 carp "Found extra label value for '$_'" for keys %v;
205              
206 7         22 return @labelvalues;
207             }
208             else {
209 2         6 return @args;
210             }
211             }
212              
213             =head1 ARGUMENTS
214              
215             =head2 name_prefix
216              
217             I
218              
219             Optional prefix to prepend to any name provided to the C functions.
220              
221             If set, this value and the registered names must be given as array references,
222             not simple strings.
223              
224             use Metrics::Any '$metrics', name_prefix => [qw( my_program_name )];
225              
226             $metrics->make_counter( events =>
227             name => [ "events" ],
228             );
229              
230             # Will create a counter named ["my_program_name", "events"] formed by the
231             # adapter.
232              
233             =head2 strict
234              
235             I
236              
237             Optional boolean which controls whether metrics must be registered by a
238             C method before they can be used (when true), or whether to attempt
239             lazily registering them when first encountered by a reporting method (when
240             false).
241              
242             When strict mode is off and a reporting method (e.g. C) is
243             invoked on an unrecognised handle, it will be lazily registered. If the metric
244             is reported with values, an attempt is made to determine what the list of
245             label names is; which will depend on the form the label values are given in.
246             Labels passed by array reference, or by hash reference for a single label will
247             work fine. If a hash reference is passed with multiple keys, a warning is
248             printed that the order may not be reliable. Finally, for (discouraged) flat
249             lists of values directly it is not possible to recover label name information
250             so an exception is thrown.
251              
252             For this reason, when operating with strict mode off, it is recommended always
253             to use the array reference form of supplying labels, to ensure they are
254             registered correctly.
255              
256             In the current version this parameter defaults true, and thus all metrics must
257             be registered in advance. This may be changed in a future version for
258             convenience in smaller modules, so paranoid authors should set it explicitly:
259              
260             use Metrics::Any::Adapter '$metrics', strict => 1;
261              
262             If strict mode is switched off, it is recommended to set a name prefix to
263             ensure that lazily-registered metrics will at least have a useful name.
264              
265             =cut
266              
267             =head1 BOOLEAN OVERRIDE
268              
269             Instances of this class override boolean truth testing. They are usually true,
270             except in the case that an adapter has already been created and it is the Null
271             type. This allows modules to efficiently test whether to report metrics at all
272             by using code such as
273              
274             if( $metrics ) {
275             $metrics->inc_counter( name => some_expensive_function() );
276             }
277              
278             While the Null adapter will simply ignore any of the methods invoked on it,
279             without this conditional test the caller would otherwise still have to
280             calculate the value that won't be used. This structure allows the calculation
281             to be avoided if metrics are not in use.
282              
283             =cut
284              
285             use overload
286             'bool' => sub {
287 26 100   26   3187 !$_[0]->{adapter} or ref $_[0]->{adapter} ne "Metrics::Any::Adapter::Null"
288             },
289             # stringify as itself otherwise bool takes over and it just prints as 1,
290             # leading to much developer confusion
291 8     8   1021 '""' => sub { $_[0] },
292 12     12   12021 fallback => 1;
  12         9677  
  12         106  
293              
294             =head1 METHODS
295              
296             $package = $metrics->package
297              
298             Returns the package name that created the collector; the package in which the
299              
300             use Metrics::Any '$metrics';
301              
302             statement was invoked.
303              
304             =cut
305              
306             sub package
307             {
308 1     1 0 259 my $self = shift;
309 1         5 return $self->{package};
310             }
311              
312             =head1 METRIC TYPES
313              
314             Each type of metric is created by one of the C methods. They all take
315             the following common arguments:
316              
317             =over 4
318              
319             =item name => ARRAY[ STRING ] | STRING
320              
321             Optional. An array of string parts, or a plain string name to use for
322             reporting this metric to its upstream service.
323              
324             Modules should preferrably use an array of string parts to specify their
325             metric names, as different adapter types may have different ways to represent
326             this hierarchially. Base-level parts of the name should come first, followed
327             by more specific parts. It is common for related metrics to be grouped by name
328             having identical prefixes but differing only in the final part.
329              
330             The name is optional; if unspecified then the handle will be used to form the
331             name, combined with a C argument if one was set for the package.
332              
333             =item description => STRING
334              
335             Optional human-readable description. May be used for debugging or other
336             purposes.
337              
338             =item labels => ARRAY[ STRING ]
339              
340             Optional reference to an array of string names to use as label names.
341              
342             A labelled metric will expect to receive additional information in its
343             reporting method to give values for these labels. This information should be
344             in either an even-length array reference of name/value pairs, or a hash
345             reference. E.g.
346              
347             $metrics->inc_counter( handle => [ labelname => $labelvalue ] );
348             $metrics->inc_counter( handle => { labelname => $labelvalue } );
349              
350             A legacy form where a plain list of values is passed, each corresponding to a
351             named label in the same order, is currently accepted but discouraged in favour
352             of the above forms.
353              
354             $metrics->inc_counter( handle => $labelvalue );
355              
356             Note that not all metric reporting adapters may be able to represent all of
357             the labels. Each should document what its behaviour will be.
358              
359             =back
360              
361             =cut
362              
363             =head2 Counter
364              
365             The L method creates a new metric which counts occurances of
366             some event within the application. Its value begins at zero, and can be
367             incremented by L whenever the event occurs.
368              
369             Some counters may simple count occurances of events, while others may count
370             in other units, for example counts of bytes. Adapters may make use of the
371             C parameter of the distribution to perform some kind of
372             adapter-specific behaviour. The following units are suggested:
373              
374             =head3 bytes
375              
376             Observations give sizes in bytes (perhaps memory buffer or network message
377             sizes), and should be integers.
378              
379             =cut
380              
381             =head2 make_counter
382              
383             $collector->make_counter( $handle, %args )
384              
385             Requests the creation of a new counter metric. The C<$handle> name should be
386             unique within the collector instance, though does not need to be unique across
387             the entire program, as it will be namespaced by the collector instance.
388              
389             The following extra arguments may be passed:
390              
391             =over 4
392              
393             =item units => STRING
394              
395             A hint to the adapter about what kind of measurements are being observed, so
396             it might take specific behaviour.
397              
398             =back
399              
400             =cut
401              
402             sub make_counter
403             {
404 22     22 1 1955 my $self = shift;
405 22         67 my ( $handle, %args ) = @_;
406              
407 22   100     127 $args{name} = $self->_metricname( $args{name} // [ $handle ] );
408              
409 22 100       266 $self->{$handle} and croak "Already have a metric '$handle'";
410 21   100     49 $self->{$handle} = [ counter => @{ $args{labels} // [] } ];
  21         117  
411              
412 21         115 $self->_adapter_call( make_counter => "$self->{package}/$handle",
413             collector => $self,
414             %args
415             );
416             }
417              
418             =head2 inc_counter
419              
420             $collector->inc_counter( $handle, $labels )
421              
422             Reports that the counter metric value be incremented by one. The C<$handle>
423             name must match one earlier created by L.
424              
425             =cut
426              
427             sub inc_counter
428             {
429 28     28 1 974 my $self = shift;
430 28         66 my ( $handle, @args ) = @_;
431              
432 28         82 my @labelvalues = $self->_labelvalues( counter => $handle, @args );
433              
434 27         81 $self->adapter->inc_counter_by( "$self->{package}/$handle", 1, @labelvalues );
435             }
436              
437             =head2 inc_counter_by
438              
439             $collector->inc_counter_by( $handle, $amount, $labels )
440              
441             Reports that a counter metric value be incremented by some specified value.
442              
443             =cut
444              
445             sub inc_counter_by
446             {
447 1     1 1 532 my $self = shift;
448 1         3 my ( $handle, $amount, @args ) = @_;
449              
450 1         3 my @labelvalues = $self->_labelvalues( counter => $handle, @args );
451              
452 1         2 $self->adapter->inc_counter_by( "$self->{package}/$handle", $amount, @labelvalues );
453             }
454              
455             =head2 Distribution
456              
457             The L method creates a new metric which counts individual
458             observations of some numerical quantity (which may or may not be integral).
459             New observations can be added by the L method.
460              
461             Some adapter types may only store an aggregated total; others may store some
462             sort of statistical breakdown, either total + count, or a bucketed histogram.
463             The specific adapter documentation should explain how it handles
464             distributions.
465              
466             Adapters may make use of the C parameter of the distribution to perform
467             some kind of adapter-specific behaviour. The following units are suggested:
468              
469             =head3 bytes
470              
471             Observations give sizes in bytes (perhaps memory buffer or network message
472             sizes), and should be integers.
473              
474             =head3 seconds
475              
476             Observations give durations in seconds.
477              
478             =cut
479              
480             =head2 make_distribution
481              
482             $collector->make_distribution( $handle, %args )
483              
484             Requests the creation of a new distribution metric.
485              
486             The following extra arguments may be passed:
487              
488             =over 4
489              
490             =item units => STRING
491              
492             A hint to the adapter about what kind of measurements are being observed, so
493             it might take specific behaviour. If unspecified, a default of C will
494             apply.
495              
496             =back
497              
498             =cut
499              
500             sub make_distribution
501             {
502 11     11 1 1171 my $self = shift;
503 11         39 my ( $handle, %args ) = @_;
504              
505 11   100     72 $args{name} = $self->_metricname( $args{name} // [ $handle ] );
506              
507 11   50     76 $args{units} //= "bytes";
508              
509 11 100       119 $self->{$handle} and croak "Already have a metric '$handle'";
510 10   100     23 $self->{$handle} = [ distribution => @{ $args{labels} // [] } ];
  10         71  
511              
512 10         72 $self->_adapter_call( make_distribution => "$self->{package}/$handle",
513             collector => $self,
514             %args
515             );
516             }
517              
518             =head2 report_distribution
519              
520             $collector->report_distribution( $handle, $amount, $labels )
521              
522             I
523              
524             Reports a new observation for the distribution metric. The C<$handle> name
525             must match one earlier created by L. The C<$amount> may
526             be interpreted by the adapter depending on the defined C type for the
527             distribution.
528              
529             This method used to be called C and is currently still
530             available as an alias.
531              
532             =cut
533              
534             sub report_distribution
535             {
536 16     16 1 1154 my $self = shift;
537 16         53 my ( $handle, $amount, @args ) = @_;
538              
539 16         47 my @labelvalues = $self->_labelvalues( distribution => $handle, @args );
540              
541 15         36 my $adapter = $self->adapter;
542              
543             # Support new and legacy name
544 15   50     91 my $method = $adapter->can( "report_distribution" ) // "inc_distribution_by";
545 15         93 $adapter->$method( "$self->{package}/$handle", $amount, @labelvalues );
546             }
547              
548             *inc_distribution_by = \&report_distribution;
549              
550             =head2 Gauge
551              
552             The L method creates a new metric which reports on the
553             instantaneous value of some measurable quantity. Unlike the other metric types
554             this does not have to only increment forwards when certain events occur, but
555             can measure a quantity that may both increase and decrease over time; such as
556             the number some kind of object in memory, or the size of some data structure.
557              
558             As an alternative to incrementing or decrementing the value when particular
559             events occur, the absolute value of the gauge can also be set directly.
560              
561             =cut
562              
563             =head2 make_gauge
564              
565             $collector->make_gauge( $handle, %args )
566              
567             Requests the creation of a new gauge metric.
568              
569             =cut
570              
571             sub make_gauge
572             {
573 10     10 1 1202 my $self = shift;
574 10         27 my ( $handle, %args ) = @_;
575              
576 10   100     63 $args{name} = $self->_metricname( $args{name} // [ $handle ] );
577              
578 10 100       134 $self->{$handle} and croak "Already have a metric '$handle'";
579 9   100     16 $self->{$handle} = [ gauge => @{ $args{labels} // [] } ];
  9         57  
580              
581 9         57 $self->_adapter_call( make_gauge => "$self->{package}/$handle",
582             collector => $self,
583             %args
584             );
585             }
586              
587             =head2 inc_gauge
588              
589             $collector->inc_gauge( $handle, $labels )
590              
591             =head2 dec_gauge
592              
593             $collector->dec_gauge( $handle, $labels )
594              
595             =head2 inc_gauge_by
596              
597             $collector->inc_gauge_by( $handle, $amount, $labels )
598              
599             =head2 dec_gauge_by
600              
601             $collector->dec_gauge_by( $handle, $amount, $labels )
602              
603             Reports that the observed value of the gauge has increased or decreased by the
604             given amount (or 1).
605              
606             =cut
607              
608             sub inc_gauge
609             {
610 7     7 1 687 my $self = shift;
611 7         19 my ( $handle, @args ) = @_;
612              
613 7         107 my @labelvalues = $self->_labelvalues( gauge => $handle, @args );
614              
615 6         17 $self->adapter->inc_gauge_by( "$self->{package}/$handle", 1, @labelvalues );
616             }
617              
618             sub dec_gauge
619             {
620 0     0 1 0 my $self = shift;
621 0         0 my ( $handle, @args ) = @_;
622              
623 0         0 my @labelvalues = $self->_labelvalues( gauge => $handle, @args );
624              
625 0         0 $self->adapter->inc_gauge_by( "$self->{package}/$handle", -1, @labelvalues );
626             }
627              
628             sub inc_gauge_by
629             {
630 4     4 1 460 my $self = shift;
631 4         13 my ( $handle, $amount, @args ) = @_;
632              
633 4         17 my @labelvalues = $self->_labelvalues( gauge => $handle, @args );
634              
635 4         13 $self->adapter->inc_gauge_by( "$self->{package}/$handle", $amount, @labelvalues );
636             }
637              
638             sub dec_gauge_by
639             {
640 0     0 1 0 my $self = shift;
641 0         0 my ( $handle, $amount, @args ) = @_;
642              
643 0         0 my @labelvalues = $self->_labelvalues( gauge => $handle, @args );
644              
645 0         0 $self->adapter->inc_gauge_by( "$self->{package}/$handle", -$amount, @labelvalues );
646             }
647              
648             =head2 set_gauge_to
649              
650             $collector->set_gauge_to( $handle, $amount, $labels )
651              
652             Reports that the observed value of the gauge is now the given amount.
653              
654             The C<$handle> name must match one earlier created by L.
655              
656             =cut
657              
658             sub set_gauge_to
659             {
660 2     2 1 10 my $self = shift;
661 2         6 my ( $handle, $amount, @args ) = @_;
662              
663 2         8 my @labelvalues = $self->_labelvalues( gauge => $handle, @args );
664              
665 2         8 $self->adapter->set_gauge_to( "$self->{package}/$handle", $amount, @labelvalues );
666             }
667              
668             =head2 Timer
669              
670             The L method creates a new metric which measures durations of
671             time consumed by the application. New observations of durations can be added
672             by the L method.
673              
674             Timer metrics may be handled by the adapter similarly to distribution metrics.
675             Moreover, adapters may choose to implement timers as distributions with units
676             of C.
677              
678             =cut
679              
680             =head2 make_timer
681              
682             $collector->make_timer( $handle, %args )
683              
684             Requests the creation of a new timer metric.
685              
686             =cut
687              
688             sub make_timer
689             {
690 10     10 1 1093 my $self = shift;
691 10         38 my ( $handle, %args ) = @_;
692              
693 10   100     62 $args{name} = $self->_metricname( $args{name} // [ $handle ] );
694              
695 10 100       111 $self->{$handle} and croak "Already have a metric '$handle'";
696 9   100     18 $self->{$handle} = [ timer => @{ $args{labels} // [] } ];
  9         49  
697              
698 9         111 $self->_adapter_call( make_timer => "$self->{package}/$handle",
699             collector => $self,
700             %args
701             );
702             }
703              
704             =head2 report_timer
705              
706             $collector->report_timer( $handle, $duration, $labels )
707              
708             I
709              
710             Reports a new duration for the timer metric. The C<$handle> name must match
711             one earlier created by L. The C<$duration> gives a time measured
712             in seconds, and may be fractional.
713              
714             This method used to called C and is currently still available as
715             an alias.
716              
717             =cut
718              
719             sub report_timer
720             {
721 12     12 1 1135 my $self = shift;
722 12         35 my ( $handle, $duration, @args ) = @_;
723              
724 12         38 my @labelvalues = $self->_labelvalues( timer => $handle, @args );
725              
726 11         31 my $adapter = $self->adapter;
727              
728             # Support new and legacy name
729 11   50     52 my $method = $adapter->can( "report_timer" ) // "inc_timer_by";
730 11         53 $adapter->$method( "$self->{package}/$handle", $duration, @labelvalues );
731             }
732              
733             *inc_timer_by = \&report_timer;
734              
735             =head1 AUTHOR
736              
737             Paul Evans
738              
739             =cut
740              
741             0x55AA;