File Coverage

blib/lib/Metrics/Any/Adapter/Test.pm
Criterion Covered Total %
statement 63 66 95.4
branch 11 16 68.7
condition 10 13 76.9
subroutine 14 15 93.3
pod 5 7 71.4
total 103 117 88.0


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::Test 0.08;
7              
8 5     5   1316 use v5.14;
  5         16  
9 5     5   23 use warnings;
  5         9  
  5         161  
10 5     5   25 use base qw( Metrics::Any::AdapterBase::Stored );
  5         11  
  5         1775  
11              
12 5     5   32 use Carp;
  5         10  
  5         256  
13              
14 5     5   29 use List::Util 1.29 qw( pairs );
  5         102  
  5         255  
15 5     5   25 use Scalar::Util qw( reftype );
  5         8  
  5         571  
16              
17             =head1 NAME
18              
19             C - a metrics reporting adapter for unit testing
20              
21             =head1 SYNOPSIS
22              
23             use Test::More;
24             use Metrics::Any::Adapter 'Test';
25              
26             {
27             Metrics::Any::Adapter::Test->clear;
28              
29             # perform some work in the code under test
30              
31             is( Metrics::Any::Adapter::Test->metrics,
32             "an_expected_metric = 1\n",
33             'Metrics were reported while doing something'
34             );
35             }
36              
37             =head1 DESCRIPTION
38              
39             This L adapter type stores reported metrics locally, allowing
40             access to them by the L method. This is useful to use in a unit test
41             to check that the code under test reports the correct metrics.
42              
43             This adapter supports timer metrics by storing as distributions. By default,
44             distributions store only a summary, giving the count and total duration. If
45             required, the full values can be stored by setting L.
46              
47             For predictable output of timer metrics in unit tests, a unit test may wish to
48             use the L method.
49              
50             =cut
51              
52             my $singleton;
53              
54             sub new
55             {
56 31     31 0 67 my $class = shift;
57 31   66     113 return $singleton //= $class->SUPER::new( @_ );
58             }
59              
60             # We're a singleton instance, so we can just store state in file-scoped
61             # lexicals
62              
63             my $timer_duration;
64             my $use_full_distributions;
65              
66             =head1 METHODS
67              
68             =cut
69              
70             =head2 metrics
71              
72             $result = Metrics::Any::Adapter::Test->metrics
73              
74             This class method returns a string describing all of the stored metric values.
75             Each is reported on a line formatted as
76              
77             name = value
78              
79             Each line, including the final one, is terminated by a linefeed. The metrics
80             are sorted alphabetically. Any multi-part metric names will be joined with
81             underscores (C<_>).
82              
83             Metrics that have additional labels are formatted with additional label names
84             and label values in declared order after the name and before the C<=> symbol:
85              
86             name l1:v1 l2:v2 = value
87              
88             =cut
89              
90             =head2 use_full_distributions
91              
92             Metrics::Any::Adapter::Test->use_full_distributions; # enables the option
93              
94             Metrics::Any::Adapter::Test->use_full_distributions( $enable );
95              
96             I
97              
98             If enabled, this option stores the full value of every reported observation
99             into distributions, rathr than just the count-and-total summary.
100              
101             Full value distributions will be formatted as a sequence of lines containing
102             the count of observations at that particular value, in square brackets,
103             followed by the summary count.
104              
105             name[v1] = c1
106             name[v2] = c2
107             ...
108             name_count = c
109              
110             In order not to be too sensitive to numerical rounding errors, values are
111             stored to only 3 decimal places.
112              
113             =cut
114              
115             sub use_full_distributions
116             {
117 1     1 1 4 shift;
118 1 50       5 ( $use_full_distributions ) = @_ ? @_ : ( 1 );
119             }
120              
121             use constant {
122 5         3225 DIST_COUNT => 0,
123             DIST_TOTAL => 1,
124 5     5   31 };
  5         8  
125              
126             sub store_distribution
127             {
128 11     11 1 17 shift;
129 11         31 my ( $storage, $amount ) = @_;
130              
131 11 100       31 if( $use_full_distributions ) {
132 3   100     12 $storage //= {};
133              
134 3         20 $storage->{ sprintf "%.3f", $amount }++;
135             }
136             else {
137 8   50     45 $storage //= [ 0, 0 ];
138 8         13 $storage->[DIST_COUNT] += 1;
139 8         15 $storage->[DIST_TOTAL] += $amount;
140             }
141              
142 11         42 return $storage;
143             }
144              
145             *store_timer = \&store_distribution;
146              
147             sub metrics
148             {
149 17     17 1 53 my $self = shift;
150 17 50       42 ref $self or $self = Metrics::Any::Adapter::Test->new;
151              
152 17         28 my @ret;
153              
154             $self->walk( sub {
155 28     28   57 my ( $type, $name, $labels, $value ) = @_;
156              
157 28         188 $name .= sprintf " %s:%s", $_->key, $_->value for pairs @$labels;
158              
159 28 100 100     181 if( $type eq "counter" or $type eq "gauge" ) {
    50 66        
160 19         89 push @ret, "$name = $value";
161             }
162             elsif( $type eq "distribution" or $type eq "timer" ) {
163 9 100       35 if( reftype $value eq "HASH" ) {
164 1         2 my $total = 0;
165 1         5 foreach my $k ( sort { $a <=> $b } keys %$value ) {
  3         10  
166 3         3 my $v = $value->{$k};
167             # Trim trailing zeroes for neatness
168 3         12 $k =~ s/\.0+$/./; $k =~ s/\.$//;
  3         10  
169              
170 3         8 push @ret, "$name\[$k] = $v";
171 3         4 $total += $v;
172             }
173 1         4 push @ret, "${name}_count = $total";
174             }
175             else {
176 8         33 push @ret, "${name}_count = " . $value->[DIST_COUNT];
177 8         64 push @ret, "${name}_total = " . $value->[DIST_TOTAL];
178             }
179             }
180             else {
181 0         0 warn "Unsure how to handle metric of type $type\n";
182             }
183 17         145 } );
184              
185 17         145 return join "", map { "$_\n" } @ret;
  39         143  
186             }
187              
188             =head2 clear
189              
190             Metrics::Any::Adapter::Test->clear
191              
192             This class method removes all of the stored values of reported metrics.
193              
194             =cut
195              
196             sub clear
197             {
198 10     10 1 5354 my $self = shift;
199 10 50       39 ref $self or $self = Metrics::Any::Adapter::Test->new;
200              
201 10         36 $self->clear_values;
202              
203 10         22 undef $timer_duration;
204             }
205              
206             =head2 override_timer_duration
207              
208             Metrics::Any::Adapter::Test->override_timer_duration( $duration )
209              
210             This class method sets a duration value, that any subsequent call to
211             C will use instead of the value the caller actually passed in. This
212             will ensure reliably predictable output in unit tests.
213              
214             Any value set here will be cleared by L.
215              
216             =cut
217              
218             sub override_timer_duration
219             {
220 0     0 1 0 shift;
221 0         0 ( $timer_duration ) = @_;
222             }
223              
224             sub report_timer
225             {
226 4     4 0 9 my $self = shift;
227 4         9 my ( $handle, $duration, @labelvalues ) = @_;
228              
229 4 50       14 $duration = $timer_duration if defined $timer_duration;
230              
231 4         23 $self->SUPER::report_timer( $handle, $duration, @labelvalues );
232             }
233              
234             =head1 AUTHOR
235              
236             Paul Evans
237              
238             =cut
239              
240             0x55AA;