Skip to content

Instantly share code, notes, and snippets.

@popozhu
Created March 12, 2014 15:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save popozhu/9509554 to your computer and use it in GitHub Desktop.
Save popozhu/9509554 to your computer and use it in GitHub Desktop.
#!/usr/bin/perl
# nagios: -epn
## pnp4nagios–0.6.21
## Copyright (c) 2005-2010 Joerg Linge (http://www.pnp4nagios.org)
##
## 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; either version 2
## of the License, or (at your option) any later version.
##
## 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
if( $< == 0 ){
print "dont try this as root \n";
exit 1;
}
use warnings;
use strict;
use POSIX;
use Getopt::Long;
use Time::HiRes qw(gettimeofday tv_interval);
use XML::Simple;
use vars qw ( $TEMPLATE %NAGIOS $t0 $t1 $rt $delayed_write $rrdfile @ds_create $count $line $name $ds_update $dstype %CTPL);
my %conf = (
TIMEOUT => 15,
CFG_DIR => "/home/pnp4nagios/etc/",
USE_RRDs => 1,
RRDPATH => "/usr/local/pnp4nagios/var/perfdata",
RRDTOOL => "/usr/local/bin/rrdtool",
RRD_STORAGE_TYPE => "MULTIPLE",
RRD_HEARTBEAT => 8640,
RRA_STEP => 60,
RRA_CFG => "/home/pnp4nagios/etc/rra.cfg",
STATS_DIR => "/usr/local/pnp4nagios/var/stats",
LOG_FILE => "/usr/local/pnp4nagios/var/perfdata.log",
LOG_FILE_MAX_SIZE => "10485760", #Truncate after 10MB
LOG_LEVEL => 0,
XML_ENC => "UTF-8",
XML_UPDATE_DELAY => 0, # Write XML only if file is older then XML_UPDATE_DELAY seconds
RRD_DAEMON_OPTS => "",
GEARMAN_HOST => "localhost:4730", # How many gearman worker childs to start
PREFORK => 2, # How many gearman worker childs to start
REQUESTS_PER_CHILD => 20000, # Restart after a given count of requests
ENCRYPTION => 1, # Decrypt mod_gearman packets
KEY => 'should_be_changed',
KEY_FILE => '/usr/local/pnp4nagios/etc/secret.key',
UOM2TYPE => { 'c' => 'DERIVE', 'd' => 'DERIVE' },
COMBINE_DATASOURCE => 1, # used when RRD_STORAGE_TYPE is MULTIPLE, combine multiple datasource info to the [SERVICEDESC].xml file which has the same [SERVICEDESC]
);
my %const = (
XML_STRUCTURE_VERSION => "4",
VERSION => "0.6.21",
);
#
# Dont change anything below these lines ...
#
#
# "rrdtool create" Syntax
#
my @default_rrd_create = ( "RRA:AVERAGE:0.5:1:2880", "RRA:AVERAGE:0.5:5:2880", "RRA:AVERAGE:0.5:30:4320", "RRA:AVERAGE:0.5:360:5840", "RRA:MAX:0.5:1:2880", "RRA:MAX:0.5:5:2880", "RRA:MAX:0.5:30:4320", "RRA:MAX:0.5:360:5840", "RRA:MIN:0.5:1:2880", "RRA:MIN:0.5:5:2880", "RRA:MIN:0.5:30:4320", "RRA:MIN:0.5:360:5840", );
Getopt::Long::Configure('bundling');
my ( $opt_d, $opt_V, $opt_h, $opt_i, $opt_n, $opt_b, $opt_gm, $opt_pidfile, $opt_daemon );
my $opt_t = my $opt_t_default = $conf{TIMEOUT}; # Default Timeout
my $opt_c = $conf{CFG_DIR} . "process_perfdata.cfg";
GetOptions(
"V" => \$opt_V,
"version" => \$opt_V,
"h" => \$opt_h,
"help" => \$opt_h,
"i" => \$opt_i,
"inetd" => \$opt_i,
"b=s" => \$opt_b,
"bulk=s" => \$opt_b,
"d=s" => \$opt_d,
"datatype=s" => \$opt_d,
"t=i" => \$opt_t,
"timeout=i" => \$opt_t,
"c=s" => \$opt_c,
"config=s" => \$opt_c,
"n" => \$opt_n,
"npcd" => \$opt_n,
"gearman:s" => \$opt_gm,
"daemon" => \$opt_daemon,
"pidfile=s" => \$opt_pidfile,
);
parse_config($opt_c);
$conf{'GLOBAL_RRD_STORAGE_TYPE'} = uc($conf{'RRD_STORAGE_TYPE'}); # store the initial value for later use
my %stats = init_stats();
my $cypher;
#
# RRDs Perl Module Detection
#
if ( $conf{USE_RRDs} == 1 ) {
unless ( eval "use RRDs;1" ) {
$conf{USE_RRDs} = 0;
}
}
#
# Include Gearman modules if needed
#
if ( defined($opt_gm) ) {
unless ( eval "use Gearman::Worker;1" ) {
print "Perl module Gearman::Worker not found\n";
exit 1;
}
unless ( eval "use MIME::Base64;1" ) {
print "Perl module MIME::Base64 not found\n";
exit 1;
}
unless ( eval "use Crypt::Rijndael;1" ) {
print "Perl module Crypt::Rijndael not found\n";
exit 1;
}
}
print_help() if ($opt_h);
print_version() if ($opt_V);
# Use the timeout specified on the command line and if none use what is in the configuration
# If timeout is not in command line or the config file use the default
$opt_t = $conf{TIMEOUT} if ( $opt_t == $opt_t_default && $opt_t != $conf{TIMEOUT} );
print_log( "Default Timeout: $opt_t_default secs.", 2 );
print_log( "Config Timeout: $conf{TIMEOUT} secs.", 2 );
print_log( "Actual Timeout: $opt_t secs.", 2 );
init_signals();
my %children = (); # keys are current child process IDs
my $children_num = 0; # current number of children
if( ! defined($opt_gm) ){
#
# synchronos / bulk / npcd mode
#
main();
}else{
#
# Gearman worker main loop
#
print_log( "process_perfdata.pl-$const{VERSION} Gearman Worker Daemon", 0 );
if($opt_gm =~ /:\d+/ ){
$conf{'GEARMAN_HOST'} = $opt_gm;
}
if($conf{ENCRYPTION} == 1){
print_log( "Encryptions is enabled", 0 );
read_keyfile($conf{'KEY_FILE'});
# fill key up to 32 bytes
$conf{'KEY'} = substr($conf{'KEY'},0,32) . chr(0) x ( 32 - length( $conf{'KEY'} ) );
$cypher = Crypt::Rijndael->new( $conf{'KEY'}, Crypt::Rijndael::MODE_ECB() );
}
daemonize();
}
#
# Subs
#
# Main function to switch to the right mode.
sub main {
my $job = shift;
my $t0 = [gettimeofday];
my $t1;
my $rt;
my $lines = 0;
# Gearman Worker
if (defined $opt_gm) {
print_log( "Gearman Worker Job start", 1 );
%NAGIOS = parse_env($job->arg);
$lines = process_perfdata();
$t1 = [gettimeofday];
$rt = tv_interval $t0, $t1;
$stats{runtime} += $rt;
$stats{rows}++;
if( ( int $stats{timet} / 60 ) < ( int time / 60 )){
store_internals();
init_stats();
}
print_log( "Gearman job end (runtime ${rt}s) ...", 1 );
return 1;
} elsif ( $opt_b && !$opt_n ) {
# Bulk mode
alarm($opt_t);
print_log( "process_perfdata.pl-$const{VERSION} starting in BULK Mode called by Nagios", 1 );
$lines = process_perfdata_file();
} elsif ( $opt_b && $opt_n ) {
# Bulk mode with npcd
alarm($opt_t);
print_log( "process_perfdata.pl-$const{VERSION} starting in BULK Mode called by NPCD", 1 );
$lines = process_perfdata_file();
} else {
# Synchronous mode
$opt_t = 5 if $opt_t > 5; # maximum timeout
alarm($opt_t);
print_log( "process_perfdata.pl-$const{VERSION} starting in SYNC Mode", 1 );
%NAGIOS = parse_env();
$lines = process_perfdata();
}
$rt = tv_interval $t0, $t1;
$stats{runtime} = $rt;
$stats{rows} = $lines;
store_internals();
print_log( "PNP exiting (runtime ${rt}s) ...", 1 );
exit 0;
}
#
# Parse %ENV and return a global hash %NAGIOS
#
sub parse_env {
my $job_data = shift;
%NAGIOS = ();
$NAGIOS{DATATYPE} = "SERVICEPERFDATA";
if(defined $opt_gm){
# Gearman Worker
#$job_data = decode_base64($job_data);
if($conf{ENCRYPTION} == 1){
$job_data = $cypher->decrypt( $job_data )
}
my @LINE = split(/\t/, $job_data);
foreach my $k (@LINE) {
$k =~ /([A-Z 0-9_]+)::(.*)$/;
$NAGIOS{$1} = $2 if ($2);
}
if ( !$NAGIOS{HOSTNAME} ) {
print_log( "Gearman job data missmatch. Please check your encryption key.", 0 );
return %NAGIOS;
}
} elsif ( defined($opt_b) ){
# Bulk Mode
my @LINE = split(/\t/, $job_data);
foreach my $k (@LINE) {
$k =~ /([A-Z 0-9_]+)::(.*)$/;
$NAGIOS{$1} = $2 if ($2);
}
}else{
if ( ( !$ENV{NAGIOS_HOSTNAME} ) and ( !$ENV{ICINGA_HOSTNAME} ) ) {
print_log( "Cant find Nagios Environment. Exiting ....", 1 );
exit 2;
}
foreach my $key ( sort keys %ENV ) {
if ( $key =~ /^(NAGIOS|ICINGA)_(.*)/ ) {
$NAGIOS{$2} = $ENV{$key};
}
}
}
if ($opt_d) {
$NAGIOS{DATATYPE} = $opt_d;
}
$NAGIOS{DISP_HOSTNAME} = $NAGIOS{HOSTNAME};
$NAGIOS{DISP_SERVICEDESC} = $NAGIOS{SERVICEDESC};
$NAGIOS{HOSTNAME} = cleanup( $NAGIOS{HOSTNAME} );
$NAGIOS{SERVICEDESC} = cleanup( $NAGIOS{SERVICEDESC} );
$NAGIOS{PERFDATA} = $NAGIOS{SERVICEPERFDATA};
$NAGIOS{CHECK_COMMAND} = $NAGIOS{SERVICECHECKCOMMAND};
if ( $NAGIOS{DATATYPE} eq "HOSTPERFDATA" ) {
$NAGIOS{SERVICEDESC} = "_HOST_";
$NAGIOS{DISP_SERVICEDESC} = "Host Perfdata";
$NAGIOS{PERFDATA} = $NAGIOS{HOSTPERFDATA};
$NAGIOS{CHECK_COMMAND} = $NAGIOS{HOSTCHECKCOMMAND};
}
print_log( "Datatype set to '$NAGIOS{DATATYPE}' ", 2 );
return %NAGIOS;
}
#
# Perfdata sanity check
#
sub process_perfdata {
if ( keys( %NAGIOS ) == 1 && defined($opt_gm) ) {
$stats{skipped}++;
return 1;
}
if ( ! defined($NAGIOS{PERFDATA}) && ! defined($opt_gm) ) {
print_log( "No Performance Data for $NAGIOS{HOSTNAME} / $NAGIOS{SERVICEDESC} ", 1 );
if ( !$opt_b ) {
print_log( "PNP exiting ...", 1 );
exit 3;
}
}
if ( $NAGIOS{PERFDATA} =~ /^(.*)\s\[(.*)\]$/ ) {
$NAGIOS{PERFDATA} = $1;
$NAGIOS{CHECK_COMMAND} = $2;
print_log( "Found Perfdata from Distributed Server $NAGIOS{HOSTNAME} / $NAGIOS{SERVICEDESC} ($NAGIOS{PERFDATA})", 1 );
}
else {
print_log( "Found Performance Data for $NAGIOS{HOSTNAME} / $NAGIOS{SERVICEDESC} ($NAGIOS{PERFDATA}) ", 1 );
}
$NAGIOS{PERFDATA} =~ s/,/./g;
$NAGIOS{PERFDATA} =~ s/\s+=/=/g;
$NAGIOS{PERFDATA} =~ s/=\s+/=/g;
$NAGIOS{PERFDATA} .= " ";
parse_perfstring( $NAGIOS{PERFDATA} );
return 1;
}
#
# Process Perfdata in Bulk Mode
#
sub process_perfdata_file {
if ( $opt_b =~ /-PID-(\d+)/ ) {
print_log( "Oops: $opt_b already processed by $1 - please check timeout settings", 0 );
}
print_log( "searching for $opt_b", 2 );
if ( -e "$opt_b" ) {
my $pdfile = "$opt_b" . "-PID-" . $$;
print_log( "renaming $opt_b to $pdfile for bulk update", 2 );
unless ( rename "$opt_b", "$pdfile" ) {
print_log( "ERROR: rename $opt_b to $pdfile failed", 1 );
exit 4;
}
print_log( "reading $pdfile for bulk update", 2 );
open( PDFILE, "< $pdfile" );
my $count = 0;
while (<PDFILE>) {
my $job_data = $_;
$count++;
print_log( "Processing Line $count", 2 );
my @LINE = split(/\t/);
%NAGIOS = (); # cleaning %NAGIOS Hash
#foreach my $k (@LINE) {
# $k =~ /([A-Z 0-9_]+)::(.*)$/;
# $ENV{ 'NAGIOS_' . $1 } = $2 if ($2);
#}
parse_env($job_data);
if ( $NAGIOS{SERVICEPERFDATA} || $NAGIOS{HOSTPERFDATA} ) {
process_perfdata();
} else {
print_log( "No Perfdata. Skipping line $count", 2 );
$stats{skipped}++;
}
}
print_log( "$count lines processed", 1 );
if ( unlink("$pdfile") == 1 ) {
print_log( "$pdfile deleted", 1 );
}else {
print_log( "Could not delete $pdfile:$!", 1 );
}
return $count;
}
else {
print_log( "ERROR: File $opt_b not found", 1 );
}
}
#
# Write Data to RRD Files
#
sub data2rrd {
my @data = @_;
my @rrd_state = ();
my $rrd_storage_type;
print_log( "data2rrd called", 2 );
$NAGIOS{XMLFILE} = $conf{RRDPATH} . "/" . $data[0]{hostname} . "/" . $data[0]{servicedesc} . ".xml";
$NAGIOS{SERVICEDESC} = $data[0]{servicedesc};
$NAGIOS{DISP_SERVICEDESC} = $data[0]{disp_servicedesc};
$NAGIOS{AUTH_SERVICEDESC} = $data[0]{auth_servicedesc} || "";
$NAGIOS{AUTH_HOSTNAME} = $data[0]{auth_hostname} || "";
$NAGIOS{MULTI_PARENT} = "";
$NAGIOS{MULTI_PARENT} = $data[0]{multi_parent} || "";
$TEMPLATE = $data[0]{template};
unless ( -d "$conf{RRDPATH}" ) {
unless ( mkdir "$conf{RRDPATH}" ) {
print_log( "mkdir $conf{RRDPATH}, permission denied ", 1 );
print_log( "PNP exiting ...", 1 );
exit 5;
}
}
unless ( -d "$conf{RRDPATH}/$NAGIOS{HOSTNAME}" ) {
unless ( mkdir "$conf{RRDPATH}/$NAGIOS{HOSTNAME}" ) {
print_log( "mkdir $conf{RRDPATH}/$NAGIOS{HOSTNAME}, permission denied ", 1 );
print_log( "PNP exiting ...", 1 );
exit 6;
}
}
#
# read xml file
# by popozhu at 2014/02/28
my @pre_datasource = ();
if (defined $data[0]{combine_datasource} && $data[0]{combine_datasource} eq "1" && $data[0]{rrd_storage_type} eq "MULTIPLE" ){
@pre_datasource = read_xml_template( $NAGIOS{XMLFILE} );
}
#
# Create PHP Template File
#
open_template( $NAGIOS{XMLFILE} );
@ds_create = ();
$ds_update = '';
for my $i ( 0 .. $#data ) {
print_log( " -- Job $i ", 3 );
my $DS = $i + 1;
#
# for each datasource
#
for my $job ( sort keys %{ $data[$i] } ) {
if ( defined $data[$i]{$job} ) {
print_log( " -- $job -> $data[$i]{$job}", 3 );
}
}
if ( uc($conf{'GLOBAL_RRD_STORAGE_TYPE'}) eq "MULTIPLE" ) {
my $file = $conf{RRDPATH} . "/" . $data[$i]{hostname} . "/" . $data[$i]{servicedesc} . ".rrd";
if ( -e $file ){
print_log("RRD_STORAGE_TYPE=MULTIPLE ignored because $file exists!", 1 ) if $i == 0;
$data[$i]{rrd_storage_type} = "SINGLE";
}
}
if ( $i == 0 ){
$ds_update = "$data[$i]{timet}";
}
if ( $data[$i]{rrd_storage_type} eq "MULTIPLE" ) {
print_log( "DEBUG: MULTIPLE Storage Type", 3 );
$DS = 1;
# PNP 0.4.x Template compatibility
$NAGIOS{RRDFILE} = "";
#
$rrd_storage_type = "MULTIPLE";
$rrdfile = $conf{RRDPATH} . "/" . $data[$i]{hostname} . "/" . $data[$i]{servicedesc} . "_" . $data[$i]{name} . ".rrd";
# DS is set to 1
@ds_create = "DS:$DS:$data[$i]{dstype}:$data[$i]{rrd_heartbeat}:$data[$i]{rrd_min}:$data[$i]{rrd_max}";
$ds_update = "$data[$i]{timet}:$data[$i]{value}";
@rrd_state = write_rrd();
@ds_create = ();
$ds_update = "";
# remove duplicate datasource(current datasource)
@pre_datasource = remove_dup_ds($data[$i]{name}, \@pre_datasource);
}
else {
print_log( "DEBUG: SINGLE Storage Type", 3 );
# PNP 0.4.x Template compatibility
$NAGIOS{RRDFILE} = $conf{RRDPATH} . "/" . $data[0]{hostname} . "/" . $data[0]{servicedesc} . ".rrd";
#
$rrd_storage_type = "SINGLE";
$rrdfile = $conf{RRDPATH} . "/" . $data[$i]{hostname} . "/" . $data[$i]{servicedesc} . ".rrd";
push( @ds_create, "DS:$DS:$data[$i]{dstype}:$data[$i]{rrd_heartbeat}:$data[$i]{rrd_min}:$data[$i]{rrd_max}" );
$ds_update = "$ds_update:$data[$i]{value}";
}
write_to_template( "TEMPLATE", $data[0]{template} );
write_to_template( "RRDFILE", $rrdfile );
write_to_template( "RRD_STORAGE_TYPE", $data[$i]{rrd_storage_type} );
write_to_template( "RRD_HEARTBEAT", $data[$i]{rrd_heartbeat} );
write_to_template( "IS_MULTI", $data[0]{multi} );
write_to_template( "DS", $DS );
write_to_template( "NAME", $data[$i]{name} );
write_to_template( "LABEL", $data[$i]{label} );
write_to_template( "UNIT", $data[$i]{uom} );
write_to_template( "ACT", $data[$i]{value} );
write_to_template( "WARN", $data[$i]{warning} );
write_to_template( "WARN_MIN", $data[$i]{warning_min} );
write_to_template( "WARN_MAX", $data[$i]{warning_max} );
write_to_template( "WARN_RANGE_TYPE", $data[$i]{warning_range_type} );
write_to_template( "CRIT", $data[$i]{critical} );
write_to_template( "CRIT_MIN", $data[$i]{critical_min} );
write_to_template( "CRIT_MAX", $data[$i]{critical_max} );
write_to_template( "CRIT_RANGE_TYPE", $data[$i]{critical_range_type} );
write_to_template( "MIN", $data[$i]{min} );
write_to_template( "MAX", $data[$i]{max} );
}
if ( $rrd_storage_type eq "SINGLE" ) {
@rrd_state = write_rrd();
}
# readd the previous datasources
foreach my $data (@pre_datasource){
my $name = $data->{"NAME"};
write_to_template( "TEMPLATE", $data->{"TEMPLATE"} );
write_to_template( "RRDFILE", $data->{"RRDFILE"} );
write_to_template( "RRD_STORAGE_TYPE", $data->{"RRD_STORAGE_TYPE"} );
write_to_template( "RRD_HEARTBEAT", $data->{"RRD_HEARTBEAT"} );
write_to_template( "IS_MULTI", $data->{"IS_MULTI"} );
write_to_template( "DS", $data->{"DS"} );
write_to_template( "NAME", $data->{"NAME"} );
write_to_template( "LABEL", $data->{"LABEL"} );
write_to_template( "UNIT", $data->{"UNIT"} );
write_to_template( "ACT", $data->{"ACT"} );
write_to_template( "WARN", "");
write_to_template( "WARN_MIN", "");
write_to_template( "WARN_MAX", "");
write_to_template( "WARN_RANGE_TYPE", "");
write_to_template( "CRIT", "");
write_to_template( "CRIT_MIN", "");
write_to_template( "CRIT_MAX", "");
write_to_template( "CRIT_RANGE_TYPE", "");
write_to_template( "MIN", "");
write_to_template( "MAX", "");
}
write_state_to_template(@rrd_state);
write_env_to_template();
close_template( $NAGIOS{XMLFILE} );
}
sub write_rrd {
my @rrd_create = ();
my @rrd_state = ();
print_log( "DEBUG: TPL-> $TEMPLATE", 3 );
print_log( "DEBUG: CRE-> @ds_create", 3 );
print_log( "DEBUG: UPD-> $ds_update", 3 );
if ( !-e "$rrdfile" ) {
@rrd_create = parse_rra_config($TEMPLATE);
if ( $conf{USE_RRDs} == 1 ) {
print_log( "RRDs::create $rrdfile @rrd_create @ds_create --start=$NAGIOS{TIMET} --step=$conf{RRA_STEP}", 2 );
RRDs::create( "$rrdfile", @rrd_create, @ds_create, "--start=$NAGIOS{TIMET}", "--step=$conf{RRA_STEP}" );
my $err = RRDs::error();
if ($err) {
print_log( "RRDs::create $rrdfile @rrd_create @ds_create --start=$NAGIOS{TIMET} --step=$conf{RRA_STEP}", 0 );
print_log( "RRDs::create ERROR $err", 0 );
@rrd_state = ( 1, $err );
$stats{error}++;
}
else {
print_log( "$rrdfile created", 2 );
@rrd_state = ( 0, "just created" );
$stats{create}++;
}
}
else {
print_log( "RRDs Perl Modules are not installed. Falling back to rrdtool system call.", 0 );
print_log( "$conf{RRDTOOL} create $rrdfile @rrd_create @ds_create --start=$NAGIOS{TIMET} --step=$conf{RRA_STEP}", 2 );
system("$conf{RRDTOOL} create $rrdfile @rrd_create @ds_create --start=$NAGIOS{TIMET} --step=$conf{RRA_STEP}");
if ( $? > 0 ) {
print_log( "$conf{RRDTOOL} create $rrdfile @rrd_create @ds_create --start=$NAGIOS{TIMET} --step=$conf{RRA_STEP}", 0 );
print_log( "rrdtool create returns $?", 0 );
@rrd_state = ( $?, "create failed" );
$stats{error}++;
}
else {
print_log( "rrdtool create returns $?", 1 );
@rrd_state = ( 0, "just created" );
$stats{create}++;
}
}
}
else {
if ( $conf{USE_RRDs} == 1 ) {
if ( $conf{RRD_DAEMON_OPTS} ) {
print_log( "RRDs::update --daemon=$conf{RRD_DAEMON_OPTS} $rrdfile $ds_update", 2 );
RRDs::update( "--daemon=$conf{RRD_DAEMON_OPTS}", "$rrdfile", "$ds_update" );
}
else {
print_log( "RRDs::update $rrdfile $ds_update", 2 );
RRDs::update( "$rrdfile", "$ds_update" );
}
my $err = RRDs::error();
if ($err) {
print_log( "RRDs::update $rrdfile $ds_update", 0 );
print_log( "RRDs::update ERROR $err", 0 );
@rrd_state = ( 1, $err );
$stats{error}++;
}
else {
print_log( "$rrdfile updated", 2 );
@rrd_state = ( 0, "successful updated" );
$stats{update}++;
}
}
else {
print_log( "RRDs Perl Modules are not installed. Falling back to rrdtool system call.", 2 );
if ( $conf{RRD_DAEMON_OPTS} ) {
print_log( "$conf{RRDTOOL} update --daemon=$conf{RRD_DAEMON_OPTS} $rrdfile $ds_update", 2 );
system("$conf{RRDTOOL} update --daemon=$conf{RRD_DAEMON_OPTS} $rrdfile $ds_update");
}
else {
print_log( "$conf{RRDTOOL} update $rrdfile $ds_update", 2 );
system("$conf{RRDTOOL} update $rrdfile $ds_update");
}
if ( $? > 0 ) {
print_log( "$conf{RRDTOOL} update $rrdfile $ds_update", 0 );
print_log( "rrdtool update returns $?", 0 );
@rrd_state = ( $?, "update failed" );
$stats{error}++;
}
else {
print_log( "rrdtool update returns $?", 1 );
@rrd_state = ( $?, "successful updated" );
$stats{update}++;
}
}
}
return @rrd_state;
}
#
# Write Template
#
sub open_template {
my $xmlfile = shift;
$delayed_write = 0;
if( -e $xmlfile ){
my $mtime = (stat($xmlfile))[9];
my $t = time();
my $age = ($t - $mtime);
if ( $age < $conf{'XML_UPDATE_DELAY'} ){
print_log( "DEBUG: XML File is $age seconds old. No update needed", 3 );
$delayed_write = 1;
return;
}
print_log( "DEBUG: XML File is $age seconds old. UPDATE!", 3 );
}
open( XML, "> $xmlfile.$$" ) or die "Cant create temporary XML file ", $!;
print XML "<?xml version=\"1.0\" encoding=\"" . $conf{XML_ENC} . "\" standalone=\"yes\"?>\n";
print XML "<NAGIOS>\n";
}
#
# Close Template FH
#
sub close_template {
return if $delayed_write == 1;
my $xmlfile = shift;
printf( XML " <XML>\n" );
printf( XML " <VERSION>%d</VERSION>\n", $const{'XML_STRUCTURE_VERSION'} );
printf( XML " </XML>\n" );
printf( XML "</NAGIOS>\n" );
close(XML);
rename( "$xmlfile.$$", "$xmlfile" );
}
#
# Add Lines
#
sub write_to_template {
return if $delayed_write == 1;
my $tag = shift;
my $data = shift;
if ( !defined $data ) {
$data = "";
}
if ( $tag =~ /^TEMPLATE$/ ) {
printf( XML " <DATASOURCE>\n" );
printf( XML " <%s>%s</%s>\n", $tag, "$data", $tag );
}
elsif ( $tag =~ /^MAX$/ ) {
printf( XML " <%s>%s</%s>\n", $tag, "$data", $tag );
printf( XML " </DATASOURCE>\n" );
}
else {
printf( XML " <%s>%s</%s>\n", $tag, "$data", $tag );
}
}
sub write_state_to_template {
return if $delayed_write == 1;
my @rrd_state = @_;
printf( XML " <RRD>\n" );
printf( XML " <RC>%s</RC>\n", $rrd_state[0] );
printf( XML " <TXT>%s</TXT>\n", $rrd_state[1] );
printf( XML " </RRD>\n" );
}
#
# Store the complete Nagios ENV
#
sub write_env_to_template {
return if $delayed_write == 1;
foreach my $key ( sort keys %NAGIOS ) {
$NAGIOS{$key} = urlencode($NAGIOS{$key});
printf( XML " <NAGIOS_%s>%s</NAGIOS_%s>\n", $key, $NAGIOS{$key}, $key );
}
}
#
# Recursive Template search
#
sub adjust_template {
my $command = shift;
my $uom = shift;
my $count = shift;
my @temp_template = split /\!/, $command;
my $initial_template = cleanup( $temp_template[0] );
my $template = cleanup( $temp_template[0] );
%CTPL = (
UOM => $uom,
COUNT => $count,
COMMAND => $command,
TEMPLATE => $template,
DSTYPE => $dstype,
RRD_STORAGE_TYPE => $conf{'RRD_STORAGE_TYPE'},
RRD_HEARTBEAT => $conf{'RRD_HEARTBEAT'},
USE_MIN_ON_CREATE => 0,
USE_MAX_ON_CREATE => 0,
);
read_custom_template ( );
#
if ( $CTPL{'TEMPLATE'} ne $initial_template ){
read_custom_template ( );
}
return %CTPL;
}
#
# Analyse check_command to find PNP Template .
#
sub read_custom_template {
my $command = $CTPL{'COMMAND'};
my $uom = $CTPL{'UOM'};
my $count = $CTPL{'COUNT'};
my @dstype_list = ();
my $use_min_on_create = 0;
my $use_max_on_create = 0;
my $rrd_storage_type = $CTPL{'RRD_STORAGE_TYPE'};
my $rrd_heartbeat = $CTPL{'RRD_HEARTBEAT'};
my $combine_data = 0;
if ( defined($conf{'UOM2TYPE'}{$uom}) ) {
$dstype = $conf{'UOM2TYPE'}{$uom};
print_log( "DEBUG: DSTYPE adjusted to $dstype by UOM", 3 );
}else {
$dstype = 'GAUGE';
}
print_log( "DEBUG: RAW Command -> $command", 3 );
my @temp_template = split /\!/, $command;
my $template = cleanup( $temp_template[0] );
$template = trim($template);
my $template_cfg = "$conf{CFG_DIR}/check_commands/$template.cfg";
print_log( "DEBUG: read_custom_template() => $command", 3 );
if ( -e $template_cfg ) {
print_log( "DEBUG: adjust_template() => $template_cfg", 3 );
my $initial_dstype = $dstype;
open FH, "<", $template_cfg;
while (<FH>) {
next if /^#/;
next if /^$/;
s/#.*//;
s/ //g;
if (/^(.*)=(.*)$/) {
if ( $1 eq "DATATYPE" ) {
$dstype = uc($2);
$dstype =~ s/ //g;
@dstype_list = split /,/, $dstype;
if ( exists $dstype_list[$count] && $dstype_list[$count] =~ /^(COUNTER|GAUGE|ABSOLUTE|DERIVE)$/ ) {
$dstype = $dstype_list[$count];
print_log( "Adapting RRD Datatype to \"$dstype\" as defined in $template_cfg with key $count", 2 );
}
elsif ( $dstype =~ /^(COUNTER|GAUGE|ABSOLUTE|DERIVE)$/ ) {
print_log( "Adapting RRD Datatype to \"$dstype\" as defined in $template_cfg", 2 );
}
else {
print_log( "RRD Datatype \"$dstype\" defined in $template_cfg is invalid", 2 );
$dstype = $initial_dstype;
}
}
if ( $1 eq "CUSTOM_TEMPLATE" ) {
print_log( "Adapting Template using ARG $2", 2 );
my $i = 1;
my @keys = split /,/, $2;
foreach my $keys (@keys) {
if ( $i == 1 && exists $temp_template[$keys] ) {
$template = trim( $temp_template[$keys] );
print_log( "Adapting Template to $template.php (added ARG$keys)", 2 );
}elsif( exists $temp_template[$keys] ){
$template .= "_" . trim( $temp_template[$keys] );
print_log( "Adapting Template to $template.php (added ARG$keys)", 2 );
}
$i++;
}
print_log( "Adapting Template to $template.php as defined in $template_cfg", 2 );
}
if ( $1 eq "USE_MIN_ON_CREATE" && $2 eq "1" ) {
$use_min_on_create = 1;
}
if ( $1 eq "USE_MAX_ON_CREATE" && $2 eq "1" ) {
$use_max_on_create = 1;
}
if ( $1 eq "RRD_STORAGE_TYPE" && uc($2) eq "MULTIPLE" ) {
$rrd_storage_type = uc($2);
}
if ( $1 eq "RRD_HEARTBEAT" ) {
$rrd_heartbeat = $2;
}
}
}
close FH;
}
else {
print_log( "No Custom Template found for $template ($template_cfg) ", 3 );
print_log( "RRD Datatype is $dstype", 3 );
}
print_log( "Template is $template.php", 3 );
$CTPL{'COMMAND'} = $template;
$CTPL{'TEMPLATE'} = $template;
$CTPL{'DSTYPE'} = $dstype;
$CTPL{'RRD_STORAGE_TYPE'} = $rrd_storage_type;
$CTPL{'RRD_HEARTBEAT'} = $rrd_heartbeat;
$CTPL{'USE_MIN_ON_CREATE'} = $use_min_on_create;
$CTPL{'USE_MAX_ON_CREATE'} = $use_max_on_create;
return %CTPL;
}
#
# Parse process_perfdata.cfg
#
sub parse_config {
my $config_file = shift;
my $line = 0;
if ( -e $config_file ) {
open CFG, '<', "$config_file";
while (<CFG>) {
$line++;
chomp;
s/ //g;
next if /^#/;
next if /^$/;
s/#.*//;
if (/^(.*)=(.*)$/) {
if ( defined $conf{$1} ) {
$conf{$1} = $2;
}
}
}
close CFG;
print_log( "Using Config File $config_file parameters", 2 );
}
else {
print_log( "Config File $config_file not found, using defaults", 2 );
}
}
#
# Parse rra.cfg
#
sub parse_rra_config {
my $template = shift;
my $rra_template = "";
my @rrd_create = @default_rrd_create;
if ( -r $conf{'CFG_DIR'} . "/" . $template . ".rra.cfg" ) {
$rra_template = $conf{'CFG_DIR'} . "/" . $template . ".rra.cfg";
print_log( "Reading $rra_template", 2 );
}
elsif ( -r $conf{'RRA_CFG'} ) {
$rra_template = $conf{'RRA_CFG'};
print_log( "Reading $conf{'RRA_CFG'}", 2 );
}
else {
print_log( "No usable rra.cfg found. Using default values.", 2 );
}
if ( $rra_template ne "" ) {
@rrd_create = ();
open RRA, "<", $rra_template;
while (<RRA>) {
next if /^#/;
next if /^$/;
s/#.*//;
if(/^RRA_STEP=(\d+)/i){
$conf{'RRA_STEP'} = $1;
next;
}
chomp;
push @rrd_create, "$_";
}
close RRA;
}
else {
@rrd_create = @default_rrd_create;
}
return @rrd_create;
}
#
# Function adapted from Nagios::Plugin::Performance
# Thanks to Gavin Carr and Ton Voon
#
sub _parse {
# Nagios::Plugin::Performance
my $string = shift;
my $tmp_string = $string;
$string =~ s/^([^=]+)=(U|[\d\.\-]+)([\w\/%]*);?([\d\.\-:~@]+)?;?([\d\.\-:~@]+)?;?([\d\.\-]+)?;?([\d\.\-]+)?;?\s*//;
if ( $tmp_string eq $string ) {
print_log( "No pattern match in function _parse($string)", 2 );
return undef;
}
return undef unless ( ( defined $1 && $1 ne "" ) && ( defined $2 && $2 ne "" ) );
# create hash from all performance data values
my %p = (
"label" => $1,
"name" => $1,
"value" => $2,
"uom" => $3,
"warning" => $4,
"critical" => $5,
"min" => $6,
"max" => $7
);
$p{label} =~ s/[&"']//g; # cleanup
$p{name} =~ s/["']//g; # cleanup
$p{name} =~ s/[\/\\]/_/g; # cleanup
$p{name} = cleanup($p{name});
if ( $p{uom} eq "%" ) {
$p{uom} = "%%";
print_log( "DEBUG: UOM adjust = $p{uom}", 3 );
}
#
# Check for warning and critical ranges
#
if ( $p{warning} && $p{warning} =~ /^([\d\.\-~@]+)?:([\d\.\-~@]+)?$/ ) {
print_log( "DEBUG: Processing warning ranges ( $p{warning} )", 3 );
$p{warning_min} = $1;
$p{warning_max} = $2;
delete( $p{warning} );
if ( $p{warning_min} =~ /^@/ ) {
$p{warning_min} =~ s/@//;
$p{warning_range_type} = "inside";
}
else {
$p{warning_range_type} = "outside";
}
}
if ( $p{critical} && $p{critical} =~ /^([\d\.\-~@]+)?:([\d\.\-~@]+)?$/ ) {
print_log( "DEBUG: Processing critical ranges ( $p{critical} )", 3 );
$p{critical_min} = $1;
$p{critical_max} = $2;
delete( $p{critical} );
if ( $p{critical_min} =~ /^@/ ) {
$p{critical_min} =~ s/@//;
$p{critical_range_type} = "inside";
}
else {
$p{critical_range_type} = "outside";
}
}
# Strip Range indicators
$p{warning} =~ s/[~@]// if($p{warning});
$p{critical} =~ s/[~@]// if($p{critical});
return ( $string, %p );
}
#
# clean Strings
#
sub cleanup {
my $string = shift;
if ($string) {
$string =~ s/[& :\/\\]/_/g;
}
return $string;
}
#
# Urlencode
#
sub urlencode {
my $string = shift;
if ($string) {
$string =~ s/([<>&])/sprintf("%%%02x",ord($1))/eg; # URLencode;
}
return $string;
}
#
# Trim leading whitespaces
#
sub trim {
my $string = shift;
$string =~ s/^\s*//g;
return $string;
}
#
# Parse the Performance String and call data2rrd()
#
sub parse_perfstring {
#
# Default RRD Datatype
# Value will be overwritten by adjust_template()
#
my %CTPL = ();
$dstype = "GAUGE";
my $perfstring = shift;
my $is_multi = "0";
my @perfs;
my @multi;
my %p;
my $use_min_on_create = 0;
my $use_max_on_create = 0;
#
# check_multi
#
if ( $perfstring =~ /^[']?([a-zA-Z0-9\.\-_\s\/\#]+)::([a-zA-Z0-9\.\-_\s]+)::([^=]+)[']?=/ ) {
$is_multi = 1;
print_log( "check_multi Perfdata start", 3 );
my $count = 0;
my $check_multi_blockcount = 0;
my $multi_parent = cleanup( $NAGIOS{SERVICEDESC} );
my $auth_servicedesc = $NAGIOS{DISP_SERVICEDESC};
while ($perfstring) {
( $perfstring, %p ) = _parse($perfstring);
if ( !$p{label} ) {
print_log( "Invalid Perfdata detected ", 1 );
$stats{invalid}++;
@perfs = ();
last;
}
if ( $p{label} =~ /^[']?([a-zA-Z0-9\.\-_\s\/\#]+)::([a-zA-Z0-9\.\-_\s]+)::([^=]+)[']?$/ ) {
@multi = ( $1, $2, $3 );
if ( $count == 0 ) {
print_log( "DEBUG: First check_multi block", 3 );
# Keep servicedesc while processing the first block.
$p{servicedesc} = cleanup( $NAGIOS{SERVICEDESC} );
$p{disp_servicedesc} = $NAGIOS{DISP_SERVICEDESC};
$p{auth_servicedesc} = $auth_servicedesc;
$p{multi} = 1;
$p{multi_parent} = $multi_parent;
}
else {
print_log( "DEBUG: A new check_multi block ($count) starts", 3 );
$p{servicedesc} = cleanup( $multi[0] ); # Use the multi servicedesc.
$p{multi} = 2;
$p{multi_parent} = $multi_parent;
$p{servicedesc} = cleanup( $multi[0] ); # Use the multi servicedesc.
$p{disp_servicedesc} = cleanup( $multi[0] ); # Use the multi servicedesc.
$p{auth_servicedesc} = $auth_servicedesc;
data2rrd(@perfs) if ( $#perfs >= 0 ); # Process when a new block starts.
@perfs = (); # Clear the perfs array.
# reset check_multi block count
$check_multi_blockcount = 0;
}
%CTPL = adjust_template( $multi[1], $p{uom}, $check_multi_blockcount++ );
if ( $CTPL{'USE_MAX_ON_CREATE'} == 1 && defined $p{max} ) {
$p{rrd_max} = $p{max};
} else {
$p{rrd_max} = "U";
}
if ( $CTPL{'USE_MIN_ON_CREATE'} == 1 && defined $p{min} ) {
$p{rrd_min} = $p{min};
} elsif( $CTPL{'DSTYPE'} eq 'DERIVE' ){
$p{rrd_min} = 0; # Add minimum value 0 if DSTYPE = DERIVE
} else {
$p{rrd_min} = "U";
}
$p{template} = $CTPL{'TEMPLATE'};
$p{dstype} = $CTPL{'DSTYPE'};
$p{rrd_storage_type} = $CTPL{'RRD_STORAGE_TYPE'};
$p{rrd_heartbeat} = $CTPL{'RRD_HEARTBEAT'};
$p{label} = cleanup( $multi[2] ); # store the original label from check_multi header
$p{name} = cleanup( $multi[2] ); # store the original label from check_multi header
$p{hostname} = cleanup( $NAGIOS{HOSTNAME} );
$p{disp_hostname} = $NAGIOS{DISP_HOSTNAME};
$p{auth_hostname} = $NAGIOS{HOSTNAME};
$p{timet} = $NAGIOS{TIMET};
push @perfs, {%p};
$count++;
}
else {
print_log( "DEBUG: Next check_multi data for block $count multiblock $check_multi_blockcount", 3 );
# additional check_multi data
%CTPL = adjust_template( $multi[1], $p{uom}, $check_multi_blockcount++ );
if ( $CTPL{'USE_MAX_ON_CREATE'} == 1 && defined $p{max} ) {
$p{rrd_max} = $p{max};
} else {
$p{rrd_max} = "U";
}
if ( $CTPL{'USE_MIN_ON_CREATE'} == 1 && defined $p{min} ) {
$p{rrd_min} = $p{min};
} elsif( $CTPL{'DSTYPE'} eq 'DERIVE' ){
$p{rrd_min} = 0; # Add minimum value 0 if DSTYPE = DERIVE
} else {
$p{rrd_min} = "U";
}
$p{template} = $CTPL{'TEMPLATE'};
$p{dstype} = $CTPL{'DSTYPE'};
$p{rrd_storage_type} = $CTPL{'RRD_STORAGE_TYPE'};
$p{rrd_heartbeat} = $CTPL{'RRD_HEARTBEAT'};
$p{hostname} = cleanup( $NAGIOS{HOSTNAME} );
$p{disp_hostname} = $NAGIOS{DISP_HOSTNAME};
$p{auth_hostname} = $NAGIOS{HOSTNAME};
$p{timet} = $NAGIOS{TIMET};
if ( $count == 1 ) {
$p{servicedesc} = cleanup( $NAGIOS{SERVICEDESC} ); # Use the servicedesc.
$p{disp_servicedesc} = $NAGIOS{DISP_SERVICEDESC}; # Use the servicedesc.
} else {
$p{servicedesc} = cleanup( $multi[0] ); # Use the multi servicedesc.
$p{disp_servicedesc} = $multi[0]; # Use the multi servicedesc.
}
$p{multi} = $is_multi;
$p{multi_parent} = $multi_parent;
$p{auth_servicedesc} = $auth_servicedesc; # Use the servicedesc.
push @perfs, {%p};
}
}
data2rrd(@perfs) if ( $#perfs >= 0 );
@perfs = ();
} else {
#
# Normal Performance Data
#
print_log( "DEBUG: Normal perfdata", 3 );
my $count = 0;
while ($perfstring) {
( $perfstring, %p ) = _parse($perfstring);
if ( !$p{label} ) {
print_log( "Invalid Perfdata detected ", 1 );
@perfs = ();
last;
}
%CTPL = adjust_template( $NAGIOS{CHECK_COMMAND}, $p{uom}, $count );
if ( $CTPL{'USE_MAX_ON_CREATE'} == 1 && defined $p{max} ) {
$p{rrd_max} = $p{max};
} else {
$p{rrd_max} = "U";
}
if ( $CTPL{'USE_MIN_ON_CREATE'} == 1 && defined $p{min} ) {
$p{rrd_min} = $p{min};
} elsif ( $CTPL{'DSTYPE'} eq 'DERIVE' ){
$p{rrd_min} = 0; # Add minimum value 0 if DSTYPE = DERIVE
} else {
$p{rrd_min} = "U";
}
$p{template} = $CTPL{'TEMPLATE'};
$p{dstype} = $CTPL{'DSTYPE'};
$p{rrd_storage_type} = $conf{'RRD_STORAGE_TYPE'};
$p{rrd_heartbeat} = $CTPL{'RRD_HEARTBEAT'};
$p{multi} = $is_multi;
$p{hostname} = cleanup( $NAGIOS{HOSTNAME} );
$p{disp_hostname} = $NAGIOS{DISP_HOSTNAME};
$p{auth_hostname} = $NAGIOS{DISP_HOSTNAME};
$p{servicedesc} = cleanup( $NAGIOS{SERVICEDESC} );
$p{disp_servicedesc} = $NAGIOS{DISP_SERVICEDESC};
$p{auth_servicedesc} = $NAGIOS{DISP_SERVICEDESC};
$p{timet} = $NAGIOS{TIMET};
$p{combine_datasource} = $conf{'COMBINE_DATASOURCE'};
push @perfs, {%p};
$count++;
}
data2rrd(@perfs) if ( $#perfs >= 0 );
@perfs = ();
}
}
#
# Write to Logfile
#
sub print_log {
my $out = shift;
my $severity = shift;
if ( $severity <= $conf{LOG_LEVEL} ) {
open( LOG, ">>" . $conf{LOG_FILE} ) || die "Can't open logfile ($conf{LOG_FILE}) ", $!;
if ( -s LOG > $conf{LOG_FILE_MAX_SIZE} ) {
truncate( LOG, 0 );
printf( LOG "File truncated" );
}
my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) = localtime(time);
printf( LOG "%02d-%02d-%02d %02d:%02d:%02d [%d] [%d] %s\n", $year + 1900, $mon + 1, $mday, $hour, $min, $sec, $$, $severity, $out );
close(LOG);
}
}
#
# Signals and Handlers
#
sub init_signals {
$SIG{'INT'} = \&handle_signal;
$SIG{'QUIT'} = \&handle_signal;
$SIG{'ALRM'} = \&handle_signal;
$SIG{'ILL'} = \&handle_signal;
$SIG{'ABRT'} = \&handle_signal;
$SIG{'FPE'} = \&handle_signal;
$SIG{'SEGV'} = \&handle_signal;
$SIG{'TERM'} = \&handle_signal;
$SIG{'BUS'} = \&handle_signal;
$SIG{'SYS'} = \&handle_signal;
$SIG{'XCPU'} = \&handle_signal;
$SIG{'XFSZ'} = \&handle_signal;
$SIG{'IOT'} = \&handle_signal;
$SIG{'PIPE'} = \&handle_signal;
$SIG{'HUP'} = \&handle_signal;
$SIG{'CHLD'} = \&handle_signal;
}
#
# Handle Signals
#
sub handle_signal {
my ($signal) = (@_);
#
# Gearman child process
#
if ( defined ( $opt_gm ) ){
if($signal eq "CHLD" && defined($opt_gm) ){
my $pid = waitpid(-1, &WNOHANG);
if($pid == -1){
print_log( "### no hanging child ###", 1 );
} elsif ( WIFEXITED($?)) {
$children_num--;
print_log( "### child $pid exited ###, children left: $children_num", 1 );
} else {
$children_num--;
print_log( "### wrong signal ###, children left: $children_num", 1 );
}
$SIG{'CHLD'} = \&handle_signal;
}
if($signal eq "INT" || $signal eq "TERM"){
local($SIG{CHLD}) = 'IGNORE'; # we're going to kill our children
kill $signal => keys %children;
print_log( "*** process_perfdata.pl terminated on signal $signal", 0 );
pidlock("remove");
exit; # clean up with dignity
}
print_log( "*** process_perfdata.pl received signal $signal (ignored)", 0 );
}else{
if ( $signal eq "ALRM" ) {
print_log( "*** TIMEOUT: Timeout after $opt_t secs. ***", 0 );
if ( $opt_b && !$opt_n ) {
print_log( "*** TIMEOUT: Deleting current file to avoid loops", 0 );
print_log( "*** TIMEOUT: Please check your process_perfdata.cfg", 0 );
}
elsif ( $opt_b && $opt_n ) {
print_log( "*** TIMEOUT: Deleting current file to avoid NPCD loops", 0 );
print_log( "*** TIMEOUT: Please check your process_perfdata.cfg", 0 );
}
if ($opt_b) {
my $pdfile = "$opt_b" . "-PID-" . $$;
if ( unlink("$pdfile") == 1 ) {
print_log( "*** TIMEOUT: $pdfile deleted", 0 );
}
else {
print_log( "*** TIMEOUT: Could not delete $pdfile:$!", 0 );
}
}
my $temp_file = "$conf{RRDPATH}/$NAGIOS{HOSTNAME}/$NAGIOS{SERVICEDESC}.xml.$$";
if ( -f $temp_file ) {
unlink($temp_file);
}
$t1 = [gettimeofday];
$rt = tv_interval $t0, $t1;
$stats{runtime} = $rt;
print_log( "*** Timeout while processing Host: \"$NAGIOS{HOSTNAME}\" Service: \"$NAGIOS{SERVICEDESC}\"", 0 );
print_log( "*** process_perfdata.pl terminated on signal $signal", 0 );
exit 7;
}
}
}
sub init_stats {
%stats = (
timet => time,
error => 0,
invalid => 0,
skipped => 0,
runtime => 0,
rows => 0,
create => 0,
update => 0,
);
}
#
# Store some internal runtime infos
#
sub store_internals {
if( ! -w $conf{'STATS_DIR'}){
print_log("*** ERROR: ".$conf{'STATS_DIR'}." is not writable or does not exist",0);
return;
}
my $statsfile = $conf{'STATS_DIR'}."/".(int $stats{timet} / 60);
open( STAT, ">> $statsfile" ) or die "Cant create statistic file ", $!;
printf(STAT "%d %f %d %d %d %d %d %d\n", $stats{timet},$stats{runtime},$stats{rows},$stats{update},$stats{create},$stats{error},$stats{invalid},$stats{skipped});
close(STAT);
check_internals();
}
#
# Search for statistic files
#
sub check_internals {
my $file;
my @files;
opendir(STATS, $conf{'STATS_DIR'});
while ( defined ( my $file = readdir STATS) ){
next if $file =~ /^\.\.?$/; # skip . and ..
next if $file =~ /-PID-/; # skip temporary files
next if $file == (int $stats{timet} / 60); # skip our current file
push @files, $file;
}
read_internals(@files);
}
#
# Read and aggregate files found by check_internals()
#
sub read_internals {
my @files = @_;
my @chunks;
foreach my $file (sort { $a <=> $b} @files){
unless ( rename($conf{'STATS_DIR'}."/".$file, $conf{'STATS_DIR'}."/".$file."-PID-".$$) ){
print_log( "ERROR: renaming stats file " . $conf{'STATS_DIR'}."/".$file . " to " . $conf{'STATS_DIR'}."/".$file."-PID-".$$ . " failed", 1 );
next;
}
open( STAT, "< ".$conf{'STATS_DIR'}."/".$file."-PID-".$$ );
%stats = (
timet => 0,
error => 0,
invalid => 0,
skipped => 0,
runtime => 0,
rows => 0,
create => 0,
update => 0,
);
while(<STAT>){
@chunks = split();
$stats{timet} = $chunks[0];
$stats{runtime} += $chunks[1];
$stats{rows} += $chunks[2];
$stats{update} += $chunks[3];
$stats{create} += $chunks[4];
$stats{error} += $chunks[5];
$stats{invalid} += $chunks[6];
$stats{skipped} += $chunks[7];
}
close(STAT);
unlink($conf{'STATS_DIR'}."/".$file."-PID-".$$);
process_internals();
}
}
#
#
#
sub process_internals {
my $last_rrd_dtorage_type = $conf{'RRD_STORAGE_TYPE'};
$conf{'RRD_STORAGE_TYPE'} = "MULTIPLE";
%NAGIOS = (
HOSTNAME => '.pnp-internal',
DISP_HOSTNAME => 'pnp-internal',
SERVICEDESC => 'runtime',
DISP_SERVICEDESC => 'runtime',
TIMET => $stats{timet},
DATATYPE => 'SERVICEPERFDATA',
CHECK_COMMAND => 'pnp-runtime',
PERFDATA => "runtime=".$stats{runtime}."s rows=".$stats{rows}." errors=".$stats{error}." invalid=".$stats{invalid}." skipped=".$stats{skipped} ." update=".$stats{update}. " create=".$stats{create}
);
parse_perfstring( $NAGIOS{PERFDATA} );
$conf{'RRD_STORAGE_TYPE'} = $last_rrd_dtorage_type;
}
#
# Gearman Worker Daemon
#
sub daemonize {
if( defined($opt_daemon) ){
print_log("daemonize init",1);
chdir '/' or die "Can't chdir to /: $!";
open STDIN, '/dev/null' or die "Can't read /dev/null: $!";
open STDOUT, '>>/dev/null' or die "Can't write to /dev/null: $!";
open STDERR, '>>/dev/null' or die "Can't write to /dev/null: $!";
defined( my $pid = fork ) or die "Can't fork: $!";
exit if $pid;
pidlock("create");
setsid or die "Can't start a new session: $!";
} else {
pidlock("create");
}
# Fork off our children.
for (1 .. $conf{'PREFORK'}) {
new_child();
print_log( "starting child process $children_num", 1 );
}
while (1) {
sleep; # wait for a signal (i.e., child's death)
for (my $i = $children_num; $i < $conf{'PREFORK'}; $i++) {
new_child(); # top up the child pool
print_log("starting new child (running = $i)",1);
}
}
return;
}
#
# start a new worker process
#
sub new_child {
my $pid;
my $sigset;
my $req = 0;
# block signal for fork
$sigset = POSIX::SigSet->new(SIGINT);
sigprocmask(SIG_BLOCK, $sigset)
or die "Can't block SIGINT for fork: $!\n";
die "fork: $!" unless defined ($pid = fork);
if ($pid) {
# Parent records the child's birth and returns.
sigprocmask(SIG_UNBLOCK, $sigset)
or die "Can't unblock SIGINT for fork: $!\n";
$children{$pid} = 1;
$children_num++;
return;
} else {
# Child can *not* return from this subroutine.
$SIG{INT} = 'DEFAULT'; # make SIGINT kill us as it did before
# unblock signals
sigprocmask(SIG_UNBLOCK, $sigset)
or die "Can't unblock SIGINT for fork: $!\n";
my $worker = Gearman::Worker->new();
my @job_servers = split(/,/, $conf{'GEARMAN_HOST'}); # allow multiple gearman job servers
$worker->job_servers(@job_servers);
$worker->register_function("perfdata", 2, sub { return main(@_); });
my %opt = (
on_complete => sub { $req++; },
stop_if => sub { if ( $req > $conf{'REQUESTS_PER_CHILD'} ) { return 1;}; },
);
print_log("connecting to gearmand '".$conf{'GEARMAN_HOST'}."'",0);
$worker->work( %opt ) while 1;
print_log("max requests per child reached (".$conf{'REQUESTS_PER_CHILD'}.")",1);
# this exit is VERY important, otherwise the child will become
# a producer of more and more children, forking yourself into
# process death.
exit;
}
}
#
# Create a pid file
#
sub pidlock {
return unless defined $opt_pidfile;
my $action = shift;
my $PIDFILE = $opt_pidfile;
if($action eq "create"){
if ( -e $PIDFILE ) {
if ( open( OLDPID, "<$PIDFILE" ) ) {
$_ = <OLDPID>;
chop($_);
my $oldpid = $_;
close(OLDPID);
if ( -e "/proc/$oldpid/cmdline" ) {
print_log("Another instance is already running with PID: $oldpid",0);
exit 0;
} else {
print_log("Pidfile $PIDFILE seems to be stale!",0);
print_log("Removing old pidfile",0);
unlink $PIDFILE;
}
}
}
if ( !open( PID, ">$PIDFILE" ) ) {
print_log("Can not create $PIDFILE ( $! )",0);
exit 1;
}
print( PID "$$\n" );
close(PID);
print_log("Pidfile ($PIDFILE) created",0);
}elsif( $action eq "remove" ){
if ( -e $PIDFILE ) {
print_log("Removing pidfile ($PIDFILE)",0);
unlink $PIDFILE;
}
}
}
#
# Read crypt key
#
sub read_keyfile {
my $file = shift;
my $key = '';
if( -r $file){
open(FH, "<", $file);
while(<FH>){
chomp(); # avoid \n on last field
$conf{'KEY'} = $_;
last;
}
close(FH);
print_log("Using encryption key specified in '$file'",0);
return 1;
}else{
print_log("Using encryption key specified in ".$conf{'CFG_DIR'}."/process_perfdata.cfg",0);
return 0;
}
}
#
#
#
sub print_help {
print <<EOD;
Copyright (c) 2005-2010 Joerg Linge <pitchfork\@pnp4nagios.org>
Use process_perfdata.pl to store Nagios Plugin Performance Data into RRD Databases
Options:
-h, --help
Print detailed help screen
-V, --version
Print version information
-t, --timeout=INTEGER
Seconds before process_perfdata.pl times out (default: $opt_t_default)
-i, --inetd
Use this Option if process_perfdata.pl is executed by inetd/xinetd.
-d, --datatype
Defaults to \"SERVICEPERFDATA\". Use \"HOSTPERFDATA\" to process Perfdata from regular Host Checks
Only used in default or inetd mode
-b, --bulk
Provide a file for bulk update
-n, --npcd
Hint the program, that it was invoked by NPCD
-c, --config
Optional process_perfdata config file
Default: /usr/local/pnp4nagios/etc/process_perfdata.cfg
Gearman Worker Options:
--gearman
Start in Gearman worker mode
--daemon
Run as daemon
--pidfile=/var/run/process_perfdata.pid
The pidfile used while running in as Gearman worker daemon
EOD
exit 0;
}
#
#
#
sub print_version {
print "Version: process_perfdata.pl $const{VERSION}\n";
print "Copyright (c) 2005-2010 Joerg Linge <pitchfork\@pnp4nagios.org>\n";
exit 0;
}
sub read_xml_template {
my $xmlfile = shift;
if(! -e $xmlfile ){
return ();
}
my $xs = XML::Simple->new();
my $xml = $xs->XMLin($xmlfile, ForceArray=>["DATASOURCE"]);
my $ds = $xml->{'DATASOURCE'};
#foreach my $data (@$ds) {
# print $data->{'NAME'}."\t".$data->{'RRDFILE'}."\n";
#}
return @$ds;
}
sub remove_dup_ds {
my $name = shift;
my $ds_array = shift;
my $i = 0;
foreach my $data (@$ds_array){
if ($data->{'NAME'} eq $name ){
splice @$ds_array, $i, 1;
last;
}
$i++;
}
return @$ds_array;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment