File Coverage

blib/lib/Net/Prometheus/Histogram.pm
Criterion Covered Total %
statement 77 77 100.0
branch 6 8 75.0
condition 17 22 77.2
subroutine 13 13 100.0
pod 3 3 100.0
total 116 123 94.3


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-2020 -- leonerd@leonerd.org.uk
5              
6             package Net::Prometheus::Histogram;
7              
8 8     8   73346 use strict;
  8         23  
  8         228  
9 8     8   39 use warnings;
  8         15  
  8         220  
10 8     8   41 use base qw( Net::Prometheus::Metric );
  8         15  
  8         1281  
11              
12             our $VERSION = '0.10';
13              
14 8     8   59 use Carp;
  8         29  
  8         563  
15 8     8   55 use List::Util 1.33 qw( any );
  8         139  
  8         548  
16              
17 8     8   117 use constant _type => "histogram";
  8         35  
  8         620  
18              
19 8         6622 use constant DEFAULT_BUCKETS => [
20             0.005,
21             0.01, 0.025, 0.05, 0.075,
22             0.1, 0.25, 0.5, 0.75,
23             1.0, 2.5, 5.0, 7.5,
24             10
25 8     8   53 ];
  8         15  
26              
27             __PACKAGE__->MAKE_child_class;
28              
29             =head1 NAME
30              
31             C - count the distribution of numeric observations
32              
33             =head1 SYNOPSIS
34              
35             use Net::Prometheus;
36             use Time::HiRes qw( time );
37              
38             my $client = Net::Prometheus->new;
39              
40             my $histogram = $client->new_histogram(
41             name => "request_seconds",
42             help => "Summary request processing time",
43             );
44              
45             sub handle_request
46             {
47             my $start = time();
48              
49             ...
50              
51             $summary->observe( time() - $start );
52             }
53              
54             =head1 DESCRIPTION
55              
56             This class provides a histogram metric - a count of the distribution of
57             individual numerical observations into distinct buckets. These are usually
58             reports of times. It is a subclass of L.
59              
60             =cut
61              
62             =head1 CONSTRUCTOR
63              
64             Instances of this class are not usually constructed directly, but instead via
65             the L object that will serve it:
66              
67             $histogram = $prometheus->new_histogram( %args )
68              
69             This takes the same constructor arguments as documented in
70             L, and additionally the following:
71              
72             =over
73              
74             =item buckets => ARRAY
75              
76             A reference to an ARRAY containing numerical upper bounds for the buckets.
77              
78             =item bucket_min => NUM
79              
80             =item bucket_max => NUM
81              
82             =item buckets_per_decade => ARRAY[ NUM ]
83              
84             I
85              
86             A more flexible alternative to specifying literal bucket sizes. The values
87             given in C are repeated, multiplied by various powers of
88             10 to generate values between C (or a default of 0.001 if not
89             supplied) and C (or a default of 1000 if not supplied).
90              
91             =back
92              
93             =cut
94              
95             sub new
96             {
97 6     6 1 4157 my $class = shift;
98 6         26 my %opts = @_;
99              
100 6 100 100     30 if( !$opts{buckets} and grep { m/^bucket/ } keys %opts ) {
  15         53  
101 3         9 _gen_buckets( \%opts );
102             }
103              
104 6   100     20 my $buckets = $opts{buckets} || DEFAULT_BUCKETS;
105              
106             $buckets->[$_] > $buckets->[$_-1] or
107 6   66     183 croak "Histogram bucket limits must be monotonically-increasing" for 1 .. $#$buckets;
108              
109 5 100 66 1   18 $opts{labels} and any { $_ eq "le" } @{ $opts{labels} } and
  1         242  
  1         48  
110             croak "A Histogram may not have a label called 'le'";
111              
112 4         22 my $self = $class->SUPER::new( @_ );
113              
114 4         17 $self->{bounds} = [ @$buckets ]; # clone it
115 4         8 $self->{bucketcounts} = {};
116 4         8 $self->{sums} = {};
117              
118 4 50       15 if( !$self->labelcount ) {
119 4         15 $self->{bucketcounts}{""} = [ ( 0 ) x ( @$buckets + 1 ) ];
120 4         9 $self->{sums}{""} = 0;
121             }
122              
123 4         17 return $self;
124             }
125              
126             sub _gen_buckets
127             {
128 3     3   7 my ( $opts ) = @_;
129              
130 3   50     8 my $min = $opts->{bucket_min} // 1E-3;
131 3   100     12 my $max = $opts->{bucket_max} // 1E3;
132              
133 3   100     6 my @values_per_decade = @{ $opts->{buckets_per_decade} // [ 1 ] };
  3         14  
134              
135 3         7 my $value;
136             my @buckets;
137              
138 3         4 $value = 1;
139 3         9 while( $value >= $min ) {
140 9         14 unshift @buckets, map { $_ * $value } @values_per_decade;
  14         29  
141              
142 9         22 $value /= 10;
143             }
144              
145 3         5 $value = 10;
146 3         12 while( $value <= $max ) {
147 9         15 push @buckets, map { $_ * $value } @values_per_decade;
  24         40  
148              
149 9         20 $value *= 10;
150             }
151              
152             # Trim overgenerated ends
153 3 50       6 @buckets = grep { $min <= $_ and $_ <= $max } @buckets;
  38         109  
154              
155 3         10 $opts->{buckets} = \@buckets;
156             }
157              
158             =head2 bucket_bounds
159              
160             @bounds = $histogram->bucket_bounds
161              
162             Returns the bounding values for each of the buckets, excluding the final
163             C<+Inf> bucket.
164              
165             =cut
166              
167             sub bucket_bounds
168             {
169 4     4 1 519 my $self = shift;
170 4         6 return @{ $self->{bounds} };
  4         29  
171             }
172              
173             =head2 observe
174              
175             $histogram->observe( @label_values, $value )
176             $histogram->observe( \%labels, $value )
177              
178             $child->observe( $value )
179              
180             Increment the histogram sum by the given value, and each bucket count by 1
181             where the value is less than or equal to the bucket upper bound.
182              
183             =cut
184              
185             __PACKAGE__->MAKE_child_method( 'observe' );
186             sub _observe_child
187             {
188 2     2   4 my $self = shift;
189 2         5 my ( $labelkey, $value ) = @_;
190              
191 2         4 my $bounds = $self->{bounds};
192 2   50     6 my $buckets = $self->{bucketcounts}{$labelkey} ||= [ ( 0 ) x ( @$bounds + 1 ) ];
193              
194 2   66     15 $value <= $bounds->[$_] and $buckets->[$_]++ for 0 .. $#$bounds;
195 2         5 $buckets->[scalar @$bounds]++;
196              
197 2         7 $self->{sums}{$labelkey} += $value;
198             }
199              
200             sub samples
201             {
202 3     3 1 16 my $self = shift;
203              
204 3         6 my $bounds = $self->{bounds};
205 3         6 my $bucketcounts = $self->{bucketcounts};
206 3         4 my $sums = $self->{sums};
207              
208             return map {
209 3         12 my $labelkey = $_;
  3         6  
210 3         6 my $buckets = $bucketcounts->{$labelkey};
211              
212             $self->make_sample( count => $labelkey, $buckets->[-1] ),
213             $self->make_sample( sum => $labelkey, $sums->{$labelkey} ),
214             ( map {
215 3         13 $self->make_sample( bucket => $labelkey, $buckets->[$_], [ le => $bounds->[$_] ] )
  9         71  
216             } 0 .. $#$bounds ),
217             $self->make_sample( bucket => $labelkey, $buckets->[-1], [ le => "+Inf" ] );
218             } sort keys %$sums;
219             }
220              
221             =head1 AUTHOR
222              
223             Paul Evans
224              
225             =cut
226              
227             0x55AA;