File Coverage

blib/lib/Metrics/Any/Adapter/Routable.pm
Criterion Covered Total %
statement 82 82 100.0
branch 15 20 75.0
condition 3 5 60.0
subroutine 14 14 100.0
pod 0 4 0.0
total 114 125 91.2


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 -- leonerd@leonerd.org.uk
5              
6             package Metrics::Any::Adapter::Routable 0.01;
7              
8 3     3   2342 use v5.24; # postfix deref
  3         11  
9 3     3   22 use warnings;
  3         7  
  3         88  
10 3     3   1565 use experimental 'signatures';
  3         10672  
  3         17  
11              
12 3     3   509 use Carp;
  3         7  
  3         208  
13 3     3   20 use List::Util 1.39 qw( any pairs );
  3         72  
  3         2249  
14              
15             require Metrics::Any::Adapter; Metrics::Any::Adapter->VERSION( '0.06' );
16              
17             =head1 NAME
18              
19             C - configurable routing of reported metrics
20              
21             =head1 SYNOPSIS
22              
23             use Metrics::Any::Adapter 'Routable',
24             targets => [
25             [ "important", "Statsd" ],
26             [ "default", "Prometheus" ],
27             [ ["default", "debug"], "File", path => "metrics.log" ],
28             ],
29             packages => {
30             "My::Application" => "important",
31             "Net::Async::HTTP" => "debug",
32             "IO::Async::*" => "debug", # wildcard matches
33             # anything else will be assigned "default"
34             };
35              
36             =head1 DESCRIPTION
37              
38             This L adapter type acts as a proxy for a set of multiple other
39             adapters, allowing an application to configure which adapter (or adapters) to
40             send particular metrics into.
41              
42             Routing of metrics is done by a "category" name. Each reported metric is
43             assigned into a category, which is a string. Each configured adapter declares
44             an interest in one or more category names. Reported metrics are then routed
45             only to those adapters which declared an interest in the category.
46              
47             Primarily the category names are set by the C configuration
48             argument. Additionally, this can be overridden by any individual metric when
49             it is constructed by providing a C parameter to the C method
50             which created it.
51              
52             =head1 ARGUMENTS
53              
54             The following additional arguments are recognised
55              
56             =head2 targets
57              
58             targets => [
59             [ $category, $type, ],
60             [ $category, $type, @args ],
61             [ [ @categories ], $type, @args ],
62             ...
63             ],
64              
65             A reference to an array containing a list of targets. Each target consists of
66             a category name (or reference array containing a list of categories), a type
67             name, and an optional set of constructor arguments, all stored in its own
68             array reference.
69              
70             These targets will all be constructed and stored by the adapter.
71              
72             =head2 packages
73              
74             packages => {
75             $package => $category,
76             ...
77             }
78              
79             A reference to a hash associating a category name with a reporting package.
80             Any metrics registered by the given package will be associated with the given
81             category name.
82              
83             A pattern can also be specified with a trailing C<::*> wildcard; this will
84             match any package name within the given namespace. Longer matches will take
85             precedence over shorter ones.
86              
87             Any reported metric that does not otherwise have a category configured will be
88             assigned the category C.
89              
90             =cut
91              
92 2         6 sub new ( $class, %args )
93 2     2 0 18 {
  2         5  
  2         4  
94 2         8 my $self = bless {
95             package_category => {},
96             metric_category => {},
97             targets => [],
98             }, $class;
99              
100 2         9 $self->add_target( @$_ ) for $args{targets}->@*;
101              
102 2         30 $self->set_category_for_package( $_->key, $_->value ) for pairs $args{packages}->%*;
103              
104 2         16 return $self;
105             }
106              
107 6         36 sub add_target ( $self, $categories, $type, @args )
  6         10  
  6         8  
108 6     6 0 10 {
  6         12  
  6         8  
109 6 100       21 ref $categories eq "ARRAY" or $categories = [ $categories ];
110              
111 6         16 my $adapter = Metrics::Any::Adapter->class_for_type( $type )->new( @args );
112              
113 6         140 push $self->{targets}->@*, [ $categories, $adapter ];
114             }
115              
116 5         9 sub category_for_package ( $self, $package )
117 5     5 0 1884 {
  5         10  
  5         6  
118 5         10 my $categories = $self->{package_category};
119              
120 5 100       24 return $categories->{$package} if exists $categories->{$package};
121              
122 3         8 while( length $package ) {
123 4 100       22 return $categories->{"${package}::*"} if exists $categories->{"${package}::*"};
124 2 100       14 $package =~ s/::[^:]+$// or last;
125             }
126 1         4 return undef;
127             }
128              
129 3         4 sub set_category_for_package ( $self, $package, $category )
  3         6  
130 3     3 0 6 {
  3         3  
  3         5  
131 3         11 $self->{package_category}{$package} = $category;
132             }
133              
134             foreach my $method (qw( make_counter make_distribution make_gauge make_timer )) {
135 4     4   150 my $code = sub ( $self, $handle, %args ) {
  4         8  
  4         5  
  4         10  
  4         9  
136 4         6 my $collector = $args{collector};
137              
138             $self->{metric_category}{$handle} = $args{category} //
139 4   66     31 $self->category_for_package( $collector->package ) //
      50        
140             # TODO: a configurable default category
141             "default";
142              
143 4         7 my @e;
144 4         9 foreach my $target ( $self->{targets}->@* ) {
145 12         23 my ( undef, $adapter ) = @$target;
146              
147 12 50       15 defined eval { $adapter->$method( $handle, %args ); 1 } or
  12         49  
  12         44  
148             push @e, $@;
149             }
150 4 50       19 die $e[0] if @e;
151             };
152              
153 3     3   26 no strict 'refs';
  3         6  
  3         910  
154             *$method = $code;
155             }
156              
157             foreach my $method (qw( inc_counter_by report_distribution inc_gauge_by set_gauge report_timer )) {
158 4     4   71 my $code = sub ( $self, $handle, @args ) {
  4         6  
  4         9  
  4         6  
  4         8  
159 4 50       12 my $category = $self->{metric_category}{$handle} or
160             croak "Unsure category for $handle";
161              
162 4         7 my @e;
163 4         8 foreach my $target ( $self->{targets}->@* ) {
164 12         24 my ( $categories, $adapter ) = @$target;
165              
166 12 100   14   47 next unless any { $_ eq $category } @$categories;
  14         38  
167              
168 8 50       21 defined eval { $adapter->$method( $handle, @args ); 1 } or
  8         32  
  8         43  
169             push @e, $@;
170             }
171 4 50       16 die $e[0] if @e;
172             };
173              
174 3     3   25 no strict 'refs';
  3         6  
  3         185  
175             *$method = $code;
176             }
177              
178             =head1 AUTHOR
179              
180             Paul Evans
181              
182             =cut
183              
184             0x55AA;