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 15     15   147924 use strict;
  15         56  
  15         426  
9 15     15   78 use warnings;
  15         25  
  15         639  
10              
11             our $VERSION = '0.11';
12              
13 15     15   122 use Carp;
  15         51  
  15         1250  
14             our @CARP_NOT = qw( Net::Prometheus );
15              
16 15     15   7858 use Ref::Util qw( is_hashref );
  15         24776  
  15         1084  
17              
18 15     15   6662 use Net::Prometheus::Types qw( Sample MetricSamples );
  15         39  
  15         949  
19              
20 15     15   110 use constant CHILDCLASS => "Net::Prometheus::Metric::_Child";
  15         31  
  15         10900  
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 31     31 1 4224 my $class = shift;
105 31         108 my %args = @_;
106              
107             defined $args{name} or
108 31 100       194 croak "Required 'name' argument missing";
109             defined $args{help} or
110 30 100       299 croak "Required 'help' argument missing";
111              
112 29         94 my $fullname = join "_", grep { defined } $args{namespace}, $args{subsystem}, $args{name};
  87         226  
113              
114 29   100     126 my $labellist = $args{labels} || [];
115              
116             # See
117             # https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels
118 29 100       268 $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 28   66     334 croak "Invalid label name '$_'" for @$labellist;
123             $_ =~ m/^__/ and
124 27   66     175 croak "Label name '$_' is reserved" for @$labellist;
125              
126             return bless {
127             fullname => $fullname,
128             help => $args{help},
129 26         164 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 72     72 1 106 my $self = shift;
150 72         240 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 81     81 1 126 my $self = shift;
164 81         109 return scalar @{ $self->{labels} };
  81         241  
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 39     39 1 7020 my $self = shift;
189 39         89 my @values = @_;
190              
191 39 100 100     156 if( @values == 1 and is_hashref( $values[0] ) ) {
192 3         6 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     12 for @$labels;
197              
198 3         5 @values = @{$href}{ @$labels };
  3         8  
199             }
200              
201 39         94 my $labelcount = $self->labelcount;
202 39 50       102 @values >= $labelcount or
203             croak "Insufficient values given for labels";
204 39 50       98 @values == $labelcount or
205             croak "Too many values given for labels";
206              
207             length $values[$_] or
208 39   66     232 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 38         92 my $value = $_;
  22         30  
214 22         47 $value =~ s/\x01/\x011/g;
215 22         36 $value =~ s/\x00/\x010/g;
216 22         62 $value;
217             } @values;
218              
219 38         100 $self->{labelvalues}{$labelkey} = \@values;
220              
221 38         194 return $self->CHILDCLASS->new(
222             $self, $labelkey
223             );
224             }
225              
226             {
227             package
228             Net::Prometheus::Metric::_Child;
229              
230             use constant {
231 15         3077 METRIC => 0,
232             LABELKEY => 1,
233 15     15   143 };
  15         41  
234              
235             sub new
236             {
237 38     38   73 my $class = shift;
238 38         82 my ( $metric, $labelkey ) = @_;
239 38         181 return bless [ $metric, $labelkey ], $class;
240             }
241              
242 29     29   90 sub metric { shift->[METRIC] }
243 38     38   152 sub labelkey { shift->[LABELKEY] }
244             }
245              
246             # A metaclass method for declaring the child class
247             sub MAKE_child_class
248             {
249 40     40 0 89 my $class = shift;
250              
251 40         120 my $childclass = "${class}::_Child";
252              
253 15     15   118 no strict 'refs';
  15         28  
  15         2065  
254              
255             # The careful ordering of these two lines should make it possible to
256             # further subclass metrics and metric child classes recursively
257 40         276 @{"${childclass}::ISA"} = $class->CHILDCLASS;
  40         832  
258 40     0   441 *{"${class}::CHILDCLASS"} = sub() { $childclass };
  40         247  
  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 70     70 0 129 my $class = shift;
266 70         137 my ( $method ) = @_;
267              
268 15     15   286 no strict 'refs';
  15         55  
  15         6025  
269 70         343 *{"${class}::${method}"} = sub {
270 28     28   10565 my $self = shift;
271 28 100       109 my @values = splice @_, 0, is_hashref( $_[0] ) ? 1 : $self->labelcount;
272              
273 28         85 $self->labels( @values )->$method( @_ );
274 70         270 };
275              
276 70         177 my $childmethod = "_${method}_child";
277              
278 70         357 *{"${class}::_Child::${method}"} = sub {
279 29     29   44 my $self = shift;
280 29         74 $self->metric->$childmethod( $self->labelkey, @_ );
281 70         260 };
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 55     55 1 1838 my $self = shift;
300 55         129 my ( $suffix, $labelkey, $value, $extralabels ) = @_;
301              
302 55         100 my $labelnames = $self->{labels};
303 55         92 my $labelvalues = $self->{labelvalues}{$labelkey};
304              
305             return Sample(
306             ( $suffix ? $self->fullname . "_$suffix" : $self->fullname ),
307 55 100       154 [ ( map { $labelnames->[$_], $labelvalues->[$_] } 0 .. $#$labelnames ), @{ $extralabels || [] } ],
  24 100       63  
  55         278  
308             $value,
309             );
310             }
311              
312             sub collect
313             {
314 11     11 0 31 my $self = shift;
315              
316             return MetricSamples(
317             $self->fullname, $self->_type, $self->{help},
318 11         37 [ $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;