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