File Coverage

blib/lib/Metrics/Any/Adapter/Prometheus.pm
Criterion Covered Total %
statement 67 68 98.5
branch 9 14 64.2
condition 15 41 36.5
subroutine 17 17 100.0
pod 1 10 10.0
total 109 150 72.6


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::Adapter::Prometheus;
7              
8 3     3   5985 use strict;
  3         6  
  3         90  
9 3     3   21 use warnings;
  3         6  
  3         119  
10              
11             our $VERSION = '0.05';
12              
13 3     3   18 use Carp;
  3         4  
  3         194  
14              
15 3     3   519 use Net::Prometheus::Registry;
  3         384  
  3         80  
16              
17 3     3   449 use Net::Prometheus::Counter;
  3         8755  
  3         111  
18 3     3   478 use Net::Prometheus::Gauge;
  3         848  
  3         106  
19 3     3   506 use Net::Prometheus::Histogram 0.10;
  3         1514  
  3         2980  
20              
21             =head1 NAME
22              
23             C - a metrics reporting adapter for Prometheus
24              
25             =head1 SYNOPSIS
26              
27             use Metrics::Any::Adapter 'Prometheus';
28              
29             =head1 DESCRIPTION
30              
31             This L adapter type reports metrics to Prometheus by using
32             L. Each metric added to the adapter will be registered with
33             the global L instance.
34              
35             It becomes the calling program's responsibility to arrange for these to be
36             HTTP accessible by using the C API.
37              
38             Distribution metrics are exported as Histograms by default. They may
39             alternatively be exported as Summaries in order to generate smaller amounts
40             of export data, by setting the C import argument to false:
41              
42             use Metrics::Any::Adapter 'Prometheus', use_histograms => 0;
43              
44             Timer metrics are implemented as distribution metrics with the units set to
45             C.
46              
47             =cut
48              
49             sub new
50             {
51 2     2 0 20 my $class = shift;
52 2         6 my ( %args ) = @_;
53              
54             return bless {
55             metrics => {},
56 2   50     36 use_histograms => $args{use_histograms} // 1,
57             }, $class;
58             }
59              
60             =head1 METHODS
61              
62             =cut
63              
64             sub mangle_name
65             {
66 8     8 0 15 my $self = shift;
67 8         17 my ( $name ) = @_;
68              
69 8 100       37 $name = join "_", @$name if ref $name eq "ARRAY";
70              
71             # TODO: Consider lowercase, squashing unallowed chars to _,...
72              
73 8         21 return $name;
74             }
75              
76             sub make_counter
77             {
78 1     1 0 18 my $self = shift;
79 1         4 my ( $handle, %args ) = @_;
80              
81 1   33     5 my $name = $self->mangle_name( delete $args{name} // $handle );
82 1   33     17 my $help = delete $args{description} // "Metrics::Any counter $handle";
83              
84 1 50       4 if( my $units = delete $args{units} ) {
85             # Append _bytes et.al. if required
86 0 0 0     0 $name .= "_$units" if length $units and $name !~ m/_\Q$units\E$/;
87             }
88             else {
89             # Prometheus policy says unitless counters take _total suffix
90 1         3 $name .= "_total";
91             }
92              
93 1         6 $self->{metrics}{$handle} = Net::Prometheus::Registry->register(
94             Net::Prometheus::Counter->new(
95             name => $name,
96             help => $help,
97             %args,
98             )
99             );
100             }
101              
102             sub inc_counter_by
103             {
104 1     1 0 228 my $self = shift;
105 1         3 my ( $handle, $amount, @labelvalues ) = @_;
106              
107 1   33     5 ( $self->{metrics}{$handle} or croak "No such counter named '$handle'" )
108             ->inc( @labelvalues, $amount );
109             }
110              
111             =head2 make_distribution
112              
113             $adapter->make_distribution( $name, %args )
114              
115             In addition to the standard arguments, the following are recognised:
116              
117             =over 4
118              
119             =item buckets => ARRAY[ NUM ]
120              
121             If present, overrides the default Histogram bucket sizes.
122              
123             =item bucket_min => NUM
124              
125             =item bucket_max => NUM
126              
127             =item buckets_per_decade => ARRAY[ NUM ]
128              
129             I
130              
131             A more flexible alternative to specifying literal bucket sizes. The values
132             given in C are repeated, multiplied by various powers of
133             10 to generate values between C (or a default of 0.001 if not
134             supplied) and C (or a default of 1000 if not supplied).
135              
136             For more information, see L.
137              
138             =back
139              
140             =cut
141              
142             my %BUCKETS_FOR_UNITS = (
143             bytes => { bucket_min => 100, bucket_max => 1E8 },
144             seconds => undef, # Prometheus defaults are fine
145             );
146              
147             sub make_distribution
148             {
149 6     6 1 12832 my $self = shift;
150 6         20 my ( $handle, %args ) = @_;
151              
152 6   33     27 my $name = $self->mangle_name( delete $args{name} // $handle );
153 6         24 my $units = delete $args{units};
154 6   66     32 my $help = delete $args{description} // "Metrics::Any $units distribution $handle";
155              
156             # Append _bytes et.al. if required
157 6 100 66     55 $name .= "_$units" if length $units and $name !~ m/_\Q$units\E$/;
158              
159 6 50       27 unless( $args{buckets} ) {
160 6 100       19 %args = ( %{ $BUCKETS_FOR_UNITS{$units} }, %args ) if $BUCKETS_FOR_UNITS{$units};
  1         6  
161             }
162              
163 6 50       20 my $metric_class = $self->{use_histograms} ? "Net::Prometheus::Histogram" :
164             "Net::Prometheus::Summary";
165              
166 6         40 $self->{metrics}{$handle} = Net::Prometheus::Registry->register(
167             $metric_class->new(
168             name => $name,
169             help => $help,
170             %args,
171             )
172             );
173             }
174              
175             sub report_distribution
176             {
177 6     6 0 1248 my $self = shift;
178 6         15 my ( $handle, $amount, @labelvalues ) = @_;
179              
180             # TODO: Sanity-check that @labelvalues is as long as the label count
181              
182 6   33     26 ( $self->{metrics}{$handle} or croak "No such distribution named '$handle'" )
183             ->observe( @labelvalues, $amount );
184             }
185              
186             *inc_distribution_by = \&report_distribution;
187              
188             sub make_gauge
189             {
190 1     1 0 5805 my $self = shift;
191 1         3 my ( $handle, %args ) = @_;
192              
193 1   33     5 my $name = $self->mangle_name( delete $args{name} // $handle );
194 1   33     15 my $help = delete $args{description} // "Metrics::Any gauge $handle";
195              
196 1         10 $self->{metrics}{$handle} = Net::Prometheus::Registry->register(
197             Net::Prometheus::Gauge->new(
198             name => $name,
199             help => $help,
200             %args,
201             )
202             );
203             }
204              
205             sub set_gauge_to
206             {
207 1     1 0 184 my $self = shift;
208 1         2 my ( $handle, $amount, @labelvalues ) = @_;
209              
210 1   33     5 ( $self->{metrics}{$handle} or croak "No such gauge named '$handle'" )
211             ->set( @labelvalues, $amount );
212             }
213              
214             sub inc_gauge_by
215             {
216 1     1 0 90 my $self = shift;
217 1         2 my ( $handle, $amount, @labelvalues ) = @_;
218              
219 1   33     4 ( $self->{metrics}{$handle} or croak "No such gauge named '$handle'" )
220             ->inc( @labelvalues, $amount );
221             }
222              
223             sub make_timer
224             {
225 1     1 0 2610 my $self = shift;
226 1         3 my ( $handle, %args ) = @_;
227              
228 1   33     8 $args{description} //= "Metrics::Any timer $handle";
229              
230 1         4 return $self->make_distribution( $handle,
231             %args,
232             units => "seconds",
233             );
234             }
235              
236             *report_timer = \&report_distribution;
237             *inc_timer_by = \&report_distribution;
238              
239             =head1 AUTHOR
240              
241             Paul Evans
242              
243             =cut
244              
245             0x55AA;