#!/usr/bin/env perl # vim: se et ts=4: # # Copyright (C) 2012, Giacomo Montagner # 2015, Yann Fertat, Romain Dessort, Jeff Palmer, # Christophe Drevet-Droguet # # This program is free software; you can redistribute it and/or modify it # under the same terms as Perl 5.10.1. # For more details, see http://dev.perl.org/licenses/artistic.html # # This program is distributed in the hope that it will be # useful, but without any warranty; without even the implied # warranty of merchantability or fitness for a particular purpose. # our $VERSION = "1.2.0"; open(STDERR, ">&STDOUT"); # CHANGELOG: # 1.0.0 - first release # 1.0.1 - fixed empty message if all proxies are OK # 1.0.2 - add perfdata # 1.0.3 - redirect stderr to stdout # 1.0.4 - fix undef vars # 1.0.5 - fix thresholds # 1.1.0 - support for HTTP interface # 1.1.1 - drop perl 5.10 requirement # 1.2.0 - add an option for ignore NOLB use strict; use warnings; use File::Basename qw/basename/; use IO::Socket::UNIX; use Getopt::Long; my $lwp = eval { require LWP::Simple; LWP::Simple->import; 1; }; sub usage { my $me = basename $0; print <. $me is distributed under GPL and the Artistic License 2.0 SEE ALSO Check out online haproxy documentation at EOU } my %check_statuses = ( UNK => "unknown", INI => "initializing", SOCKERR => "socket error", L4OK => "layer 4 check OK", L4CON => "connection error", L4TMOUT => "layer 1-4 timeout", L6OK => "layer 6 check OK", L6TOUT => "layer 6 (SSL) timeout", L6RSP => "layer 6 protocol error", L7OK => "layer 7 check OK", L7OKC => "layer 7 conditionally OK", L7TOUT => "layer 7 (HTTP/SMTP) timeout", L7RSP => "layer 7 protocol error", L7STS => "layer 7 status error", ); my @status_names = (qw/OK WARNING CRITICAL UNKNOWN/); # Defaults my $swarn = 80.0; my $scrit = 90.0; my $sock = "/var/run/haproxy.sock"; my $url; my $user = ''; my $pass = ''; my $dump; my $ignore_maint; my $ignore_nolb; my $proxy; my $no_proxy; my $help; # Read command line Getopt::Long::Configure ("bundling"); GetOptions ( "c|critical=i" => \$scrit, "d|dump" => \$dump, "h|help" => \$help, "m|ignore-maint" => \$ignore_maint, "n|ignore-nolb" => \$ignore_nolb, "p|proxy=s" => \$proxy, "P|no-proxy=s" => \$no_proxy, "s|sock|socket=s" => \$sock, "U|url=s" => \$url, "u|user|username=s" => \$user, "x|pass|password=s" => \$pass, "w|warning=i" => \$swarn, ); # Want help? if ($help) { usage; exit 3; } my $haproxy; if ($url and $lwp) { my $geturl = $url; if ($user ne '') { $url =~ /^([^:]*:\/\/)(.*)/; $geturl = $1.$user.':'.$pass.'@'.$2; } $geturl .= ';csv'; $haproxy = get($geturl); } elsif ($url) { my $haproxyio; my $getcmd = "curl --insecure -s --fail " . "--user '$user:$pass' '".$url.";csv'"; open $haproxyio, "-|", $getcmd; while (<$haproxyio>) { $haproxy .= $_; } close($haproxyio); } else { # Connect to haproxy socket and get stats my $haproxyio = new IO::Socket::UNIX ( Peer => $sock, Type => SOCK_STREAM, ); die "Unable to connect to haproxy socket: $sock\n$@" unless $haproxyio; print $haproxyio "show stat\n" or die "Print to socket failed: $!"; $haproxy = ''; while (<$haproxyio>) { $haproxy .= $_; } close($haproxyio); } # Dump stats and exit if requested if ($dump) { print($haproxy); exit 0; } # Get labels from first output line and map them to their position in the line my @hastats = ( split /\n/, $haproxy ); my $labels = $hastats[0]; die "Unable to retrieve haproxy stats" unless $labels; chomp($labels); $labels =~ s/^# // or die "Data format not supported."; my @labels = split /,/, $labels; { no strict "refs"; my $idx = 0; map { $$_ = $idx++ } @labels; } # Variables I will use from here on: our $pxname; our $svname; our $status; our $slim; our $scur; my @proxies = split ',', $proxy if $proxy; my @no_proxies = split ',', $no_proxy if $no_proxy; my $exitcode = 0; my $msg; my $checked = 0; my $perfdata = ""; # Remove excluded proxies from the list if both -p and -P options are # specified. my %hash; @hash{@no_proxies} = undef; @proxies = grep{ not exists $hash{$_} } @proxies; foreach (@hastats) { chomp; next if /^#/; next if /^[[:space:]]*$/; my @data = split /,/, $_; if (@proxies) { next unless grep {$data[$pxname] eq $_} @proxies; }; if (@no_proxies) { next if grep {$data[$pxname] eq $_} @no_proxies; }; # Is session limit enforced? if ($data[$slim]) { $perfdata .= sprintf "%s-%s=%u;%u;%u;0;%u;", $data[$pxname], $data[$svname], $data[$scur], $swarn * $data[$slim] / 100, $scrit * $data[$slim] / 100, $data[$slim]; # Check current session # against limit my $sratio = $data[$scur]/$data[$slim]; if ($sratio >= $scrit / 100 || $sratio >= $swarn / 100) { $exitcode = $sratio >= $scrit / 100 ? 2 : $exitcode < 2 ? 1 : $exitcode; $msg .= sprintf "%s:%s sessions: %.2f%%; ", $data[$pxname], $data[$svname], $sratio * 100; } } # Check of BACKENDS if ($data[$svname] eq 'BACKEND') { if ($data[$status] ne 'UP') { $msg .= sprintf "BACKEND: %s is %s; ", $data[$pxname], $data[$status]; $exitcode = 2; } # Check of FRONTENDS } elsif ($data[$svname] eq 'FRONTEND') { if ($data[$status] ne 'OPEN') { $msg .= sprintf "FRONTEND: %s is %s; ", $data[$pxname], $data[$status]; $exitcode = 2; } # Check of servers } else { if ($data[$status] ne 'UP') { next if ($ignore_maint && $data[$status] eq 'MAINT'); next if ($ignore_nolb && $data[$status] eq 'NOLB'); next if $data[$status] eq 'no check'; # Ignore server if no check is configured to be run next if $data[$svname] eq 'sock-1'; $exitcode = 2; our $check_status; $msg .= sprintf "server: %s:%s is %s", $data[$pxname], $data[$svname], $data[$status]; $msg .= sprintf " (check status: %s)", $check_statuses{$data[$check_status]} if $check_statuses{$data[$check_status]}; $msg .= "; "; } } ++$checked; } unless ($msg) { $msg = @proxies ? sprintf("checked proxies: %s", join ', ', sort @proxies) : "checked $checked proxies."; } print "Check haproxy $status_names[$exitcode] - $msg|$perfdata\n"; exit $exitcode;