File Coverage

blib/lib/Net/Prometheus/Metric.pm
Criterion Covered Total %
statement 134 142 94.3
branch 16 18 88.8
condition 12 17 70.5
subroutine 51 53 96.2
pod 18 25 72.0
total 231 255 90.5


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, 2016-2026 -- leonerd@leonerd.org.uk
5              
6             package Net::Prometheus::Metric 0.16;
7              
8 16     16   412713 use v5.20;
  16         91  
9 16     16   91 use warnings;
  16         32  
  16         1077  
10              
11 16     16   113 use feature qw( postderef signatures );
  16         52  
  16         2615  
12 16     16   113 no warnings qw( experimental::postderef experimental::signatures );
  16         50  
  16         683  
13              
14 16     16   96 use Carp;
  16         47  
  16         1588  
15             our @CARP_NOT = qw( Net::Prometheus );
16              
17 16     16   3227 use meta 0.009; # GvCVu bugfix
  16         7025  
  16         617  
18 16     16   88 no warnings 'meta::experimental';
  16         41  
  16         797  
19              
20 16     16   8941 use Ref::Util qw( is_hashref );
  16         41489  
  16         1696  
21              
22 16     16   8653 use Net::Prometheus::Types qw( Sample MetricSamples );
  16         94  
  16         1252  
23              
24 16     16   136 use constant CHILDCLASS => "Net::Prometheus::Metric::_Child";
  16         30  
  16         16928  
25              
26             =head1 NAME
27              
28             C - the base class for observed metrics
29              
30             =head1 DESCRIPTION
31              
32             =for highlighter language=perl
33              
34             This class provides the basic methods shared by the concrete subclasses,
35              
36             =over 2
37              
38             =item *
39              
40             L - a snapshot value-reporting metric
41              
42             =item *
43              
44             L - a monotonically-increasing counter metric
45              
46             =item *
47              
48             L - summarise individual numeric observations
49              
50             =item *
51              
52             L - count the distribution of numeric observations
53              
54             =back
55              
56             =cut
57              
58             =head1 CONSTRUCTOR
59              
60             =cut
61              
62             =head2 new
63              
64             $metric = Net::Prometheus::Metric->new(
65             name => $name,
66             help => $help,
67             );
68              
69             The constructor is not normally used directly by instrumented code. Instead it
70             is more common to use one of the C methods on the containing
71             L client instance so that the new metric is automatically
72             registered as a collector, and gets exported by the render method.
73              
74             $metric = $prometheus->new_counter(
75             name => $name,
76             help => $help,
77             );
78              
79             In either case, it returns a newly-constructed metric.
80              
81             Takes the following named arguments:
82              
83             =over
84              
85             =item namespace => STR
86              
87             =item subsystem => STR
88              
89             Optional strings giving the namespace and subsystem name parts of the variable
90             name.
91              
92             =item name => STR
93              
94             The basename of the exported variable.
95              
96             =item help => STR
97              
98             Descriptive help text for the variable.
99              
100             =item labels => ARRAY of STR
101              
102             Optional ARRAY reference giving the names of labels for the metric.
103              
104             =back
105              
106             =cut
107              
108 37         124 sub new ( $class, %args )
109 37     37 1 522152 {
  37         141  
  37         59  
110             defined $args{name} or
111 37 100       322 croak "Required 'name' argument missing";
112             defined $args{help} or
113 36 100       402 croak "Required 'help' argument missing";
114              
115 35         132 my $fullname = join "_", grep { defined } $args{namespace}, $args{subsystem}, $args{name};
  105         274  
116              
117 35   100     164 my $labellist = $args{labels} || [];
118              
119             # See
120             # https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels
121 35 100       500 $fullname =~ m/^[a-zA-Z_:][a-zA-Z0-9_:]*$/ or
122             croak "Invalid metric name '$fullname'";
123              
124             $_ =~ m/^[a-zA-Z_][a-zA-Z0-9_]*$/ or
125 34   66     372 croak "Invalid label name '$_'" for @$labellist;
126             $_ =~ m/^__/ and
127 33   66     236 croak "Label name '$_' is reserved" for @$labellist;
128              
129             return bless {
130             fullname => $fullname,
131             help => $args{help},
132 32         341 labels => $labellist,
133             labelvalues => {},
134             }, $class;
135             }
136              
137             =head1 METHODS
138              
139             =cut
140              
141             =head2 fullname
142              
143             $fullname = $metric->fullname;
144              
145             Returns the full name for the metric. This is formed by joining any of the
146             defined values for C, C and C with C<'_'>.
147              
148             =cut
149              
150             sub fullname ( $self )
151 182     182 1 244 {
  182         219  
  182         230  
152 182         807 return $self->{fullname};
153             }
154              
155             =head2 labelcount
156              
157             $labels = $metric->labelcount;
158              
159             Returns the number of labels defined for this metric.
160              
161             =cut
162              
163             sub labelcount ( $self )
164 113     113 1 191 {
  113         207  
  113         168  
165 113         332 return scalar $self->{labels}->@*;
166             }
167              
168             =head2 labels
169              
170             $child = $metric->labels( @values );
171              
172             $child = $metric->labels( { name => $value, name => $value, ... } );
173              
174             Returns a child metric to represent the general one with the given set of
175             labels. The label values may be provided either in a list corresponding to the
176             list of label names given at construction time, or by name in a single HASH
177             reference.
178              
179             The child instance supports the same methods to control the value of the
180             reported metric as the parent metric object, except that any label values are
181             already provided.
182              
183             This object may be cached for efficiency.
184              
185             =cut
186              
187 60         106 sub labels ( $self, @values )
188 60     60 1 28237 {
  60         149  
  60         95  
189 60 100 100     278 if( @values == 1 and is_hashref( $values[0] ) ) {
190 19         39 my $labels = $self->{labels};
191 19         32 my $href = $values[0];
192              
193             defined $href->{$_} or croak "No value for $_ label given"
194 19   33     107 for @$labels;
195              
196 19         61 @values = $href->@{ @$labels };
197             }
198              
199 60         170 my $labelcount = $self->labelcount;
200 60 50       207 @values >= $labelcount or
201             croak "Insufficient values given for labels";
202 60 50       163 @values == $labelcount or
203             croak "Too many values given for labels";
204              
205             length $values[$_] or
206 60   66     370 croak "Value for $self->{labels}[$_] may not empty" for 0 .. $#values;
207              
208             my $labelkey = join "\x00", map {
209             # Encode \x00 or \x01 as \x{01}0 or \x{01}1 in order to escape the \x00
210             # but preserve full lexical ordering
211 59         160 my $value = $_;
  38         71  
212 38         123 $value =~ s/\x01/\x011/g;
213 38         70 $value =~ s/\x00/\x010/g;
214 38         132 $value;
215             } @values;
216              
217 59         212 $self->{labelvalues}{$labelkey} = \@values;
218              
219 59         247 return $self->CHILDCLASS->new(
220             $self, $labelkey
221             );
222             }
223              
224             {
225             package
226             Net::Prometheus::Metric::_Child;
227              
228             use constant {
229 16         17484 METRIC => 0,
230             LABELKEY => 1,
231 16     16   134 };
  16         48  
232              
233 59         133 sub new ( $class, $metric, $labelkey )
  59         119  
234 59     59   98 {
  59         105  
  59         128  
235 59         346 return bless [ $metric, $labelkey ], $class;
236             }
237              
238 50     50   81 sub metric ( $self ) { $self->[METRIC] }
  50         77  
  50         74  
  50         192  
239 59     59   93 sub labelkey ( $self ) { $self->[LABELKEY] }
  59         93  
  59         84  
  59         305  
240             }
241              
242             # A metaclass method for declaring the child class
243             sub MAKE_child_class ( $class )
244 44     44 0 104 {
  44         103  
  44         56  
245             # The careful ordering of these two changes should make it possible to
246             # further subclass metrics and metric child classes recursively
247 44         2205 my $childclass_metapkg = meta::get_package( "${class}::_Child" );
248 44         914 $childclass_metapkg->get_or_add_symbol( '@ISA' )->reference->@* =
249             $class->CHILDCLASS;
250              
251 44         1088 my $class_metapkg = meta::get_package( $class );
252 50         111 $class_metapkg->add_named_sub(
253 50     50 0 323 CHILDCLASS => sub ( $ = undef ) { "${class}::_Child" }
  50     14 0 98  
  50     14 0 74  
        14 0    
        50      
254 44         425 );
255              
256             # All Metric subclasses should support ->remove
257 44         311 $class->MAKE_child_method( 'remove' );
258             }
259              
260             # A metaclass method for declaring what Metric subclass methods are proxied
261             # via child instances
262 121         221 sub MAKE_child_method ( $class, $method )
263 121     121 0 200 {
  121         238  
  121         165  
264 121         2067 my $class_metapkg = meta::get_package( $class );
265              
266 49     49 1 28157 $class_metapkg->add_named_sub( $method => sub ( $self, @args ) {
  49     32 1 80  
  49     38 1 101  
  49     14 1 74  
        25 1    
        14 1    
        49 1    
        14 1    
        14 1    
        14 1    
        32 1    
        32      
267 49 100       225 my @values = splice @args, 0, is_hashref( $args[0] ) ? 1 : $self->labelcount;
268              
269 49         188 $self->labels( @values )->$method( @args );
270 121         952 } );
271              
272 121         2039 my $childclass_metapkg = meta::get_package( "${class}::_Child" );
273              
274 121         302 my $childmethod = "_${method}_child";
275 50     50   88 $childclass_metapkg->add_named_sub( $method => sub ( $self, @args ) {
  50     33   92  
  50     39   108  
  50     14   70  
        25      
        14      
        50      
        14      
        14      
        14      
        33      
        33      
276 50         126 $self->metric->$childmethod( $self->labelkey, @args );
277 121         1017 } );
278             }
279              
280             =head2 make_sample
281              
282             $sample = $metric->make_sample( $suffix, $labelkey, $value, $extralabels );
283              
284             Returns a new L structure to represent the
285             given value, by expanding the opaque C<$labelkey> value into its actual label
286             names and values and appending the given suffix (which may be an empty string)
287             to the metric's fullname. If provided, the suffix will be separated by an
288             underscore C<'_'>. If provided, C<$extralabels> provides more label names and
289             values to be added to the sample.
290              
291             =cut
292              
293 163         213 sub make_sample ( $self, $suffix, $labelkey, $value, $extralabels = undef )
  163         227  
  163         189  
  163         210  
294 163     163 1 3061 {
  163         213  
  163         186  
295 163         223 my $labelnames = $self->{labels};
296 163         230 my $labelvalues = $self->{labelvalues}{$labelkey};
297              
298             return Sample(
299             ( $suffix ? $self->fullname . "_$suffix" : $self->fullname ),
300 163 100       459 [ ( map { $labelnames->[$_], $labelvalues->[$_] } 0 .. $#$labelnames ), ( $extralabels || [] )->@* ],
  129 100       659  
301             $value,
302             );
303             }
304              
305 13         25 sub collect ( $self, $ )
306 13     13 0 21 {
  13         19  
307             return MetricSamples(
308             $self->fullname, $self->_type, $self->{help},
309 13         47 [ $self->samples ],
310             );
311             }
312              
313             =head2 samples
314              
315             @samples = $metric->samples;
316              
317             An abstract method in this class, this method is intended to be overridden by
318             subclasses.
319              
320             Called during the value collection process, this method should return a list
321             of L instances containing the values to report
322             from this metric.
323              
324             =cut
325              
326             sub samples ( $self )
327 0     0 1   {
  0            
  0            
328 0           croak "Abstract Net::Prometheus::Metric->samples invoked directly";
329             }
330              
331             =head2 remove
332              
333             $metric->remove( @values );
334              
335             $metric->remove( { name => $value, name => $value, ... } );
336              
337             I
338              
339             Removes a single labelset from the metric. This stops it being reported by
340             future calls to L.
341              
342             =cut
343              
344             # created by MAKE_child_class
345              
346             =head2 clear
347              
348             $metric->clear;
349              
350             I
351              
352             Removes all the labelsets from the metric, resetting it back to an initial
353             empty state.
354              
355             =cut
356              
357             # must be created by each child class
358             sub clear ( $self )
359 0     0 1   {
  0            
  0            
360 0           croak "Abstract Net::Prometheus::Metric->clear invoked directly";
361             }
362              
363             =head1 AUTHOR
364              
365             Paul Evans
366              
367             =cut
368              
369             0x55AA;