process_perfdata.pl 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. #!/usr/local/bin/perl -w
  2. # author: Al Tobey <albert.tobey@priority-health.com>
  3. # what: process perfdata from Nagios and put it into RRD files
  4. # license: GPL - http://www.fsf.org/licenses/gpl.txt
  5. #
  6. # Todo:
  7. # * more documentation (POD) & comments
  8. # * clean up a bit, make it more configurable - possibly a config file
  9. use strict;
  10. use lib qw( /opt/nagios/libexec );
  11. use utils qw( %ERRORS );
  12. use vars qw( $debug_file %data $debug $rrd_base $process_func $rrd_type );
  13. use RRDs;
  14. $debug_file = undef; #"/var/opt/nagios/perfdata.out";
  15. $rrd_base = '/var/opt/nagios/rrds';
  16. $process_func = \&process_multi;
  17. $rrd_type = 'GAUGE';
  18. $data{hostname} = shift(@ARGV);
  19. $data{metric} = shift(@ARGV);
  20. $data{timestamp} = shift(@ARGV);
  21. $data{perfdata} = join( " ", @ARGV ); $data{perfdata} =~ s/\s+/ /g;
  22. # make sure there's data to work with
  23. exit $ERRORS{OK} if ( !defined($data{hostname}) || !defined($data{metric})
  24. || !defined($data{timestamp}) || !defined($data{perfdata})
  25. || $data{perfdata} eq ' ' || $data{perfdata} eq '' );
  26. if ( defined($debug_file) ) {
  27. open( LOGFILE, ">>$debug_file" );
  28. print LOGFILE "$data{hostname}\t$data{metric}\t$data{timestamp}\t$data{perfdata}\n\n";
  29. }
  30. # make sure host directory exists
  31. if ( !-d "$rrd_base/$data{hostname}" ) {
  32. mkdir( "$rrd_base/$data{hostname}" )
  33. || warn "could not create host directory $rrd_base/$data{hostname}: $!";
  34. }
  35. our $rrd_dir = $rrd_base .'/'. $data{hostname};
  36. # --------------------------------------------------------------------------- #
  37. # do some setup based on the name of the metric
  38. # host data
  39. if ( $data{metric} eq "HOSTCHECK" ) {
  40. $rrd_dir .= '/hostperf';
  41. }
  42. # processing disk information
  43. elsif ( $data{metric} =~ /_DISK$/ ) {
  44. $rrd_dir .= '/disk';
  45. }
  46. # network interface information
  47. elsif ( $data{metric} =~ /^IFACE_/ ) {
  48. $rrd_dir .= '/interfaces';
  49. $rrd_type = [ 'COUNTER', 'COUNTER' ];
  50. }
  51. # process performance statistics
  52. elsif ( $data{metric} =~ /_PROC$/ ) {
  53. $rrd_dir .= '/processes';
  54. $process_func = \&process_single;
  55. $rrd_type = [ 'COUNTER', 'GAUGE' ];
  56. }
  57. # a resonable guess
  58. elsif ( $data{perfdata} =~ /=/ ) {
  59. $process_func = \&process_single;
  60. }
  61. # everything else
  62. else {
  63. $rrd_dir .= '/other';
  64. }
  65. # --------------------------------------------------------------------------- #
  66. # call the proper processing function set up above (functions defined below)
  67. our @processed = ( $process_func->() );
  68. # --------------------------------------------------------------------------- #
  69. if ( !-d $rrd_dir ) {
  70. mkdir( $rrd_dir ) || warn "could not mkdir( $rrd_dir ): $!";
  71. }
  72. foreach my $datum ( @processed ) {
  73. if ( defined($debug_file) ) {
  74. print LOGFILE $datum->{rrd_name}, " = ", join('--',@{$datum->{data}}), "\n"
  75. }
  76. my $rrdfile = $rrd_dir.'/'.$datum->{rrd_name};
  77. # create the RRD file if it doesn't already exist
  78. if ( !-e $rrdfile ) {
  79. # create a non-useful datasource title for each "part"
  80. RRDs::create( $rrdfile, "-b", $data{timestamp}, "-s", 300, $process_func->($datum, 1),
  81. "RRA:AVERAGE:0.5:1:600",
  82. "RRA:MAX:0.5:1:600",
  83. "RRA:AVERAGE:0.5:6:600",
  84. "RRA:MAX:0.5:6:600",
  85. "RRA:AVERAGE:0.5:24:600",
  86. "RRA:MAX:0.5:24:600",
  87. "RRA:AVERAGE:0.5:288:600",
  88. "RRA:MAX:0.5:288:600"
  89. );
  90. if ( my $ERROR = RRDs::error ) { print "ERROR: $ERROR\n"; exit $ERRORS{UNKNOWN}; }
  91. }
  92. else {
  93. # create a template to make sure data goes into the RRD as planned
  94. if ( defined($debug_file) ) {
  95. print LOGFILE "updating $rrdfile with data:\n\t",
  96. join(':', $data{timestamp}, @{$datum->{data}}), "\n";
  97. }
  98. # update the RRD file
  99. RRDs::update( $rrdfile, '-t', $process_func->($datum),
  100. join(':', $data{timestamp}, @{$datum->{data}}) );
  101. if ( my $ERROR = RRDs::error ) { print "ERROR: $ERROR\n"; exit $ERRORS{UNKNOWN}; }
  102. }
  103. }
  104. # --------------------------------------------------------------------------- #
  105. if ( defined($debug_file) ) {
  106. print LOGFILE "-------------------------------------------------------------------------------\n";
  107. close( LOGFILE );
  108. }
  109. exit $ERRORS{OK};
  110. # /opt=value,value,value:/=value,value,value - into multiple rrd's
  111. sub process_multi {
  112. my( $datum, $create ) = @_;
  113. # return a string for creating new RRDs
  114. if ( defined($create) && $create == 1 ) {
  115. my @DS = ();
  116. for ( my $i=0; $i<scalar(@{$datum->{data}}); $i++ ) {
  117. # allow the RRD type to be set in the switch/if above
  118. my $tmp_rrd_type = $rrd_type;
  119. if ( ref($rrd_type) eq 'ARRAY' ) { $tmp_rrd_type = $rrd_type->[$i] }
  120. # put the new datasource into the list
  121. push( @DS, "DS:$data{metric}$i:GAUGE:86400:U:U" );
  122. }
  123. return @DS;
  124. }
  125. # return a template for updating an RRD
  126. elsif ( defined($datum) && !defined($create) ) {
  127. my @template = ();
  128. for ( my $i=0; $i<scalar(@{$datum->{data}}); $i++ ) {
  129. push( @template, $data{metric}.$i );
  130. }
  131. return join( ':', @template );
  132. }
  133. # break the data up into parts for processing (updates and creates)
  134. else {
  135. my @processed = ();
  136. foreach my $part ( split(/:/, $data{perfdata}) ) { # break the line into parts
  137. my @parts = split( /,/, $part ); # break the part into parts
  138. my $rrd_name = $parts[0]; # figure out a good name for the RRD
  139. if ( $parts[0] =~ /^\// ) { # handle /'s in disk names
  140. $rrd_name = $parts[0];
  141. $rrd_name =~ s#/#_#g; $rrd_name =~ s/^_//; $rrd_name =~ s/_$//;
  142. if ( $parts[0] eq '/' ) { $rrd_name = 'root' };
  143. }
  144. # store our munged data in an array of hashes
  145. push( @processed, { rrd_name => $rrd_name, name => shift(@parts), data => [ @parts ] } );
  146. }
  147. return @processed;
  148. }
  149. }
  150. # name=value:name=value - into one rrd
  151. sub process_single {
  152. my( $datum, $create ) = @_;
  153. my( @names, @values ) = ();
  154. foreach my $part ( split(/:/, $data{perfdata}) ) {
  155. my( $name, $value ) = split( /=/, $part );
  156. push( @names, $name );
  157. push( @values, $value );
  158. }
  159. if ( defined($create) && $create == 1 ) {
  160. my @DS = ();
  161. for( my $i=0; $i<scalar(@names); $i++ ) {
  162. my $tmp_rrd_type = $rrd_type;
  163. if ( ref($rrd_type) eq 'ARRAY' ) { $tmp_rrd_type = $rrd_type->[$i] }
  164. push( @DS, 'DS:'.$names[$i].":$tmp_rrd_type:86400:U:U" );
  165. }
  166. return @DS;
  167. }
  168. elsif ( defined($datum) && !defined($create) ) {
  169. return join( ':', @names );
  170. }
  171. else {
  172. return( {rrd_name=>lc($data{metric}), name=>$data{metric}, data=>[@values]} );
  173. }
  174. }