1 |
racvision |
131 |
#!/usr/bin/perl -w |
2 |
|
|
# |
3 |
|
|
# Copyright (c) 2010 Stéphane Urbanovski <stephane.urbanovski@ac-nancy-metz.fr> |
4 |
|
|
# |
5 |
|
|
# This program is free software; you can redistribute it and/or |
6 |
|
|
# modify it under the terms of the GNU General Public License |
7 |
|
|
# as published by the Free Software Foundation; either version 2 |
8 |
|
|
# of the License, or (at your option) any later version. |
9 |
|
|
# |
10 |
|
|
# This program is distributed in the hope that it will be useful, |
11 |
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty |
12 |
|
|
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 |
|
|
# GNU General Public License for more details. |
14 |
|
|
# |
15 |
|
|
# you should have received a copy of the GNU General Public License |
16 |
|
|
# along with this program (or with Nagios); if not, write to the |
17 |
|
|
# Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
18 |
|
|
# Boston, MA 02111-1307, USA |
19 |
|
|
# |
20 |
|
|
# $Id: $ |
21 |
|
|
|
22 |
|
|
use strict; # should never be differently :-) |
23 |
|
|
use warnings; |
24 |
|
|
|
25 |
|
|
|
26 |
|
|
use Locale::gettext; |
27 |
|
|
use File::Basename; # get basename() |
28 |
|
|
|
29 |
|
|
use POSIX qw(setlocale); |
30 |
|
|
use Time::HiRes qw(time); # get microtime |
31 |
|
|
use POSIX qw(mktime); |
32 |
|
|
|
33 |
|
|
use Nagios::Plugin ; |
34 |
|
|
|
35 |
|
|
use LWP::UserAgent; # http client |
36 |
|
|
use HTTP::Request; # used by LWP::UserAgent |
37 |
|
|
use HTTP::Status; # to get http err msg |
38 |
|
|
|
39 |
|
|
|
40 |
|
|
use Data::Dumper; |
41 |
|
|
|
42 |
|
|
|
43 |
|
|
my $PROGNAME = basename($0); |
44 |
|
|
'$Revision: 1.0 $' =~ /^.*(\d+\.\d+) \$$/; # Use The Revision from RCS/CVS/SVN |
45 |
|
|
my $VERSION = $1; |
46 |
|
|
|
47 |
|
|
my $DEBUG = 0; |
48 |
|
|
my $TIMEOUT = 9; |
49 |
|
|
|
50 |
|
|
# i18n : |
51 |
|
|
setlocale(LC_MESSAGES, ''); |
52 |
|
|
textdomain('nagios-plugins-perl'); |
53 |
|
|
|
54 |
|
|
|
55 |
|
|
my $np = Nagios::Plugin->new( |
56 |
|
|
version => $VERSION, |
57 |
|
|
blurb => _gt('Plugin to check HAProxy stats url'), |
58 |
racvision |
135 |
usage => "Usage: %s [ -v|--verbose ] -u <url> [-t <timeout>] [-U <username>] [-P <password>] [ -c|--critical=<threshold> ] [ -w|--warning=<threshold> ]", |
59 |
racvision |
131 |
timeout => $TIMEOUT+1 |
60 |
|
|
); |
61 |
|
|
$np->add_arg ( |
62 |
|
|
spec => 'debug|d', |
63 |
|
|
help => _gt('Debug level'), |
64 |
|
|
default => 0, |
65 |
|
|
); |
66 |
|
|
$np->add_arg ( |
67 |
racvision |
135 |
spec => 'username|U=s', |
68 |
|
|
help => _gt('Username for HTTP Auth'), |
69 |
|
|
required => 0, |
70 |
|
|
); |
71 |
|
|
$np->add_arg ( |
72 |
|
|
spec => 'password|P=s', |
73 |
|
|
help => _gt('Password for HTTP Auth'), |
74 |
|
|
required => 0, |
75 |
|
|
); |
76 |
|
|
$np->add_arg ( |
77 |
racvision |
131 |
spec => 'w=f', |
78 |
|
|
help => _gt('Warning request time threshold (in seconds)'), |
79 |
|
|
default => 2, |
80 |
|
|
label => 'FLOAT' |
81 |
|
|
); |
82 |
|
|
$np->add_arg ( |
83 |
|
|
spec => 'c=f', |
84 |
|
|
help => _gt('Critical request time threshold (in seconds)'), |
85 |
|
|
default => 10, |
86 |
|
|
label => 'FLOAT' |
87 |
|
|
); |
88 |
|
|
$np->add_arg ( |
89 |
|
|
spec => 'url|u=s', |
90 |
|
|
help => _gt('URL of the HAProxy csv statistics page.'), |
91 |
|
|
required => 1, |
92 |
|
|
); |
93 |
|
|
|
94 |
|
|
|
95 |
|
|
$np->getopts; |
96 |
|
|
|
97 |
|
|
$DEBUG = $np->opts->get('debug'); |
98 |
|
|
my $verbose = $np->opts->get('verbose'); |
99 |
racvision |
135 |
my $username = $np->opts->get('username'); |
100 |
|
|
my $password = $np->opts->get('password'); |
101 |
racvision |
131 |
|
102 |
|
|
# Thresholds : |
103 |
|
|
# time |
104 |
|
|
my $warn_t = $np->opts->get('w'); |
105 |
|
|
my $crit_t = $np->opts->get('c'); |
106 |
|
|
|
107 |
|
|
my $url = $np->opts->get('url'); |
108 |
|
|
|
109 |
|
|
|
110 |
|
|
# Create a LWP user agent object: |
111 |
|
|
my $ua = new LWP::UserAgent( |
112 |
|
|
'env_proxy' => 0, |
113 |
|
|
'timeout' => $TIMEOUT, |
114 |
|
|
); |
115 |
|
|
$ua->agent(basename($0)); |
116 |
|
|
|
117 |
|
|
# Workaround for LWP bug : |
118 |
|
|
$ua->parse_head(0); |
119 |
|
|
|
120 |
|
|
if ( defined($ENV{'http_proxy'}) ) { |
121 |
|
|
# Normal http proxy : |
122 |
|
|
$ua->proxy(['http'], $ENV{'http_proxy'}); |
123 |
|
|
# Https must use Crypt::SSLeay https proxy (to use CONNECT method instead of GET) |
124 |
|
|
$ENV{'HTTPS_PROXY'} = $ENV{'http_proxy'}; |
125 |
|
|
} |
126 |
|
|
|
127 |
|
|
# Build and submit an http request : |
128 |
|
|
my $request = HTTP::Request->new('GET', $url); |
129 |
racvision |
135 |
# Authenticate if username and password are supplied |
130 |
|
|
if ( defined($username) && defined($password) ) { |
131 |
|
|
$request->authorization_basic($username, $password); |
132 |
|
|
} |
133 |
racvision |
131 |
my $timer = time(); |
134 |
|
|
my $http_response = $ua->request( $request ); |
135 |
|
|
$timer = time()-$timer; |
136 |
|
|
|
137 |
|
|
|
138 |
|
|
|
139 |
|
|
my $status = $np->check_threshold( |
140 |
|
|
'check' => $timer, |
141 |
|
|
'warning' => $warn_t, |
142 |
|
|
'critical' => $crit_t, |
143 |
|
|
); |
144 |
|
|
|
145 |
|
|
$np->add_perfdata( |
146 |
|
|
'label' => 't', |
147 |
|
|
'value' => sprintf('%.6f',$timer), |
148 |
|
|
'min' => 0, |
149 |
|
|
'uom' => 's', |
150 |
|
|
'threshold' => $np->threshold() |
151 |
|
|
); |
152 |
|
|
|
153 |
|
|
if ( $status > OK ) { |
154 |
|
|
$np->add_message($status, sprintf(_gt("Response time degraded: %.6fs !"),$timer) ); |
155 |
|
|
} |
156 |
|
|
|
157 |
|
|
|
158 |
|
|
my $message = 'msg'; |
159 |
|
|
|
160 |
|
|
|
161 |
|
|
if ( $http_response->is_error() ) { |
162 |
|
|
my $err = $http_response->code." ".status_message($http_response->code)." (".$http_response->message.")"; |
163 |
|
|
$np->add_message(CRITICAL, _gt("HTTP error: ").$err ); |
164 |
|
|
|
165 |
|
|
} elsif ( ! $http_response->is_success() ) { |
166 |
|
|
my $err = $http_response->code." ".status_message($http_response->code)." (".$http_response->message.")"; |
167 |
|
|
$np->add_message(CRITICAL, _gt("Internal error: ").$err ); |
168 |
|
|
} |
169 |
|
|
|
170 |
|
|
|
171 |
|
|
($status, $message) = $np->check_messages(); |
172 |
|
|
|
173 |
|
|
if ( $http_response->is_success() ) { |
174 |
|
|
|
175 |
|
|
# Get xml content ... |
176 |
|
|
my $stats = $http_response->content; |
177 |
|
|
if ($DEBUG) { |
178 |
|
|
print "------------------===http output===------------------\n$stats\n-----------------------------------------------------\n"; |
179 |
|
|
print "t=".$timer."s\n"; |
180 |
|
|
}; |
181 |
|
|
|
182 |
|
|
my @fields = (); |
183 |
|
|
my @rows = split(/\n/,$stats); |
184 |
|
|
if ( $rows[0] =~ /#\ \w+/ ) { |
185 |
|
|
$rows[0] =~ s/#\ //; |
186 |
|
|
@fields = split(/\,/,$rows[0]); |
187 |
|
|
} else { |
188 |
|
|
$np->nagios_exit(UNKNOWN, _gt("Can't find csv header !") ); |
189 |
|
|
} |
190 |
|
|
|
191 |
|
|
my %stats = (); |
192 |
|
|
for ( my $y = 1; $y < $#rows; $y++ ) { |
193 |
|
|
my @values = split(/\,/,$rows[$y]); |
194 |
|
|
if ( !defined($stats{$values[0]}) ) { |
195 |
|
|
$stats{$values[0]} = {}; |
196 |
|
|
} |
197 |
|
|
if ( !defined($stats{$values[0]}{$values[1]}) ) { |
198 |
|
|
$stats{$values[0]}{$values[1]} = {}; |
199 |
|
|
} |
200 |
racvision |
135 |
for ( my $x = 2,; $x <= $#values; $x++ ) { |
201 |
racvision |
131 |
# $stats{pxname}{svname}{valuename} |
202 |
|
|
$stats{$values[0]}{$values[1]}{$fields[$x]} = $values[$x]; |
203 |
|
|
} |
204 |
|
|
} |
205 |
|
|
# print Dumper(\%stats); |
206 |
|
|
my %stats2 = (); |
207 |
|
|
my $okMsg = ''; |
208 |
|
|
foreach my $pxname ( keys(%stats) ) { |
209 |
|
|
$stats2{$pxname} = { |
210 |
|
|
'act' => 0, |
211 |
|
|
'acttot' => 0, |
212 |
|
|
'bck' => 0, |
213 |
|
|
'bcktot' => 0, |
214 |
|
|
'scur' => 0, |
215 |
|
|
'slim' => 0, |
216 |
|
|
}; |
217 |
|
|
foreach my $svname ( keys(%{$stats{$pxname}}) ) { |
218 |
|
|
if ( $stats{$pxname}{$svname}{'type'} eq '2' ) { |
219 |
|
|
my $svstatus = $stats{$pxname}{$svname}{'status'} eq 'UP'; |
220 |
|
|
my $active = $stats{$pxname}{$svname}{'act'} eq '1'; |
221 |
|
|
my $activeDescr = $active ? _gt("Active service") :_gt("Backup service") ; |
222 |
|
|
if ( $stats{$pxname}{$svname}{'status'} eq 'UP' ) { |
223 |
|
|
logD( sprintf(_gt("%s '%s' is up on '%s' proxy."),$activeDescr,$svname,$pxname) ); |
224 |
|
|
} elsif ( $stats{$pxname}{$svname}{'status'} eq 'DOWN' ) { |
225 |
|
|
$np->add_message(CRITICAL, sprintf(_gt("%s '%s' is DOWN on '%s' proxy !"),$activeDescr,$svname,$pxname) ); |
226 |
|
|
} |
227 |
|
|
if ( $stats{$pxname}{$svname}{'act'} eq '1' ) { |
228 |
|
|
$stats2{$pxname}{'acttot'}++; |
229 |
|
|
$stats2{$pxname}{'act'} += $svstatus; |
230 |
|
|
|
231 |
|
|
} elsif ($stats{$pxname}{$svname}{'bck'} eq '1') { |
232 |
|
|
$stats2{$pxname}{'bcktot'}++; |
233 |
|
|
$stats2{$pxname}{'bck'} += $svstatus; |
234 |
|
|
} |
235 |
|
|
$stats2{$pxname}{'scur'} += $stats{$pxname}{$svname}{'scur'}; |
236 |
|
|
logD( "Current sessions : ".$stats{$pxname}{$svname}{'scur'} ); |
237 |
|
|
|
238 |
|
|
} elsif ( $stats{$pxname}{$svname}{'type'} eq '0' ) { |
239 |
|
|
$stats2{$pxname}{'slim'} = $stats{$pxname}{$svname}{'slim'}; |
240 |
|
|
} |
241 |
|
|
} |
242 |
|
|
if ( $stats2{$pxname}{'acttot'} > 0 ) { |
243 |
|
|
$okMsg .= ' '.$pxname.' (Active: '.$stats2{$pxname}{'act'}.'/'.$stats2{$pxname}{'acttot'}; |
244 |
|
|
if ( $stats2{$pxname}{'bcktot'} > 0 ) { |
245 |
|
|
$okMsg .= ' , Backup: '.$stats2{$pxname}{'bck'}.'/'.$stats2{$pxname}{'bcktot'}; |
246 |
|
|
} |
247 |
|
|
$okMsg .= ')'; |
248 |
|
|
$np->add_perfdata( |
249 |
|
|
'label' => 'sess_'.$pxname, |
250 |
|
|
'value' => $stats2{$pxname}{'scur'}, |
251 |
|
|
'min' => 0, |
252 |
|
|
'uom' => 'sessions', |
253 |
|
|
'max' => $stats2{$pxname}{'slim'}, |
254 |
|
|
); |
255 |
|
|
} |
256 |
|
|
} |
257 |
|
|
|
258 |
|
|
# print Dumper(\%stats2); |
259 |
|
|
($status, $message) = $np->check_messages('join' => ' '); |
260 |
|
|
|
261 |
|
|
if ( $status == OK ) { |
262 |
|
|
$message = $okMsg; |
263 |
|
|
|
264 |
|
|
} |
265 |
|
|
|
266 |
|
|
} |
267 |
|
|
# if ( $verbose ) { |
268 |
|
|
# ($status, $message) = $np->check_messages('join' => '<br/>','join_all' => '<br/>'); |
269 |
|
|
# } else { |
270 |
|
|
# ($status, $message) = $np->check_messages('join' => '<br/>'); |
271 |
|
|
# } |
272 |
|
|
|
273 |
|
|
|
274 |
|
|
$np->nagios_exit($status, $message ); |
275 |
|
|
|
276 |
|
|
|
277 |
|
|
sub logD { |
278 |
|
|
print STDERR 'DEBUG: '.$_[0]."\n" if ($DEBUG); |
279 |
|
|
} |
280 |
|
|
sub logW { |
281 |
|
|
print STDERR 'WARNING: '.$_[0]."\n" if ($DEBUG); |
282 |
|
|
} |
283 |
|
|
# Gettext wrapper |
284 |
|
|
sub _gt { |
285 |
|
|
return gettext($_[0]); |
286 |
|
|
} |
287 |
|
|
|
288 |
|
|
|
289 |
|
|
__END__ |
290 |
|
|
|
291 |
|
|
=head1 NAME |
292 |
|
|
|
293 |
|
|
This Nagios plugins check the statistics url provided by HAProxy (http://haproxy.1wt.eu/). |
294 |
|
|
|
295 |
|
|
|
296 |
|
|
=head1 NAGIOS CONGIGURATIONS |
297 |
|
|
|
298 |
|
|
In F<checkcommands.cfg> you have to add : |
299 |
|
|
|
300 |
|
|
define command { |
301 |
|
|
command_name check_haproxy |
302 |
|
|
command_line $USER1$/check_haproxy.pl -u $ARG1$ |
303 |
|
|
} |
304 |
|
|
|
305 |
|
|
|
306 |
|
|
In F<services.cfg> you just have to add something like : |
307 |
|
|
|
308 |
|
|
define service { |
309 |
|
|
host_name haproxy.exemple.org |
310 |
|
|
normal_check_interval 10 |
311 |
|
|
retry_check_interval 5 |
312 |
|
|
contact_groups linux-admins |
313 |
|
|
service_description HAProxy |
314 |
|
|
check_command check_haproxy!http://haproxy.exemple.org/haproxy?stats;csv |
315 |
|
|
} |
316 |
|
|
|
317 |
|
|
=head1 AUTHOR |
318 |
|
|
|
319 |
|
|
Stéphane Urbanovski <stephane.urbanovski@ac-nancy-metz.fr> |
320 |
|
|
|
321 |
|
|
=cut |