#!/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=<hostname> --monport=<port>

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 <directory> B<Where is the top directory ?>

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 <directory> B<Where will go the cgi scripts in the local filesystem ?>

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 <path> B<What is the rrdcgi complete name (including its name) ?>

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 <directory> B<Where is the top directory for the cgi scripts ?>

If the --topdir option is absolute, ie it begins with "/", you
do not need this option.

 rrdmon --topdir=./foo \
        --topdirforcgi=../

--rrddir <directory> B<Where will go the rrd databases ?>

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 <directory> B<Where will go the rrd databases ?>

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 <directory> B<Where will go the images ?>

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 <directory> B<Where will go the images ?>

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 <directory> B<Where will go the images ?>

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

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 ($topdir, $topdirforcgi, $rrdDir, $cgiDir, $rrdcgi, $imagedir,
	$cgiuser, $rrddirforcgi, $imagedirforcgi, $imagedirforweb,
	$strftime, $greenlimit, $norefreshcgi, $checkcgi,
	%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 .= "<A HREF='$cgiFileName'>$periodString</A> ";
		}else{
			$fillet .= ""
				. "<font size=+3>"
				. "<A HREF='$cgiFileName'>$periodString</A></B> "
				. "</font>"
				;
		}
	}
	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 <<EOF;
\#!$rrdcgi
<RRD::GOODFOR $refreshFlag$refreshtime
>
<HTML>
	<HEAD>
		<TITLE>
			$monServer:$monPort $periodString MON server statistics 
		</TITLE>
	</HEAD>
	<BODY>
		<CENTER>
		<font size=+3>
 			<B>$monServer:$monPort</B> MON server statistics
		</font>
		</CENTER>
		<CENTER>$fillet</CENTER>
		<CENTER>
		<P>
EOF
		
		foreach my $watch (sort keys %s) {
			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 <<EOF;
<RRD::GRAPH $imageCompleteName
	--imginfo '<A HREF=\"$imageCompleteName\"><IMG SRC=$imagedirforweb/%s WIDTH=%lu HEIGHT=%lu ALT=\"$imagedirforcgi\/$imageBodyName\"></A>'
	--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\\:%1.0lf'
	GPRINT:centStatusAve:MIN:'min\\:%1.0lf'
	GPRINT:centStatusAve:MAX:'max\\:%1.0lf'
	GPRINT:centStatusAve:LAST:'last\\:%1.0lf'
	COMMENT:"    <RRD::TIME::LAST $rrddirforcgi/$rrdName.rrd \"$strftime\">"
	COMMENT:'\\n'
	COMMENT:'\\n'
	COMMENT:"MON server\:$monServer port\:$monPort interval\:${interval}s"
	COMMENT:"Group\:$watch service\:$service   "
>
EOF
			}
		}
		print NEWCGIFILE <<EOF;
		</P>
		</CENTER>
		<HR>
Page  created   on <B>$creationDatePosix</B> by <B>$getpwuid[0]</B><BR>
Page downloaded on <B><RRD::TIME::NOW \"$strftime\"></B><BR>
Page up to date until next <B>$refreshstr</B><BR>

	</BODY>
</HTML>
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 = "rrdtool 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 = "rrdtool last $rrdFile";
	($debugRRDstuff) and print "executing: $command\n";

	($statusCommand, $commandOutput) = execute($command);

	if ($commandOutput =~ /^-1/){
		warn
			"rrdtool 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 = "rrdtool 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 = "rrdtool";
	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,

		"cgiuser=s"            => \$cgiuser,
		"rrdcgipath=s"         => \$rrdcgi,
		"topdircgi=s"          => \$topdirforcgi,
		"rrddircgi=s"          => \$rrddirforcgi,
		"imagedircgi=s"        => \$imagedirforcgi,
		"imagedirweb=s"        => \$imagedirforweb,
		"strftime=s"           => \$strftime,
		"greenlimit=i"         => \$greenlimit,
		"norefreshcgi"         => \$norefreshcgi,
		"checkcgi"             => \$checkcgi,
	
		"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;

	$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';
	$greenlimit        = defined($greenlimit)        ?  $greenlimit     : 95;
	$norefreshcgi      = defined($norefreshcgi)      ?  $norefreshcgi   : 0;
	$checkcgi          = defined($checkcgi)          ?  $checkcgi       : 0;

	@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 "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 "greenlimit       : $greenlimit\n";
	($debugOptions) and print "norefreshcgi     : $norefreshcgi\n";
	($debugOptions) and print "checkcgi         : $checkcgi\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 <<EOF;

usage                 : $0 [options]

options
 --help               : print the command usage.
 --pidfile   <string> : the file where the processus identifier (PID)
                        is stored.
			default is /tmp/rrdmon<--monserver>+<--monport>.pid

 --monserver <string> : the MON server to contact.
                        default is Localhost.
 --monport      <int> : the MON port to contact.
                        default is 2583.
 --monuser   <string> : the MON user to authenticate.
                        default is none.
 --monpasswd <string> : the MON password to authenticate.
                        default is none.
 --noend              : run until the end of the universe unless killed.
 --monretry     <int> : interval between mon reconnection between failure.
                        when --noend option is used. default is 60 secondes.
 --interval     <int> : 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

 --topdir    <string> : the top directory to store the data
                        default is current directory ("./")
 --rrddir    <string> : the directory where are the round robin databases.
                        default is <--topdir>/rrdbases
 --cgidir    <string> : the directory where are the cgi scripts.
                        default is <--topdir>/cgi-bin
 --imagedir <string>  : the directory where are the images
                        default is <--topdir>/images
 --Drrd               : print rrd debug information.

 --rrdcgipath  <string>  : the shebang line (without "#!") where stay 
                           the rrdcgi binary.
                           default is /usr/local/bin/rrdcgi
 --cgiuser   <string>    : the user that runs cgi scripts on web server
                           default is "nobody"
 --topdircgi <string>    : the top directory viewed by the cgi scripts.
                           default is "../".
 --rrddircgi <string>    : the rrd directory viewed by the cgi scripts.
                           default is <--topdircgi>/rrdbases
 --imagedircgi <string>  : the images directory viewed by the cgi scripts.
                           default is <--topdircgi>/images
 --imagedirweb <string>  : the images directory viewed by the user on a browser.
                           default is <--imagedircgi>
 --strftime     <string> : the stfrtime string to use in graphics
                           default is '%A %d %B %Y %H:%M:%S %Z'
                           see stfrtime.3 (man stfrtime)
 --greenlimit      <int> : 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).

 --primaryperiod   <int> : 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=. \\
    --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 \\
    --cgiuser=nobody \\
    --topdircgi=.. \\
    --rrddircgi=rrdbases \\
    --imagedircgi=../images \\
    --imagedirweb=../images \\
    --strftime='%A %d %B %Y %H:%M:%S %Z' \\
    --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
}
