#!/usr/bin/perl -w =head1 NAME rrdmon - generate rrd bases from a remote mon server =head1 OVERVIEW RRDMON is a tool to graph the service statuses reported by MON, using RRDTOOL. rrdmon collects the data by the network, creates or updates the rrd bases and generates dynamic, good and meaningful graphical web pages. All was made to avoid configuration work. =head1 FEATURES =over =item * Keeps the database in a fixed size, thanks to RRDTOOL. =item * Generates different graphics to scale years, months, weeks, days and hours statistics or any other period. =item * Minimalize the configuration. In fact, in most of the cases, no configuration is needed at all. Information is searched on the fly. If the configuration MON server changes then RRDMON changes too, but it keeps the old statistics. =item * The data granularity is taken from the MON server. If a monitor is run every 3 minutes, rrdmon will query the server every 3 minutes for that service. It will input the data to rrdtool just one time per interval. =item * The graphics are created by a command line or on demand by a web interface. The graphics are only generated when it worth to do it. =back =head1 STARTING All was made to avoid configuration work. =head2 For the lazy joe Get MON and RRDTOOL, install them. Run a MON server on localhost, port 2583. untar the RRDMON archive where you want. Create a directory where you want to stand rrdmon data (images, cgi scripts, rrd databases). Choose a place exported by a web server, for example /usr/local/apache/htdocs/rrdmon/, if you want rrdmon be visible by others (you do, no?). Go in that directory and type: rrdmon This will create 3 directories named cgi-bin/, images/, and rrdbases/. If any problem occurs, rrdmon will complain on STDOUT or STDERR, depending on the problem. When there is no problem, rrdmon mutes (UNIX or chinese philosophy, if what you want to say is not better than the silence, keep quiet). If your MON server is not Localhost on port 2583 you can try: rrdmon --monserver= --monport= You can safely type (and read): rrdmon --help Go in the cgi-bin/ directory and type: echo | ./Localhost+2583+7200.cgi If there is no file, you are unlucky. Go to the next section. The png images (gif creations are uglyly patented, sorry) are created in the images/ directory. See them with a graphic tool (GIMP is good but so is Lynx). That is all folks. Have fun. =head2 For the webmaster joe Set you web server (Apache is good) to map .cgi extension with the cgi handler, or make directory cgi-bin/ become a cgi-bin directory. In other words, tell your web server that uppon client request, do not give the file itself but run it and give the stdout result to the client. Plus, the cgi user must have write permissions on the directory images/. The default cgi user is 'nobody'. So perhaps you will need a command like: chown nobody.nobody images/ or a less secure one (not so bad if PUT HTTP request is not allowed in the images/ directory, which is the default): chmod a+w images/ If the cgi user is not 'nobody', adjust the previous command with yours. If you do not understand what I am talking about, you should give up this reading, learn a bit about HTTP and CGI and come back. Let us go on, I speak too much. Run a web browser (Lynx is so cute for this job) and try one of the cgi scripts, by just going on the url you chose to map rrdmon, for example http://localhost/rrdmon/cgi-bin/. If you can see some beautifull pictures then you are lucky. Now, run rrdmon for eternity : rrdmon --noend That is all folks. Have fun. =head2 what can I do safely? =over =item * run : rrdmon --help =item * remove the images. rm images/* =item * remove the cgi scripts. rm cgi-bin/* =item * kill rrdmon with Ctrl-C or Ctrl-\ killall rrdmon =back =head2 what can I do unsafely? =over =item * remove the rrd bases. You will loose your data. =item * cross the road in England. =back =head2 For the unlucky joe If you are unluky with the previous setup or if you disagree with the default configuration, read on. You can configure rrdmon with several options. --topdir B This directory is the base with which all default paths will be prepend. Default is "./" rrdmon --topdir=/usr/local/apache/htdocs/rrdmon \ --imagedirforweb=/rrdmon/images --cgidir B use the --cgidir option. By default, it is <--topdir>/cgi-bin I do not think it will be a good value for you. if <--cgidir> begins with a slash ("/") then this path is absolute, else it is relative to <--topdir>. rrdmon have to havethe write permission on this directory. Example : rrdmon --topdir=/usr/local/apache/htdocs/rrdmon \ --cgidir=/usr/local/apache/cgi-bin \ --imagedirforweb=/rrdmon/images --rrdcgi B use the --rrdcgi option. RRDMON needs it to add the shebang line in the cgi scripts. The shebang line tells the UNIX system how to interpret the code (#!/usr/local/bin/rrdcgi). Example : rrdmon --rrdcgi=/usr/local/bin/rrdcgi --topdirforcgi B If the --topdir option is absolute, ie it begins with "/", you do not need this option. rrdmon --topdir=./foo \ --topdirforcgi=../ --rrddir B rrdmon uses this directory to store the round robin databases. By default, it is <--topdir>/rrdbases use the --rrddir option to set this directory. relative path is appended to <--topdir>. Example : rrdmon --rrddir=/usr/local/apache/htdocs/rrdmon/rrdbases --rrddirforcgi B No, this is not a copy and paste mistake. In fact a problem occurs in you use a relative path for the --rrddir option. The cgi scripts can not guess where the rrdbases files are. So there is this silly option. Is is the path used by the cgi scripts. By default, it is <--topdirforcgi>/rrdbases If you give an absolute path then it is an absolute path, else it is relative to <--topdirforcgi>. Most of the times, the web server does a chroot in the directory where is the cgi script, before running it; so you can give a relative path from the cgi directory. Example : rrdmon --rrddirforcgi=../rrdbases --imagedir B use the --imagedir option to set this directory. By default, it is it is <--topdir>/images You can give a relative path (appended to <--topdir> option) or an absolute one. The user id used by your web server to run the cgi scripts must have write permissions in this directory. Example : rrdmon --imagedir=/usr/local/apache/htdocs/rrdmon/images --imagedircgi B You know the story now. use the --imagedirforcgi option to set this directory. By default, it is <--topdirforcgi>/images You can give a relative path (appended to <--topdirforcgi>) or an absolute one. Example : rrdmon --imagedircgi=../images --imagedirweb B This is not a joke. The story gets complicated. The web user accesses the images by a different path, sometimes. use the --imagedirweb option to set this directory. By default, it is <--imagedircgi> option. Example : rrdmon --imagedirweb=../images =head1 LINKS RRDMON can be found at : ftp://ftp.linux-france.org/pub/prj/minotaure/ MON can be found at : http://www.kernel.org/software/mon/ The Linux kernel is also there because of Benedict, Jim, and Transmeta. RRDTOOL can be found at http://ee-staff.ethz.ch/~oetiker/webtools/rrdtool/ PERL can be found at http://www.perl.com =cut use strict; use English; use Getopt::Long; use DirHandle; use File::Basename; use POSIX qw(strftime); use lib('/users/gilles/public_html/minotaur-0.05/lib'); use Mon::Client; # General variables my ($help, $pidfile, $noEnd, $intervalLoop, $intervalLoopReal); # RRD and CGI variables my ( $rrdtoolpath, $topdir, $topdirforcgi, $rrdDir, $cgiDir, $rrdcgi, $imagedir, $cgiuser, $rrddirforcgi, $imagedirforcgi, $imagedirforweb, $strftime, ${numformat}, $greenlimit, $norefreshcgi, $checkcgi, $docpage, %rrdDirFiles, @rrdPeriods, @cgiNames, $primaryDataPointPeriod ); # MON variables my ($monServer, $monPort, $monConnectionStatus, $monRetry, $monUser, $monPasswd, $monServerTime, %s); # DEBUG variables my ($debugMonConnection, $debugRRDstuff, $debugOptions, $debugGENstuff); =head1 LITERATE PROGRAMING From here the documentation is not up to date since it is not up at all. I could not get Knut's book, out of print. 'quote for nedit. Prefer to see the ugly code :-) =cut =head1 GET OPTIONS I know that the variables names are often too long. I like that. It makes very difficult to keep the code in 80 columns. Nobody has never complain nor patch anything, nobody cares. If you add an option, please update the usage() function AND the default behavior AND the debugOption output. Thanks. =cut getoptions(); usage(), exit(0) if ($help); defaultvalues(); createpidfile(); checkconfig(); rrdcgiremove(); mainloop(); # burk ! ################################################################################ ################################## END OF MAIN ################################# ################################################################################ sub createpidfile { my $result = open PIDFILE, ">$pidfile"; unless ($result) { warn "Could not create PID file $pidfile\n"; return; }; print PIDFILE $$; close PIDFILE; } sub monstuff { =head1 MON CONNECTION We connect the MON server to ask it its local time, the operationnal status of every services. =cut my($monCommand, $mon, $minInterval); $mon = new Mon::Client ( host => $monServer, port => $monPort, username => $monUser, password => $monPasswd, ); unless(defined($mon->connect)){ warn "Could not connect to MON server $monServer on port $monPort\n", scalar(localtime), "\n", "Suggestion : Is it running ?\n", ; return(undef); } if (defined($monUser) and defined($monPasswd)){ $monCommand = $mon->login(); $debugMonConnection and print "MON login = [$monCommand]\n"; }else{ $debugMonConnection and print "No MON login\n"; } $monServerTime = $mon->servertime(); $debugMonConnection and print "MON server time : [$monServerTime]\n"; unless (defined($monServerTime)) { warn "Could not get MON server time. ", "Will use local time to update rrd bases. Not a nice choice !"; $monServerTime = time; } %s = $mon->list_opstatus; unless (defined(%s)) { warn "Could not get MON list_opstatus"; return undef; }; $monCommand = $mon->disconnect(); $debugMonConnection and print "MON disconnect = [$monCommand]\n"; dumpOpstatusVariables(%s) if ($debugMonConnection); $minInterval = getMinInterval(%s); $intervalLoopReal = max(min(int($minInterval), $intervalLoop), 1); $debugMonConnection and print "next loop in $intervalLoopReal secondes\n"; return 1; } sub getMinInterval { my(%s) = @_; my($minInterval) = 31536000000; # 1000 years ! May be I'll be dead ... foreach my $watch (keys %s) { foreach my $service (keys %{$s{$watch}}) { $minInterval = min($minInterval, $s{$watch}{$service}{'interval'}); } } return($minInterval); } sub min { my $min = shift(@_); if (defined($min)){ foreach my $value (@_) { $min = $value if $min > $value; } return $min; }else{ return undef; } } sub max { my $max = shift(@_); if (defined($max)){ foreach my $value (@_) { $max = $value if $max < $value; } return $max; }else{ return undef; } } sub rrdstuff { =head1 CREATE AND UPDATE RRD BASES We look at the rrddir directory to see if there is already the databases. If databases are missing we create them and we create the different entries for generating the graphics. By default four graphics are made, like in MRTG but you can override this behavior. 1 day is 86400 secondes. Graphics are : 2 hours are 2 * 60 min = 7200 s 2 days are 2 * 1 day = 172800 s 2 weeks are 14 * 1 day = 1209600 s 2 months are 62 * 1 day = 5356800 s 1 year is 366 * 1 day = 31622400 s 20 years are 20 * 365 days = 630720000 s =cut my($needToUpdateIncludeFiles); rrdreaddir(); foreach my $watch (keys %s) { foreach my $service (keys %{$s{$watch}}) { my($rrdFile, $filePrefix, $command, $statusCommand, $interval, $last_check, $opstatus); my(%rrd); $interval = $s{$watch}{$service}{'interval'}; $last_check = $s{$watch}{$service}{'last_check'}; $opstatus = $s{$watch}{$service}{'opstatus'}; # Sometimes MON have services with no monitor unless (isinteger($interval)){ warn "$watch $service has no interval. ", "Now interval is set with $intervalLoop secondes"; $s{$watch}{$service}{'interval'} = $intervalLoop; } %rrd = rrdprepare($interval, @rrdPeriods); $filePrefix = "$monServer" . "+$monPort" . "+${watch}" . "+${service}" . "+$s{$watch}{$service}{'interval'}"; $rrdFile = "$rrdDir/$filePrefix.rrd"; ($debugRRDstuff) and printf( "%-20s interval = %s\n" . " " x 20 . " last_check = %s\n" . " " x 20 . " opstatus = %s\n", "$watch $service", $interval, $last_check, $opstatus); unless (defined($rrdDirFiles{basename($rrdFile)})) { rrdcreate($rrdFile, $last_check, $interval, %rrd); ++$needToUpdateIncludeFiles; }else{ ($debugRRDstuff) and print "Already created $rrdFile\n"; } # Now we check if everything seems good rrdcheck($rrdFile, $last_check, $interval, %rrd); # Now we update the rrd base rrdupdate($rrdFile, $monServerTime, $last_check, $interval, $opstatus, %rrd); } } createCGIFiles($needToUpdateIncludeFiles); } sub formatperiod { my ($periodInSecondes) = @_; my $periodString; my $periodInMinutes = int($periodInSecondes/ 60); my $periodInHours = int($periodInSecondes/ 3600); my $periodInDays = int($periodInSecondes/ 86400); my $periodInWeeks = int($periodInSecondes/ 604800); my $periodInMonths = int($periodInSecondes/ 2419200); my $periodInYears = int($periodInSecondes/ 31536000); my $periodInCentury = int($periodInSecondes/ 3153600000); SWITCH: { $periodString = "$periodInCentury centuries", last SWITCH if ($periodInCentury > 1); $periodString = "$periodInCentury century", last SWITCH if ($periodInCentury == 1); $periodString = "$periodInYears years", last SWITCH if ($periodInYears > 1); $periodString = "$periodInYears year", last SWITCH if ($periodInYears == 1); $periodString = "$periodInMonths months", last SWITCH if ($periodInMonths > 1); $periodString = "$periodInMonths month", last SWITCH if ($periodInMonths == 1); $periodString = "$periodInWeeks weeks", last SWITCH if ($periodInWeeks > 1); $periodString = "$periodInWeeks week", last SWITCH if ($periodInWeeks == 1); $periodString = "$periodInDays days", last SWITCH if ($periodInDays > 1); $periodString = "$periodInDays day", last SWITCH if ($periodInDays == 1); $periodString = "$periodInHours hours", last SWITCH if ($periodInHours > 1); $periodString = "$periodInHours hour", last SWITCH if ($periodInHours == 1); $periodString = "$periodInMinutes minutes", last SWITCH if ($periodInMinutes > 1); $periodString = "$periodInMinutes minute", last SWITCH if ($periodInMinutes == 1); $periodString = "$periodInSecondes secondes", last SWITCH if ($periodInSecondes > 1); $periodString = "$periodInSecondes secondes", last SWITCH if ($periodInSecondes <= 1); } return $periodString; } sub fillet { my ($numBold) = @_; my $fillet; foreach my $num (1 .. scalar(@rrdPeriods)) { my $period = $rrdPeriods[$num-1]; my $cgiFileName = "$cgiNames[$num-1]"; my $periodString = formatperiod($period); unless ($numBold == $num) { $fillet .= "$periodString "; }else{ $fillet .= "" . "" . "$periodString " . "\n" ; } } return($fillet); } sub createCGIFiles { ($debugGENstuff) and print "create cgi files, if needed\n"; my $creationDate = localtime; my $creationDatePosix = strftime($strftime, localtime); my (@getpwuid) = getpwuid($REAL_USER_ID); my ($need) = @_; foreach my $num (1 .. scalar(@rrdPeriods)) { my $period = $rrdPeriods[$num-1]; my $cgiFileCompleteName = "$cgiDir/$cgiNames[$num-1]"; my $fillet = fillet($num); my($existFile) = stat($cgiFileCompleteName); ($debugGENstuff) and print "stat $cgiFileCompleteName : ", (defined($existFile)) ? 'exists' : 'does not exist, creating', "\n"; next unless($need or (not $existFile)); ($debugGENstuff) and print "update $cgiFileCompleteName : ", ($need) ? 'yes, updating' : 'yes, creating', "\n"; # create cgi file ($debugRRDstuff) and print "period $num : $rrdPeriods[$num-1] $cgiNames[$num-1]\n"; my $periodString = formatperiod($rrdPeriods[$num-1]); ($debugGENstuff) and print "period = $periodString\n"; # Since $primaryDataPointPeriod exist the following $refreshtime is wrong #my $refreshtime = $rrdPeriods[$num-1]/int(($rrdPeriods[0]/$intervalLoopReal)); # better one my $refreshtime = max($rrdPeriods[$num-1], $primaryDataPointPeriod) /int(($primaryDataPointPeriod/$intervalLoopReal)); ($debugGENstuff) and print "refresh time = $refreshtime s\n"; my $refreshstr = formatperiod($refreshtime); my $refreshFlag = ($norefreshcgi) ? "" : "-"; open NEWCGIFILE, ">$cgiFileCompleteName" or die "Could not create the cgi file $cgiFileCompleteName"; print NEWCGIFILE < $monServer:$monPort $periodString MON server statistics
$monServer:$monPort MON server statistics
$fillet

EOF foreach my $watch (sort keys %s) { print NEWCGIFILE "$watch\n"; # If you want a map of services. #foreach my $service (sort keys %{$s{$watch}}) {} } print NEWCGIFILE "

"; foreach my $watch (sort keys %s) { print NEWCGIFILE "\n"; foreach my $service (sort keys %{$s{$watch}}) { my($interval) = $s{$watch}{$service}{'interval'}; my($imageBodyName, $imageCompleteName, $rrdName); $imageBodyName = "$monServer+$monPort+$period+$watch+$service+$interval.png"; $imageCompleteName = "$imagedirforcgi/$imageBodyName"; $rrdName = "$monServer+$monPort+$watch+$service+$interval"; print NEWCGIFILE </\\ \"$imagedirforcgi\/$imageBodyName\"' --start -$period --lazy --title "$watch/$service for $periodString" --vertical-label 'STATUS in \%' --width 600 DEF:statusAve=$rrddirforcgi/$rrdName.rrd:opstatus:AVERAGE CDEF:centStatusAve=100,statusAve,* CDEF:greenCentStatusAve=centStatusAve,UN,UNKN,$greenlimit,centStatusAve,GT,UNKN,centStatusAve,IF,IF CDEF:redCentStatusAve=centStatusAve,UN,UNKN,$greenlimit,centStatusAve,GT,centStatusAve,UNKN,IF,IF CDEF:yelCentStatusAve=centStatusAve,UN,100,UNKN,IF DEF:statusMin=$rrddirforcgi/$rrdName.rrd:opstatus:MIN CDEF:centStatusMin=100,statusMin,* AREA:redCentStatusAve\#FF0000:"down" AREA:greenCentStatusAve\#00FF00:"up" AREA:yelCentStatusAve\#D0D0D0:"nop" HRULE:$greenlimit\#0000FF:$greenlimit COMMENT:' ' GPRINT:centStatusAve:AVERAGE:'ave\\:${numformat}lf' GPRINT:centStatusAve:MIN:'min\\:${numformat}lf' GPRINT:centStatusAve:MAX:'max\\:${numformat}lf' GPRINT:centStatusAve:LAST:'last\\:${numformat}lf' COMMENT:'\\n' COMMENT:"" COMMENT:'\\n' COMMENT:'\\n' COMMENT:"MON server\:$monServer port\:$monPort interval\:${interval}s" COMMENT:"Group\:$watch service\:$service " > \\/
EOF } } print NEWCGIFILE <


This page was Dates               RRDMON
Created on $creationDatePosix by $getpwuid[0] doc
Downloaded on download
Up to date until next $refreshstr author
EOF close NEWCGIFILE; chmod 0755, $cgiFileCompleteName; } } sub rrdreaddir { my $rrdDirHandle = new DirHandle $rrdDir; ($debugRRDstuff) and print("reading $rrdDir", "\n"); if (defined($rrdDirHandle)){ while (defined(my $name = $rrdDirHandle->read)) { ($debugRRDstuff) and print "-> $name \n"; if ($name =~ /\A.*\.rrd\Z/) { $rrdDirFiles{$name} = 1; ($debugRRDstuff) and print "-> RRD file : $name \n"; } } $rrdDirHandle->close; }else{ die "Could not read directory $rrdDir $!"; } } sub rrdprepare { my(%rrd, $interval, $period, $rows); # Argument reminder : # ($interval, period1, period2, ...) $interval = shift(@_); # All RRA have the same number of rows $rows = int(($primaryDataPointPeriod / $interval) + 0.5); my $count = 1; foreach my $item (@_) { $period = $item; $rrd{$count}{'period'} = $period; $rrd{$count}{'rows'} = $rows; # Can be 0 $rrd{$count}{'steps'} = int($rrd{$count}{'period'}/($rrd{$count}{'rows'} * $interval)); #$rrd{$count}{'stepInterval'} = $rrd{$count}{'steps'} * $interval; $count++; } return(%rrd); } sub rrdcreate { # Be careful, only one hash is allowed at the end my($rrdFile, $last_check, $interval, %rrd) = @_; my($command, $statusCommand, $commandOutput); ($debugRRDstuff) and print "Have to create $rrdFile\n"; unless (isinteger($last_check)) { warn "last_check not available\n", "Will create $rrdFile next time perhaps\n"; return; } print "Creating $rrdFile\n"; $command = "$rrdtoolpath create" . " \\\n" . " $rrdFile" . " \\\n" . " --start " . ($last_check -1) . " \\\n" . " --step " . $interval . "\\\n" . " DS:opstatus:GAUGE:" . (2*$interval) . ":0:1" . " \\\n" ; foreach my $count (1 .. scalar(@rrdPeriods)) { next if ($rrd{$count}{'steps'} == 0); $command .= "" . " \\\n" . " RRA:AVERAGE:0.5:" . $rrd{$count}{'steps'} . ":" . $rrd{$count}{'rows'} . " \\\n" . " RRA:MIN:0.5:" . $rrd{$count}{'steps'} . ":" . $rrd{$count}{'rows'} . " RRA:MAX:0.5:" . $rrd{$count}{'steps'} . ":" . $rrd{$count}{'rows'} . " RRA:LAST:0.5:" . $rrd{$count}{'steps'} . ":" . $rrd{$count}{'rows'} ; } ($debugRRDstuff) and print "$command\n"; ($statusCommand, $commandOutput) = execute($command); } sub rrdupdate { my(@output, $curent, $time, $value, $command, $statusCommand, $commandOutput, $lastupdate); # Be careful, only one hash is allowed at the end my($rrdFile, $monServerTime, $last_check, $interval, $opstatus, %rrd) = @_; # Do we need to update ? # I know this is UGLY $command = "$rrdtoolpath last $rrdFile"; ($debugRRDstuff) and print "executing: $command\n"; ($statusCommand, $commandOutput) = execute($command); if ($commandOutput =~ /^-1/){ warn "$rrdtoolpath last command failed\n", "output : [$commandOutput]\n"; return undef; }else{ chomp($lastupdate = $commandOutput); } ($debugRRDstuff) and print "LAST : [$lastupdate]\n"; if (defined($last_check) and (isinteger($last_check)) and($last_check > $lastupdate)) { # We need to update $command = "$rrdtoolpath update $rrdFile" . " --template opstatus $last_check:$opstatus"; ($debugRRDstuff) and print "executing $command\n"; ($statusCommand, $commandOutput) = execute($command); if($statusCommand){return undef}; }else{ # We do not need to update ($debugRRDstuff) and print "No update needed\n"; } } sub rrdcgiremove { ($debugGENstuff) and print "remove cgi files\n"; foreach my $num (1 .. scalar(@rrdPeriods)) { my($periodInDays); my($cgiFileCompleteName) = "$cgiDir/$cgiNames[$num-1]"; # remove cgi file unlink($cgiFileCompleteName); } } sub execute { my($command) = @_; my($commandOutput, $statusCommand); $commandOutput = `$command`; $statusCommand = $?/256; if($statusCommand){ warn "system call failed\n", "command : [$command]\n", "status : \$?=[$?]\, exit=[$statusCommand]\n", "output : [$commandOutput]\n"; return ($?, $commandOutput); }else{ return (0, $commandOutput); } } sub rrdcheck { # what a nice check ! # print "Hey honey, do not forget to write the rrdcheck() subroutine\n"; } sub dumpOpstatusVariables { my(%s) = @_; foreach my $watch (keys %s) { foreach my $service (keys %{$s{$watch}}) { foreach my $var (keys %{$s{$watch}{$service}}) { print "$watch $service $var=$s{$watch}{$service}{$var}\n"; } } } } sub isinteger { my ($int) = @_; return undef unless (defined($int)); if ($int =~ /\d+/) { return 1; }else{ return 0; } } sub appendir { # my($appendir, $dir) = @_; my($result); if ($dir =~ m|\A/|) { my $result = $dir; $result =~ s|//+|/|g; return($result); }else{ my $result = "$appendir/$dir"; $result =~ s|//+|/|g; return($result); } } sub checkconfig { ($debugGENstuff) and print "checking configuration...\n"; foreach my $dir ($topdir, $rrdDir, $cgiDir, $imagedir) { checkdir($dir); } checkrrdtool(); checkcgi() if ($checkcgi); ($debugGENstuff) and print "checking configuration done\n"; } sub checkrrdtool { my $command = $rrdtoolpath; my($statusCommand, $commandOutput) = execute($command); if ($statusCommand) { warn "rrdtool was not found. Please install it before running $0\n"; exit 1; }else{ ($debugGENstuff) and print "rrdtool is here, good !\n"; } } sub checkcgi { my $saveEUID = $EUID; my $myname = getpwuid($EUID); ($debugGENstuff) and print "This process has EUID $EUID -> I am $myname\n"; unless ($EUID == 0) { ($debugGENstuff) and print "Not enough permission to check the cgi directory properly\n", "run $0 as root or do the following stuff:\n", "- change the directory owner of [$imagedir] to [$cgiuser]:\n", " or make it writable by [$cgiuser].\n", " chown $cgiuser.$cgiuser $imagedir\n", " or\n", " chmod a+w $imagedir\n", "\n", "- go in the [$cgiDir] and try one of the cgi script with a command like\n", " echo | ./script.cgi\n", " it will create some images in the directory [$imagedir]\n", "\n"; return; } my ($cgiuseruid, $cgiusergid) = (getpwnam($cgiuser))[2,3]; ($debugGENstuff) and print "You want cgi EUID of $cgiuser -> $cgiuseruid\n"; ($debugGENstuff) and print "trying to change owner $imagedir to $cgiuser\n"; my $count = chown($cgiuseruid, $cgiusergid, $imagedir); unless ($count) { warn "change owner $imagedir to $cgiuser failed. do it yourself !\n"; }else{ ($debugGENstuff) and print "change owner $imagedir to $cgiuser : success\n"; } unless ($EUID == $cgiuseruid) { ($debugGENstuff) and print "changing EUID from $myname to $cgiuser, for test\n"; $EUID = $cgiuseruid; if ($EUID != $cgiuseruid) { ($debugGENstuff) and print "change failed\n"; return; }else{ ($debugGENstuff) and print "change succeeded\n", "but the coder is too lazy. Please wait until next release\n"; } ($debugGENstuff) and print "restoring EUID to $myname\n"; $EUID = $saveEUID; }else{ # EUID already set to $cgiuseruid } } sub checkdir { my($dir) = @_; unless (-e $dir) { print "Directory $dir does not exist, you should create it : mkdir $dir\n", "Ok, I try to do it for you with permission mode 0755 : "; if (mkdir($dir, 0755)) { print "done\n"; }else{ print "failed, do it for me please !\nbye bye, see you soon :-)\n"; exit 1; } }elsif(! -d $dir){ print "Directory $dir is not a directory. Change this please !\n"; exit 1; }elsif(! -w $dir){ print "Directory $dir is not writable. Change this please !\n", "(unless it is writable by $cgiuser)\n"; exit 1; }else { ($debugGENstuff) and print "Directory $dir seems ok\n"; } } sub getoptions { GetOptions( "help" => \$help, "pidfile=s" => \$pidfile, "monserver=s" => \$monServer, "monport=i" => \$monPort, "monuser=s" => \$monUser, "monpasswd=s" => \$monPasswd, "noend" => \$noEnd, "monretry=i" => \$monRetry, "interval=i" => \$intervalLoop, "topdir=s" => \$topdir, "rrddir=s" => \$rrdDir, "cgidir=s" => \$cgiDir, "imagedir=s" => \$imagedir, "rrdtoolpath=s" => \$rrdtoolpath, "cgiuser=s" => \$cgiuser, "rrdcgipath=s" => \$rrdcgi, "topdircgi=s" => \$topdirforcgi, "rrddircgi=s" => \$rrddirforcgi, "imagedircgi=s" => \$imagedirforcgi, "imagedirweb=s" => \$imagedirforweb, "strftime=s" => \$strftime, "numformat=s" => \${numformat}, "greenlimit=f" => \$greenlimit, "norefreshcgi" => \$norefreshcgi, "checkcgi" => \$checkcgi, "docpage=s" => \$docpage, "primaryperiod=i" => \$primaryDataPointPeriod, "rrdperiod=s@" => \@rrdPeriods, "cginame=s@" => \@cgiNames, "Dgen" => \$debugGENstuff, "Dopt" => \$debugOptions, "Drrd" => \$debugRRDstuff, "Dmon" => \$debugMonConnection, ); } sub defaultvalues { $monServer = defined($monServer) ? $monServer : 'Localhost' ; $monPort = defined($monPort) ? $monPort : 2583; $pidfile = defined($pidfile) ? $pidfile : '/tmp/rrdmon+'.$monServer.'+'.$monPort.'.pid'; $monRetry = defined($monRetry) ? $monRetry : 60; $intervalLoop = defined($intervalLoop) ? $intervalLoop : 300; $intervalLoopReal = $intervalLoop; $rrdtoolpath = defined($rrdtoolpath) ? $rrdtoolpath : 'rrdtool'; $topdir = defined($topdir) ? $topdir : './'; $rrdDir = defined($rrdDir) ? appendir($topdir, $rrdDir) : appendir($topdir, 'rrdbases'); $cgiDir = defined($cgiDir) ? appendir($topdir, $cgiDir) : appendir($topdir, 'cgi-bin'); $imagedir = defined($imagedir) ? appendir($topdir, $imagedir) : appendir($topdir, 'images'); $cgiuser = defined($cgiuser) ? $cgiuser : 'nobody'; $topdirforcgi = defined($topdirforcgi) ? $topdirforcgi : '../'; $rrddirforcgi = defined($rrddirforcgi) ? appendir($topdirforcgi, $rrddirforcgi) : appendir($topdirforcgi, 'rrdbases'); $imagedirforcgi = defined($imagedirforcgi) ? appendir($topdirforcgi, $imagedirforcgi) : appendir($topdirforcgi, 'images'); $imagedirforweb = defined($imagedirforweb) ? $imagedirforweb : $imagedirforcgi; $rrdcgi = defined($rrdcgi) ? $rrdcgi : '/usr/local/bin/rrdcgi'; $strftime = defined($strftime) ? $strftime : '%A %d %B %Y %H:%M:%S %Z'; ${numformat} = defined(${numformat}) ? ${numformat} : '%1.1'; $greenlimit = defined($greenlimit) ? $greenlimit : 95; $norefreshcgi = defined($norefreshcgi) ? $norefreshcgi : 0; $checkcgi = defined($checkcgi) ? $checkcgi : 0; $docpage = defined($docpage) ? $docpage : '../../doc/rrdmon.html'; @rrdPeriods = defined(@rrdPeriods) ? @rrdPeriods : qw(7200 172800 1209600 5356800 31622400 630720000); @cgiNames = defined(@cgiNames) ? @cgiNames : map("$monServer+$monPort+$_.cgi",@rrdPeriods); # Default is keep 2 days of primary data point, by default. $primaryDataPointPeriod = defined($primaryDataPointPeriod) ? $primaryDataPointPeriod : 172800; ($debugOptions) and print "rrdtoolpath : $rrdtoolpath\n"; ($debugOptions) and print "topdir : $topdir\n"; ($debugOptions) and print "rrdDir : $rrdDir\n"; ($debugOptions) and print "cgiDir : $cgiDir\n"; ($debugOptions) and print "topdircgi : $topdirforcgi\n"; ($debugOptions) and print "rrddircgi : $rrddirforcgi\n"; ($debugOptions) and print "imagedir : $imagedir\n"; ($debugOptions) and print "imagedircgi : $imagedirforcgi\n"; ($debugOptions) and print "imagedirweb : $imagedirforweb\n"; ($debugOptions) and print "rrdcgi : $rrdcgi\n"; ($debugOptions) and print "strftime : $strftime\n"; ($debugOptions) and print "numformat : ${numformat}\n"; ($debugOptions) and print "greenlimit : $greenlimit\n"; ($debugOptions) and print "norefreshcgi : $norefreshcgi\n"; ($debugOptions) and print "checkcgi : $checkcgi\n"; ($debugOptions) and print "docpage : $docpage\n"; ($debugOptions) and print "monServer : $monServer\n"; ($debugOptions) and print "monPort : $monPort\n"; ($debugOptions) and print "pidfile : $pidfile\n"; ($debugOptions) and print "monRetry : $monRetry\n"; ($debugOptions) and print "intervalLoop : $intervalLoop\n"; ($debugOptions) and print "rrdPeriods : @rrdPeriods\n"; ($debugOptions) and print "cgiNames : @cgiNames\n"; ($debugOptions) and print "primaryperiod : $primaryDataPointPeriod\n"; } sub mainloop { do { MAIN_LOOP: { # init the MON information %s = (); $monConnectionStatus = monstuff(); unless (defined($monConnectionStatus)){ if ($noEnd) { warn "Could not get information from the MON server\n", "Will attempt to reconnect in $monRetry secondes\n"; sleep($monRetry); redo MAIN_LOOP; }else{ warn "Could not get information from the MON server\n", "bye bye\n"; exit(1); } } rrdstuff(); if ($noEnd) { $debugGENstuff and print "sleeping for $intervalLoopReal s\n"; sleep($intervalLoopReal); } } } while ($noEnd); } sub usage { print < : the file where the processus identifier (PID) is stored. default is /tmp/rrdmon<--monserver>+<--monport>.pid --monserver : the MON server to contact. default is Localhost. --monport : the MON port to contact. default is 2583. --monuser : the MON user to authenticate. default is none. --monpasswd : the MON password to authenticate. default is none. --noend : run until the end of the universe unless killed. --monretry : interval between mon reconnection between failure. when --noend option is used. default is 60 secondes. --interval : interval between updates, in secondes. default is 300 secondes. The real interval is the minimal value between this and all the mon server intervals (how often monitors run). This value is taken if all services have no monitors or interval gretter than this value. --Dmon : print debugging values got from the MON connection --rrdtoolpath : path for rrdtool default is just rrdtool (supposed to be in the PATH variable) --topdir : the top directory to store the data default is current directory ("./") --rrddir : the directory where are the round robin databases. default is <--topdir>/rrdbases --cgidir : the directory where are the cgi scripts. default is <--topdir>/cgi-bin --imagedir : the directory where are the images default is <--topdir>/images --Drrd : print rrd debug information. --rrdcgipath : the shebang line (without "#!") where stay the rrdcgi binary. default is /usr/local/bin/rrdcgi --cgiuser : the user that runs cgi scripts on web server default is "nobody" --topdircgi : the top directory viewed by the cgi scripts. default is "../". --rrddircgi : the rrd directory viewed by the cgi scripts. default is <--topdircgi>/rrdbases --imagedircgi : the images directory viewed by the cgi scripts. default is <--topdircgi>/images --imagedirweb : the images directory viewed by the user on a browser. default is <--imagedircgi> --strftime : the stfrtime string to use in graphics default is '%A %d %B %Y %H:%M:%S %Z' see stfrtime.3 (man stfrtime) --numformat : the string format to use in graphics default is '%1.1' --greenlimit : the value above which status is green. default is 95 --norefreshcgi : disable the automatic refresh of the web page. --checkcgi : perform some check within the cgi directory and the cgi user (must be root to do this). --docpage : where is the documentation from the cgi-bin directory. default is ../../doc/rrdmon.html --primaryperiod : How long is the primary data point period in sec. Default is 172800 secondes (2 days) You should understand RRDTOOL before changing this. --rrdperiod : give the periods to keep. You must give one option per period. (see the default line below) defaults are 7200 : 2 hours 172800 : 2 days 1209600 : 2 weeks 5356800 : 2 months 31622400 : 1 year 630720000 : 20 years --cginame : the name of each cgi script for each <--rrdperiod>. You have to give the name in the same order as --rrdperiod options. You should not use this option. --Dgen : print general debug information. --Drrd : print rrd debug information. --Dmon : print debugging values got from the MON connection --Dopt : print option and variables values. with no option, the default behavior is exactly like the command: $0 \\ --rrddir=. \\ --rrdtoolpath=rrdtool \\ --interval=300 \\ --pidfile=/tmp/rrdmon+Localhost+2583.pid \\ --monserver=Localhost \\ --monport=2583 \\ --monretry=60 \\ --interval=300 \\ --topdir=. \\ --rrddir=rrdbases \\ --cgidir=cgi-bin \\ --imagedir=images \\ --rrdcgipath=/usr/local/bin/rrdcgi \\ --docpage=../../doc/rrdmon.html --cgiuser=nobody \\ --topdircgi=.. \\ --rrddircgi=rrdbases \\ --imagedircgi=../images \\ --imagedirweb=../images \\ --strftime='%A %d %B %Y %H:%M:%S %Z' \\ --numformat='%1.1' \\ --greenlimit=95 \\ --primaryperiod 172800 \\ --rrdperiod 7200 \\ --rrdperiod 172800 \\ --rrdperiod 1209600 \\ --rrdperiod 5356800 \\ --rrdperiod 31622400 \\ --rrdperiod 630720000 \\ --cginame Localhost+2583+7200.cgi \\ --cginame Localhost+2583+172800.cgi \\ --cginame Localhost+2583+1209600.cgi \\ --cginame Localhost+2583+5356800.cgi \\ --cginame Localhost+2583+31622400.cgi \\ --cginame Localhost+2583+630720000.cgi EOF }