#!/usr/bin/perl

###############################################################################
#
# Copyright (c) 2012  Luis Caballero Cruz  <luiscc1989@gmail.com>
# 
# postgresql.pl Perl script que funciona como plugin de Agente en
#			Pandora FMS para monitorizar datos estadisticos generales de un 
#			servidor SGBD PostgreSQL y datos especificos de cada base de datos 
#			que almacena de forma local o remota.
#           
#
# 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 of the License.
# 
# 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.	
#
###############################################################################

use strict;
use warnings;

use DBI;
use Getopt::Std;

my $dbname="";
my $host="";
my $port="";
my $username="";
my $password="";


# -----------------------------------------------------------------------------
#Ayuda/documentacion para la correcta ejecucion del plugin
# -----------------------------------------------------------------------------

sub help {
    
print "Plugin de agente para monitorizar SGBD PostgreSQL en Pandora FMS\n";
print "Gestiona datos estadisticos GENERALES de un servidor PostgreSQL y
ESPECIFICOS de cada Base de Datos que almacena.\n\n";
print "Sintaxis para su uso: \n\n";
print "\t -d <base_datos> (opcional), por defecto dbname=username 
 \t -u <usuario>\n\t -c <contraseña>
 \t -h <IP_server_PostgreSQL> (opcional), por defecto=localhost
 \t -p <puerto> (opcional), por defecto=5432\n
\t -a (ayuda)\n\n";
print "Ejemplo de uso: \n\n";
print "./postgresql.pl -d database -u superusuario -c password -h 192.168.1.2";
print "\n./postgresql.pl -u superusuario -c contraseña \n";
print "(Aqui la conexion se realizara en localhost y a una BD con el nombre del
superusuario introducido como argumento) \n";
exit;

}

# -----------------------------------------------------------------------------
# Muestra un error y sale del programa
# -----------------------------------------------------------------------------

sub error {
   
        print (STDERR "[error] $_[0]\n");
        exit 1;
}
   
# -----------------------------------------------------------------------------
# Configura el plugin a traves de los parametros introducidos como entrada 
# -----------------------------------------------------------------------------

sub config {
	
    my %opts;
   
    # Lee la entrada al plugin:
    if(getopts ('d:u:c:h:p:at', \%opts) == 0 ){
    	error("Entrada no indicada correctamente para alguno de los 
    	parametros.");
    	
    }
    
    # Ayuda:
    if (defined ($opts{'a'})){
    	help();
    }
   
    # Base de datos:
    if (defined ($opts{'d'})){
    	$dbname=$opts{'d'};
    }
    
    # Usuario:
    if (defined ($opts{'u'})){
    	$username=$opts{'u'};
    }else{
    	error ("Es necesario indicar un nombre de usuario para realizar la 
    	conexion al servidor PostgreSQL.");
    }
   
    # Password:
    if (defined ($opts{'c'})) {
        $password= $opts{'c'};
    }else{
    	error ("Es necesario indicar la contraseña del usuario para realizar la
    	 conexion al servidor PostgreSQL.");
    }
   
    # Direccion IP servidor PostgreSQL:
    # La expresion regular asegura que la IPv4 del host destino sea de forma: 
    # 0-255.0-255.0-255.0-255
    # La expresion regular asegura que si en lugar de una IP, el host destino 
    # es un nombre a resolver alojado en /etc/hosts (Ej valido: 
    # VM_Server-PostgreSQL2), 
   	# 	dicho nombre no contenga ningun caracter no admitido por dicho fichero:
   	# 		Caracteres no validos: $ ' " \ | # & ( ) ` ; < >  . (0-9)* 
   	#(espacio en blanco o vacío)

     if (defined ($opts{'h'})) {
       	$host  = $opts{'h'};
       	 if ($host !~ /^[(a-zA-Z)*(*_\-^ª!@·~%½¬{}\/\[\]¿?¡=+¨ç:€ñ´)*]*$/ && 
       	 ($host  !~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/
            || $1 < 0 || $1 > 255 || $2 < 0 || $2 > 255
            || $3 < 0 || $3 > 255 || $4 < 0 || $4 > 255)) {
            error ("Nomenclatura en su direccion IP o nombre de equipo $host no
             valida.");
        }
        
        # Si no se especifica ninguna direccion IP (remota) del servidor 
        # PostgreSQL, se intentara la conexion en localhost.
        
    }elsif ($host eq ""){
    	$host="localhost";
    	
    }	
     	
    # Puerto:
    if (defined ($opts{'p'})) {
        $port= $opts{'p'};
    }elsif($port eq ""){
    	$port="5432";
    }
    
    
    # Si no se introduce un nombre de BD para la conexion, intentara conectarse
    # a una BD con el mismo nombre que el usuario.
	# Esta propiedad se ha querido conservar porque existe como tal en las 
	# conexiones a servidores PostgreSQL mediante linea de comandos.
    
    
    if($dbname eq ""){
		$dbname=$username;
	}
	
	# Test:
	# Esta opcion no debe utilizarse en la ejecucion normal del plugin con 
	# objetivos de monitorizar en Pandora FMS.
	# Fue creada para realizar pruebas de test y mostrar resultados mas nitidos
	# evitando que la ejecucion de este plugin conlleve mostrar por pantalla 
	# todas las salidas en formato XML.
	

}

# -----------------------------------------------------------------------------
# Programa Principal
# -----------------------------------------------------------------------------

config();

# Conexión al servidor de PostgreSQL con los parametros introducidos por linea 
# de comandos.

my $dbh = DBI->connect("dbi:Pg:dbname=$dbname;host=$host;port=$port",
                      $username,
                      $password,
                      {AutoCommit => 0, RaiseError => 1, PrintError => 0}
                     );

# Comienzan los modulos a monitorizar por el agente de Pandora FMS.

# Monitorizacion del numero de usuarios conectados por cada rol en un servidor 
# de PostgreSQL.
	
my $consulta1_users = $dbh->selectall_arrayref('SELECT usename, count(*) FROM 
pg_stat_activity GROUP BY usename');

	for ( @$consulta1_users ){
		my($user,$nconex)=@$_;
    	
    
		print "<module>\n";
		print "<name>Conexiones del usuario: <![CDATA[". $user ."]]></name>\n";
		print "<type><![CDATA[generic_data]]></type>\n";
		print "<data><![CDATA[" . $nconex . "]]></data>\n";
		print "<description>Numero de Conexiones del Usuario </description>\n";
		print "</module>\n";
    	
	} 
	
# Monitorizacion del espacio utilizado en disco por cada base de datos 
# contenida en el servidor de PostgreSQL.
	
my $consulta2_disco = $dbh->selectall_arrayref('SELECT 
datname,pg_database_size(oid) 
FROM pg_database');

for ( @$consulta2_disco ){
	my($dataname,$diskspace)=@$_;
		
	print "<module>\n";
	print "<name>Disco utilizado por:<![CDATA[".$dataname."]]></name>\n";
	print "<type><![CDATA[generic_data]]></type>\n";
	print "<data><![CDATA[" . $diskspace . "]]></data>\n";
	print "<description>Size de la BD en bytes</description>\n";
	print "</module>\n";
		
}
	

# Monitorizacion del sub-proceso BgWriter para la optimizacion y tunning de 
# servidores PostgreSQL.

my $consulta3_bgwriter = $dbh->selectall_arrayref('SELECT 
pg_stat_get_bgwriter_timed_checkpoints(),
pg_stat_get_bgwriter_requested_checkpoints(),
buffers_checkpoint,buffers_clean,buffers_backend,
buffers_alloc FROM pg_stat_bgwriter');

for ( @$consulta3_bgwriter ){
	my($buffers_timed,$buffers_requested,$buffers_checkpoint,
	$buffers_clean,$buffers_backend,$buffers_alloc)=@$_;
	
	print "<module>\n";
	print "<name>Buffers_timed_Checkpoints</name>\n";
	print "<type><![CDATA[generic_data]]></type>\n";
	print "<data><![CDATA[" . $buffers_timed . "]]></data>\n";
	print "<description>CheckPoints porque expiro el timeout</description>\n";
	print "</module>\n";
		
	print "<module>\n";
	print "<name>Buffers_requested_Checkpoints</name>\n";
	print "<type><![CDATA[generic_data]]></type>\n";
	print "<data><![CDATA[" . $buffers_requested . "]]></data>\n";
	print "<description>CheckPoints bajo solicitud</description>\n";
	print "</module>\n";
		
	print "<module>\n";
	print "<name>Buffers_Checkpoints</name>\n";
	print "<type><![CDATA[generic_data]]></type>\n";
	print "<data><![CDATA[" . $buffers_checkpoint . "]]></data>\n";
	print "<description>Buffers escritos por CheckPoints</description>\n";
	print "</module>\n";
		
	print "<module>\n";
	print "<name>Buffers_clean</name>\n";
	print "<type><![CDATA[generic_data]]></type>\n";
	print "<data><![CDATA[" . $buffers_clean . "]]></data>\n";
	print "<description>Buffers limpiados por BgWriter</description>\n";
	print "</module>\n";
		
	print "<module>\n";
	print "<name>Buffers_backend</name>\n";
	print "<type><![CDATA[generic_data]]></type>\n";
	print "<data><![CDATA[" . $buffers_backend . "]]></data>\n";
	print "<description>Buffers escritos por procesos</description>\n";
	print "</module>\n";
		
	print "<module>\n";
	print "<name>Buffers_alloc</name>\n";
	print "<type><![CDATA[generic_data]]></type>\n";
	print "<data><![CDATA[" . $buffers_alloc . "]]></data>\n";
	print "<description>Buffers asignados globalmente</description>\n";
	print "</module>\n";
	
}
	
# Monitorizacion del numero de bloques leidos en las diferentes BD, asi como 
# los aciertos en cache y los bloques leidos de disco.	
# Monitorizacion del numero de transacciones en las diferentes BD, commits y 
# rollbacks.
# Monitorizacion del numero de tuplas devueltas, buscadas, insertadas, 
# actualizadas y borradas en las diferentes BD.
# Monitorizacion del numero de procesos activos de servidor conectados en cada
# base de datos en PostgreSQL.
	
my $consulta4_db = $dbh->selectall_arrayref('SELECT db.datname,blks_read,
pg_stat_get_db_blocks_fetched(datid),pg_stat_get_db_blocks_hit(datid),
xact_commit,xact_rollback,tup_returned,tup_fetched,tup_inserted,tup_updated,
tup_deleted,pg_stat_get_db_numbackends(datid) 
FROM (pg_database AS db JOIN pg_stat_database sdb ON db.datname=sdb.datname)');

for ( @$consulta4_db ){
	my($dataname,$blocks_disks,$blocks_fetched,$blocks_hit,$trans_commits,
	$trans_rollbacks,$tup_returned,$tup_fetched,$tup_inserted,$tup_updated,
	$tup_deleted,$active_process)=@$_;
	
	print "<module>\n";
	print "<name>Blocks fetched in: <![CDATA[". $dataname . "]]></name>\n";
	print "<type><![CDATA[generic_data]]></type>\n";
	print "<data><![CDATA[" . $blocks_fetched . "]]></data>\n";
	print "<description>Bloques leidos en la BD</description>\n";
	print "</module>\n";
		
	print "<module>\n";
	print "<name>Blocks hit in: <![CDATA[". $dataname . "]]></name>\n";
	print "<type><![CDATA[generic_data]]></type>\n";
	print "<data><![CDATA[" . $blocks_hit . "]]></data>\n";
	print "<description>Bloques acertados en cache en la BD</description>\n";
	print "</module>\n";
		
	print "<module>\n";
	print "<name>Blocks disks in: <![CDATA[". $dataname . "]]></name>\n";
	print "<type><![CDATA[generic_data]]></type>\n";
	print "<data><![CDATA[" . $blocks_disks . "]]></data>\n";
	print "<description>Bloques leidos en disco en la BD</description>\n";
	print "</module>\n";
		
	print "<module>\n";
	print "<name>Commits in: <![CDATA[". $dataname . "]]></name>\n";
	print "<type><![CDATA[generic_data]]></type>\n";
	print "<data><![CDATA[" . $trans_commits . "]]></data>\n";
	print "<description>Commits realizados en la BD</description>\n";
	print "</module>\n";
		
	print "<module>\n";
	print "<name>Rollbacks in: <![CDATA[". $dataname . "]]></name>\n";
	print "<type><![CDATA[generic_data]]></type>\n";
	print "<data><![CDATA[" . $trans_rollbacks . "]]></data>\n";
	print "<description>Rollbacks realizados en la BD</description>\n";
	print "</module>\n";
		
	print "<module>\n";
	print "<name>Returned in: <![CDATA[". $dataname . "]]></name>\n";
	print "<type><![CDATA[generic_data]]></type>\n";
	print "<data><![CDATA[" . $tup_returned . "]]></data>\n";
	print "<description>Tuplas devueltas en la BD</description>\n";
	print "</module>\n";
		
	print "<module>\n";
	print "<name>Fetched in: <![CDATA[". $dataname . "]]></name>\n";
	print "<type><![CDATA[generic_data]]></type>\n";
	print "<data><![CDATA[" . $tup_fetched . "]]></data>\n";
	print "<description>Tuplas buscadas en la BD</description>\n";
	print "</module>\n";
		
	print "<module>\n";
	print "<name>Inserted in: <![CDATA[". $dataname . "]]></name>\n";
	print "<type><![CDATA[generic_data]]></type>\n";
	print "<data><![CDATA[" . $tup_inserted . "]]></data>\n";
	print "<description>Tuplas insertadas en la BD</description>\n";
	print "</module>\n";
		
	print "<module>\n";
	print "<name>Updated in: <![CDATA[". $dataname . "]]></name>\n";
	print "<type><![CDATA[generic_data]]></type>\n";
	print "<data><![CDATA[" . $tup_updated . "]]></data>\n";
	print "<description>Tuplas actualizadas en la BD</description>\n";
	print "</module>\n";
		
	print "<module>\n";
	print "<name>Deleted in: <![CDATA[". $dataname . "]]></name>\n";
	print "<type><![CDATA[generic_data]]></type>\n";
	print "<data><![CDATA[" . $tup_deleted . "]]></data>\n";
	print "<description>Tuplas eliminadas en la BD</description>\n";
	print "</module>\n";
		
	print "<module>\n";
	print "<name>Active Process in: <![CDATA[". $dataname . "]]></name>\n";
	print "<type><![CDATA[generic_data]]></type>\n";
	print "<data><![CDATA[" . $active_process . "]]></data>\n";
	print "<description>Procesos activos de servidor</description>\n";
	print "</module>\n";
	
	
}

my $rc = $dbh->disconnect(); 
exit;

