File Coverage

blib/lib/Metrics/Any/Adapter/Prometheus.pm
Criterion Covered Total %
statement 128 129 99.2
branch 9 14 64.2
condition 15 41 36.5
subroutine 24 24 100.0
pod 1 11 9.0
total 177 219 80.8


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