File Coverage

blib/lib/Mojo/Netdata/Collector/HTTP.pm
Criterion Covered Total %
statement 74 89 83.1
branch 27 36 75.0
condition 16 29 55.1
subroutine 7 9 77.7
pod 2 2 100.0
total 126 165 76.3


line stmt bran cond sub pod time code
1             package Mojo::Netdata::Collector::HTTP;
2 1     1   509 use Mojo::Base 'Mojo::Netdata::Collector', -signatures;
  1         2  
  1         8  
3              
4 1     1   822 use Mojo::UserAgent;
  1         97064  
  1         8  
5 1     1   48 use Mojo::Netdata::Util qw(logf safe_id);
  1         2  
  1         54  
6 1     1   7 use Time::HiRes qw(time);
  1         3  
  1         9  
7              
8             require Mojo::Netdata;
9             our $VERSION = '0.04';
10              
11             has concurrency => 4;
12             has jobs => sub ($self) { +[] };
13             has type => 'HTTP';
14             has ua => sub { Mojo::UserAgent->new(insecure => 0, connect_timeout => 5, request_timeout => 5) };
15             has update_every => 30;
16              
17 2     2 1 19 sub register ($self, $config, $netdata) {
  2         4  
  2         4  
  2         4  
  2         4  
18             $config->{update_every} ? $self->update_every($config->{update_every})
19 2 50       11 : $netdata->update_every >= 10 ? $self->update_every($netdata->update_every)
    50          
20             : $self->update_every(30);
21              
22 2 50       37 $self->ua->insecure($config->{insecure}) if defined $config->{insecure};
23 2 50       9 $self->ua->connect_timeout($config->{connect_timeout}) if defined $config->{connect_timeout};
24 2 50       8 $self->ua->request_timeout($config->{request_timeout}) if defined $config->{request_timeout};
25 2 50 50     15 $self->ua->proxy->detect if $config->{proxy} // 1;
26 2   33     127 $self->ua->transactor->name($config->{user_agent} || "Mojo-Netdata/$VERSION (Perl)");
27 2   50     83 $self->concurrency($config->{concurrency} || 4);
28 2         16 $self->jobs([]);
29              
30 2 50       15 my @jobs = ref $config->{jobs} eq 'HASH' ? %{$config->{jobs}} : @{$config->{jobs}};
  0         0  
  2         8  
31 2         10 while (my $url = shift @jobs) {
32 10 100       66 my $job = $self->_make_job($url => ref $jobs[0] eq 'HASH' ? shift @jobs : {}, $config);
33 10 100       2038 push @{$self->jobs}, $job if $job;
  8         24  
34             }
35              
36 2 50       14 return @{$self->jobs} ? $self : undef;
  2         5  
37             }
38              
39 0     0 1 0 sub update_p ($self) {
  0         0  
  0         0  
40 0         0 my ($ua, @p) = ($self->ua);
41              
42             return Mojo::Promise->map(
43             {concurrency => $self->concurrency},
44             sub {
45 0     0   0 my ($job, $t0) = ($_, time);
46 0         0 my $tx = $ua->build_tx(@{$job->[0]});
  0         0  
47 0         0 return $ua->start_p($tx)->then(sub ($tx) {
48 0         0 $job->[1]->($tx, $t0);
49 0         0 })->catch(sub ($err) {
50 0         0 $job->[1]->($tx, $t0, {message => $err});
51 0         0 });
52             },
53 0         0 @{$self->jobs}
  0         0  
54             );
55             }
56              
57 10     10   15 sub _make_job ($self, $url, $params, $defaults) {
  10         17  
  10         17  
  10         15  
  10         14  
  10         13  
58 10         31 $url = Mojo::URL->new($url);
59 10 100       1885 return undef unless my $host = $url->host;
60              
61 9   100     63 my $headers = Mojo::Headers->new->from_hash($defaults->{headers} || {});
62 9 100       265 $headers->header($_ => $params->{headers}{$_}) for keys %{$params->{headers} || {}};
  9         41  
63 9 100       66 ($headers->header(Host => $url->host), $url->host($params->{via})) if $params->{via};
64              
65 9   66     80 my $dimension = $params->{dimension} || $headers->host || $url->host;
66 9   66     114 my $family = $params->{family} || $defaults->{family} || $headers->host || $url->host;
67 9   50     85 my $log_level = $defaults->{log_level} || 'debug';
68              
69 9         42 my $code_chart = $self->chart("${family}_code")->title("HTTP Status code for $family")
70             ->context('httpcheck.code')->family($family)->units('#');
71              
72 9 100       347 if ($code_chart->dimension($dimension)) {
73 1         11 logf(warnings => 'Family "%s" already has dimension "%s".', $family, $dimension);
74 1         5 return undef;
75             }
76              
77 8         33 my $time_chart = $self->chart("${family}_time")->title("Response time for $family")
78             ->context('httpcheck.responsetime')->family($family)->units('ms');
79              
80 8         292 $code_chart->dimension($dimension => {});
81 8         30 $time_chart->dimension($dimension => {});
82              
83 6     6   6 my $update = sub ($tx, $t0, $err = undef) {
  6         532  
  6         8  
  6         11  
  6         9  
84 6   33     29 $err ||= $tx->error;
85 6         112 my $req = $tx->req;
86 6   50     25 my $code = $tx->res->code // 0;
87 6   50     56 my @msg = ($req->method, $req->url, $err || {code => $code}, $req->headers->to_hash(1));
88 6 50 33     244 logf(($code >= 200 && $code < 300 ? $log_level : 'warnings'), '%s %s == %s %s', @msg);
89              
90 6         57 $time_chart->dimension($dimension => {value => int(1000 * (time - $t0))});
91 6         23 $code_chart->dimension($dimension => {value => $code});
92 8         42 };
93              
94 8         14 my @data;
95 8         30 push @data, $headers->to_hash(1);
96             push @data,
97             exists $params->{json} ? (json => $params->{json})
98             : exists $params->{form} ? (form => $params->{form})
99             : exists $params->{body} ? ($params->{body})
100 8 100       157 : ();
    100          
    100          
101              
102 8   100     34 my $http_method = $params->{method} || 'GET';
103 8         32 logf(debug => 'Tracking %s %s %s', $http_method, $url, $data[0]);
104 8         29 return [[$http_method, $url->to_unsafe_string, @data], $update];
105             }
106              
107             1;
108              
109             =encoding utf8
110              
111             =head1 NAME
112              
113             Mojo::Netdata::Collector::HTTP - A website collector for Mojo::Netdata
114              
115             =head1 SYNOPSIS
116              
117             =head2 Config
118              
119             Below is an example C config file. Note
120             that the file can have any name and you have have as many as you want, as long
121             as it has the C<.conf.pl> extension.
122              
123             {
124             # Required
125             collector => 'Mojo::Netdata::Collector::HTTP',
126              
127             # Optional
128             concurrency => 4, # Number of HTTP requests at the same time
129             insecure => 0, # Set to "1" to allow insecure SSL/TLS connections
130             connect_timeout => 5, # Max time for the connection to be established
131             request_timeout => 5, # Max time for the whole request to complete
132             proxy => 1, # Set to "0" to disable proxy auto-detect
133             update_every => 30, # How often to run the "jobs" below
134             user_agent => '...', # Custom User-Agent name
135              
136             # Default values, unless defined in the job
137             family => 'default-family-name',
138             headers => {'X-Merged-With' => 'headers inside job config'},
139              
140             # Required - List of URLs and an optional config hash (object)
141             jobs => [
142              
143             # List of URLs to check (Config is optional)
144             'https://superwoman.example.com',
145             'https://superman.example.com',
146              
147             # URL and config parameters
148             'https://example.com' => {
149             method => 'GET', # GET (Default), HEAD, POST, ...
150             headers => {'X-Foo' => 'bar'}, # HTTP headers
151              
152             # Replace "host" in the URL with this IP and set the "Host" header
153             via => '192.168.2.1',
154              
155             # Set "dimension" to get a custom label in the chart.
156             # Default to the "Host" header or the host part of the URL.
157             dimension => 'foo', # Default: "example.com"
158              
159             # Set "family" to group multiple domains together in one chart,
160             # Default to the "Host" header or the host part of the URL.
161             family => 'bar', # Default: "example.com"
162              
163             # Only one of these can be present
164             json => {...}, # JSON HTTP body
165             form => {key => $value}, # Form data
166             body => '...', # Raw HTTP body
167             },
168             ],
169             };
170              
171             =head2 Health
172              
173             Here is an example C file:
174              
175             template: web_server_code
176             on: httpcheck.code
177             class: Errors
178             type: Web Server
179             component: HTTP endpoint
180             plugin: mojo
181             lookup: max -5m absolute foreach *
182             every: 1m
183             warn: $this >= 300 && $this < 500
184             crit: $this >= 500 && $this != 503
185             to: webmaster
186              
187             template: web_server_up
188             on: httpcheck.code
189             class: Errors
190             type: Web Server
191             component: HTTP endpoint
192             plugin: mojo
193             lookup: min -5m absolute foreach *
194             every: 1m
195             crit: $this == 0
196             units: up/down
197             to: webmaster
198              
199             =head1 DESCRIPTION
200              
201             L is a collector that can chart web page
202             response time and HTTP status codes.
203              
204             =head1 ATTRIBUTES
205              
206             =head2 concurrency
207              
208             $int = $collector->concurrency;
209              
210             Number of requests that should be performed at the same time. Default is 4.
211              
212             =head2 jobs
213              
214             $array_ref = $collector->jobs;
215              
216             A list of jobs generated by L.
217              
218             =head2 type
219              
220             $str = $collector->type;
221              
222             Defaults to "HTTP".
223              
224             =head2 ua
225              
226             $ua = $collector->ua;
227              
228             Holds a L.
229              
230             =head2 update_every
231              
232             $num = $chart->update_every;
233              
234             Default value is 30. See L for more
235             details.
236              
237             =head1 METHODS
238              
239             =head2 register
240              
241             $collector = $collector->register(\%config, $netdata);
242              
243             Returns a L<$collector> object if any "jobs" are defined in C<%config>. Will
244             also set L from C<%config> or use L
245             if it is 10 or greater.
246              
247             =head2 update_p
248              
249             $p = $collector->update_p;
250              
251             Gathers information about the "jobs" registered.
252              
253             =head1 SEE ALSO
254              
255             L and L.
256              
257             =cut