#!@PERL@ # nagios: -epn ## @PKG_NAME@–@PKG_VERSION@ ## Copyright (c) 2005-2015 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. @PERL_LIB_PATH_CODE@ 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 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 => "@sysconfdir@/", USE_RRDs => 1, RRDPATH => "@PERFDATA_DIR@", RRDTOOL => "@RRDTOOL@", RRD_STORAGE_TYPE => "SINGLE", RRD_HEARTBEAT => 8640, RRA_STEP => 60, RRA_CFG => "@sysconfdir@/rra.cfg", STATS_DIR => "@localstatedir@/stats", LOG_FILE => "@PERFDATA_LOG@", LOG_FILE_MAX_SIZE => "10485760", #Truncate after 10MB LOG_LEVEL => @DEBUG@, 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 => '@sysconfdir@/secret.key', UOM2TYPE => { 'c' => 'DERIVE', 'd' => 'DERIVE' }, ); my %const = ( XML_STRUCTURE_VERSION => "@XML_STRUCTURE_VERSION@", VERSION => "@PKG_VERSION@", ); # # 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_s, $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, "s" => \$opt_s, "stdin" => \$opt_s, "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 = 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 && !$opt_s ) { # 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 && !$opt_s ) { # 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(); } elsif ( $opt_s ) { # STDIN mode alarm($opt_t); print_log( "starting in STDIN Mode", 1 ); $lines = process_perfdata_stdin(); } 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) || defined($opt_s) ){ # Bulk Mode/Stdin 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 && !$opt_s ) { 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} =~ s/\\n//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 () { 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 ); } } # # Process Perfdata in STDIN Mode # sub process_perfdata_stdin { print_log( "reading from STDIN for bulk update", 2 ); my $count = 0; while () { my $job_data = $_; $count++; print_log( "Processing Line $count", 2 ); my @LINE = split(/\t/); %NAGIOS = (); # cleaning %NAGIOS Hash 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 ); return $count; } # # 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; } } # # 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 = ""; } 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(); } 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.", 2 ); 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 "\n"; print XML "\n"; } # # Close Template FH # sub close_template { return if $delayed_write == 1; my $xmlfile = shift; printf( XML " \n" ); printf( XML " %d\n", $const{'XML_STRUCTURE_VERSION'} ); printf( XML " \n" ); printf( XML "\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 " \n" ); printf( XML " <%s>%s\n", $tag, "$data", $tag ); } elsif ( $tag =~ /^MAX$/ ) { printf( XML " <%s>%s\n", $tag, "$data", $tag ); printf( XML " \n" ); } else { printf( XML " <%s>%s\n", $tag, "$data", $tag ); } } sub write_state_to_template { return if $delayed_write == 1; my @rrd_state = @_; printf( XML " \n" ); printf( XML " %s\n", $rrd_state[0] ); printf( XML " %s\n", $rrd_state[1] ); printf( XML " \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 " %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'}; 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 () { 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 () { $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 () { 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}; my $seen_multi_label = ""; while ($perfstring) { ( $perfstring, %p ) = _parse($perfstring); if ( !$p{label} ) { print_log( "Invalid Perfdata detected ", 1 ); $stats{invalid}++; @perfs = (); last; } if ( $p{label} =~ /$seen_multi_label/ ) { # multi label format for each perfdata item (e.g Icinga2) # we're in a sub tree of a multi block, adjust label for further processing my $tmp_prefix = $seen_multi_label."::"; $p{label} =~ s/$tmp_prefix//; } if ( $p{label} =~ /^[']?([a-zA-Z0-9\.\-_\s\/\#]+)::([a-zA-Z0-9\.\-_\s]+)::([^=]+)[']?$/ ) { @multi = ( $1, $2, $3 ); $seen_multi_label = $multi[0]."::".$multi[1]; 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} = $CTPL{'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}; 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($?)) { print_log( "### child $pid exited ###", 1 ); $children--; } else { print_log( "### wrong signal ###", 1 ); $children--; } $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 && !$opt_s ) { 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 && !$opt_s ) { print_log( "*** TIMEOUT: Deleting current file to avoid NPCD loops", 0 ); print_log( "*** TIMEOUT: Please check your process_perfdata.cfg", 0 ); } if ($opt_b && !$opt_s ) { 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(){ @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", 1 ); } while (1) { sleep; # wait for a signal (i.e., child's death) for (my $i = $children; $i < $conf{'PREFORK'}; $i++) { print_log("starting new child (running = $i)",1); new_child(); # top up the child pool } } 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++; 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 ); 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" ) ) { $_ = ; 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(){ 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 < 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 -s, --stdin Read input from stdin -n, --npcd Hint the program, that it was invoked by NPCD -c, --config Optional process_perfdata config file Default: @sysconfdir@/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 \n"; exit 0; }