File Coverage

blib/lib/Monitor/MetricsAPI/Server/Routes.pm
Criterion Covered Total %
statement 9 22 40.9
branch 0 2 0.0
condition n/a
subroutine 3 5 60.0
pod n/a
total 12 29 41.3


line stmt bran cond sub pod time code
1 13     13   72 use strict;
  13         26  
  13         491  
2 13     13   61 use warnings;
  13         26  
  13         764  
3              
4             package Monitor::MetricsAPI::Server::Routes;
5             $Monitor::MetricsAPI::Server::Routes::VERSION = '0.900';
6 13     13   7366 use Dancer2;
  13         4297023  
  13         114  
7              
8             set serializer => 'JSON';
9              
10             =head1 NAME
11              
12             Monitor::MetricsAPI::Server::Routes
13              
14             =head1 SYNOPSIS
15              
16             You should not interact with this module directly in your code. Please refer to
17             L<Monitor::MetricsAPI> for how to integrate this service with your application.
18              
19             =head1 DESCRIPTION
20              
21             The base Dancer2 application which provides HTTP API routes for MetricsAPI.
22              
23             =cut
24              
25             =head1 ROUTES
26              
27             The following routes are provided by this server for viewing the collected
28             metrics.
29              
30             =cut
31              
32             =head2 /
33              
34             Returns a small JSON structure with metadata about the metrics collector. May
35             be used as a heartbeat for the service, as it performs no computations and does
36             not invoke any metrics callbacks.
37              
38             =cut
39              
40             get '/' => sub {
41             return {
42             status => 'ok',
43             message => 'Metrics service alive.',
44             service => _service_info(),
45             };
46             };
47              
48             =head2 /all
49              
50             Returns a full export of all metrics that have been configured in the
51             containing application. This will include the invoking of all callback metrics.
52             Depending on your metrics collection, this can be a computationally expensive
53             process.
54              
55             If you have any non-trivial callback metrics collected by your application, you
56             should exercise care in your calls to this route.
57              
58             =cut
59              
60             get '/all' => sub {
61             my $coll = Monitor::MetricsAPI->collector;
62              
63             return {
64             status => 'collector_fail',
65             message => 'Could not access metrics collector.',
66             service => _service_info(),
67             } unless defined $coll;
68              
69             return {
70             status => 'no_metrics',
71             message => 'The collector does not have any metrics defined.',
72             service => _service_info(),
73             } unless scalar(keys(%{$coll->metrics})) > 0;
74              
75             return {
76             status => 'ok',
77             message => 'Request processed successfully.',
78             service => _service_info(),
79             metrics => _expand_metrics(values %{$coll->metrics}),
80             };
81             };
82              
83             =head2 /metric/**
84              
85             Returns a single named metric, instead of the entire collection of metrics. The
86             use of this route is substantially preferred for monitoring systems which use
87             checks against individual metrics, as it limits the invocations of potentially
88             expensive callback metrics (assuming your application configures any).
89              
90             A complete metric name must be provided. For returning groups of metrics under
91             a common namespace, use the /metrics/ route instead.
92              
93             =cut
94              
95             get '/metric/**' => sub {
96             my ($mparts) = splat;
97              
98             my $coll = Monitor::MetricsAPI->collector;
99             my $metric_name = join('/', @{$mparts});
100              
101             return {
102             status => 'collector_fail',
103             message => 'Could not access metrics collector.',
104             service => _service_info(),
105             } unless defined $coll;
106              
107             return {
108             status => 'not_found',
109             message => 'Invalid metric name provided.',
110             service => _service_info(),
111             } unless exists $coll->metrics->{$metric_name};
112              
113             return {
114             status => 'ok',
115             message => 'Request processed successfully.',
116             service => _service_info(),
117             metrics => _expand_metrics($coll->metric($metric_name)),
118             };
119             };
120              
121             =head2 /metrics/**
122              
123             Returns a collection of metrics under the given namespace. Each component of
124             the namespace prefix you wish to return must be fully specified, but you can
125             select as deep or as shallow a namespace as you like. In other words, if you
126             have the following metrics:
127              
128             messages/incoming/total
129             messages/incoming/rejected
130             messages/outgoing/total
131             messages/outgoing/supressed
132             users/total
133              
134             And your monitoring application calls this service with the following path:
135              
136             /metrics/messages/outgoing
137              
138             Then this route will return two metrics:
139              
140             messages/outgoing/total
141             messages/outgoing/supressed
142              
143             But if you try to call this service with:
144              
145             /metrics/messages/out
146              
147             You will receive an error response.
148              
149             If you specify the full name of a single metric, you will receive only that
150             metric back in the response output. Given that, why would you ever use the
151             "/metric/**" route over this one? Using the single-metric route when you truly
152             only want a single metric will return an error if the name you give is a
153             namespace instead of a single metric. It is possible this may be useful in
154             some circumstances.
155              
156             =cut
157              
158             get '/metrics/**' => sub {
159             my ($nsparts) = splat;
160              
161             my $coll = Monitor::MetricsAPI->collector;
162             my $prefix = join('/', @{$nsparts});
163              
164             return {
165             status => 'collector_fail',
166             message => 'Could not access metrics collector.',
167             service => _service_info(),
168             } unless defined $coll;
169              
170             return {
171             status => 'no_group',
172             message => 'Must provide metric group prefix.',
173             service => _service_info(),
174             } unless defined $prefix && $prefix =~ m{\w+};
175              
176             my @metrics =
177             map { $coll->metric($_) }
178             grep { $_ =~ m{^$prefix(/|$)} }
179             keys %{$coll->metrics};
180              
181             return {
182             status => 'not_found',
183             message => 'Invalid metric group name provided.',
184             service => _service_info(),
185             } unless @metrics > 0;
186              
187             return {
188             status => 'ok',
189             message => 'Request processed successfully.',
190             service => _service_info(),
191             metrics => _expand_metrics(@metrics),
192             };
193             };
194              
195             sub _expand_metrics {
196 0     0     my (@metrics) = @_;
197              
198 0           my %m;
199              
200 0           foreach my $metric (@metrics) {
201 0           my @path = split(m|/|, $metric->name);
202 0           my $name = pop @path;
203 0           my $val = $metric->value;
204 0           my $key = \%m;
205 0           foreach my $part (@path) {
206 0 0         $key->{$part} = {} unless exists $key->{$part};
207 0           $key = $key->{$part};
208             }
209 0           $key->{$name} = $val;
210             }
211              
212 0           return \%m;
213             }
214              
215             sub _service_info {
216             return {
217 0     0     name => 'Monitor::MetricsAPI',
218             version => $Monitor::MetricsAPI::VERSION,
219             };
220             }
221              
222             =head1 OUTPUT STRUCTURE
223              
224             Unless otherwise noted, all routes return a similar data structure to the one
225             described in this section.
226              
227             =head2 Complete Example
228              
229             { "status": "ok",
230             "message: "Request processed successfully.",
231             "service": {
232             "name": "Monitor::MetricsAPI",
233             "version": "0.001",
234             },
235             "metrics": {
236             "messages": {
237             "incoming": {
238             "total": 5019,
239             "rejected": 104
240             },
241             "outgoing": {
242             "total": 1627,
243             "suppressed": 5
244             },
245             },
246             "users": {
247             "total": 1928
248             }
249             }
250             }
251              
252             =head2 Status
253              
254             The status attribute is present in every response, and anything other than the
255             string "ok" indicates an error condition. Responses with a non-"ok" status may
256             not contain any additional attributes beyond a message which will contain an
257             error message.
258              
259             Note that the API server will still return an HTTP status code of 200 even when
260             there is an error displaying your metrics (e.g. you have requested a metric
261             which does not exist). The HTTP status codes are used only for indicating
262             whether the HTTP server itself is functioning properly and is able to process
263             the incoming HTTP request. HTTP status codes are not overloaded to also serve
264             as indicators of metrics "health."
265              
266             =head2 Message
267              
268             Contains a human-readable message, useful for discerning the reason for failed
269             requests. Otherwise easily ignored.
270              
271             =head2 Service
272              
273             The value of the service attribute is an object containing metadata about the
274             metrics reporting service.
275              
276             =head2 Metrics
277              
278             The metrics attribute contains an object of all metrics which matched the
279             request, nested according to the categories you used when defining your
280             metrics. In the case of requests to the "/all" route, this will be every metric
281             collected by the service for your application. For both the "/metric" and
282             "/metrics" routes, only those metrics which matched your request will appear in
283             the object, and all others will be omitted from the output. Callback metrics
284             which are not present in the API output are not invoked when constructing the
285             response.
286              
287             The data type of each metric's value will depend on the type of metric that is
288             being collected. Counters and gauges will return numbers, boolean metrics will
289             return 1 (true), 0 (false), or null, and string metrics will return, well,
290             strings. Callback metrics can return any type, entirely dependent upon what the
291             subroutine you provided for the callback does. List metrics will return an
292             array containing values of whatever type was push()'ed onto the list within
293             your application.
294              
295             =head1 AUTHORS
296              
297             Jon Sime <jonsime@gmail.com>
298              
299             =head1 LICENSE AND COPYRIGHT
300              
301             This software is copyright (c) 2015 by OmniTI Computer Consulting, Inc.
302              
303             This module is free software; you can redistribute it and/or
304             modify it under the same terms as Perl itself. See L<perlartistic>.
305              
306             This program is distributed in the hope that it will be useful,
307             but WITHOUT ANY WARRANTY; without even the implied warranty of
308             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
309              
310             =cut
311              
312             1;