File Coverage

blib/lib/Metrics/Any/Adapter/Prometheus.pm
Criterion Covered Total %
statement 83 84 98.8
branch 9 14 64.2
condition 15 41 36.5
subroutine 22 22 100.0
pod 1 11 9.0
total 130 172 75.5


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