#! /usr/bin/perl -w # -*- mode: cperl -*- ################################################################### # MRTG 2.17.7 Multi Router Traffic Grapher ################################################################### # Created by Tobias Oetiker # and Dave Rand # # For individual Contributers check the CHANGES file # ################################################################### # # Distributed under the GNU General Public License # ################################################################### my @STARTARGS=($0,@ARGV); @main::DEBUG=qw(); # DEBUG TARGETS # cfg - watch the config file reading # dir - directory mangeling # base - basic program flow # tarp - target parser # snpo - snmp polling # snpo2 - more snmp debug # coca - confcache operations # repo - track confcache repopulation # fork - forking view # time - some timing info # log - logging of data via rateup or rrdtool # eval - trace eval experssions # prof - add timeing info some interesting bits of code $main::GRAPHFMT="png"; # There older perls tend to behave peculiar with # large integers ... require 5.005; use strict; # addon jpt BEGIN { # Automatic OS detection ... do NOT touch if ( $^O =~ /^(ms)?(dos|win(32|nt)?)/i ) { $main::OS = 'NT'; $main::SL = '\\'; $main::PS = ';'; } elsif ( $^O =~ /^NetWare$/i ) { $main::OS = 'NW'; $main::SL = '/'; $main::PS = ';'; } elsif ( $^O =~ /^VMS$/i ) { $main::OS = 'VMS'; $main::SL = '.'; $main::PS = ':'; } elsif ( $^O =~ /^os2$/i ) { $main::OS = 'OS2'; $main::SL = '/'; $main::PS = ';'; } else { $main::OS = 'UNIX'; $main::SL = '/'; $main::PS = ':'; } if ( $ENV{LANG} and $ENV{LANG} =~ /UTF.*8/i ){ my $args = join " ", map { /\s/ ? "\"$_\"" : $_ } @ARGV; $args ||= ""; print <) = ($uid,$uid) ; die "$NOW: ERROR failed to set UID to $uid\n" unless ($< == $uid and $> == $uid); $logfile = $opts{logging} || $logfile; if (defined $logfile){ setup_loghandlers $logfile; warn "Started mrtg with config \'$cfgfile\'\n"; } # lets make sure that there are not two mrtgs running in parallel. # so we lock on the cfg file. Nothing fancy, just a lockfile $lockfile = $opts{"lock-file"} || $lockfile; if (! defined $lockfile) { $lockfile = $cfgfile."_l"; } if (! defined $templock) { $templock = $lockfile."_" . $$ ; } debug('base', "Creating Lockfiles $lockfile,$templock"); &lockit($lockfile,$templock); debug('base', "Reading Config File: $cfgfile"); my $cfgfile_age = -M $cfgfile; readcfg($cfgfile,\@routers,\%cfg,\%rcfg); imggen($cfg{icondir} || $cfg{imagedir} || $cfg{workdir}); # Enable or disable snmpv3 if(defined $cfg{enablesnmpv3}) { $cfg{enablesnmpv3} = lc($cfg{enablesnmpv3}); } else { $cfg{enablesnmpv3} = 'no'; } if ($cfg{threshmailserver}) { if (eval {local $SIG{__DIE__};require Net::SMTP;}) { import Net::SMTP; debug('base', "Loaded Net::SMTP module for ThreshMail."); } else { die "$NOW: WARNING: Can't load Net::SMTP module. This is required for ThreshMail."; } } # Check we have the necessary libraries for IPv6 support if ($cfg{enablesnmpv3} eq 'yes') { if (eval {local $SIG{__DIE__};require Net_SNMP_util;}) { import Net_SNMP_util; debug('base', "SNMP V3 libraries found, SNMP V3 enabled."); } else { warn "$NOW: WARNING: SNMP V3 libraries not found, SNMP V3 disabled.\n"; $cfg{enablesnmpv3} = 'no'; require SNMP_util; import SNMP_util; } } else { # load V1/V2 libraries require SNMP_util; import SNMP_util; } # Enable or disable IPv6 if(defined $cfg{enableipv6}) { $cfg{enableipv6} = lc($cfg{enableipv6}); } else { $cfg{enableipv6} = 'no'; } # Check we have the necessary libraries for IPv6 support if ($cfg{enableipv6} eq 'yes') { if ( eval {local $SIG{__DIE__};require Socket; require Socket6; require IO::Socket::INET6;}) { import Socket; import Socket6; debug('base', "IPv6 libraries found, IPv6 enabled."); } else { warn "$NOW: WARNING: IPv6 libraries not found, IPv6 disabled.\n"; $cfg{enableipv6} = 'no'; } } # Module invocation for sending a copy of the time series data to graphite. if(defined $cfg{sendtographite}) { require Net::Graphite; } # from our last run we kept some info about # the configuration of our devices around debug('base', "Reading Interface Config cache"); $confcachefile = $opts{"confcache-file"} || $confcachefile; if ( !defined($confcachefile) ) { $confcachefile = $cfgfile; $confcachefile =~ s/\.[^.\/]+$//; $confcachefile .= ".ok"; } my $confcache = readconfcache($confcachefile); # Check the config and create the target object debug('base', "Checking Config File"); my @target; cfgcheck(\@routers, \%cfg, \%rcfg, \@target, \%opts); # exit here if we only check the config file # in case of an error, cfgcheck() already exited if (defined $opts{check}) { debug('base', "Remove Lock Files"); close LOCK; unlink ($templock, $lockfile); debug('base', "Exit after successful config file check"); exit 0; } # postload rrdtool support if ($cfg{logformat} eq 'rrdtool'){ debug('base', "Loading RRD support"); require 'RRDs.pm'; } # set the locale my $LOC; if ( $cfg{'language'} and defined($lang2tran::LOCALE{"\L$cfg{'language'}\E"})) { debug('base', "Loading Locale for ".$cfg{'language'}); $LOC=$lang2tran::LOCALE{"\L$cfg{'language'}\E"}; } else { debug('base', "Loading default Locale"); $LOC=$lang2tran::LOCALE{'default'}; } # Daemon Code my $last_time=0; my $curent_time; my $sleep_time; if (defined $opts{"daemon"}) { $cfg{'runasdaemon'} = "yes"; } &demonize_me($pidfile,$cfgfile) if defined $cfg{'runasdaemon'} and $cfg{'runasdaemon'} =~ /y/i and $MRTG_lib::OS ne 'VMS' and not (defined $cfg{'nodetach'} and $cfg{'nodetach'} =~ /y/i); # auto restart on die if running as demon $SIG{__DIE__} = sub { warn $_[0]; warn "*** Restarting after 10 seconds in an attempt to recover from the error above\n"; sleep 10; exec @STARTARGS; } if $cfg{'runasdaemon'}; debug('base', "Starting main Loop"); do { # Do this loop once for native mode and forever in daemon mode my $router; $NOW = timestamp; # get the time debug 'time', "loop start ".localtime(time); #if we run as daemon, we sleep in between collection cycles $sleep_time= (int($cfg{interval}*60))-(time-$last_time); if ($sleep_time > 0 ) { #If greater than 0 the sleep that amount of time debug('time', "Sleep time $sleep_time seconds"); sleep ($sleep_time); } elsif ($last_time > 0) { warn "$NOW: WARNING: data collection did not complete within interval!\n"; } $last_time=time; # set meta expires if there is an index file # 2000/05/03 Bill McGonigle if (defined $cfg{'writeexpires'}) { my $exp = &expistr($cfg{'interval'}); my $fil; $fil = "$cfg{'htmldir'}index.html" if -e "$cfg{'htmldir'}index.html"; $fil = "$cfg{'htmldir'}index.htm" if -e "$cfg{'htmldir'}index.htm"; if (defined $fil) { open(META, ">$fil.meta"); print META "Expires: $exp\n"; close(META); } } # Use SNMP to populate the target object debug('base', "Populate Target object by polling SNMP and". " external Datasources"); debug 'time', "snmp read start ".localtime(time); readtargets($confcache,\@target, \%cfg); $NOW = timestamp; # get the time # collect data for each router or pseudo target (`executable`) debug 'time', "target loop start ".localtime(time); foreach $router (@routers) { debug('base', "Act on Router/Target $router"); if (defined $rcfg{'setenv'}{$router}) { my $line = $rcfg{'setenv'}{$router}; while ( $line =~ s/([^=]+)=\"([^\"]*)\"\s*// ) # " - unconfuse the highliter { $ENV{$1}=$2; } } my($savetz) = $ENV{'TZ'}; if (defined $rcfg{'timezone'}{$router}) { $ENV{'TZ'} = $rcfg{'timezone'}{$router}; if ( $main::OS eq 'UNIX' ){ require 'POSIX.pm'; POSIX::tzset(); } } my ($inlast, $outlast, $uptime, $name, $time) = getcurrent(\@target, $router, \%rcfg, \%cfg); if ( defined($inlast) and defined($outlast)) { $EXITCODE = $EXITCODE | 1; } else { $EXITCODE = $EXITCODE | 2; } debug('base', "Get Current values: in:".( defined $inlast ? $inlast : "undef").", out:". ( defined $outlast? $outlast : "undef").", up:". ( defined $uptime ? $uptime : "undef").", name:". ( defined $name ? $name : "undef").", time:". ( defined $time ? $time : "undef")); #abort, if the router is not responding. if ($cfg{'logformat'} ne 'rrdtool') { # undefined values are ok for rrdtool ! #if ( not defined $inlast or not defined $outlast){ # warn "$NOW: WARNING: Skipping Update of $router, inlast is not defined\n" # unless defined $inlast; # warn "$NOW: WARNING: Skipping Update of $router, outlast is not defined\n" # unless defined $outlast; # next; #} if (defined $inlast and $inlast < 0) { $inlast += 2**31; # this is likely to be a broken snmp counter ... lets compensate } if (defined $outlast and $outlast < 0) { $outlast += 2**31; # this is likely to be a broken snmp counter ... lets compensate } } my ($maxin, $maxout, $maxpercent, $avin, $avout, $avpercent,$avmxin, $avmxout, $cuin, $cuout, $cupercent); debug('base', "Create Graphics"); if ($rcfg{'options'}{'dorelpercent'}{$router}) { ($maxin, $maxout, $maxpercent, $avin, $avout, $avpercent, $cuin, $cuout, $cupercent, $avmxin, $avmxout) = writegraphics($router, \%cfg, \%rcfg, $inlast, $outlast, $time,$LOC, \%opts); } else { ($maxin, $maxout ,$avin, $avout, $cuin, $cuout, $avmxin, $avmxout) = writegraphics($router, \%cfg, \%rcfg, $inlast, $outlast, $time,$LOC, \%opts); } # skip this update if we did not get anything usefull out of # writegraphics next if not defined $maxin; debug('base', "Check for Thresholds"); threshcheck(\%cfg,\%rcfg,$cfgfile,$router,$cuin,$cuout); if (defined $opts{'log-only'}){ debug('base', "Disable Graph and HTML generation"); } if ($cfg{logformat} eq 'rateup' and not defined $opts{'log-only'} ){ debug('base', "Check for Write HTML Pages"); writehtml($router, \%cfg, \%rcfg, $maxin, $maxout, $maxpercent, $avin, $avout, $avmxin, $avmxout, $avpercent, $cuin, $cuout, $cupercent, $uptime, $name, $LOC) } # clonedirectory($router,\%cfg, \%rcfg); # #put TZ things back in shape ... if ($savetz) { $ENV{'TZ'} = $savetz; } else { delete $ENV{'TZ'}; } if ( $main::OS eq 'UNIX' ){ require 'POSIX.pm'; POSIX::tzset(); }; } # Has the cfg file been modified since we started? if so, reload it. if ( -M $cfgfile < $cfgfile_age and $cfg{'runasdaemon'} and $cfg{'runasdaemon'} =~ /y/i ) { # reload the configuration $cfgfile_age = -M $cfgfile; debug('base', "Re-reading Config File: $cfgfile"); @routers = (); %cfg = (); %rcfg = (); readcfg($cfgfile,\@routers,\%cfg,\%rcfg); cfgcheck(\@routers, \%cfg, \%rcfg, \@target, \%opts); } debug('base', "End of main Loop"); } while ($cfg{'runasdaemon'} and $cfg{'runasdaemon'} =~ /y/i ); #In daemon mode run forever debug('base', "Exit main Loop"); # OK we are done, remove the lock files ... debug('base', "Remove Lock Files"); close LOCK; unlink ($templock, $lockfile); debug('base', "Store Interface Config Cache"); delete $$confcache{___updated} if exists $$confcache{___updated}; # make sure everything gets written out not only the updated entries writeconfcache($confcache,$confcachefile); if ( ! $cfg{'runasdaemon'} or $cfg{'runasdaemon'} !~ /y/i ) { if ( ($EXITCODE & 1) and ($EXITCODE & 2) ) { # At least one target was sucessful exit 91; } elsif ( not ($EXITCODE & 1) and ($EXITCODE & 2) ) { # All targets failed exit 92; } } } # ( $inlast, $outlast, $uptime, $name, $time ) = # &getcurrent( $target, $rou, $rcfg, $cfg ) # Calculate monitored data for device $rou based on information in @$target # and referring to configuration data in %$rcfg and %$cfg. In the returned # list, $inlast and $outlast are the input and output monitored data values, # $uptime is the device uptime, $name is the device name, and $time is the # current time when the calculation was performed. sub getcurrent { my( $target, $rou, $rcfg, $cfg ) = @_; # Hash indexed by $mode for conveniently saving $inlast and $outlast my %last; # Initialize uptime, device name, and data collection time to empty strings my $uptime = ''; my $name = ''; my $time = ''; # Calculate input and output monitored data foreach my $mode( qw( _IN_ _OUT_ ) ) { # Initialize monitored data, warning message, and death message # to empty strings my $data; my $warning; my $death; { # Code block used to calculate monitoring data # Localize warning and death exception handlers to capture # error message less any leading and trailing white space local $SIG{ __WARN__ } = sub { $_[0] =~ /^\s*(.+?)\s*$/; $warning = $1; }; local $SIG{ __DIE__ } = sub { $_[0] =~ /^\s*(.+?)\s*$/; $death = $1; }; # Calculate monitoring data. $rcfg->{ target }{ $rou } contains # a Perl expression for the calculation. $data = eval "$rcfg->{target}{$rou}"; } # Test for various exceptions occurring in the calculation if( $warning ) { warn "$NOW: ERROR: Target[$rou][$mode] '$$rcfg{target}{$rou}' (warn): $warning\n"; $data = undef; } elsif( $death ) { warn "$NOW: ERROR: Target[$rou][$mode] '$$rcfg{target}{$rou}' (kill): $death\n"; $data = undef; } elsif( $@ ) { warn "$NOW: ERROR: Target[$rou][$mode] '$$rcfg{target}{$rou}' (eval): $@\n"; $data = undef; } elsif( not defined $data ) { warn "$NOW: ERROR: Target[$rou][$mode] '$$rcfg{target}{$rou}' did not eval into defined data\n"; $data = undef; } elsif( $data and $data !~ /^[-+]?\d+(\.\d*)?([eE][+-]?[0-9]+)?$/ ) { warn "$NOW: ERROR: Target[$rou][$mode] '$$rcfg{target}{$rou}' evaluated to '$data' instead of a number\n"; $data = undef; } elsif( length( $data ) > 190 ) { warn "$NOW: ERROR: $mode value: '$data' is way to long ...\n"; $data = undef; } else { # At this point data is considered valid. Round to an integer # unless RRDTool is in use and this is a gauge if (not ( $cfg->{ logformat } eq 'rrdtool' and defined $rcfg->{ options }{ gauge }{ $rou })){ if (ref $data and ref $data eq 'Math::BigFloat') { $data->ffround( 0 ) } else { $data = sprintf "%.0f", $data; } } # Remove any leading plus sign $data =~ s/^\+//; } $last{ $mode } = $data; } # Set $u to the unique index of the @$target array referred to in the # monitored data calculation for the current device. $u will be set to # -1 if that index is not unique. my $u = $rcfg->{ uniqueTarget }{ $rou }; # Get the uptime, device name, and data collection time from the @$target # array if the monitored data calculation refers only to one target. # Otherwise it doesn't make sense to do this. if( $u >= 0 ) { $uptime = $target->[ $u ]{ _UPTIME_ }; $name = $target->[ $u ]{ _NAME_ }; $time = $target->[ $u ]{ _TIME_ }; if ($time =~ /^([-0-9.]+)$/) { $time = $1; } } # Set the time to the current time if it was not set above $time = time unless $time; # Cache uptime location for reading name my( $uploc ); # Get the uptime and device name from the alternate location (community@host or # (OID:community@host or OID) that may have been specified with the RouterUptime # target keyword if( defined $rcfg->{ routeruptime }{ $rou } ) { my( $noid, $nloc ) = split( /:/, $rcfg->{ routeruptime }{ $rou }, 2 ); # If only location (community@host) was specified then # move the location details into the right place if( $noid =~ /@/ ) { $nloc = $noid; $noid = undef; } # If no OID (community@host) was specified use the hardcoded default if( not $noid ) { $noid = 'sysUptime'; } # If no location (community@host) was specified use values from the # unique target referred to in the monitored data calculation if( not $nloc ){ if ($u >= 0) { my $comm = $target->[ $u ]{ Community }; my $host = $target->[ $u ]{ Host }; my $opt = $target->[ $u ]{ SnmpOpt }; $nloc = "$comm\@$host$opt"; } else { die "$NOW: ERROR: You must specify the location part of the RouterUptime oid for non unique targets! ($rou)\n"; } } $uploc = $nloc; # Get the device uptime if $noid(OID) and $nloc (community@host) have been specified # one way or the other debug('base', "Fetching sysUptime and sysName from: $noid:$nloc"); ( $uptime, $name ) = snmpget( $uploc, $rcfg->{ snmpoptions }{ $rou }, $noid, 'sysName'); } # Get the device name from the alternate location (OID or # OID:community@host) that may have been specified with the RouterName # target keyword if( defined $rcfg->{ routername }{ $rou } ) { my( $noid, $nloc ) = split( /:/, $rcfg->{ routername }{ $rou }, 2 ); # If no location (community@host) was specified use values from the # unique target referred to in the monitored data calculation if( $u >= 0 and not $nloc ) { my $comm = $target->[ $u ]{ Community }; my $host = $target->[ $u ]{ Host }; my $opt = $target->[ $u ]{ SnmpOpt }; $nloc = "$comm\@$host$opt"; } # Get the location from the RouterUptime keyword if that is defined # and $nloc has not otherwise been specified $nloc = $uploc if $uploc and not $nloc; # Get the device name if $nloc (community@host) has been specified # one way or the other debug('base', "Fetching sysName from: $noid:$nloc"); ( $name ) = snmpget( $nloc, $$rcfg{snmpoptions}{ $rou }, $noid ) if $nloc; } return ( $last{ _IN_ }, $last{ _OUT_ }, $uptime, $name, $time ); } sub rateupcheck ($) { my $router = shift; if ($?) { my $value = $?; my $signal = $? & 127; #ignore the most significant bit #as it is always one when it is a returning #child says dave ... if (($MRTG_lib::OS ne 'UNIX') || ($signal != 127)) { my $exitval = $? >> 8; warn "$NOW: WARNING: rateup died from Signal $signal\n". " with Exit Value $exitval when doing router '$router'\n". " Signal was $signal, Returncode was $exitval\n" } } } sub clonedirectory { my($router,$cfg, $rcfg) = @_; require File::Copy; import File::Copy; return unless ( $$rcfg{'clonedirectory'}{$router} ); my ($clonedstdir, $clonedsttarget ,$srcname, $dstname); ($clonedstdir, $clonedsttarget ) = split (/,|\s+/, $$rcfg{'clonedirectory'}{$router}) if ( $$rcfg{'clonedirectory'}{$router} =~ /,|\S\s+\S/ ); if ( defined $clonedsttarget ) { $clonedsttarget =~ s/\s+//; $clonedsttarget = lc($clonedsttarget); } else { $clonedstdir = $$rcfg{'clonedirectory'}{$router}; } if ( $$rcfg{'directory'}{$router} ne $clonedstdir) { $clonedstdir =~ s/\s+$//; $clonedstdir .= "/" unless ($clonedstdir =~ /\/$/); my $fullpathsrcdir = "$$cfg{'logdir'}$$rcfg{'directory'}{$router}"; my $fullpathdstdir = "$$cfg{'logdir'}$clonedstdir"; die "$NOW: ERROR: Destination dir: $fullpathdstdir not found for cloning process\n" unless ( -e $fullpathdstdir ); die "$NOW: ERROR: Destination dir: $fullpathdstdir is not a directory destination for cloning process\n" unless ( -d $fullpathdstdir ); die "$NOW: ERROR: Destination dir: $fullpathdstdir is not writeable for cloning process\n" unless ( -w $fullpathdstdir ); if ( defined $clonedsttarget ) { debug('base', "Clone directory $fullpathsrcdir to $fullpathdstdir " . "renaming target $router to $clonedsttarget"); } else { debug('base', "Clone directory $fullpathsrcdir to $fullpathdstdir"); } foreach my $srcfile (<$fullpathsrcdir$router\[.-\]*>) { debug('base', "copying $srcfile $fullpathdstdir"); copy("$srcfile","$fullpathdstdir") or warn "$NOW: WARNING: Cloning $srcfile to $fullpathdstdir unsuccessful; $!\n"; if ($srcfile =~ /\.html/i) { debug('base', "altering $fullpathdstdir/$router.$$rcfg{'extension'}{$router}"); # my $dirrel = "../" x ($$rcfg{'clonedirectory'}{$router} =~ tr|/|/|); # debug('base', "dirrel $dirrel $clonedstdir"); open(HTML,"$fullpathdstdir/$router.$$rcfg{'extension'}{$router}"); my @CLONEHTML = ; close(HTML); foreach ( @CLONEHTML ) { if ( defined $clonedsttarget and /$router/ ) { debug('base', "altering $router to $clonedsttarget in html file"); s/$router/$clonedsttarget/i; } if ( /SRC=/i and /$$rcfg{'directory'}{$router}/ ) { debug('base', "altering from $_"); s|(\.\./)+|$dirrel|; s|$$rcfg{'directory'}{$router}|$clonedstdir|; debug('base', "altering to $_"); } } open(HTML,">$fullpathdstdir/$router.$$rcfg{'extension'}{$router}"); print HTML $_ for ( @CLONEHTML ); close(HTML); } if ( defined $clonedsttarget ) { $srcfile =~ /.+\/(.+)?$/; $srcname = $1; $dstname = $srcname; $dstname =~ s/$router/$clonedsttarget/; debug('base', "Clone renaming $srcname to $dstname at $fullpathdstdir"); rename("$fullpathdstdir/$srcname","$fullpathdstdir/$dstname") or warn "$NOW: WARNING: Renaming $fullpathdstdir/$srcname to $fullpathdstdir/$dstname unsuccessful; $!\n"; } } } else { warn "$NOW: WARNING: Cloning to the same place suspended. ; $!\n"; } } sub writegraphics { my($router, $cfg, $rcfg, $inlast, $outlast, $time,$LOC, $opts) = @_; my($absmax,$maxv, $maxvi, $maxvo, $i, $period, $res); my(@exec, @mxvls, @metas); my(%maxin, %maxout, %maxpercent, %avin, %avout, %avmxin, %avmxout, %avpercent, %cuin, %cuout, %cupercent); my($rrdinfo); @metas = (); $maxvi = $$rcfg{'maxbytes1'}{$router}; $maxvo = $$rcfg{'maxbytes2'}{$router}; if ($maxvi > $maxvo) { $maxv = $maxvi; } else { $maxv = $maxvo; } $absmax = $$rcfg{'absmax'}{$router}; $absmax = $maxv unless defined $absmax; if ($absmax < $maxv) { die "$NOW: ERROR: AbsMax: $absmax is smaller than MaxBytes: $maxv\n"; } # select whether the datasource gives relative or absolute return values. my $up_abs="u"; $up_abs='m' if defined $$rcfg{'options'}{'perminute'}{$router}; $up_abs='h' if defined $$rcfg{'options'}{'perhour'}{$router}; $up_abs='d' if defined $$rcfg{'options'}{'derive'}{$router}; $up_abs='a' if defined $$rcfg{'options'}{'absolute'}{$router}; $up_abs='g' if defined $$rcfg{'options'}{'gauge'}{$router}; my $dotrrd = "$$cfg{'logdir'}$$rcfg{'directory'}{$router}$router.rrd"; my $dotlog = "$$cfg{'logdir'}$$rcfg{'directory'}{$router}$router.log"; my $reallog = $$cfg{logformat} eq 'rrdtool' ? $dotrrd : $dotlog; if (defined $$cfg{maxage} and -e $reallog and time()-$$cfg{maxage} > (stat($reallog))[9]){ warn "$NOW: ERROR: skipping update of $router. As $reallog is older than MaxAge ($$cfg{maxage} s)\n"; return undef; } if ($$cfg{logformat} eq 'rrdtool') { debug('base',"start RRDtool section"); # make sure we got some sane default here my %dstype = qw/u COUNTER a ABSOLUTE g GAUGE h COUNTER m COUNTER d DERIVE/; $up_abs = $dstype{$up_abs}; # update the database. # set minimum/maximum values. use 'U' if we cannot get good values # the lower bound is hardcoded to 0 my $absi = $maxvi; my $abso = $maxvo; $absi = $abso = $$rcfg{'absmax'}{$router} if defined $$rcfg{'absmax'}{$router}; debug('base',"maxi:$absi, maxo:$abso"); $absi = 'U' if $absi == 0; $abso = 'U' if $abso == 0; # check to see if we already have an RRD file or have to create it # maybe we can convert an .log file to the new rrd format if( $RRDs::VERSION >= 1.4 and $$cfg{rrdcached} and $$cfg{rrdcached} !~ /^unix:/ ) { # rrdcached in network mode. No log conversion possible. # In this mode, we cannot use absolute paths. So, we strip logdir from the name. $dotrrd = "$$rcfg{'directory'}{$router}$router.rrd"; if( $RRDs::VERSION < 1.49 ) { # This version of RRD doesnt support info, create and tune debug('base',"Unable to verify RRD file with this version of rrdcached"); if( !$main::verified_rrd{$dotrrd} ) { warn "WARN: Unable to verify $dotrrd with this version of RRDTool\n"; $main::verified_rrd{$dotrrd} = 1; } } elsif( !$main::verified_rrd{$dotrrd} ) { # Test to see if it exists debug('base',"Attempting to verify RRD file via rrdcached"); $rrdinfo = RRDs::info($dotrrd,'--daemon',$$cfg{rrdcached},'--noflush'); if(!$rrdinfo) { # doesnt exist, or cannot be accessed my $e = RRDs::error(); warn "$NOW: Cannot access $dotrrd; will attempt to (re)create it: $e\n" if $e; # don't fail if interval is not set my $interval = $$cfg{interval}; my $minhb = int($$cfg{interval} * 60)*2; $minhb = 600 if ($minhb <600); my $rows = $$rcfg{'rrdrowcount'}{$router} || int( 4000 / $interval); my $rows30m = $$rcfg{'rrdrowcount30m'}{$router} || 800; my $rows2h = $$rcfg{'rrdrowcount2h'}{$router} || 800; my $rows1d = $$rcfg{'rrdrowcount1d'}{$router} || 800; my @args = ($dotrrd, '-b', $time-10, '-s', int($interval * 60), "DS:ds0:$up_abs:$minhb:0:$absi", "DS:ds1:$up_abs:$minhb:0:$abso", "RRA:AVERAGE:0.5:1:$rows", ( $interval < 30 ? ("RRA:AVERAGE:0.5:".int(30/$interval).":".$rows30m):()), "RRA:AVERAGE:0.5:".int(120/$interval).":".$rows2h, "RRA:AVERAGE:0.5:".int(1440/$interval).":".$rows1d, "RRA:MAX:0.5:1:$rows", ( $interval < 30 ? ("RRA:MAX:0.5:".int(30/$interval).":".$rows30m):()), "RRA:MAX:0.5:".int(120/$interval).":".$rows2h, "RRA:MAX:0.5:".int(1440/$interval).":".$rows1d); # do we have holt winters rras defined here ? if (defined $$rcfg{'rrdhwrras'} and defined $$rcfg{'rrdhwrras'}{$router}){ push @args, split(/\s+/, $$rcfg{'rrdhwrras'}{$router}); } push @args,"--daemon", $$cfg{rrdcached}; debug('base',"create $dotrrd via rrdcached"); debug('log', "RRDs::create(".join(',',@args).")"); RRDs::create(@args); $e = RRDs::error(); die "$NOW: ERROR: Cannot create RRD ".join(',',@args)."- $e\n" if $e; } else { # Does the RRD file need to be tuned? if( ($rrdinfo->{"ds[ds0].max"} != $absi) ||($rrdinfo->{"ds[ds1].max"} != $abso) ||($rrdinfo->{"ds[ds0].type"} ne $up_abs) ||($rrdinfo->{"ds[ds1].type"} ne $up_abs) ) { debug('base',"RRD file needs to be tuned"); warn "$NOW: RRDFile $dotrrd needs to be tuned but cannot do this remotely.\n"; } } $main::verified_rrd{$dotrrd} = 1; } else { debug('base',"No need to verify this file again"); } } elsif (-e $dotlog and not -e $dotrrd) { debug('base',"converting $dotlog to RRD format"); if(defined $RRDs::VERSION and $RRDs::VERSION < 1.000271){ die "$NOW: ERROR: RRDtool version 1.0.27 or later required to perform log2rrd conversion\n"; } log2rrd($router,$cfg,$rcfg); } elsif (! -e $dotrrd) { #nope it seems we have to create a new one debug('base',"create $dotrrd"); # create the rrd if it doesn't exist # don't fail if interval is not set my $interval = $$cfg{interval}; my $minhb = int($$cfg{interval} * 60)*2; $minhb = 600 if ($minhb <600); my $rows = $$rcfg{'rrdrowcount'}{$router} || int( 4000 / $interval); my $rows30m = $$rcfg{'rrdrowcount30m'}{$router} || 800; my $rows2h = $$rcfg{'rrdrowcount2h'}{$router} || 800; my $rows1d = $$rcfg{'rrdrowcount1d'}{$router} || 800; my @args = ($dotrrd, '-b', $time-10, '-s', int($interval * 60), "DS:ds0:$up_abs:$minhb:0:$absi", "DS:ds1:$up_abs:$minhb:0:$abso", "RRA:AVERAGE:0.5:1:$rows", ( $interval < 30 ? ("RRA:AVERAGE:0.5:".int(30/$interval).":".$rows30m):()), "RRA:AVERAGE:0.5:".int(120/$interval).":".$rows2h, "RRA:AVERAGE:0.5:".int(1440/$interval).":".$rows1d, "RRA:MAX:0.5:1:$rows", ( $interval < 30 ? ("RRA:MAX:0.5:".int(30/$interval).":".$rows30m):()), "RRA:MAX:0.5:".int(120/$interval).":".$rows2h, "RRA:MAX:0.5:".int(1440/$interval).":".$rows1d); # do we have holt winters rras defined here ? if (defined $$rcfg{'rrdhwrras'} and defined $$rcfg{'rrdhwrras'}{$router}){ push @args, split(/\s+/, $$rcfg{'rrdhwrras'}{$router}); } debug('log', "RRDs::create(".join(',',@args).")"); RRDs::create(@args); my $e = RRDs::error(); die "$NOW: ERROR: Cannot create RRD ".join(',',@args)."- $e\n" if $e; } elsif ( -M $dotrrd > 0 ) { # update the minimum/maximum according to maxbytes/absmax # and (re)set the data-source-type to reflect cfg changes # cost: 1 read/write cycle, but update will reuse the buffered data # in daemon mode this will only happen in the first round my @args = ($dotrrd, '-a', "ds0:$absi", '-a', "ds1:$abso", '-d', "ds0:$up_abs", '-d', "ds1:$up_abs"); debug('log', "RRDs::tune(@args)"); my $start = gettimeofday(); RRDs::tune(@args); debug('prof',sprintf("RRDs::tune $dotrrd - %.3fs",gettimeofday()-$start)); my $e = RRDs::error(); warn "$NOW: ERROR: Cannot tune logfile: $e\n" if $e; } # update the rrd $inlast = 'U' unless defined $inlast and $inlast =~ /\S/ and $inlast ne '##UNDEF##'; $outlast = 'U' unless defined $outlast and $outlast =~ /\S/ and $outlast ne '##UNDEF##'; debug('log', "RRDs::update($dotrrd, '$time:$inlast:$outlast')"); my $start = gettimeofday(); my $rrddata = 0; if ( $RRDs::VERSION >= 1.4 and $$cfg{rrdcached} ){ RRDs::update($dotrrd, '--daemon', $$cfg{rrdcached}, "$time:$inlast:$outlast"); debug('prof',sprintf("RRDs::update $dotrrd (rrdcached) - %.3fs",gettimeofday()-$start)); $rrddata = \{ dummy => "" }; } elsif ( $RRDs::VERSION >= 1.2 ){ $rrddata=RRDs::updatev($dotrrd, "$time:$inlast:$outlast"); debug('prof',sprintf("RRDs::updatev $dotrrd - %.3fs",gettimeofday()-$start)); } else { RRDs::update($dotrrd, "$time:$inlast:$outlast"); debug('prof',sprintf("RRDs::update $dotrrd - %.3fs",gettimeofday()-$start)); } my $e = RRDs::error(); warn "$NOW: ERROR: Cannot update $dotrrd with '$time:$inlast:$outlast' $e\n" if ($e); # Send a copy of the time series data to graphite if(defined $$cfg{sendtographite}) { my @a = split ",",$$cfg{sendtographite}; if (not $graphiteObj) { $graphiteObj = Net::Graphite->new( host => $a[0], port => $a[1], trace => 0, proto => "tcp", timeout => 1, fire_and_forget => 1, return_connect_error => 0 ); if($graphiteObj->connect) { debug('log',"successfully opened graphite socket ($graphiteObj)\n"); } else { debug('log',"graphite connect error: $!\n"); } } # make a copy of the router name for use in the graphite namespace my $graphiterouter = $router; # periods are delimiters in the graphite namespace so change them to underscores $graphiterouter =~ s/_/\./g; # commas are not allowed $graphiterouter =~ s/,//g; #remove commas #set up MRTG's 'in' var naming for graphite my $graphitein; if ($$rcfg{'legendi'}{$router}) { $graphitein = $$rcfg{'legendi'}{$router}; # if the 'in' var contains an underscore, change to a period so graphite will delimit on it $graphitein =~ s/_/\./g; } else { # if legendi is not specified, just use 'in' $graphitein = "in"; } #set up MRTG's 'out' var naming for graphite my $graphiteout; if ($$rcfg{'legendo'}{$router}) { $graphiteout = $$rcfg{'legendo'}{$router}; # if the 'out' var contains an underscore, change to a period so graphite will delimit on it $graphiteout =~ s/_/\./g; } else { # if legendo is not specified, just use 'out' $graphiteout = "out"; } my $graphitensprefix="m2g"; # verify that the 'in' value is numeric (ints, floats, +, -) , and send to graphite if($inlast =~ /^[+-]?([0-9]*[.])?[0-9]+$/) { # send the 'in' var data to graphite, and log it appropriately debug('log', "graphite->send($graphitensprefix.$graphiterouter.$graphitein,$inlast,$time)\n"); $graphiteObj->send( path => "$graphitensprefix.$graphiterouter.$graphitein", value => "$inlast", time => $time, ); } else { debug('log', "WARNING - skipped graphite->send($graphitensprefix.$graphiterouter.$graphitein,$inlast,$time) because value ($inlast) is non-numeric\n"); } # verify that the 'out' value is numeric (ints, floats, +, -) , and send to graphite if($outlast =~ /^[+-]?([0-9]*[.])?[0-9]+$/) { # send the 'out' var data to graphite, and log it appropriately debug('log', "graphite->send($graphitensprefix.$graphiterouter.$graphiteout,$outlast,$time)\n"); $graphiteObj->send( path => "$graphitensprefix.$graphiterouter.$graphiteout", value => "$outlast", time => $time, ); } else { debug('log', "WARNING - skipped graphite->send($graphitensprefix.$graphiterouter.$graphiteout,$outlast,$time) because value ($outlast) is non-numeric\n"); } } # done sending to graphite for this iteration if ( $RRDs::VERSION < 1.2 ){ # get the rrdtool-processed values back from rrdtool # for the threshold checks (we cannot use the fetched data) $start = gettimeofday(); my $info = RRDs::info($dotrrd); debug('prof',sprintf("RRDs::info $dotrrd - %.3fs",gettimeofday()-$start)); my $lasttime = $info->{last_update} - $info->{last_update} % $info->{step}; debug('log', "RRDs::info($dotrrd)"); $e = RRDs::error(); warn "$NOW: ERROR: Cannot 'info' $dotrrd: $e\n" if ($e); $start = gettimeofday(); my $fetch = (RRDs::fetch($dotrrd,'AVERAGE','-s',$lasttime-1,'-e',$lasttime))[3]; debug('prof',sprintf("RRDs::fetch $dotrrd - %.3fs",gettimeofday()-$start)); $e = RRDs::error(); warn "$NOW: ERROR: Cannot 'fetch' $dotrrd: $e\n" if ($e); debug('log', "RRDs::fetch($dotrrd,'AVERAGE','-s',$lasttime,'-e',$lasttime)"); $cuin{d}{$router} = $fetch->[0][0]; $cuout{d}{$router} = $fetch->[0][1]; } elsif ( $RRDs::VERSION >= 1.4 and $$cfg{rrdcached} ){ # Cannot check thresholds } elsif($rrddata) { my $utime = $time - ($time % int($cfg->{interval}*60)); $cuin{d}{$router} = $rrddata->{"[$utime]RRA[AVERAGE][1]DS[ds0]"}; $cuout{d}{$router} = $rrddata->{"[$utime]RRA[AVERAGE][1]DS[ds1]"}; $cuin{d_hwfail}{$router} = $rrddata->{"[$utime]RRA[FAILURES][1]DS[ds0]"}; $cuout{d_hwfail}{$router} = $rrddata->{"[$utime]RRA[FAILURES][1]DS[ds1]"}; } my $in = defined $cuin{d}{$router} ? $cuin{d}{$router} : "???" ; my $out = defined $cuout{d}{$router} ? $cuout{d}{$router} : "???" ; debug('log', " got: $in/$out"); # the html pages and the graphics are created at "call time" so that's it! # (the returned hashes are empty, it's just to minimize the changes to mrtg) if ($$rcfg{'options'}{'dorelpercent'}{$router}) { return (\%maxin, \%maxout, \%maxpercent, \%avin, \%avout, \%avpercent, \%cuin, \%cuout, \%cupercent, \%avmxin, \%avmxout); } return (\%maxin, \%maxout, \%avin, \%avout, \%cuin, \%cuout, \%avmxin, \%avmxout ); } ########## rrdtool users have left here ############### ((($MRTG_lib::OS eq 'NT' or $MRTG_lib::OS eq 'OS2') and (-e "${FindBin::Bin}${MRTG_lib::SL}rateup.exe")) or (($MRTG_lib::OS eq 'NW') and (-e "SYS:/Mrtg/bin/rateup.nlm")) or (-x "${FindBin::Bin}${MRTG_lib::SL}rateup")) or die "$NOW: ERROR: Can't Execute '${FindBin::Bin}${MRTG_lib::SL}rateup'\n"; # rateup does not know about undef so we make inlast and outlast ready for rateup #warn "$NOW: ERROR: inlast is undefined. Skipping $router\n" unless defined $inlast; #warn "$NOW: ERROR: outlast is undefined. Skipping $router\n" unless defined $outlast; #return undef unless defined $inlast and defined $outlast; # set values to -1 to tell rateup about unknown values $inlast = -1 unless defined $inlast; $outlast = -1 unless defined $outlast; # untaint in and out if ($inlast =~ /^([-0-9.]+)$/) { $inlast = $1; } if ($outlast =~ /^([-0-9.]+)$/) { $outlast = $1; } if ($$rcfg{'options'}{'dorelpercent'}{$router}) { @exec = ("${FindBin::Bin}${MRTG_lib::SL}rateup", "$$cfg{'logdir'}$$rcfg{'directory'}{$router}","$router", $time, $$rcfg{'options'}{'unknaszero'}{$router} ? '-z':'-Z', "$up_abs"."p", $inlast, $outlast, $absmax, "C", $$rcfg{'rgb1'}{$router},$$rcfg{'rgb2'}{$router}, $$rcfg{'rgb3'}{$router},$$rcfg{'rgb4'}{$router}, $$rcfg{'rgb5'}{$router}); } else { @exec = ("${FindBin::Bin}${MRTG_lib::SL}rateup", "$$cfg{'logdir'}$$rcfg{'directory'}{$router}","$router", $time, $$rcfg{'options'}{'unknaszero'}{$router} ? '-z':'-Z', "$up_abs", $inlast, $outlast, $absmax, "c", $$rcfg{'rgb1'}{$router},$$rcfg{'rgb2'}{$router}, $$rcfg{'rgb3'}{$router},$$rcfg{'rgb4'}{$router}); } # If this list grows anymore would it be more efficient to have an # array to look up the command line option to send to rateup rather # than have a long list to check? push (@exec, '-t') if defined $$rcfg{'options'}{'transparent'}{$router}; push (@exec, '-0') if defined $$rcfg{'options'}{'withzeroes'}{$router}; push (@exec, '-b') if defined $$rcfg{'options'}{'noborder'}{$router}; push (@exec, '-a') if defined $$rcfg{'options'}{'noarrow'}{$router}; push (@exec, '-i') if defined $$rcfg{'options'}{'noi'}{$router}; push (@exec, '-o') if defined $$rcfg{'options'}{'noo'}{$router}; push (@exec, '-l') if defined $$rcfg{'options'}{'logscale'}{$router}; push (@exec, '-x') if defined $$rcfg{'options'}{'expscale'}{$router}; push (@exec, '-m') if defined $$rcfg{'options'}{'secondmean'}{$router}; push (@exec, '-p') if defined $$rcfg{'options'}{'printrouter'}{$router}; my $maxx = $$rcfg{'xsize'}{$router}; my $maxy = $$rcfg{'ysize'}{$router}; my $xscale = $$rcfg{'xscale'}{$router}; my $yscale = $$rcfg{'yscale'}{$router}; my $growright = 0+($$rcfg{'options'}{'growright'}{$router} or 0); my $bits = 0+($$rcfg{'options'}{'bits'}{$router} or 0); my $integer = 0+($$rcfg{'options'}{'integer'}{$router} or 0); my $step = 5*60; my $rop; my $ytics = $$rcfg{'ytics'}{$router}; my $yticsf= $$rcfg{'yticsfactor'}{$router}; my $timestrfmt = $$rcfg{'timestrfmt'}{$router}; my $timestrpos = ${MRTG_lib::timestrpospattern}{uc $$rcfg{'timestrpos'}{$router}}; if (not defined $$rcfg{'ylegend'}{$router}){ if ($bits){ $$rcfg{'ylegend'}{$router} = &$LOC("Bits per minute") if defined $$rcfg{'options'}{'perminute'}{$router}; $$rcfg{'ylegend'}{$router} = &$LOC("Bits per hour") if defined $$rcfg{'options'}{'perhour'}{$router}; } else { $$rcfg{'ylegend'}{$router} = &$LOC("Bytes per minute") if defined $$rcfg{'options'}{'perminute'}{$router}; $$rcfg{'ylegend'}{$router} = &$LOC("Bytes per hour") if defined $$rcfg{'options'}{'perhour'}{$router}; } } if ($$rcfg{'ylegend'}{$router}) { push (@exec, "l", "[$$rcfg{'ylegend'}{$router}]"); } my $sign = ($$rcfg{'unscaled'}{$router} and $$rcfg{'unscaled'}{$router} =~ /d/) ? 1 : -1; if ($$rcfg{'pngtitle'}{$router}) { push (@exec, "T", "[$$rcfg{'pngtitle'}{$router}]"); } if ($$rcfg{'timezone'}{$router}) { push (@exec, "Z", "$$rcfg{'timezone'}{$router}"); } if ($$rcfg{'kilo'}{$router}) { push (@exec, "k", $$rcfg{'kilo'}{$router}); } if ($$rcfg{'kmg'}{$router}) { push (@exec, "K", $$rcfg{'kmg'}{$router}); } if ($$rcfg{'weekformat'}{$router}) { push (@exec, "W", $$rcfg{'weekformat'}{$router}); } my $SAGE = (time - $main::STARTTIME) / 3600 / 24; # current script age if (not defined $$opts{'log-only'}){ if (not defined $$rcfg{'suppress'}{$router} or $$rcfg{'suppress'}{$router} !~ /d/) { # VMS: should work for both now push (@exec, "i", "$$cfg{'imagedir'}$$rcfg{'directory'}{$router}${router}-day.${main::GRAPHFMT}", $sign*$maxvi, $sign*$maxvo, $maxx, $maxy, ,$xscale, $yscale, $growright, $step, $bits, $ytics, $yticsf, $timestrfmt, $timestrpos); @mxvls = ("d"); push (@metas, "$$cfg{'imagedir'}$$rcfg{'directory'}{$router}${router}-day.${main::GRAPHFMT}", $$cfg{'interval'}); } if (((not -e "$$cfg{'imagedir'}$$rcfg{'directory'}{$router}${router}-week.${main::GRAPHFMT}") or ((-M "$$cfg{'imagedir'}$$rcfg{'directory'}{$router}${router}-week.${main::GRAPHFMT}") + $SAGE >= 0.5/24)) and (not defined $$rcfg{'suppress'}{$router} or $$rcfg{'suppress'}{$router} !~/w/) ) { $step=30*60; $sign = (defined $$rcfg{'unscaled'}{$router} and $$rcfg{'unscaled'}{$router} =~ /w/) ? 1 : -1; push (@mxvls , "w"); $rop =(defined $$rcfg{'withpeak'}{$router} and $$rcfg{'withpeak'}{$router} =~ /w/) ? "p" : "i"; push (@exec, $rop ,"$$cfg{'imagedir'}$$rcfg{'directory'}{$router}${router}-week.${main::GRAPHFMT}", $sign*$maxvi, $sign*$maxvo, $maxx, $maxy, $xscale, $yscale, $growright, $step, $bits, $ytics, $yticsf, $timestrfmt, $timestrpos); push (@metas, "$$cfg{'imagedir'}$$rcfg{'directory'}{$router}${router}-week.${main::GRAPHFMT}", 30); } if (((not -e "$$cfg{'imagedir'}$$rcfg{'directory'}{$router}${router}-month.${main::GRAPHFMT}") or (( -M "$$cfg{'imagedir'}$$rcfg{'directory'}{$router}${router}-month.${main::GRAPHFMT}") + $SAGE >= 2/24)) and (not defined $$rcfg{'suppress'}{$router} or $$rcfg{'suppress'}{$router} !~ /m/)) { $step=2*60*60; $sign = (defined $$rcfg{'unscaled'}{$router} and $$rcfg{'unscaled'}{$router} =~ /m/) ? 1 : -1; push (@mxvls , "m"); $rop =(defined $$rcfg{'withpeak'}{$router} and $$rcfg{'withpeak'}{$router} =~ /m/) ? "p" : "i"; push (@exec, $rop ,"$$cfg{'imagedir'}$$rcfg{'directory'}{$router}${router}-month.${main::GRAPHFMT}", $sign*$maxvi, $sign*$maxvo, $maxx, $maxy, $xscale, $yscale, $growright, $step, $bits, $ytics, $yticsf, $timestrfmt, $timestrpos); push (@metas, "$$cfg{'imagedir'}$$rcfg{'directory'}{$router}${router}-month.${main::GRAPHFMT}", 120); } if (((not -e "$$cfg{'imagedir'}$$rcfg{'directory'}{$router}${router}-year.${main::GRAPHFMT}") or (( -M "$$cfg{'imagedir'}$$rcfg{'directory'}{$router}${router}-year.${main::GRAPHFMT}") + $SAGE >= 1)) and (not defined $$rcfg{'suppress'}{$router} or $$rcfg{'suppress'}{$router} !~/y/)) { $step=24*60*60; $sign = (defined $$rcfg{'unscaled'}{$router} and $$rcfg{'unscaled'}{$router} =~ /y/) ? 1 : -1; push (@mxvls , "y"); $rop =(defined $$rcfg{'withpeak'}{$router} and $$rcfg{'withpeak'}{$router} =~ /y/) ? "p" : "i"; push (@exec, $rop, "$$cfg{'imagedir'}$$rcfg{'directory'}{$router}${router}-year.${main::GRAPHFMT}", $sign*$maxvi, $sign*$maxvo, $maxx, $maxy, $xscale, $yscale, $growright, $step, $bits, $ytics, $yticsf, $timestrfmt, $timestrpos); push (@metas, "$$cfg{'imagedir'}$$rcfg{'directory'}{$router}${router}-year.${main::GRAPHFMT}", 1440); } } # VMS: this might work now ... or does VMS NOT know about pipes? # NT doesn't have fork() so an open(xxx,"-|") won't work # OS2 fork() have bug with socket handles. In RunAsDaemon mode it fail # after first loop (with "socket operation on non socket" message. if ($MRTG_lib::OS eq 'VMS' or $MRTG_lib::OS eq 'NT' or $MRTG_lib::OS eq 'OS2'){ map { s/"/\\"/; $_ = '"'.$_.'"' if /\s/ } @exec; open (RATEUP, join (" ", @exec)."|") or do { warn "$NOW: WARNING: rateup (".(join " ", @exec ). ") did not work: $!\n"; return; } } elsif ($MRTG_lib::OS eq 'NW'){ map { s/"/\\"/; $_ = '"'.$_.'"' if /\s/ } @exec; # Stuff around because of Perl problems. open (NWPARMS, ">"."$$cfg{'imagedir'}$router.dat") or do { warn "$NOW: WARNING: Rateup parameters [$$cfg{'imagedir'}$router.dat] [open] failed.\n"; return; }; print NWPARMS join (" ", @exec); close NWPARMS; # Now run Rateup with path to Parameters. open (RATEUP, "SYS:/Mrtg/bin/rateup -f $$cfg{'imagedir'}$router.dat"."|") or do { warn "$NOW: WARNING: SYS:/Mrtg/bin/rateup -f $$cfg{'imagedir'}$router.dat did NOT work.\n"; return; } } else { $! = undef; open (RATEUP,"-|") or do { exec @exec or warn "$NOW: WARNING: rateup (".(join " ", @exec ). ") did not work: $!\n"; }; } debug('log', join(" ", @exec)); if (open (HTML,"<$$cfg{'htmldir'}$$rcfg{'directory'}{$router}$router.$$rcfg{'extension'}{$router}")) { for ($i=0 ; $i<200 ; $i++) { last if eof(HTML); $_= ; if (/' . "\n"; print HTML '' . "\n"; print HTML "\t" . '' . "\n"; print HTML "\t\t" . '' . "\n"; print HTML "\t\t" . '' . "\n"; # Not functional in HTML file in modern web browsers - use HTTP header instead # # # # # print HTML "\t\t" . '' . "$$rcfg{'title'}{$router}" . '' . "\n"; foreach $peri (qw(d w m y)) { print HTML <<"TEXT"; TEXT if ($$rcfg{'options'}{'dorelpercent'}{$router} and defined $$maxpercent{$peri}{$router}) { print HTML <<"TEXT"; TEXT } print HTML <<"TEXT"; TEXT if ($$rcfg{'options'}{'dorelpercent'}{$router}) { print HTML <<"TEXT"; TEXT } print HTML "\n" if defined $$cuin{$peri}{$router}; print HTML "\n" if defined $$cuout{$peri}{$router}; if ($$rcfg{'options'}{'dorelpercent'}{$router} and $$cupercent{$peri}{$router} ) { print HTML <<"TEXT"; TEXT } print HTML <<"TEXT" if $$avmxin{$peri}{$router} and $$avmxout{$peri}{$router}; TEXT } $namestring = "'$name'" if $name; defined $$rcfg{backgc}{$router} or $$rcfg{backgc}{$router} = "#fff"; $$rcfg{'rgb1'}{$router} = "" unless defined $$rcfg{'rgb1'}{$router}; $$rcfg{'rgb2'}{$router} = "" unless defined $$rcfg{'rgb2'}{$router}; $$rcfg{'rgb3'}{$router} = "" unless defined $$rcfg{'rgb3'}{$router}; $$rcfg{'rgb4'}{$router} = "" unless defined $$rcfg{'rgb4'}{$router}; $$rcfg{'rgb5'}{$router} = "" unless defined $$rcfg{'rgb5'}{$router}; $$rcfg{'rgb6'}{$router} = "" unless defined $$rcfg{'rgb6'}{$router}; print HTML " "; # allow for \n in addhead defined $$rcfg{addhead}{$router} or $$rcfg{addhead}{$router} = ""; defined $$rcfg{pagetop}{$router} or $$rcfg{pagetop}{$router} = ""; if (defined $$rcfg{bodytag}{$router}) { if ($$rcfg{bodytag}{$router} !~ / "; print HTML " $$rcfg{bodytag}{$router} $$rcfg{'pagetop'}{$router} "; print HTML "

"; if (defined $$rcfg{'timezone'}{$router}){ print HTML &$LOC("The statistics were last updated $Today $$rcfg{'timezone'}{$router}"); } else { print HTML &$LOC("The statistics were last updated $Today"); } if ($uptime and ! $$rcfg{options}{noinfo}{$router}) { print HTML ",
\n". &$LOC("at which time $namestring had been up for $uptime.") } print HTML "

"; my %sample= ('d' => "`Daily' Graph (".$interval.' Minute', 'w' => "`Weekly' Graph (30 Minute", 'm' => "`Monthly' Graph (2 Hour", 'y' => "`Yearly' Graph (1 Day"); my %full = ('d' => 'day', 'w' => 'week', 'm' => 'month', 'y' => 'year'); my $InCo; if (!(defined $$rcfg{'options'}{'noi'}{$router})) { if (exists $$rcfg{'legendi'}{$router}) { if ($$rcfg{'legendi'}{$router} ne "") { $InCo=$$rcfg{'legendi'}{$router}; } } else { $InCo=&$LOC("In"); } } my $OutCo; if (!(defined $$rcfg{'options'}{'noo'}{$router})) { if (exists $$rcfg{'legendo'}{$router}) { if ($$rcfg{'legendo'}{$router} ne "") { $OutCo=$$rcfg{'legendo'}{$router}; } } else { $OutCo=&$LOC("Out"); } } my $PercentCo; if (defined $$rcfg{'legend5'}{$router}) { if ($$rcfg{'legend5'}{$router} ne "") { $PercentCo=$$rcfg{'legend5'}{$router}; } } else { $PercentCo=&$LOC("Percentage"); } foreach $peri (qw(d w m y)) { next if defined $$rcfg{'suppress'}{$router} and $$rcfg{'suppress'}{$router} =~/$peri/; my $gifw; if ($$rcfg{'options'}{'dorelpercent'}{$router}) { $gifw=sprintf("%.0f",($$rcfg{'xsize'}{$router}*$$rcfg{'xscale'}{$router}+ +100+30) *$$rcfg{'xzoom'}{$router}); } else { $gifw=sprintf("%.0f",($$rcfg{'xsize'}{$router}*$$rcfg{'xscale'}{$router} +100) *$$rcfg{'xzoom'}{$router}); } my $gifh=sprintf("%.0f",($$rcfg{'ysize'}{$router}*$$rcfg{'yscale'}{$router}+35) *$$rcfg{'yzoom'}{$router}); # take the image directory away from the html directory to give us relative links my $imagepath = ( $cfg->{htmldir} ne $cfg->{imagedir} ) ? "$dirrel$$cfg{imagehtml}$$rcfg{directory_web}{$router}" : ""; print HTML "

".&$LOC("$sample{$peri}").&$LOC(' Average)')."

\"$full{$peri}\" "; my(@foo)=($rcfg,$LOC); print HTML " " if $InCo; print HTML " " if $OutCo; print HTML " " if ($$rcfg{'options'}{'dorelpercent'}{$router} and $PercentCo); print HTML " " if ($$rcfg{'options'}{'avgpeak'}{$router} and $InCo and $OutCo); print HTML "
" . &$LOC("Max") . " " . &$LOC("Average") . " " . &$LOC("Current") . "
" . $InCo . " ".&fmi($$maxin{$peri}{$router}, $$rcfg{'maxbytes1'}{$router}, $router, @foo)." ".&fmi($$avin{$peri}{$router}, $$rcfg{'maxbytes1'}{$router}, $router, @foo)." ".&fmi($$cuin{$peri}{$router}, $$rcfg{'maxbytes1'}{$router}, $router, @foo)."
" . $OutCo . " ".&fmi($$maxout{$peri}{$router}, $$rcfg{'maxbytes2'}{$router}, $router, @foo)." ".&fmi($$avout{$peri}{$router}, $$rcfg{'maxbytes2'}{$router}, $router, @foo)." ".&fmi($$cuout{$peri}{$router}, $$rcfg{'maxbytes2'}{$router}, $router, @foo)."
" . $PercentCo . " ".sprintf("%0.1f %%",($$maxpercent{$peri}{$router} || 0))." ".sprintf("%0.1f %%",($$avpercent{$peri}{$router} || 0 ))." ".sprintf("%0.1f %%",($$cupercent{$peri}{$router} || 0 ))."
" . &$LOC("Average max 5 min values for $sample{$peri} interval):") . " $InCo " . &fmi($$avmxin{$peri}{$router}, $$rcfg{'maxbytes1'}{$router}, $router, @foo) . "/ $OutCo " . &fmi($$avmxout{$peri}{$router}, $$rcfg{'maxbytes2'}{$router}, $router, @foo) . "
\n"; } if (!(defined $$rcfg{'options'}{'nolegend'}{$router})) { print HTML "
"; print HTML " " if $InCo; print HTML " " if $OutCo; if ($$rcfg{'withpeak'}{$router}) { print HTML " " if $InCo; print HTML " " if $OutCo; } if ($$rcfg{'options'}{'dorelpercent'}{$router}) { print HTML " "; } print HTML "
$$rcfg{'col1'}{$router} ### $leg1
$$rcfg{'col2'}{$router} ### $leg2
$$rcfg{'col3'}{$router} ### $leg3
$$rcfg{'col4'}{$router} ### $leg4
$$rcfg{'col5'}{$router} ### $leg5
"; } if (!(defined $$rcfg{'options'}{'nobanner'}{$router})) { my $gifPath; if (defined $$cfg{icondir}) { $gifPath = $$cfg{icondir}; #lets make sure there is a trailing path separator $gifPath =~ s|/*$|/|; } else { $gifPath = "$dirrel$$cfg{imagehtml}"; } print HTML< TEXT } print HTML $$rcfg{'pagefoot'}{$router} if defined $$rcfg{'pagefoot'}{$router}; print HTML < TEXT close HTML; if (defined $$cfg{'writeexpires'} and $$cfg{'writeexpires'} =~ /^y/i) { open(HTMLG, ">$$cfg{'htmldir'}$$rcfg{'directory'}{$router}$router.". "$$rcfg{'extension'}{$router}.meta") || do { warn "$NOW: WARNING: Writing $$cfg{'htmldir'}$$rcfg{'directory'}{$router}$router.". "$$rcfg{'extension'}{$router}.meta: $!\n"; next }; print HTMLG "Expires: $expiration\n"; close(HTMLG); } } sub printusage { print < mrtg-2.17.7 - Multi Router Traffic Grapher Copyright 1995-2006 by Tobias Oetiker Licensed under the Gnu GPL. If you want to know more about this tool, you might want to read the docs. You can find everything on the mrtg website: http://oss.oetiker.ch/mrtg/ USAGEDESC exit(1); } sub lockit { my ($lockfile,$templock) = @_; if ($MRTG_lib::OS eq 'VMS' or $MRTG_lib::OS eq 'NT' or $MRTG_lib::OS eq 'OS2') { # too sad NT and VMS can't do links we'll do the diletants lock if (-e $lockfile and not unlink $lockfile) { my($lockage) = time()-(stat($lockfile))[9]; die "$NOW: ERROR: I guess another mrtg is running. A lockfile ($lockfile)\n". " aged $lockage seconds is hanging around and I can't remove\n". " it because another process is still using it."; } open (LOCK, ">$lockfile") or die "$NOW: ERROR: Creating lockfile $lockfile: $!\n"; print LOCK "$$\n"; close LOCK; open (LOCK, "<$lockfile") or die "$NOW: ERROR: Reading lockfile $lockfile for owner check: $!\n"; my($read)=; chomp($read); die "$NOW: ERROR: Someone else just got the lockfile $lockfile\n" unless $$ == $read; } else { # now, lets do it the UNIX way ... Daves work ... open(LOCK,">$templock") or die "$NOW: ERROR: Creating templock $templock: $!"; $main::Cleanfile = $templock; if (!link($templock,$lockfile)) { # Lock file exists - deal with it. my($nlink,$lockage) = (stat($lockfile))[3,9]; $lockage = time() - $lockage; if ($nlink < 2 or $lockage > 30*60) { #lockfile is alone and old unlink($lockfile) || do{ unlink $templock; die "$NOW: ERROR: Can't unlink stale lockfile ($lockfile). Permissions?\n"}; link($templock,$lockfile) || do{ unlink $templock; die "$NOW: ERROR: Can't create lockfile ($lockfile).\n". "Permission problem or another mrtg locking succesfully?\n"}; } else { unlink $templock; die "$NOW: ERROR: It looks as if you are running two copies of mrtg in parallel on\n". " the same config file. There is a lockfile ($lockfile) and it is\n". " is only $lockage seconds old ... Check your crontab.\n". " (/etc/crontab and /var/spool/cron/root) \n" if $lockage < 4; die "$NOW: ERROR: I guess another mrtg is running. A lockfile ($lockfile) aged\n". "$lockage seconds is hanging around. If you are sure that no other mrtg\n". "is running you can remove the lockfile\n"; } } } } sub threshmail ($$$$){ my $server = shift; my $from = shift; my $to = shift; my $message = shift; debug('base',"sending threshmail from $from to $to"); my $smtp = Net::SMTP->new([split /\s*,\s*/, $server],Timeout=>5) or do { warn "$NOW: ERROR: could not send thresholdmail to $to"; return }; $smtp->mail($from); $smtp->to(split(/\s*,\s*/, $to)); $smtp->data(); $smtp->datasend($message); $smtp->dataend(); $smtp->quit; } sub threshcheck { # threshold checking by Tom Muggli # ... fsck'd up but fixed by Juha Laine my ($cfg,$rcfg,$cfgfile,$router,$cuin,$cuout) = @_; my $threshfile; my %cu = ( i=> $cuin, o=>$cuout ); # are we going to keep state ? if (defined $$cfg{'threshdir'}){ ensureSL(\$$cfg{'threshdir'}); $threshfile = $$cfg{'threshdir'}.(split /\Q$MRTG_lib::SL\E/, $cfgfile)[-1].".$router"; } # setup environment for external scripts if (defined $rcfg->{'threshdesc'}{$router}) { $ENV{THRESH_DESC}=$rcfg->{'threshdesc'}{$router}; } else { delete $ENV{THRESH_DESC}; } if (defined $rcfg->{'hwthreshdesc'}{$router}) { $ENV{HWTHRESH_DESC}=$rcfg->{'hwthreshdesc'}{$router}; } else { delete $ENV{HWTHRESH_DESC}; } for my $dir (qw(i o)){ # in and out my %thresh = ( thresh => $cu{$dir}{d}{$router}, # if we are looking at an rrd with holtwinters RRAs # we get a failures count. hwthresh => $cu{$dir}{d_hwfail}{$router} ); for my $type (keys %thresh){ for my $bound (qw(min max)){ # it gets set inside the loop because threshval gets modified further # down in the code my $threshval = $thresh{$type}; next if not defined $threshval; my $boundval = $rcfg->{$type.$bound.$dir}{$router}; next unless defined $boundval; my $realval = ""; my $realthresh = ""; if ($boundval =~ s/%$//) { # defined in % of maxbytes # 2 decimals in % $realval = "% ($threshval)"; $realthresh = "% (".sprintf("%.1f",($rcfg->{"maxbytes".($dir eq 'i' ? 1 : 2)}{$router} * $boundval / 100)).")"; $threshval = sprintf "%.1f", ($threshval / $rcfg->{"maxbytes".($dir eq 'i' ? 1 : 2)}{$router} * 100); # the new code } my $msghead = ""; $msghead = "From: $cfg->{threshmailsender}\nTo: $rcfg->{${type}.'mailaddress'}{$router}" if $rcfg->{${type}.'mailaddress'}{$router} and $cfg->{threshmailsender}; my $pagetop = $rcfg->{pagetop}{$router} || ''; $pagetop =~ s|

.*?

||; $pagetop =~ s|\s*\s*(.*?)\s*(.*?)\s*\s*|$1 $2\n|g; $pagetop =~ s|\s*<.+?>\s*\n?||g; my $msgbody = <{title}{$router} Target: $router Type: $type Direction: $dir Bound: $bound Threshold: $boundval$realthresh Current: $threshval$realval $pagetop MESSAGE $msgbody .= "\n$rcfg->{$type.'desc'}{$router}\n" if $rcfg->{$type.'desc'}{$router}; if (($bound eq 'min' and $boundval > $threshval) or ($bound eq 'max' and $boundval < $threshval)) { # threshold was broken... my $message = <{$type.'prog'.$dir}{$router}, $router, $rcfg->{$type.$bound.$dir}{$router}, $threshval,($rcfg->{$type.'desc'}{$router} ||"No Description")); # Check if we use the status file or not... if ( defined $threshfile ) { if ( not -e $threshfile.".".$type.$bound.uc($dir) ) { # Create a file to indicate a threshold problem for the time after the problem open THRESHTOUCH, ">".$threshfile.".".$type.$bound.uc($dir) or warn "$NOW: WARNING: Creating $threshfile.".$bound.uc($dir).": $!\n"; close THRESHTOUCH; if (defined $rcfg->{$type.'prog'.$dir}{$router}){ debug('base',"run threshprog$dir: ".(join ",",@exec)); system @exec; } threshmail $cfg->{threshmailserver},$cfg->{threshmailsender},$rcfg->{$type.'mailaddress'}{$router},$message if $rcfg->{$type.'mailaddress'}{$router} } else { debug('base',"NOT acting on BROKEN threshold since $threshfile.$type$bound$dir exists"); } } elsif ( not defined $cfg->{$type.'hyst'} or ($bound eq 'min' and $boundval - $cfg->{$type.'hyst'}* $boundval < $threshval) or ($bound eq 'max' and $boundval + $cfg->{$type.'hyst'}* $boundval > $threshval) ) { # no threshold dir so run on every 'break' if (defined $rcfg->{$type.'prog'.$dir}{$router}){ debug('base',"run ${type}prog$dir: ".(join ",",@exec)); system @exec; } threshmail $cfg->{threshmailserver},$cfg->{threshmailsender},$rcfg->{$type.'mailaddress'}{$router},$message if $rcfg->{$type.'mailaddress'}{$router}; } } else { # no threshold broken ... my @exec = ( $rcfg->{$type.'progok'.$dir}{$router}, $router, $rcfg->{$type.$bound.$dir}{$router}, $threshval); my $message = <{$type.$bound.$dir}{$router} vs $threshval) Threshold UN-BROKEN ------------------- $msgbody MESSAGE # Check if we use the status file or not... if ( defined $threshfile ) { if ( -e $threshfile.".".$type.$bound.uc($dir) ){ unlink "$threshfile.".$type.$bound.uc($dir); if (defined $rcfg->{$type.'progok'.$dir}{$router}){ debug('base',"run ${type}progok$dir: ".(join ",",@exec)); system @exec; } threshmail $cfg->{threshmailserver},$cfg->{threshmailsender},$rcfg->{$type.'mailaddress'}{$router},$message if $rcfg->{$type.'mailaddress'}{$router}; } } } } # for my $bound ... } # for my $type } # for my $dir } sub getexternal ($) { my $command = shift; my $in=undef; my $out=undef; my $uptime="unknown"; my $name="unknown"; open (EXTERNAL , $command."|") or warn "$NOW: WARNING: Running '$command': $!\n"; warn "$NOW: WARNING: Could not get any data from external command ". "'".$command. "'\nMaybe the external command did not even start. ($!)\n\n" if eof EXTERNAL; chomp( $in=) unless eof EXTERNAL; chomp( $out=) unless eof EXTERNAL; chomp( $uptime=) unless eof EXTERNAL; chomp( $name=) unless eof EXTERNAL; close EXTERNAL; # strip returned date $uptime =~ s/^\s*(.*?)\s*/$1/; $name =~ s/^\s*(.*?)\s*/$1/; # do we have numbers in the external programs answer ? if ( not defined $in ) { warn "$NOW: WARNING: Problem with External get '$command':\n". " Expected a Number for 'in' but nothing'\n\n"; } elsif ( $in eq 'UNKNOWN' ) { $in = undef; } elsif ( $in !~ /([-+]?\d+(.\d+)?)/ ) { warn "$NOW: WARNING: Problem with External get '$command':\n". " Expected a Number for 'in' but got '$in'\n\n"; $in = undef; } else { $in = $1; } if ( not defined $out ) { warn "$NOW: WARNING: Problem with External get '$command':\n". " Expected a Number for 'out' but nothing'\n\n"; } elsif ( $out eq 'UNKNOWN' ) { $out = undef; } elsif ( $out !~ /([-+]?\d+(.\d+)?)/ ) { warn "$NOW: WARNING: Problem with External get '$command':\n". " Expected a Number for 'out' but got '$out'\n\n"; $out = undef; } else { $out = $1; } debug('snpo',"External result:".($in||"undef")." out:".($out||"undef")." uptime:".($uptime||"undef")." name:".($name||"undef")); return ($in,$out,time,$uptime,$name); } sub getsnmparg ($$$$){ my $confcache = shift; my $target = shift; my $cfg = shift; my $populated = shift; my $retry = 0; my $hostname = $$target{Host}; my $hostkey = "$$target{Community}\@$$target{Host}$$target{SnmpOpt}"; if ($$target{ipv4only}) { if (not ( $hostname =~ /^\d+\.\d+\.\d+\.\d+$/ or gethostbyname $hostname) ){ warn "$NOW: WARNING: Skipping host $hostname as it does not resolve to an IPv4 address\n"; return 'DEADHOST'; } } else { if($hostname =~ /^\[(.*)\]$/) { # Numeric IPv6 address. Check that it's valid $hostname = substr($hostname, 1); chop $hostname; if(! inet_pton(AF_INET6(), $hostname)) { warn "$NOW: WARNING: Skipping host $hostname: invalid IPv6 address\n"; return 'DEADHOST'; } } else { # Hostname. Look it up my @res; my ($too,$port,$otheropts) = split(':', $$target{SnmpOpt}, 3); $port = 161 unless defined $port; @res = getaddrinfo($hostname, $port, Socket::AF_UNSPEC(), Socket::SOCK_DGRAM()); if (scalar (@res) < 5) { warn "$NOW: WARNING: Skipping host $hostname as it does not resolve to an IPv4 or IPv6 address\n"; return 'DEADHOST'; } } } RETRY: my @ifnum = (); my @OID = (); # Find apropriate Interface to poll from for my $i (0..1) { if ($$target{IfSel}[$i] eq 'If') { $ifnum[$i] = ".".$$target{Key}[$i]; debug('snpo',"simple If: $ifnum[$i]"); } elsif($$target{IfSel}[$i] eq 'None') { $ifnum[$i] = ""; } else { $$target{Key}[$i] =~ s/\s+$//; # no trainling whitespace in keys ... if (not defined readfromcache($confcache,$hostkey,$$target{IfSel}[$i],$$target{Key}[$i])) { debug('snpo',"($i) Populate ConfCache for $$target{Host}$$target{SnmpOpt}"); populateconfcache($confcache,"$$target{Community}\@$$target{Host}$$target{SnmpOpt}",$$target{ipv4only},1,$$target{snmpoptions}); $$populated{$hostname} = 1; # set cache population to true for this cycle and host } if (not defined readfromcache($confcache,$hostkey,$$target{IfSel}[$i],$$target{Key}[$i])) { warn "$NOW: WARNING: Could not match host:'$$target{Community}\@$$target{Host}$$target{SnmpOpt}' ref:'$$target{IfSel}[$i]' key:'$$target{Key}[$i]'\n"; return 'NOMATCH'; } else { $ifnum[$i] = ".".readfromcache($confcache,$hostkey,$$target{IfSel}[$i],$$target{Key}[$i]); debug('snpo',"($i) Confcache Match $$target{Key}[$i] -> $ifnum[$i]"); } } if ($ifnum[$i] !~ /^$|^\.\d+$/) { warn "$NOW: WARNING: Can not determine". " ifNumber for $$target{Community}\@$$target{Host}$$target{SnmpOpt} \tref: '$$target{IfSel}[$i]' \tkey: '$$target{Key}[$i]'\n"; return 'NOMATCH'; } } for my $i (0..1) { # add ifget methodes call for a cross check; for ($$target{IfSel}[$i]) { /^Eth$/ && do { push @OID, "ifPhysAddress".$ifnum[$i]; last }; /^Ip$/ && do { push @OID, "ipAdEntIfIndex".".".$$target{Key}[$i];last }; /^Descr$/ && do { push @OID, "ifDescr".$ifnum[$i]; last }; /^Type$/ && do { push @OID, "ifType".$ifnum[$i]; last }; /^Name$/ && do { push @OID, "ifName".$ifnum[$i]; last }; } push @OID ,$$target{OID}[$i].$ifnum[$i]; } # we also want to know uptime and system name unless we are if ( not defined $$cfg{nomib2} and $$cfg{logformat} ne 'rrdtool' ) { if ( $OID[0] !~ /^cache.+$/ and $OID[0] !~ /^\Q1.3.6.1.4.1.3495.1\E/ ) { push @OID, qw(sysUptime sysName); } else { push @OID, qw(cacheUptime cacheSoftware cacheVersionId) } } # pull that data debug('snpo',"SNMPGet from $$target{Community}\@$$target{Host}$$target{SnmpOpt} -- ".(join ",", @OID)); my @ret; # make sure we have no error messages hanging round. $SNMP_Session::errmsg = undef; $Net_SNMP_util::ErrorMessage = undef; my $targtemp = $$target{Community}.'@'.$$target{Host}.$$target{SnmpOpt}; $targtemp = v4onlyifnecessary($targtemp, $$target{ipv4only}); my @snmpoids = grep !/^(Pseudo|WaLK|GeTNEXT|CnTWaLK)|IndexPOS/, @OID; if (defined $$cfg{singlerequest}){ #LH local $BER::pretty_print_timeticks = 0; foreach my $oid (@snmpoids){ push @ret, snmpget($targtemp,$$target{snmpoptions},$oid); } } else { @ret = snmpget($targtemp,$$target{snmpoptions},@snmpoids); } my @newret; for (@OID) { /^PseudoZero$/ && do { push @newret, 0; next; }; /^PseudoOne$/ && do { push @newret, 1; next; }; s/^WaLK(\d*)// && do { my $idx = $1 || 0; my $oid=$_;push @newret, (split /:/, (snmpwalk($targtemp,$$target{snmpoptions},$oid))[$idx],2)[1]; debug('snpo',"snmpwalk '$oid' -> ".($newret[-1]||'UNDEF'));next}; s/^GeTNEXT// && do { my $oid=$_;push @newret, (split /:/, snmpgetnext($targtemp,$$target{snmpoptions},$oid),2)[1]; debug('snpo',"snmpgetnext '$oid' -> ".($newret[-1]||'UNDEF'));next}; s/^CnTWaLK// && do { my $oid=$_;my @insts= (snmpwalk($targtemp,$$target{snmpoptions},$_)); undef @insts if( $insts[1] || '') =~/no/i; push @newret, scalar @insts; debug('snpo',"snmpCountwalk '$oid' -> ".($newret[-1]||'UNDEF'));next}; /IndexPOS.*\.(\d*)/ && do { my $idx=$1; s/IndexPOS/$idx/; s/\.\d*$//; push @newret, snmpget($targtemp,$$target{snmpoptions},$_); debug('snpo', "snmpget of oid '$_' after replacement of IndexPOS"); next}; push @newret, shift @ret; } @ret = @newret; debug('snpo',"SNMPfound -- ".(join ", ", map {"'".($_||"undef")."'"} @ret)); $ret[-2] = $ret[-2].' '.$ret[-1] if $OID[-1] and $OID[-1] eq 'cacheVersionId'; my $time = time; my @final; # lets do some reality check for my $i (0..1) { # some ifget methodes call for a cross check; for ($$target{IfSel}[$i]) { /^Eth$/ && do { my $bin = shift @ret || 0xff; my $eth = unpack 'H*', $bin; my @eth; while ($eth =~ s/^..//){ push @eth, $&; } my $phys=join '-', @eth; if ($phys ne $$target{Key}[$i]) { debug('snpo', "($i) eth if crosscheck got $phys expected $$target{Key}[$i]"); if (not $retry) { $retry=1; # remove broken entry storeincache($confcache,$hostkey,$$target{IfSel}[$i],$$target{Key}[$i],undef); debug('repo',"($i) goto RETRY force if cache repopulation"); goto RETRY; } else { warn "$NOW: WARNING: could not match&get". " $$target{Host}$$target{SnmpOpt}/$$target{OID}[$i] for Eth $$target{Key}[$i]\n"; return 'NOMATCH'; } }; debug ('snpo',"($i) Eth crosscheck OK"); }; /^Ip$/ && do { my $if = shift @ret || 'none'; if ($ifnum[$i] ne '.'.$if) { debug('repo', "($i) IP if crosscheck got .$if expected $ifnum[$i]"); if (not $retry) { $retry=1; # remove broken entry storeincache($confcache,$hostkey,$$target{IfSel}[$i],$$target{Key}[$i],undef); debug('repo',"($i) goto RETRY force if cache repopulation"); goto RETRY; } else { warn "$NOW: WARNING: could not match&get". " $$target{Host}$$target{SnmpOpt}/$$target{OID}[$i] for IP $$target{Key}[$i]\n"; return 'NOMATCH'; } } debug ('snpo',"($i) IP crosscheck OK"); }; /^(Descr|Name|Type)$/ && do { my $descr = shift @ret || 'Empty'; $descr =~ s/[\0- ]+$//; # remove excess spaces and stuff if ($descr ne $$target{Key}[$i]) { debug('repo', "($i) $_ if crosscheck got $descr expected $$target{Key}[$i]"); if (not $retry) { $retry=1; # remove broken entry storeincache($confcache,$hostkey,$$target{IfSel}[$i],$$target{Key}[$i],undef); debug('repo',"($i) goto RETRY force if cache repopulation"); goto RETRY; } else { warn "$NOW: WARNING: could not match&get". " $$target{Host}$$target{SnmpOpt}/$$target{OID}[$i] for $_ '$$target{Key}[$i]'\n"; return 'NOMATCH'; } } debug ('snpo',"($i) $_ crosscheck OK"); }; } # no sense continuing here ... if there is no data ... if (defined $SNMP_Session::errmsg and $SNMP_Session::errmsg =~ /no response received/){ $SNMP_Session::errmsg = undef; warn "$NOW: WARNING: skipping because at least the query for $OID[0] on $$target{Host} did not succeed\n"; return 'DEADHOST'; } if (defined $Net_SNMP_util::ErrorMessage and $Net_SNMP_util::ErrorMessage =~ /No response from remote/){ $Net_SNMP_util::ErrorMessage = undef; warn "$NOW: WARNING: skipping because at least the query for $OID[0] on $$target{Host} did not succeed\n"; return 'DEADHOST'; } if ($$target{OID}[$i] =~ /if(Admin|Oper)Hack/) { push @final, ((shift @ret) == 1) ? 1:0; } else { push @final, shift @ret; } } my @res = ( @final,$time, @ret); # Convert in and out values to integers with a user-defined subroutine # specified by the Conversion target key if( $target->{ Conversion } ) { foreach my $ri( 0..1 ) { next unless defined $res[ $ri ]; local $SIG{__DIE__}; my $exp = "&MRTGConversion::$target->{ Conversion }( \$res[\$ri] )"; $res[ $ri ] = eval $exp; warn "$NOW: WARNING: evaluation of \"$exp\" failed\n$@\n" if $@; } } # have some cleanup first, it seems that some agents # are adding newlines to what they return map{ $_ =~ s/\n|\r//g if defined $_ } @res; map{ $_ =~ s/^\s+//g if defined $_ } @res; map{ $_ =~ s/\s+$//g if defined $_ } @res; # in and out should be numbers only for my $ri (0..1){ # for folks using rrdtool I am allowing numbers # with decimals here if ( defined $res[$ri] and $res[$ri] !~ /^[-+]?\d+(.\d+)?$/ ) { warn "$NOW: WARNING: Expected a number but got '$res[$ri]'\n"; $res[$ri] = undef; } } return @res; } # read target function ... sub readtargets ($$$) { my ($confcache,$target,$cfg) = @_; my $forks = $$cfg{forks}; my $trgnum = $#{$target}+1; if (defined $forks and $forks > 1 and $trgnum > 1){ $forks = $trgnum if $forks > $trgnum; my $split = int($trgnum / $forks) + 1; my @hand; # get them forks to work ... for (my $i = 0; $i < $forks;$i++) { local *D; my $sleep_count=0; my $pid; do { $pid = open(D, "-|"); unless (defined $pid) { warn "$NOW: WARNING cannot fork: $!\n"; die "$NOW: ERROR bailing out after 6 failed forkattempts" if $sleep_count++ > 6; sleep 10; } } until defined $pid; if ($pid) { # parent $hand[$i] = *D; # funky file handle magic ... debug ('fork',"Parent $$ after fork of child $i"); } else { # child debug ('fork',"Child $i ($$) after fork"); my $res = ""; my %deadhost; my %populated; for (my $ii = $i * $split; $ii < ($i+1) * $split and $ii < $trgnum; $ii++){ my $targ = $$target[$ii]; my @res; if ($$targ{Methode} eq 'EXEC') { @res = getexternal($$targ{Command}); } else { # parent if (not $deadhost{$$targ{Community}.$$targ{Host}}) { @res = getsnmparg($confcache,$targ,$cfg,\%populated); if ( $res[0] and $res[0] eq 'DEADHOST') { # guess we got a blank here @res = ( undef,undef,time,undef,undef); $deadhost{$$targ{Community}.$$targ{Host}} = 1; warn "$NOW: WARNING: no data for $$targ{OID}[0]&$$targ{OID}[1]:$$targ{Community}\@$$targ{Host}. Skipping further queries for Host $$targ{Host} in this round.\n" } elsif ($res[0] and $res[0] eq 'NOMATCH'){ @res = (undef,undef,time,undef,undef); } } else { @res = ( undef,undef,time,undef,undef); } } for (my $iii=0;$iii<5;$iii++){ if (defined $res[$iii]){ $res .= "$res[$iii]\n"; } else { $res .= "##UNDEF##\n"; } } } debug ('fork',"Child $i ($$) waiting to deliver"); print $res; # we only talk after the work has been done to # otherwhise we might get blocked # return updated hosts from confcache writeconfcache($confcache,'&STDOUT') if defined $$confcache{___updated}; exit 0; } } # happy reaping ... my $vin =''; # vector of pipe file-descriptors from children for (my $i = 0; $i < $forks;$i++) { vec($vin, fileno($hand[$i]), 1) = 1; } my $left = $forks; while ($left) { my $rout = $vin; # read vector my $eout = $vin; # exception vector my $nfound = select($rout, undef, $eout, undef); # no timeout if (1 > $nfound) { die sprintf("$NOW: ERROR: select returned %d: $!\n", $nfound); } for (my $i = 0; $i < $forks; $i++) { next unless defined $hand[$i] and defined fileno($hand[$i]); # this does not seem to work reliably # if (vec($eout, fileno($hand[$i]), 1)) { # die "$NOW: ERROR: fork $i has died ahead of time?\n"; # } next unless vec($rout, fileno($hand[$i]), 1); vec($vin, fileno($hand[$i]), 1) = 0; # remove this child fd debug ('fork',"Parent reading child $i"); my $h = $hand[$i]; for (my $ii = $i * $split; $ii < ($i+1) * $split and $ii < $trgnum; $ii++){ my $targ = $$target[$ii]; my @res; for (0..4){ my $line = <$h>; # must be a simple scalar here else it wont work die "$NOW: ERROR: fork $i has died ahead of time ...\n" if not defined $line; chomp $line; # debug ('fork',"reading for $ii $line"); $line = undef if $line eq "##UNDEF##"; push @res,$line; }; ($$targ{_IN_}, $$targ{_OUT_}, $$targ{_TIME_}, $$targ{_UPTIME_}, $$targ{_NAME_}) = @res; if ($] >= 5.0061){ $$targ{_IN_} = Math::BigFloat->new($$targ{_IN_}) if $$targ{_IN_}; $$targ{_OUT_} = Math::BigFloat->new($$targ{_OUT_}) if $$targ{_OUT_}; } } # feed confcache entries my $lasthost =""; while (<$h>){ chomp; my ($host,$method,$key,$value) = split (/\t/, $_); if ($host ne $lasthost){ debug ('fork',"start clearing confcache on first entry for target $host"); clearfromcache($confcache,$host); debug ('fork',"finished clearing confcache"); } $lasthost = $host; storeincache($confcache,$host,$method,$key,$value); } close $h; --$left; } } } else { my %deadhost; my %populated; foreach my $targ (@$target) { if ($$targ{Methode} eq 'EXEC') { debug('snpo', "run external $$targ{Command}"); ($$targ{_IN_}, $$targ{_OUT_}, $$targ{_TIME_}, $$targ{_UPTIME_}, $$targ{_NAME_}) = getexternal($$targ{Command}); } elsif ($$targ{Methode} eq 'SNMP' and not $deadhost{$$targ{Host}}) { debug('snpo', "run snmpget from $$targ{OID}[0]&$$targ{OID}[1]:$$targ{Community}\@$$targ{Host}"); ($$targ{_IN_}, $$targ{_OUT_}, $$targ{_TIME_}, $$targ{_UPTIME_}, $$targ{_NAME_}) = getsnmparg($confcache,$targ,$cfg,\%populated); if ( $$targ{_IN_} and $$targ{_IN_} eq 'DEADHOST') { $$targ{_IN_} = undef; $$targ{_TIME_} =time; # guess we got a blank here $deadhost{$$targ{Host}} = 1; warn "$NOW: WARNING: no data for $$targ{OID}[0]&$$targ{OID}[1]:$$targ{Community}\@$$targ{Host}. Skipping further queries for Host $$targ{Host} in this round.\n" } if ( $$targ{_IN_} and $$targ{_IN_} eq 'NOMATCH') { $$targ{_IN_} = undef; $$targ{_TIME_} =time; } } else { $$targ{_IN_} = undef; $$targ{_OUT_} = undef; $$targ{_TIME_} = time; $$targ{_UPTIME_} = undef; $$targ{_NAME_} = undef; } if ($] >= 5.008 ){ $$targ{_IN_} = new Math::BigFloat "$$targ{_IN_}" if $$targ{_IN_}; $$targ{_OUT_} = new Math::BigFloat "$$targ{_OUT_}" if $$targ{_OUT_}; } } } } sub imggen ($) { my $dir = shift; if ( ! -r "$dir${main::SL}mrtg-l.png" and open W, ">$dir${main::SL}mrtg-l.png" ){ binmode W; print W unpack ('u', <<'UUENC'); MB5!.1PT*&@H -24A$4@ #\ 9! , !TA.O' &%!,5$5]9GTT M79A?8HB(9WFR;6G_=DWM=%35<5R_M[A2 6)+1T0'%F&(ZP 1%)1$%4 M>-JMDKUOPD ,Q:E4]CH?S0PMG8-.$6L1A*YMJ,DQ%5?[_ON>$Y (2$R>= MY;-^OF=;GNCM\SFY#_#GW$IU0WMP/#O5HSGNW8"9R)/92$NQ\Z:GUDG/@.@! M)CP#4H\ >LT>)NB!A0]8,"]&0.(#WY92V<\$=FM4WHV? J M=$58=%-I9VYA='5R90!D,C(W8S$dir${main::SL}mrtg-m.png" ){ binmode W; print W unpack ('u', <<'UUENC'); MB5!.1PT*&@H -24A$4@ !D 9! , VQYA0 &%!,5$5]9GTT M79A588QN9(*7:73_=DWL=%31-IC"$4"!0P$>"E&2B:I,%Z((!"(I$)X88H@GJ :A)<$Y@@*I8)YAA">("N( M%PYD"#L+"IJ#Y4!FN(86N4',# )J"4TO*R\O!_$"@::'@HTEQ /K _&$0+P M(.T:6@A2@62?H#B*6TRQN#,C9\ "IT15AT4VEG;F%T M=7)E #1E,S8X-S$P,38Q-S)A96%B.3,Y8SEA,F5D-31B86(U@DWZ,@ ET M15AT1&5L87D ,C4P(RC.$P YT15AT4&%G90 R-7@R-2LP*S"#D2 ? ) $E%3D2N0F"" UUENC close W; } if ( ! -r "$dir${main::SL}mrtg-r.png" and open W, ">$dir${main::SL}mrtg-r.png" ){ binmode W; print W unpack ('u', <<'UUENC'); MB5!.1PT*&@H -24A$4@ 80 9! , ##'$3) &%!,5$4T79@\ M8YQ5=JAPC;>:K\S\_?[5WNFZR-PB%CO! 6)+1T0'%F&(ZP !=5)1$%4 M>-KM6$ESVE@0;BW 58: K_)"YDH(.%=YL/"511)7O""N3AS!WY_^NM^3Q)+, M4C55DZJ1*2&]U]O7Z\,4T"]__0_A;US.O\7$$#H*PR=R[_'@#? A9X3KLW,K MNRX_*L=MGK^#PQ)[$USW+J@'U)E,!JIGS9"+RG0^B!(NDM,ZMB'G,M_?6 MF@^[_#7D[U)*!V_N!#+4$)>M@+Z(.J/1)PNAI4]=5K# 0_,)'Z(4UXH*A<#/ M2SQ\3+?[=!F4Q&M?Z!8@2-?4X+O*L^Q\$PPQ;MA.R= 65RRWR/:[U&#PTVR_ MR4*JI#3?9#E-$U)#W)5*GE&3[Y&!T%P+?QR2)U;UUOB(0@A0"-Z*.AOVC)=^ MY=L&-T/L"$G !,KBW"V-3PU[.\8W@5N7J)W(?L'&WB5LP6_*X10OP+2L21G/ MH7OA=#:1&M)F-1FI\^A+8B&H %;A+\U>LP8A)HN^RZOC9^BZY#=#_&10NA8" M.5E !^R7H/2R=0G!6PH%1\%-!W@<"GU+PN4Q.Z2D6-K!2-;D]-=J"-2H?&C> M7"B$ABCW."HV-PX@%"6$YHS<3:@.K!$K2;N"4-0A\)X'.R[?%R4$MXQ"TT9, M7*Y:IP)!8N0L=Z0IVUJH(9!51H%B1)9A]$%,S3QB[_P0 EA;B\ W*KM/%?%Q M%*@8'$*0K?Y@54'0*+ !XZ@&P;C'2N$W?SU&\BX"Z"YKH08ATBB,^WCHW[U5 MCK6U$!U'H3$WA9<<1\%+2I8R"H:]@ZT=Q8%9LK1%Z!1AA< XH902XAM@X#<; MA?;J) HL-F[!KJ*U%KC:DDD M_NQ\.9M$RD9?TE!AX\HJ8E,+IJE^'Q6+.H1X\K#A;V\FI.B6F=)&B$+E>'9T M51>-EPD:$VVE;#C>'CJ2:=.FJ?9>1_'20-CQ'_ESAN"5Y?Q4LT'=SFS?^.7* MYFU%;!(IFTP>!K!Q%=8AL.)7D@8/AL;K:#3A#LW#*>0H> <0(-"]YF!:*>X2 MUG&GD;D08Z0] ,((4XWG0O;)0-@2UTPW8@V^G0NG46BO^FNB,@I.7A$?EO.W MK+1)V<4R&H<$E]MR-K7@GD!HB;DL!4G%N>VP;?YJCPE?\''#<:4C!?S7?-IH MXG%'2N"B.&2Y/^M(*ZE!6PM.]L..5#4997R@5W:5"SWE* JV([U5 M*6W8H>EBM=_O>9B8CF2C0)M:.1L)@-*(!$_\^+C?S8TF.FJJO;6?& AP20*A MB6DR[*FS':G[7)5<_,&2F1-N>:+LBJ2^+#Z1Q1?WV02(3PR2/'*IX M=,_1!P+ %2,A]5Q'TBSR49QN_$P5\?$9Z>2 P=;DXN8B..I(+ 2/'76SBU,P MC9=6BD28R\^O)=+1&2E2"#/C%CX7M++)J&#SFMP*GD^F,_SCC+.'42'H+;&) M0LH]YTU8>HM#"!3_GI@4.:R%*_3\U\EC:M+I8_I]-,T&9*3H0:4U/RYG:-+9 M):7)9D,JX$_9*=,TW>)74"X0F@ Y50B83W?\'H!$"]80*XF;"P2P>%NUJ&2_ M?-51V7N3)7)?E!]B_$V:OMMJ^,+CZMYPMK??9;'SXBG"J;4C%PBPVX5^T]1" M6U-M/;K_[#>U>Q4<$?_I=?&SS>NP_(5\X=Z<FT="M4^ 0W1%6'13;V9T=V%R90! *",I26UA9V5-86=I8VL@ M-"XR+CD@.3DO,#DO,#$@8W)IC9 M\ "IT15AT4VEG;F%T=7)E #$R,3