#!/usr/bin/perl
# -*- perl -*-

# Plugin to monitor PostgreSQL query rate; returns the number of 
# sequential scans initiated, rows returned by sequential reads,
# index scans initiated, rows returned by index scans, inserts,
# updates, and deletes.
# 
# Find out more at 
#  http://www.postgresql.org/docs/8.2/interactive/monitoring-stats.html
#  (should work with PostgreSQL 7.x and 8.x)
#
# BjØrn Ruberg <bjorn@linpro.no> is a mack-daddy.
# Licenced under GPL v2.
#
# Revision History:
#  2005/03/10: v0.0001  Vajtsz (from openproject.hu)
#    "this is my first munin plugin sorry for dummy code"
#  2007/09/25: v1.0 Moses Moore <moc.iazom@sesom>
#    Nicolai Langfeldt - "I wounder if one of you would be bothered
#    to rework [this] over the pattern of postgres_block_read_ ..."
#  2007/12/17 v1.0.1  Tim Retout <tim.retout@credativ.co.uk>
#    Use libpq environment variables for configuration.
#
# Usage:
#
#       Symlink into /etc/munin/plugins/ and add the monitored
#       database to the filename. e.g.:
#
#       ln -s /usr/share/munin/plugins/postgres_block_read_ \
#         /etc/munin/plugins/postgres_block_read_SomeDatabase
#       This should, however, be given through autoconf and suggest.
#
#       If required, give username, password and/or PostgreSQL server
#       host through environment variables.
#
#       You must also activate PostgreSQL statistics. See
#       http://www.postgresql.org/docs/8.2/interactive/monitoring-stats.html
#       for how to enable this. Specifically, the following lines must
#       exist in your postgresql.conf:
#
#           stats_start_collector = true
#           stats_block_level = true
#
#
# Parameters:
#
# 	config
# 	autoconf
#
# Configuration variables:
#
#	PGHOST		- Database server to use. Defaults to using ident
#			  authentication with the local server.
#	PGPORT		- Port to connect to. Defaults to '5432'.
#	PGDATABASE	- Database to connect to. Defaults to 'template1'.
#	PGUSER		- User to connect as, if necessary.
#	PGPASSWORD	- Corresponding password to use, if necessary.
#
#	(See libpq documentation for more.)
#	Note that PGDATABASE will default to 'template1' in this plugin, and
#	without PGHOST it will try ident authentication with the local server,
#	as the user that the plugin is running as.
#
# Configuration example:
#
#	# Use local server, ident authentication with the 'postgres' user.
#	[postgres_*]
#	user postgres
#
#	# Use local server, TCP authentication with a username and password.
#	[postgres_*]
#	env.PGHOST localhost
#	env.PGUSER someuser
#	env.PGPASSWORD somepassword
#
#
# Magic markers
#%# family=auto
#%# capabilities=suggest

use strict;
use DBI;
use Data::Dumper;
use vars qw ( $debug $suggest $configure $dbh );

# Default to template1 database.
$ENV{'PGDATABASE'} ||= 'template1';

if (exists $ARGV[0]) {
    if ($ARGV[0] eq 'autoconf') {
        # Check for DBD::Pg
        if (! eval "require DBD::Pg;") {
            print "no (DBD::Pg not found)";
            exit 1;
        }
        # Then we try to detect Postgres presence.
        my $tempdbh = DBI->connect ('dbi:Pg:', '', '');
        if ($tempdbh) {
            print "yes\n";
            exit 0;
        } else {
            print "no (Can't connect to given host, please check environment settings)\n";
            exit 1;
        }
    } elsif ($ARGV[0] eq 'debug') {
        # Set debug flag
        $debug = 1;
    } elsif ($ARGV[0] eq 'config') {
        # Set config flag
        $configure = 1;
    } elsif ($ARGV[0] eq 'suggest') {
        # doesn't always work
        my @datasources = DBI->data_sources ('Pg');
        foreach my $dsn (grep !/\=template\d$/, @datasources) {
          (my $db = $dsn) =~ s/^.*=//;
          $db =~ s/"//g;    # "
          $db =~ s/ /\\ /g; # is whitespace allowed in database names?
          print "$db\n";
        }
        exit 0;
    }
}

# Must do this here, after checking for autoconf/suggest/etc, because the
# plugin must be able to run before it is linked to the databases.
my ($dbname) = ($0 =~ m/postgres_queries_(\S+)$/);
die "No dbname configured (did you make the proper symlink?)" unless $dbname;

my @datasources = DBI->data_sources ('Pg')
    or die ("Can't read any possible data sources: $?");

my $dbh = DBI->connect ('dbi:Pg:', '', '', {RaiseError =>1});
unless($dbh) {
    die("Error connecting to database. (". $DBI::errstr .")\n");
}

if ($configure) {
    print <<EOF;
graph_title Postgres queries on $dbname
graph_args --base 1000
graph_vlabel Queries per \${graph_period}
graph_category PostgreSQL
graph_info Shows number of select, insert, update and delete queries
sel_seq.label s_selects
sel_seq.info Sequential selects on all tables
sel_seq.type DERIVE
sel_seq.min 0
sel_seq_rows.label s_select rows
sel_seq_rows.info Rows returned from sequential selects
sel_seq_rows.type DERIVE
sel_seq.min 0
sel_idx.label i_selects
sel_idx.info Sequential selects on all indexes
sel_idx.type DERIVE
sel_seq.min 0
sel_idx_rows.label i_select rows
sel_idx_rows.info Rows returned form index selects
sel_idx_rows.type DERIVE
sel_seq_rows.min 0
inserts.label inserts
inserts.info Rows inserted on all tables
inserts.type DERIVE
inserts.min 0
updates.label updates
updates.info Rows updated on all tables
updates.type DERIVE
updates.min 0
deletes.label deletes
deletes.info Rows deleted from all tables
deletes.type DERIVE
deletes.min 0
EOF
} else {
    my $sql = "SELECT SUM(seq_scan),SUM(seq_tup_read), ";
    $sql   .= "SUM(idx_scan),SUM(idx_tup_fetch), ";
    $sql   .= "SUM(n_tup_ins),SUM(n_tup_upd),SUM(n_tup_del) ";
    $sql   .= "from pg_stat_all_tables";
    print "# $sql\n" if $debug;
    my $sth = $dbh->prepare($sql);
    $sth->execute();
    if ($sth->rows > 0) {
      printf ("# Rows: %d\n",  $sth->rows) if $debug;
      my ($ss, $str, $is, $itf, $nti, $ntu, $ntd) = $sth->fetchrow_array();
      print "sel_seq.value $ss\n";
      print "sel_seq_rows.value $str\n";
      print "sel_idx.value $is\n";
      print "sel_idx_rows.value $itf\n";
      print "inserts.value $nti\n";
      print "updates.value $ntu\n";
      print "deletes.value $ntd\n";
    }
}

