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 13     13   143258 use strict;
  13         44  
  13         390  
9 13     13   68 use warnings;
  13         24  
  13         494  
10              
11             our $VERSION = '0.10';
12              
13 13     13   77 use Carp;
  13         53  
  13         1089  
14             our @CARP_NOT = qw( Net::Prometheus );
15              
16 13     13   6330 use Ref::Util qw( is_hashref );
  13         20649  
  13         903  
17              
18 13     13   5210 use Net::Prometheus::Types qw( Sample MetricSamples );
  13         30  
  13         796  
19              
20 13     13   93 use constant CHILDCLASS => "Net::Prometheus::Metric::_Child";
  13         26  
  13         9083  
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 4109 my $class = shift;
105 31         106 my %args = @_;
106              
107             defined $args{name} or
108 31 100       202 croak "Required 'name' argument missing";
109             defined $args{help} or
110 30 100       280 croak "Required 'help' argument missing";
111              
112 29         87 my $fullname = join "_", grep { defined } $args{namespace}, $args{subsystem}, $args{name};
  87         275  
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       285 $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     307 croak "Invalid label name '$_'" for @$labellist;
123             $_ =~ m/^__/ and
124 27   66     177 croak "Label name '$_' is reserved" for @$labellist;
125              
126             return bless {
127             fullname => $fullname,
128             help => $args{help},
129 26         178 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 111 my $self = shift;
150 72         228 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 125 my $self = shift;
164 81         105 return scalar @{ $self->{labels} };
  81         231  
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 6994 my $self = shift;
189 39         79 my @values = @_;
190              
191 39 100 100     157 if( @values == 1 and is_hashref( $values[0] ) ) {
192 3         4 my $labels = $self->{labels};
193 3         5 my $href = $values[0];
194              
195             defined $href->{$_} or croak "No value for $_ label given"
196 3   33     11 for @$labels;
197              
198 3         5 @values = @{$href}{ @$labels };
  3         7  
199             }
200              
201 39         82 my $labelcount = $self->labelcount;
202 39 50       97 @values >= $labelcount or
203             croak "Insufficient values given for labels";
204 39 50       89 @values == $labelcount or
205             croak "Too many values given for labels";
206              
207             length $values[$_] or
208 39   66     292 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         96 my $value = $_;
  22         37  
214 22         40 $value =~ s/\x01/\x011/g;
215 22         39 $value =~ s/\x00/\x010/g;
216 22         61 $value;
217             } @values;
218              
219 38         95 $self->{labelvalues}{$labelkey} = \@values;
220              
221 38         188 return $self->CHILDCLASS->new(
222             $self, $labelkey
223             );
224             }
225              
226             {
227             package
228             Net::Prometheus::Metric::_Child;
229              
230             use constant {
231 13         2560 METRIC => 0,
232             LABELKEY => 1,
233 13     13   117 };
  13         41  
234              
235             sub new
236             {
237 38     38   67 my $class = shift;
238 38         93 my ( $metric, $labelkey ) = @_;
239 38         182 return bless [ $metric, $labelkey ], $class;
240             }
241              
242 29     29   90 sub metric { shift->[METRIC] }
243 38     38   137 sub labelkey { shift->[LABELKEY] }
244             }
245              
246             # A metaclass method for declaring the child class
247             sub MAKE_child_class
248             {
249 32     32 0 74 my $class = shift;
250              
251 32         87 my $childclass = "${class}::_Child";
252              
253 13     13   101 no strict 'refs';
  13         26  
  13         1697  
254              
255             # The careful ordering of these two lines should make it possible to
256             # further subclass metrics and metric child classes recursively
257 32         213 @{"${childclass}::ISA"} = $class->CHILDCLASS;
  32         650  
258 32     0   306 *{"${class}::CHILDCLASS"} = sub() { $childclass };
  32         204  
  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 56     56 0 100 my $class = shift;
266 56         111 my ( $method ) = @_;
267              
268 13     13   208 no strict 'refs';
  13         47  
  13         5144  
269 56         335 *{"${class}::${method}"} = sub {
270 28     28   10486 my $self = shift;
271 28 100       113 my @values = splice @_, 0, is_hashref( $_[0] ) ? 1 : $self->labelcount;
272              
273 28         93 $self->labels( @values )->$method( @_ );
274 56         201 };
275              
276 56         162 my $childmethod = "_${method}_child";
277              
278 56         267 *{"${class}::_Child::${method}"} = sub {
279 29     29   51 my $self = shift;
280 29         71 $self->metric->$childmethod( $self->labelkey, @_ );
281 56         214 };
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 1791 my $self = shift;
300 55         130 my ( $suffix, $labelkey, $value, $extralabels ) = @_;
301              
302 55         87 my $labelnames = $self->{labels};
303 55         90 my $labelvalues = $self->{labelvalues}{$labelkey};
304              
305             return Sample(
306             ( $suffix ? $self->fullname . "_$suffix" : $self->fullname ),
307 55 100       162 [ ( map { $labelnames->[$_], $labelvalues->[$_] } 0 .. $#$labelnames ), @{ $extralabels || [] } ],
  24 100       62  
  55         287  
308             $value,
309             );
310             }
311              
312             sub collect
313             {
314 11     11 0 20 my $self = shift;
315              
316             return MetricSamples(
317             $self->fullname, $self->_type, $self->{help},
318 11         35 [ $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;