#!/usr/bin/perl -w

=head1 NAME

squid - Multigraph-capable plugin to monitor Squid

=head1 NOTES

This plugin will produce multiple graphs showing:

 - the number of requests (replaces squid_requests);
 - the traffic (replaces squid_traffic);
 - the size of the cache (replaces squid_cache);
 - the traffic to the ICP peers (replaces squid_icp);
 - the mean size of stored objects (replaces squid_objectsize).

=head1 CONFIGURATION

The following configuration parameters are used by this plugin

 [squid]
  env.host     - hostname to connect to
  env.port     - port number to connect to
  env.username - username used for authentication
  env.password - password used for authentication


=head2 DEFAULT CONFIGURATION

 [squid]
  env.host 127.0.0.1
  env.port 3128

=head2 WILDCARD CONFIGURATION

It's possible to use the plugin in a virtual-node capacity, in which
case the host configuration will default to the hostname following the
underscore:

 [squid_someserver]
  env.host someserver
  env.port 3128

=head1 AUTHOR

  Copyright (C) 2013 Diego Elio Pettenò
  Copyright (C) 2008 Bjorn Ruberg
  Copyright (C) 2004 Audun Ytterdal
  Copyright (C) 2004 Jimmy Olsen
  Copyright (C) 2004 Tore Anderson

=head1 LICENSE

Gnu GPLv2

=begin comment

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 dated June, 1991.

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.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.

=end comment

=head1 MAGIC MARKERS

 #%# family=auto
 #%# capabilities=autoconf

=cut

use strict;
use Munin::Plugin;
use Munin::Plugin::Framework;
use IO::Socket::INET;


my $plugin = Munin::Plugin::Framework->new;

$plugin->{hostname} = $1 if $Munin::Plugin::me =~ /_([^_]+)$/;

my $peeraddr = $ENV{'host'} || $plugin->{hostname} || '127.0.0.1';
my $peerport = $ENV{'port'} || '3128';

my $username = $ENV{'username'};
my $password = $ENV{'password'};

my $requirements = undef;
if (! eval "require HTTP::Headers" or ! eval "require HTTP::Response" ) {
  $requirements = "HTTP::Message not found";
}

sub getobject {
  my $socket = IO::Socket::INET->new(PeerAddr => $peeraddr,
				     PeerPort => $peerport,
				     Proto => 'tcp',
				     Timeout => 25);
  if ( ! $socket ) {
    $requirements = $!;
    return undef;
  }

  my ($object) = @_;

  my $request_line = sprintf("GET cache_object://%s/%s HTTP/1.0\r\n",
			     $peeraddr, $object);
  my $headers = HTTP::Headers->new('Accept' => '*/*',
				   'User-Agent' => sprintf("munin/%s (squid)",
							   $Munin::Common::Defaults::MUNIN_VERSION)
				  );
  if ( $username and $password ) {
    $headers->authorization_basic($username, $password);
    $headers->proxy_authorization_basic($username, $password);
  }

  $socket->print($request_line . $headers->as_string("\r\n") . "\r\n");
  my $response = HTTP::Response->parse(join('', $socket->getlines));

  $socket->close();

  return $response;
}

sub get_icp {
  my $server_list = getobject('server_list');
  if ( not defined $server_list ) {
    return undef;
  }
  if ( $server_list->{content} =~ /There are no neighbors/ ) {
    return undef;
  }

  my @lines = split(/\r\n/, $server_list->{content});

  my $ret;
  my $id = "";
  for (my $i = 0; $i <= $#lines; $i++) {
    chomp $lines[$i];
    if ($lines[$i] =~ /Address[^:]+:\s*([\d\.]+)\s*$/) {
      my $host = $1;
      $id = "h" . $host;
      $id =~ s/\.//g;

      my $h;
      if ($h = Net::hostent::gethost ($host)) {
	$ret->{$id}->{host} = lc $h->name;
      } else {
	$ret->{$id}->{host} = $host;
      }
    } elsif ($lines[$i] =~ /FETCHES\s*:\s*(\d+)/) {
      $ret->{$id}->{fetches} = $1;
    }
  }
}

my $counters = getobject('counters');
my $storedir = getobject('storedir');
my $info     = getobject('info');
my $icp_data = get_icp;

if ( $requirements ) {
  $plugin->{autoconf} = "no ($requirements)";
} elsif ( !$counters->is_success ) {
  $plugin->{autoconf} = "no ($counters->status_line)";
  $plugin->{autoconf} =~ s/[\r\n]//g;
}

if ( defined $counters && $counters->{is_success} ) {
  my $hits =
    ($counters->content =~ /client_http\.hits = ([0-9]+)/) ? $1 : "U";
  my $errors =
    ($counters->content =~ /client_http\.errors = ([0-9]+)/) ? $1 : "U";
  my $requests =
    ($counters->content =~ /client_http\.requests = ([0-9]+)/) ? $1 : undef;
  my $misses = $requests ? ($requests - $errors - $hits) : "U";

  my $in =
    ($counters->content =~ /client_http\.kbytes_in = ([0-9]+)/) ? ($1 * 1024) : "U";
  my $out =
    ($counters->content =~ /client_http\.kbytes_out = ([0-9]+)/) ? ($1 * 1024) : "U";
  my $hits_out =
    ($counters->content =~ /client_http\.hit_kbytes_out = ([0-9]+)/) ? ($1 * 1024) : "U";

  $plugin->add_graphs
    (
     squid_requests =>
     {
      title => "Squid client requests",
      args => "--base 1000 -l 0",
      vlabel => "requests per \${graph_period}",
      order => "hits errors misses",
      total => "total",
      category => "network",
      fields =>
      {
       hits =>
       {
	type => "DERIVE",
	draw => "AREASTACK",
	min => 0,
	value => $hits,
       },
       errors =>
       {
	type => "DERIVE",
	draw => "AREASTACK",
	min => 0,
	value => $errors,
       },
       misses =>
       {
	type => "DERIVE",
	draw => "AREASTACK",
	min => 0,
	value => $misses,
       },
      },
     },
     "squid_traffic" =>
     {
      title => "Squid Traffic",
      args => "--base 1024 -l 0",
      vlabel => "bytes per \${graph_period}",
      order => "in out hits_out",
      fields =>
      {
       in =>
       {
	label => "received",
	type => "DERIVE",
	min => "0",
	value => $in,
       },
       out =>
       {
	label => "sent",
	type => "DERIVE",
	min => "0",
	value => $out,
       },
       hits_out =>
       {
	label => "sent from cache",
	type => "DERIVE",
	min => "0",
	value => $hits_out,
       },
      },
     },
    );
}

if ( defined $storedir && $storedir->is_success ) {
  my $max =
    ($storedir->content =~ /Maximum (?:Swap )?Size\s*:\s*([0-9]+) KB/) ? ($1 * 1024) : "U";
  my $current =
    ($storedir->content =~ /Current (?:Store Swap )?Size\s*:\s*([0-9]+) KB/) ? ($1 * 1024) : "U";

  $plugin->add_graphs
    (
     "squid_swap" =>
     {
      title => "Squid swap size",
      order => "max current",
      vlabel => "bytes",
      args => "--base 1024 -l 0",
      fields =>
      {
       max => { label => "Maximum Swap Size", value => $max },
       current => { label => "Current Store Swap Size", value => $current }
      }
     },
    );
}

if ( defined $info && $info->is_success ) {
  my $size = 'U';
  if ( $info->content =~ /Mean Object Size:\s*([0-9.]+) ([KMG])B/i ) {
    $size = $1;
    my $unit = $2;
    if ( $unit eq 'K' ) {
      $size *= 1024;
    } elsif ( $unit eq 'M' ) {
      $size *= (1024*1024);
    } elsif ( $unit eq 'G' ) {
      $size *= (1024*1024*1024);
    }
  }

  $plugin->add_graphs
    (
     "squid_objectsize" =>
     {
      title => "Squid object size",
      vlabel => "bytes",
      args => "--base 1024 -l 0",
      fields =>
      {
       size => { label => "Mean Object Size", value => $size }
      },
     }
    );
}

if ( $icp_data ) {
  $plugin->add_graphs
    (
     "squid_icp" =>
     {
      title => "Squid Relay Statistics",
      vlabel => "requests per \${graph_period}",
      args => "--base 1000 -l 0",
      total => "total",
      fields => {},
     },
    );

  foreach my $i (sort keys %{$icp_data}) {
    $plugin->{graphs}->{"squid_icp"}->{fields}->{$i} =
      {
       label => $icp_data->{$i}->{host},
       type => "DERIVE",
       min => 0,
       draw => "AREASTACK",
       value => $icp_data->{$i}->{fetches}
      };
  }
}

$plugin->run;
