File Coverage

blib/lib/Net/Prometheus/Metric.pm
Criterion Covered Total %
statement 94 96 97.9
branch 16 18 88.8
condition 12 17 70.5
subroutine 22 24 91.6
pod 6 9 66.6
total 150 164 91.4


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