pnp4nagios/scripts/rrd_convert.pl.in

614 lines
15 KiB
Perl
Raw Permalink Normal View History

2017-05-20 15:26:21 +02:00
#!@PERL@
## @PKG_NAME@@PKG_VERSION@ rrd_convert.pl
2017-05-20 15:29:39 +02:00
## Copyright (c) 2006-2015 Joerg Linge (http://www.pnp4nagios.org)
2017-05-20 15:26:21 +02:00
##
## 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@
use strict;
use warnings;
use Getopt::Long;
use Time::HiRes qw(gettimeofday tv_interval);
use File::Find;
use File::Copy;
if( $< == 0 ){
print "dont try this as root \n";
exit 1;
}
#
# Some global Vars
#
my %conf = (
CFG_DIR => "@sysconfdir@/",
RRDPATH => "@PERFDATA_DIR@",
RRDTOOL => "@RRDTOOL@",
LOG_LEVEL => 0,
DRY_RUN => 0,
FORCE => 0,
RRD_BACKUP => 1,
RRD_STORAGE_TYPE => "SINGLE",
TMP_DIR => '/tmp/rrd_convert',
RRD_DAEMON_OPTS => "",
XML_MAX_AGE => 3600,
);
Getopt::Long::Configure('bundling');
2017-10-20 17:10:51 +02:00
my ( $opt_V, $opt_h, $opt_c, $opt_l, $opt_x, $opt_p, $opt_s, $opt_o, $opt_r );
2017-05-20 15:26:21 +02:00
# defaults
$opt_x = 1;
GetOptions(
"V|version" => \$opt_V,
"h|help" => \$opt_h,
"c|check_command=s" => \$opt_c,
"p|cfg_dir=s" => \$opt_p,
"l|list_commands" => \$opt_l,
2017-10-20 17:10:51 +02:00
"s|stepwise" => \$opt_s,
2017-05-20 15:26:21 +02:00
"x|no_structure_check" => \$opt_x,
2017-10-20 17:10:51 +02:00
"o|log_old_xml" => \$opt_o,
2017-05-20 15:26:21 +02:00
"d|dry-run" => \$conf{DRY_RUN},
"t|tmp_dir=s" => \$conf{TMP_DIR},
"force" => \$conf{FORCE},
2017-10-20 17:10:51 +02:00
"r|read=s" => \$opt_r,
2017-05-20 15:26:21 +02:00
);
2017-10-20 17:10:51 +02:00
if (defined($opt_r)) {
$opt_c = "ALL";
}
2017-05-20 15:26:21 +02:00
print_help() if $opt_h;
print_help_opt_p() if !$opt_p;
2017-10-20 17:10:51 +02:00
print_help() if (!$opt_c and !$opt_l) and (!$opt_r);
2017-05-20 15:26:21 +02:00
print_version() if $opt_V;
if($opt_p){
$conf{CFG_DIR} = $opt_p;
}
parse_config($conf{CFG_DIR}."/process_perfdata.cfg");
if ( $conf{RRD_DAEMON_OPTS} ){
$conf{RRD_DAEMON_OPTS} = "--daemon=".$conf{RRD_DAEMON_OPTS};
}
my @STRUCT;
my %FILEHANDLE;
my @commands; # list of commands
my @worklist; # list of found xml files
my %ds_list;
my %original_ds_list;
my $max_age = time() - $conf{XML_MAX_AGE};
my %stats = (
'rrd_in' => 0,
'rrd_out' => 0,
'old_xml' => 0,
'xml_without_rrd' => 0,
'runtime' => 0,
);
main();
sub main{
check_storage_type();
2017-10-20 17:10:51 +02:00
if ($opt_o) {
create_dir($conf{TMP_DIR});
open(OLDXMLLOG, ">", $conf{TMP_DIR}."/pnp_old_xml_files.list");
print OLDXMLLOG "- generated by rrd_convert.pl - \n";
print OLDXMLLOG "The following XML files are older than $conf{XML_MAX_AGE} seconds:\n\n";
}
build_worklist();
close(OLDXMLLOG);
2017-05-20 15:26:21 +02:00
summary();
if($opt_l){ # List commands and exit
summary_command_list();
exit;
}
if($#worklist+1 > 0 ){
2017-10-20 17:10:51 +02:00
my $question = "Start converter? " . ($opt_s ? "(Each conversion will be prompted.) " : "");
my $answer = read_choice($question . "[n|y]");
2017-05-20 15:26:21 +02:00
unless ( $answer =~ m/^y$/i ){
print "Exit...\n";
exit;
}
}else{
print "Check Command '".$opt_c."' not found in any XML File\n";
print "\n";
print "\n";
summary_command_list();
exit;
}
check_custom_template();
write_custom_template();
my $t0 = [gettimeofday];
my $i = 0;
2017-10-20 17:10:51 +02:00
my $answered = "";
2017-05-20 15:26:21 +02:00
foreach my $xmlfile ( @worklist ) {
2017-10-20 17:10:51 +02:00
if($opt_s && ($answered !~ $xmlfile)){
my $answer = read_choice("Continue with $xmlfile [n|y]?");
unless ( $answer =~ m/^y$/i ){
print "Exit... (remember to remove the custom template, if created in this run!)\n";
exit;
}
$answered = $xmlfile;
}
2017-05-20 15:26:21 +02:00
$i++;
undef %ds_list;
undef %original_ds_list;
my($host,$service) = parse_xml_filename($xmlfile);
my ($rrdfile) = $xmlfile =~ /^(.*)\.xml$/;
$rrdfile .= ".rrd";
if(-r $rrdfile){
create_dir($conf{TMP_DIR});
my $dumpfile = sprintf("%s/%s-%s.dump",$conf{TMP_DIR},$host,$service);
print "File ".$i."/".($#worklist+1)."\n";
rrdtool_dump($rrdfile,$dumpfile);
parse_pnp_xml($xmlfile);
build_ds_list($rrdfile);
next if check_ds_list();
open_files($host,$service);
manipulate_rrd_dump($dumpfile);
close_files();
restore_files($host,$service);
backup_rrd_file($rrdfile);
}
}
my $t1 = [gettimeofday];
$stats{runtime} = tv_interval $t0, $t1;
print "DONE\n";
stats();
}
sub build_ds_list{
my $rrdfile = shift;
my @info;
@info = `$conf{'RRDTOOL'} info $rrdfile`;
if( $? > 0 ){
print "ERROR: $conf{'RRDTOOL'} info $rrdfile returns with $?\n";
exit 1;
}
foreach(@info){
if(m/ds\[(\d+)\]\.type/ ) {
$ds_list{$1} = $1;
}
}
my $test = keys %ds_list;
%original_ds_list = %ds_list;
}
sub check_ds_list{
my $rrd_ds_count = keys %ds_list;
my $xml_ds_count = $#STRUCT;
if($rrd_ds_count == $xml_ds_count){
return 0;
}elsif($rrd_ds_count <= $xml_ds_count && $opt_x){
printf("OK: RRD contains '%s' DS but XML contains '%s'. Convert forced by --no_structure_check\n",$rrd_ds_count,$xml_ds_count);
return 0;
}else{
printf ("ERROR: RRD Structure mismatch. DS Count is '%s' but should be '%s'\n",$rrd_ds_count,$xml_ds_count);
return 1;
}
}
2017-10-20 17:10:51 +02:00
sub build_worklist {
if ($opt_r) {
process_xml_files($opt_r);
close XMLLIST;
}else{
find(\&find_xml_files, $conf{RRDPATH});
}
}
# bulk mode, find all XML files
sub find_xml_files{
2017-05-20 15:26:21 +02:00
if(m/.xml$/){
#printf("File: %s\n",$File::Find::name);
2017-10-20 17:10:51 +02:00
xml2worklist($File::Find::name);
}
}
# file mode, process file with XML filenames
sub process_xml_files{
my $xmllist = shift;
-r $xmllist or die "Cannot open $xmllist: $!";
open XMLLIST, "<$opt_r";
foreach (<XMLLIST>) {
my $file = $_;
chomp($file);
m/.xml$/ && xml2worklist($file);
}
}
sub xml2worklist {
my $xmlfile = shift;
my ($rrdfile) = $xmlfile =~ /^(.*)\.xml$/;
$rrdfile .= ".rrd";
my $mtime = (stat($xmlfile))[9];
if ( $mtime < $max_age ){
$stats{old_xml}++;
if ($opt_o) {
print OLDXMLLOG $xmlfile . ": " . scalar localtime($mtime) ."\n";
2017-05-20 15:26:21 +02:00
}
2017-10-20 17:10:51 +02:00
return;
}
open(XML, $xmlfile);
while (<XML>) {
if(/TEMPLATE>(.*)</){
my ($t) = split("!",$1);
push(@commands,$t);
if(( defined $opt_c) and ($t =~ /^$opt_c$/)){
if( -e $rrdfile ){
#print "Found: ".$t." in ".$xmlfile."\n";
push(@worklist,$xmlfile);
}else{
$stats{xml_without_rrd}++;
2017-05-20 15:26:21 +02:00
}
2017-10-20 17:10:51 +02:00
}elsif (( defined $opt_c) and ($opt_c eq 'ALL') ) {
if( -e $rrdfile ){
# Keyword 'ALL' retunrs all XML Files
push(@worklist,$xmlfile);
}else{
$stats{xml_without_rrd}++;
}
}
}
2017-05-20 15:26:21 +02:00
}
2017-10-20 17:10:51 +02:00
close(XML);
2017-05-20 15:26:21 +02:00
}
sub parse_xml_filename{
my $xmlfile = shift;
$_ = $xmlfile;
if( m/([^\/]+)\/([^\/]+)\.xml$/i ){
return ($1, $2);
}
}
sub summary{
my %seen;
my @uniqed = grep !$seen{$_}++, @commands;
print "\n";
printf "%-40s %s\n" ,"Search pattern",$opt_c if( defined ($opt_c) );
printf "%-40s %s\n" ,"XML Files analyzed",$#commands+1;
printf "%-40s %s\n" ,"XML Files found",$#worklist+1;
printf "%-40s %s\n" ,"XML Files without RRD",$stats{'xml_without_rrd'};
printf "%-40s %s\n" ,"Old XML Files ignored",$stats{'old_xml'};
printf "%-40s %s\n" ,"Number of unique check_commands",$#uniqed+1;
if($conf{DRY_RUN} == 1){
printf "%-40s %s\n" ,"Dry run?","[YES]";
printf "%-40s %s\n" ,"Temp Directory",$conf{TMP_DIR};
print "\n\n";
print "This is only a 'dry run'. The new RRD Files are stored in '$conf{TMP_DIR}'\n";
print "\n";
}
}
sub summary_command_list{
my %seen;
my @uniqed = grep !$seen{$_}++, @commands;
printf "\\ List of Check Commands\n";
foreach my $key (sort { $seen{$b} <=> $seen{$a} } keys %seen ) {
printf " |- %-36s %5s\n",$key,$seen{$key};
}
}
sub stats{
print "\n\n \\Statistics:\n";
foreach my $key (sort { $stats{$b} cmp $stats{$a} } keys %stats ) {
printf " |- %-15s %s\n",$key,$stats{$key};
}
}
sub create_dir{
my $dir = shift;
unless ( -d "$dir" ) {
unless ( mkdir "$dir" ) {
print "ERROR: $dir is not writable\n";
exit 1;
}
}
}
sub open_files(){
my $host = shift;
my $service = shift;
foreach my $ds (keys %ds_list){
my $file = sprintf("%s/%s-%s-%s.restore",$conf{TMP_DIR},$host,$service,$STRUCT[$ds]{NAME});
#print "Open Filehandle ".$file."\n";
open($FILEHANDLE{$ds}, ">", $file);
}
}
sub close_files(){
foreach my $ds (keys %ds_list){
#$ds--;
#print "Close Filehandle ".$STRUCT[$ds]{NAME}."\n";
close($FILEHANDLE{$ds});
}
}
sub write_to_files{
my $data = shift;
foreach my $ds (keys %ds_list){
print { $FILEHANDLE{$ds} } $data;
}
}
sub restore_files(){
my $host = shift;
my $service = shift;
my $err;
$| = 1;
print "Restoring File\n";
foreach my $ds (keys %ds_list){
my $rrdfile = '';
my $restorefile = sprintf("%s/%s-%s-%s.restore",$conf{TMP_DIR},$host,$service,$STRUCT[$ds]{NAME});
if( $conf{'DRY_RUN'} == 1 ){
$rrdfile = sprintf("%s/%s/%s_%s.rrd",$conf{TMP_DIR},$host,$service,$STRUCT[$ds]{NAME});
create_dir(sprintf("%s/%s", $conf{TMP_DIR},$host ));
}else{
$rrdfile = sprintf("%s/%s/%s_%s.rrd",$conf{RRDPATH},$host,$service,$STRUCT[$ds]{NAME});
}
print "$rrdfile\n";
$err = system("$conf{'RRDTOOL'} restore -f '$restorefile' '$rrdfile'");
if($err){
printf("RRDtool Error: %s\n",$err);
exit;
}
unlink($restorefile);
$stats{rrd_out}++;
}
print "... done\n";
$| = 0;
}
sub backup_rrd_file{
my $rrdfile = shift;
2017-10-20 17:10:51 +02:00
if ( $conf{RRD_BACKUP} == 1 && $conf{'DRY_RUN'} == 0 ){
2017-05-20 15:26:21 +02:00
move($rrdfile, $rrdfile.".backup");
}
}
sub parse_pnp_xml{
my $xmlfile = shift;
undef @STRUCT;
#print "reading $xmlfile\n";
open(XML, $xmlfile);
my $DATASOURCE = 0;
while (<XML>) {
if(/<DATASOURCE>/){
$DATASOURCE++;
}
if(/<RRD>/){
$DATASOURCE = 0;
}
if(/<([A-Z_]+)>(.*)<\/[A-Z_]+>/ && $DATASOURCE != -1){
$STRUCT[$DATASOURCE]{$1} = $2;
}
}
close(XML);
return @STRUCT;
}
sub rrdtool_dump{
my $rrdfile = shift;
my $dumpfile = shift;
my $err;
print "RRDtool dump to $dumpfile\n";
if ( $conf{RRD_DAEMON_OPTS} ){
$err = system("$conf{'RRDTOOL'} dump $conf{RRD_DAEMON_OPTS} $rrdfile > $dumpfile");
}else{
$err = system("$conf{'RRDTOOL'} dump $rrdfile > $dumpfile");
}
if($err){
printf("RRDtool Error: %s\n",$err);
exit;
}
$stats{rrd_in}++;
return $dumpfile;
}
sub manipulate_rrd_dump{
my $tmpfile = shift;
my $i = 0;
open (XML,$tmpfile);
my @ROW = ();
my $tmpds = 1;
my $inside_ds_block = 0;
print "Manipulating $tmpfile\n";
while (<XML>){
$i++;
unless ( $i % 5000 ){
$| = 1; print "."; $| = 0;
}
my $d = $_;
#
# A Data Row
if(m/<row>/){
m/(.*<row>)/;
my $rowstart = $1;
@ROW = m{<v>([^<].*?)<\/v>}gc;
my $fh = 1;
foreach my $VAL (@ROW){
undef %ds_list;
$ds_list{$fh} = $fh;
write_to_files($rowstart."<v>".$VAL."</v></row>\n");
$fh++;
}
next;
}
if(m/<ds>/){
$inside_ds_block = 1;
undef %ds_list;
$ds_list{$tmpds} = $tmpds;
write_to_files($d);
$tmpds++;
next;
}
if(m/<cdp_prep>/){
write_to_files($d);
$inside_ds_block = 0;
$tmpds = 1;
%ds_list = %original_ds_list;
next;
}
if(m/<\/ds>/){
write_to_files($d);
$inside_ds_block = 0;
# write to all files alter </ds>
%ds_list = %original_ds_list;
next;
}
if(m/<\/database>/){
# write to all files alter </database>
%ds_list = %original_ds_list;
write_to_files($d);
next;
}
if($inside_ds_block == 1){
# rename DS
$d =~ s/<name>(.*)<\/name>/<name> 1 <\/name>/;
}
write_to_files($d);
}
close(XML);
print "... done $i lines\n";
unlink($tmpfile);
}
#
# Parse process_perfdata.cfg
#
sub parse_config {
my $config_file = shift;
my $line = 0;
if( ! -e $config_file ){
print "$config_file not found\n";
exit;
}
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;
}
}
#
# Change RRD_STORAGE_TYPE to MULTIPLE
#
sub change_config {
my $cfg_file = $conf{'CFG_DIR'}."/process_perfdata.cfg";
my $error = system("sed -i -e ".'s/\s*RRD_STORAGE_TYPE\s*=\s*SINGLE/RRD_STORAGE_TYPE=MULTIPLE/i'." $cfg_file");
}
sub check_storage_type{
if($conf{'RRD_STORAGE_TYPE'} eq "MULTIPLE"){
2017-10-20 17:10:51 +02:00
print "RRD_STORAGE_TYPE is already globally set to ".$conf{'RRD_STORAGE_TYPE'}."\n";
2017-05-20 15:26:21 +02:00
}
}
sub check_custom_template {
my $command = $opt_c;
if ( $conf{DRY_RUN} == 1 ){
print "No config check while DRY_RUN = 1\n";
return;
}
if ( $command eq "ALL" ){
return;
}
my $config_file = $conf{'CFG_DIR'}."/check_commands/".$command.".cfg";
my $storage_type = "MULTIPLE";
if ( -e $config_file && $conf{'FORCE'} == 0 ) {
print "\nConfig for command $command does already exist ($config_file)\n\n";
exit 0;
}
}
sub write_custom_template {
2017-10-20 17:10:51 +02:00
# do not write custom template in --read mode
return if $opt_r;
2017-05-20 15:26:21 +02:00
my $command = $opt_c;
if ( $conf{DRY_RUN} == 1 ){
print "No config check while DRY_RUN = 1\n";
return;
}
if ( $opt_c eq "ALL"){
change_config();
return;
}
my $config_file = $conf{'CFG_DIR'}."/check_commands/".$command.".cfg";
my $storage_type = "MULTIPLE";
open(CFG, ">", $config_file);
print CFG "# Generated by rrd_convert.pl @PKG_VERSION@\n";
print CFG "RRD_STORAGE_TYPE = MULTIPLE\n";
close(CFG);
if ( -s $config_file ) {
print "\nConfig for command $command created ($config_file)\n";
}
}
sub read_choice{
my $question = shift;
print $question.":";
my $answer = <STDIN>;
chomp $answer;
return $answer;
}
sub print_help{
print "Usage: $0 --check_command=<nagios_check_command>\n";
print " --cfg_dir=<path_to_pnp_etc_dir>\n";
print " [ --list_commands ]\n";
print " [ --dry-run ]\n";
print " [ --tmp_dir=<temp directory> ]\n";
print " [ --no_structure_check ]\n";
2017-10-20 17:10:51 +02:00
print " [ --log_old_xml ]\n";
print " [ --stepwise ]\n";
print " [ --read=<file with XML filenames> ]\n";
2017-05-20 15:26:21 +02:00
print "\n";
2017-10-20 17:10:51 +02:00
print "This script is used to switch to RRD_STORAGE_TYPE = MULTIPLE for all RRDs/a given Nagios Check Command/single RRDs \n";
2017-05-20 15:26:21 +02:00
print "More info online http://docs.pnp4nagios.org/pnp-0.6/rrd_convert\n";
exit;
}
sub print_help_opt_p{
print "\n";
print "--cfg_dir not set\n";
print "Please provide the path to your PNP4Nagios config directory\n";
print "\n";
print_help();
}
sub print_version{
print "Version @PKG_VERSION@\n";
exit;
}