#!/usr/bin/perl
#
# Monitorix - A lightweight system monitoring tool.
#
# Copyright (C) 2005-2012 by Jordi Sanfeliu <jordi@fibranet.cat>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#

require 5.006;

no strict "vars";
no warnings "once";
#use warnings;
use RRDs;
use POSIX;
use File::Basename;
use LWP::UserAgent;
use Socket;
use DBI;
use Getopt::Std;
use Cwd 'abs_path';
use locale;

# Force a standard locale
$ENV{LANG} = "";
setlocale(LC_TIME, "C");

# Signals to Trap and Handle
$SIG{'INT' } = 'INT_handler';
$SIG{'ABRT'} = 'INT_handler';
$SIG{'QUIT'} = 'INT_handler';
$SIG{'TRAP'} = 'INT_handler';
$SIG{'STOP'} = 'INT_handler';
$SIG{'TERM'} = 'INT_handler';
$SIG{'CHLD'} = 'CHLD_handler';
$SIG{'HUP' } = 'HUP_handler';
$SIG{'ALRM'} = 'ALRM_handler';

use constant VERSION	=> "2.5.1";
use constant RELDATE	=> "23-Apr-2012";

my @suppsys = ("Linux", "FreeBSD", "OpenBSD");
my @graphs;
my @graphs_debug;
my $root_disk;
my $root_disk_p;
my $os;
my $kernel_branch;

sub INT_handler {
	my ($signal) = @_;
	logger("SIG$signal caught, exiting.");
	exit(0);
}

sub CHLD_handler {
	my $pid = waitpid(-1, WNOHANG);
}

sub HUP_handler {
	my ($signal) = @_;
	my $myself = (caller(0))[3];
	logger("$myself: received SIG$signal.");
	close(STDOUT);
	close(STDERR);
	unless(open(STDOUT, ">> $LOG_FILE")) {
		logger("Can't write to LOG: $!");
	}
	unless(open(STDERR, ">> $LOG_FILE")) {	# >>&STDOUT XXX
		logger("Can't write to LOG: $!");
	}
	logger("$myself: reopening log file.");
}

sub ALRM_handler {
	my $myself = (caller(0))[3];
	my ($sec, $min, $hour, $mday) = localtime(time);

	if($sec == 0) {
		foreach my $g (@graphs) {
			my ($t) = split('_', $g);
			logger("$myself: calling $g()") unless !$opt_d;
			if(!grep {$_ eq $t} (@graphs_debug)) {
				undef($t);
			}
			eval {&$g($t);};
			if($@) {
				logger("$g(): $@");
			}
		}
		if($PC_LAN eq "Y" && $PC_ENABLE_MONTHLY_REPORTS eq "Y") {
			if($min == 0 && $hour == 0) {
				get_counters();
				if($mday == 1) {
					send_reports();
				}
			}
		}
	}
	alarm(1);
}

sub logger {
	my ($msg) = @_;

	$msg = localtime() . " - " . $msg;
	print("$msg\n");
}

sub daemonize {
	if(fork) {
		exit(0);	# parent exits
	}

	setsid();
	foreach(0 .. (sysconf(&POSIX::_SC_OPEN_MAX) || 1024)) {
		close($_);
	}
	unless(chdir("/")) {
		die("Can't chdir to /: $!");
	}
	unless(umask(022)) {
		die("Unable to umask 022: $!");
	}
#	unless(open(STDIN, "< /dev/null")) {
#		die("Can't read /dev/null: $!");
#	}
	unless(open(STDOUT, ">> $LOG_FILE")) {
		die("Can't write to LOG: $!");
	}
	unless(open(STDERR, ">> $LOG_FILE")) {	# >>&STDOUT XXX
		die("Can't write to LOG: $!");
	}
}

sub usage {
	print(STDERR << "EOF");
Usage: monitorix -c configfile [-p pidfile] [-d none | graph[,graph] | all ] [-v]

EOF
	exit(1);
}

sub create_index {
	my $myself = (caller(0))[3];

	my $n;
	my $gname;
	my %rgraphs = reverse %GRAPHS;

	if($THEME_COLOR eq "black") {
		$bgcolor = $BLACK{main_bg};
		$table_back_color = $BLACK{graph_bg};
		$title_back_color = $BLACK{title_bg};
		$title_fore_color = $BLACK{title_fg};
	} elsif(!$THEME_COLOR || $THEME_COLOR eq "white") {
		$bgcolor = "#ffffff";
		$table_back_color = "#CCCCCC";
		$title_back_color = "#777777";
		$title_fore_color = "#CCCC00";
	} else {
		logger("$myself: ERROR: invalid value in \$THEME_COLOR") unless !$opt_d;
	}

	if(!open(OUT, "> $BASE_DIR/index.html")) {
		die "unable to create ${BASE_DIR}index.html. $!";
	}
	print(OUT <<EOF);
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html>
 <head>
  <title>$TITLE</title>
  <link rel="shortcut icon" href="$FAVICON">
 </head>
 <body bgcolor="$bgcolor" text="#888888" vlink="#888888" link="#888888">
  <center>
  <p>
  <br>
  <font face="Verdana, sans-serif">
  <table>
    <tr>
      <td>
        <a href="http://www.monitorix.org/"><img src="logo_top.png" border="0"></a>
      </td>
    </tr>
    <tr>
      <td>
        <h2 align="right">v@{[VERSION]}</h2>
      </td>
    </tr>
  </table>
  <p>
  <form action="$BASE_CGI/monitorix.cgi" method="get">
    <table cellspacing="5" cellpadding="0" bgcolor="$table_back_color" border="1">
      <tr>
        <td bgcolor="$title_back_color">
          <font color="$title_fore_color">
<b>&nbsp;Hostname&nbsp;</b>
          </font>
       </td>
        <td bgcolor="$title_back_color">
          <font color="$title_fore_color">
<b>&nbsp;Graph&nbsp;</b>
          </font>
       </td>
     </tr>
      <tr>
EOF

	if(!$FAVICON) {
		logger("WARNING: the option \$FAVICON is missing in your configuration file. Do you really have the latest version?");
	}
	print(OUT "        <td bgcolor='$bgcolor'>\n");
	print(OUT "          <select name='mode' size='1'>\n");
	print(OUT "            <option value='localhost'>Local Host</option>\n");
	if(scalar(@REMOTEHOST_LIST) && $MULTIHOST eq "Y") {
		print(OUT "            <optgroup label='Multihost'>\n");
		print(OUT "            <option value='multihost.all'>All Hosts</option>\n");
		for($n = 0; $n < scalar(@REMOTEHOST_LIST); $n += 2) {
			print(OUT "              <option value='multihost." . ($n / 2). "'>" . $REMOTEHOST_LIST[$n] . "</option>\n");
                              }
		print(OUT "            </optgroup>\n");
	}

	if(scalar(@PC_LIST) && $PC_LAN eq "Y") {
		print(OUT "            <optgroup label='LAN PCs'>\n");
		print(OUT "              <option value='pc.all'>All LAN PCs</option>\n");
		for($n = 0; $n < scalar(@PC_LIST); $n++) {
			print(OUT "              <option value='pc." . $n . "'>" . $PC_LIST[$n] . "</option>\n");
                              }
		print(OUT "            </optgroup>\n");
	}
	print(OUT "          </select>\n");
	print(OUT "        </td>\n");



	print(OUT "        <td bgcolor='$bgcolor'>\n");
	print(OUT "          <select name='graph' size='1'>\n");
	print(OUT "            <option value='all'>All graphs</option>\n");
	foreach my $g (@GRAPH_NAME) {
		if($GRAPH_ENABLE{$g} eq "Y") {
			print(OUT "            <optgroup label='" . $GRAPH_TITLE{$g} . "'>\n");
			if($g eq "proc") {
				for($n = 0; $n < $PROC_MAX; $n++) {
					$gname = "_" . $g;
					print(OUT "              <option value='" . $gname . $n . "'>" . $rgraphs{$gname} . " " . $n . "</option>\n");
				}
				next;
			}
			if($g eq "net") {
				my $n2;
				for($n = 0; $n < scalar(@NET_LIST); $n++) {
					$gname = "_" . $g;
					for($n2 = 1; $n2 <= 3; $n2++) {
						$str = $rgraphs{$gname . $n2};
						$str =~ s/^/$NET_LIST[$n] /;
						print(OUT "              <option value='" . $gname . $n . $n2 . "'>" . $str . "</option>\n");
					}
				}
				next;
			}
			if($g eq "port") {
				for($n = 0; $n < $PORT_MAX && $n < scalar(@PORT_LIST); $n++) {
					$gname = "_" . $g;
					print(OUT "              <option value='" . $gname . $n . "'>" . $rgraphs{$gname} . " " . $PORT_LIST[$n] . " (" . $PORT_NAME[$n] . ")" . "</option>\n");
				}
				next;
			}
			if($g eq "fail2ban") {
				for($n = 0; $n < scalar(@FAIL2BAN_LIST); $n++) {
					$gname = "_" . $g;
					print(OUT "              <option value='" . $gname . $n . "'>" . ($FAIL2BAN_DESC[$n] ? $FAIL2BAN_DESC[$n] : $rgraphs{$gname}) . "</option>\n");
				}
				next;
			}
			$n = 0;
			foreach my $k (sort keys %rgraphs) {
				if($k =~ m/$g/) {
					$gname = "_" . $g . ++$n;
					if($rgraphs{$gname}) {
						print(OUT "              <option value='" . $gname ."'>" . $rgraphs{$gname} . "</option>\n");
					}
				}
			}
			print(OUT "            </optgroup>\n");
		}
	}
	print(OUT "          </select>\n");
	print(OUT "        </td>\n");


	print(OUT <<EOF);
     </tr>
    </table>
    <p>
    <table cellspacing="5" cellpadding="0" bgcolor="$table_back_color" border="1">
     <tr>
        <td bgcolor="$title_back_color">
          <input type="radio" checked name="when" value="1day">
            <font color="$title_fore_color"><b>Daily&nbsp;</b></font>
        </td>
        <td bgcolor="$title_back_color">
          <input type="radio" name="when" value="1week">
            <font color="$title_fore_color"><b>Weekly&nbsp;</b></font>
        </td>
        <td bgcolor="$title_back_color">
          <input type="radio" name="when" value="1month">
            <font color="$title_fore_color"><b>Monthly&nbsp;</b></font>
        </td>
        <td bgcolor="$title_back_color">
          <input type="radio" name="when" value="1year">
            <font color="$title_fore_color"><b>Yearly&nbsp;</b></font>
        </td>
      </tr>
    </table>
    <p>
    <input type="hidden" name="color" value="$THEME_COLOR">
    <input type="submit" value="    Ok    ">
  </form>
  </font>
  </center>
 </body>
</html>
EOF
	close(OUT);
}

# SYSTEM graph
# ----------------------------------------------------------------------------
sub system_init {
	my $myself = (caller(0))[3];

	if(!(-e $SYSTEM_RRD)) {
		logger("Creating '$SYSTEM_RRD' file.");
		eval {
			RRDs::create($SYSTEM_RRD,
				"--step=60",
				"DS:system_load1:GAUGE:120:0:U",
				"DS:system_load5:GAUGE:120:0:U",
				"DS:system_load15:GAUGE:120:0:U",
				"DS:system_nproc:GAUGE:120:0:U",
				"DS:system_npslp:GAUGE:120:0:U",
				"DS:system_nprun:GAUGE:120:0:U",
				"DS:system_npwio:GAUGE:120:0:U",
				"DS:system_npzom:GAUGE:120:0:U",
				"DS:system_npstp:GAUGE:120:0:U",
				"DS:system_npswp:GAUGE:120:0:U",
				"DS:system_mtotl:GAUGE:120:0:U",
				"DS:system_mbuff:GAUGE:120:0:U",
				"DS:system_mcach:GAUGE:120:0:U",
				"DS:system_mfree:GAUGE:120:0:U",
				"DS:system_macti:GAUGE:120:0:U",
				"DS:system_minac:GAUGE:120:0:U",
				"DS:system_val01:GAUGE:120:0:U",
				"DS:system_val02:GAUGE:120:0:U",
				"DS:system_val03:GAUGE:120:0:U",
				"DS:system_val04:GAUGE:120:0:U",
				"DS:system_val05:GAUGE:120:0:U",
				"RRA:AVERAGE:0.5:1:1440",
				"RRA:AVERAGE:0.5:30:336",
				"RRA:AVERAGE:0.5:60:744",
				"RRA:AVERAGE:0.5:1440:365",
				"RRA:MIN:0.5:1:1440",
				"RRA:MIN:0.5:30:336",
				"RRA:MIN:0.5:60:744",
				"RRA:MIN:0.5:1440:365",
				"RRA:MAX:0.5:1:1440",
				"RRA:MAX:0.5:30:336",
				"RRA:MAX:0.5:60:744",
				"RRA:MAX:0.5:1440:365",
				"RRA:LAST:0.5:1:1440",
				"RRA:LAST:0.5:30:336",
				"RRA:LAST:0.5:60:744",
				"RRA:LAST:0.5:1440:365",
			);
		};
		my $err = RRDs::error;
		if($@ || $err) {
			logger("$@") unless !$@;
			if($err) {
				logger("ERROR: while creating $SYSTEM_RRD: $err");
				if($err eq "RRDs::error") {
					logger("... is the RRDtool Perl package installed?");
				}
			}
			return;
		}
	}

	if($ENABLE_ALERTS eq "Y") {
		if(! -x $ALERT_LOADAVG_SCRIPT) {
			logger("$myself: ERROR: script '$ALERT_LOADAVG_SCRIPT' doesn't exist or don't has execute permissions.");
		}
	}

	our $system_hist = 0;
	push(@graphs, "system_update");
	if($opt_d eq "all" || $debug) {
		logger("$myself: Ok");
	}
}

sub system_update {
	my $myself = (caller(0))[3];
	my ($debug) = @_;

	my $load1;
	my $load5;
	my $load15;
	my $nproc;
	my $npslp;
	my $nprun;
	my $npwio;
	my $npzom;
	my $npstp;
	my $npswp;
	my $mtotl;
	my $mbuff;
	my $mcach;
	my $mfree;
	my $macti;
	my $minac;
	my $val01 = 0;
	my $val02 = 0;
	my $val03 = 0;
	my $val04 = 0;
	my $val05 = 0;

	my $rrdata = "N";

	$npwio = $npzom = $npstp = $npswp = 0;

	if($os eq "Linux") {
		open(IN, "/proc/loadavg");
		while(<IN>) {
			if(/^(\d+\.\d+) (\d+\.\d+) (\d+\.\d+) (\d+)\/(\d+)/) {
				$load1 = $1;
				$load5 = $2;
				$load15 = $3;
				$nprun = $4;
				$npslp = $5;
			}
		}
		close(IN);
		$nproc = $npslp + $nprun;

		open(IN, "/proc/meminfo");
		while(<IN>) {
			if(/^MemTotal:\s+(\d+) kB$/) {
				$mtotl = $1;
				next;
			}
			if(/^MemFree:\s+(\d+) kB$/) {
				$mfree = $1;
				next;
			}
			if(/^Buffers:\s+(\d+) kB$/) {
				$mbuff = $1;
				next;
			}
			if(/^Cached:\s+(\d+) kB$/) {
				$mcach = $1;
				last;
			}
		}
		close(IN);
		$macti = $minac = "";
	} elsif($os eq "FreeBSD") {
		my $page_size;
		open(IN, "sysctl -n vm.loadavg |");
		while(<IN>) {
			if(/^\{ (\d+\.\d+) (\d+\.\d+) (\d+\.\d+) \}$/) {
				$load1 = $1;
				$load5 = $2;
				$load15 = $3;
			}
		}
		close(IN);
		open(IN, "sysctl vm.vmtotal |");
		while(<IN>) {
			if(/^Processes:\s+\(RUNQ:\s+(\d+) Disk.*? Sleep:\s+(\d+)\)$/) {
				$nprun = $1;
				$npslp = $2;
			}
			if(/^Free Memory Pages:\s+(\d+)K$/) {
				$mfree = $1;
			}
		}
		close(IN);
		$nproc = $npslp + $nprun;
		$mtotl = `sysctl -n hw.realmem`;
		$mbuff = `sysctl -n vfs.bufspace`;
		$mcach = `sysctl -n vm.stats.vm.v_cache_count`;

		chomp($mbuff);
		$mbuff = $mbuff / 1024;
		chomp($mtotl);
		$mtotl = $mtotl / 1024;
		$page_size = `sysctl -n vm.stats.vm.v_page_size`;
		$macti = `sysctl -n vm.stats.vm.v_active_count`;
		$minac = `sysctl -n vm.stats.vm.v_inactive_count`;
		chomp($page_size, $mcach, $macti, $minac);
		$mcach = ($page_size * $mcach) / 1024;
		$macti = ($page_size * $macti) / 1024;
		$minac = ($page_size * $minac) / 1024;
	} elsif($os eq "OpenBSD") {
		open(IN, "sysctl -n vm.loadavg |");
		while(<IN>) {
			if(/^(\d+\.\d+) (\d+\.\d+) (\d+\.\d+)$/) {
				$load1 = $1;
				$load5 = $2;
				$load15 = $3;
			}
		}
		close(IN);
		open(IN, "top -b |");
		while(<IN>) {
			if(/ processes:/) {
				$_ =~ s/:/,/;
				my (@tmp) = split(',', $_);
				foreach(@tmp) {
					my ($num, $desc) = split(' ', $_);
					$nproc = $num unless $desc ne "processes";
					if($desc eq "idle" || $desc eq "stopped" || $desc eq "zombie") {
						$npslp += $num;
					}
					if($desc eq "running" || $desc eq "on") {
						$nprun += $num;
					}
				}
			}
			if(/^Memory: Real: (\d+)\w\/\d+\w act\/tot  Free: (\d+)\w  /) {
				$macti = $1;
				$mfree = $2;
				$macti = int($macti) * 1024;
				$mfree = int($mfree) * 1024;
				last;
			}
		}	
		close(IN);
		$mtotl = `sysctl -n hw.physmem`;
		chomp($mtotl);
		$mtotl = $mtotl / 1024;
	}

	chomp(
		$load1,
		$load5,
		$load15,
		$nproc,
		$npslp,
		$nprun,
		$npwio,
		$npzom,
		$npstp,
		$npswp,
		$mtotl,
		$mbuff,
		$mcach,
		$mfree,
		$macti,
		$minac,
	);

	# SYSTEM alert
	if($ENABLE_ALERTS eq "Y") {
		if(!$ALERT_LOADAVG_THRESHOLD || $load15 < $ALERT_LOADAVG_THRESHOLD) {
			$system_hist = 0;
		} else {
			if(!$system_hist) {
				$system_hist = time;
			}
			if($system_hist > 0 && (time - $system_hist) > $ALERT_LOADAVG_TIMEINTVL) {
				if(-x $ALERT_LOADAVG_SCRIPT) {
					system($ALERT_LOADAVG_SCRIPT . " " .$ALERT_LOADAVG_TIMEINTVL . " " . $ALERT_LOADAVG_THRESHOLD . " " . $load15);
				}
				$system_hist = time;
			}
		}
	}

	$rrdata .= ":$load1:$load5:$load15:$nproc:$npslp:$nprun:$npwio:$npzom:$npstp:$npswp:$mtotl:$mbuff:$mcach:$mfree:$macti:$minac:$val01:$val02:$val03:$val04:$val05";
	RRDs::update($SYSTEM_RRD, $rrdata);
	if($opt_d eq "all" || $debug) {
		logger("$myself: $rrdata");
	}
	my $err = RRDs::error;
	logger("ERROR: while updating $SYSTEM_RRD: $err") if $err;
}

# KERN graph
# ----------------------------------------------------------------------------
sub kern_init {
	my $myself = (caller(0))[3];

	if(!(-e $KERN_RRD)) {
		logger("Creating '$KERN_RRD' file.");
		eval {
			RRDs::create($KERN_RRD,
				"--step=60",
				"DS:kern_user:GAUGE:120:0:100",
				"DS:kern_nice:GAUGE:120:0:100",
				"DS:kern_sys:GAUGE:120:0:100",
				"DS:kern_idle:GAUGE:120:0:100",
				"DS:kern_iow:GAUGE:120:0:100",
				"DS:kern_irq:GAUGE:120:0:100",
				"DS:kern_sirq:GAUGE:120:0:100",
				"DS:kern_steal:GAUGE:120:0:100",
				"DS:kern_guest:GAUGE:120:0:100",
				"DS:kern_cs:COUNTER:120:0:U",
				"DS:kern_dentry:GAUGE:120:0:100",
				"DS:kern_file:GAUGE:120:0:100",
				"DS:kern_inode:GAUGE:120:0:100",
				"DS:kern_forks:COUNTER:120:0:U",
				"DS:kern_vforks:COUNTER:120:0:U",
				"DS:kern_val03:GAUGE:120:0:100",
				"DS:kern_val04:GAUGE:120:0:100",
				"DS:kern_val05:GAUGE:120:0:100",
				"RRA:AVERAGE:0.5:1:1440",
				"RRA:AVERAGE:0.5:30:336",
				"RRA:AVERAGE:0.5:60:744",
				"RRA:AVERAGE:0.5:1440:365",
				"RRA:MIN:0.5:1:1440",
				"RRA:MIN:0.5:30:336",
				"RRA:MIN:0.5:60:744",
				"RRA:MIN:0.5:1440:365",
				"RRA:MAX:0.5:1:1440",
				"RRA:MAX:0.5:30:336",
				"RRA:MAX:0.5:60:744",
				"RRA:MAX:0.5:1440:365",
				"RRA:LAST:0.5:1:1440",
				"RRA:LAST:0.5:30:336",
				"RRA:LAST:0.5:60:744",
				"RRA:LAST:0.5:1440:365",
			);
		};
		my $err = RRDs::error;
		if($@ || $err) {
			logger("$@") unless !$@;
			if($err) {
				logger("ERROR: while creating $KERN_RRD: $err");
				if($err eq "RRDs::error") {
					logger("... is the RRDtool Perl package installed?");
				}
			}
			return;
		}
	}

	# Since 2.2.0 two new values are used (kern_val01 and kern_val02) to
	# support 'forks' and 'vforks'. These two values need to be converted
	# to COUNTER and renamed.
	RRDs::tune($KERN_RRD,
		"--data-source-rename=kern_val01:kern_forks",
		"--data-source-type=kern_forks:COUNTER",
		"--maximum=kern_forks:U",
	);
	RRDs::tune($KERN_RRD,
		"--data-source-rename=kern_val02:kern_vforks",
		"--data-source-type=kern_vforks:COUNTER",
		"--maximum=kern_vforks:U",
	);

	our %kernel_hist = ();
	push(@graphs, "kern_update");
	if($opt_d eq "all" || $debug) {
		logger("$myself: Ok");
	}
}

sub kern_update {
	my $myself = (caller(0))[3];
	my ($debug) = @_;

	my $user;
	my $nice;
	my $sys;
	my $idle;
	my $iow;
	my $irq;
	my $sirq;
	my $steal;
	my $guest;
	my $cs;
	my $dentry;
	my $file;
	my $inode;
	my $forks;
	my $vforks;
	my $val03;
	my $val04;
	my $val05;
	
	my $lastuser = 0;
	my $lastnice = 0;
	my $lastsys = 0;
	my $lastidle = 0;
	my $lastiow = 0;
	my $lastirq = 0;
	my $lastsirq = 0;
	my $laststeal = 0;
	my $lastguest = 0;

	my $rrdata = "N";

	if($kernel_hist{'kernel'}) {
		(undef, $lastuser, $lastnice, $lastsys, $lastidle, $lastiow, $lastirq, $lastsirq, $laststeal, $lastguest) = split(' ', $kernel_hist{'kernel'});
	}

	if($os eq "Linux") {
		open(IN, "/proc/stat");
		while(<IN>) {
			if(/^cpu /) {
				(undef, $user, $nice, $sys, $idle, $iow, $irq, $sirq, $steal, $guest) = split(' ', $_);
				$kernel_hist{'kernel'} = $_;
				next;
			}
			if(/^ctxt (\d+)$/) {
				# avoid initial spike
				$cs = int($1) unless !$kernel_hist{'cs'};
				$kernel_hist{'cs'} = int($1) unless $kernel_hist{'cs'};
				next;
			}
			if(/^processes (\d+)$/) {
				# avoid initial spike
				$forks = int($1) unless !$kernel_hist{'forks'};
				$kernel_hist{'forks'} = int($1) unless $kernel_hist{'forks'};
				$vforks = 0;
				last;
			}
		}
		close(IN);
		open(IN, "/proc/sys/fs/dentry-state");
			while(<IN>) {
				if(/^(\d+)\s+(\d+)\s+/) {
					$dentry = ($2 * 100) / $1;
				}
			}
		close(IN);
		open(IN, "/proc/sys/fs/file-nr");
			while(<IN>) {
				if(/^(\d+)\s+\d+\s+(\d+)$/) {
					$file = ($1 * 100) / $2;
				}
			}
		close(IN);
		open(IN, "/proc/sys/fs/inode-nr");
			while(<IN>) {
				if(/^(\d+)\s+(\d+)$/) {
					$inode = ($2 * 100) / $1;
				}
			}
		close(IN);
	} elsif($os eq "FreeBSD") {
		my $max;
		my $num;
		my $data;

		my $cptime = `sysctl -n kern.cp_time`;
		chomp($cptime);
		my @tmp = split(' ', $cptime);
		($user, $nice, $sys, $iow, $idle) = @tmp;

		$kernel_hist{'kernel'} = join(' ', "cpu", $user, $nice, $sys, $idle, $iow);
		$data = `sysctl -n vm.stats.sys.v_swtch`;
		chomp($data);
		$cs = int($data) unless !$kernel_hist{'cs'};
		$kernel_hist{'cs'} = int($data) unless $kernel_hist{'cs'};

		$data = `sysctl -n vm.stats.vm.v_forks`;
		chomp($data);
		$forks = int($data) unless !$kernel_hist{'forks'};
		$kernel_hist{'forks'} = int($data) unless $kernel_hist{'forks'};

		$data = `sysctl -n vm.stats.vm.v_vforks`;
		chomp($data);
		$vforks = int($data) unless !$kernel_hist{'vforks'};
		$kernel_hist{'vforks'} = int($data) unless $kernel_hist{'vforks'};

		$max = `sysctl -n kern.maxfiles`;
		chomp($max);
		$num = `sysctl -n kern.openfiles`;
		chomp($num);
		$file = ($num * 100) / $max;

		$max = `sysctl -n kern.maxvnodes`;
		chomp($max);
		$num = `sysctl -n vfs.numvnodes`;
		chomp($num);
		$inode = ($num * 100) / $max;
	} elsif($os eq "OpenBSD") {
		my $max;
		my $num;
		my $data;

		my $cptime = `sysctl -n kern.cp_time`;
		chomp($cptime);
		my @tmp = split(',', $cptime);
		($user, $nice, $sys, $iow, $idle) = @tmp;
		$kernel_hist{'kernel'} = join(' ', "cpu", $user, $nice, $sys, $idle, $iow);
		open(IN, "vmstat -s |");
		while(<IN>) {
			if(/^\s*(\d+) cpu context switches$/) {
				$cs = int($1) unless !$kernel_hist{'cs'};
				$kernel_hist{'cs'} = int($1) unless $kernel_hist{'cs'};
				last;
			}
		}
		close(IN);

		$data = `sysctl -n kern.forkstat.forks`;
		chomp($data);
		$forks = int($data) unless !$kernel_hist{'forks'};
		$kernel_hist{'forks'} = int($data) unless $kernel_hist{'forks'};

		$data = `sysctl -n kern.forkstat.vforks`;
		chomp($data);
		$vforks = int($data) unless !$kernel_hist{'vforks'};
		$kernel_hist{'vforks'} = int($data) unless $kernel_hist{'vforks'};

		$max = `sysctl -n kern.maxfiles`;
		chomp($max);
		$num = `sysctl -n kern.nfiles`;
		chomp($num);
		$file = ($num * 100) / $max;

		$max = `sysctl -n kern.maxvnodes`;
		chomp($max);
		$data = `sysctl -n kern.malloc.kmemstat.vnodes`;
		($num) = ($data =~ m/^\(inuse = (\d+), /);
		$inode = ($num * 100) / $max;
	}

	# Linux kernel 2.4 and early 2.6 versions lacks these values
	$iow = 0 unless $iow;
	$irq = 0 unless $irq;
	$sirq = 0 unless $sirq;
	$steal = 0 unless $sirq;
	$guest = 0 unless $guest;
	$lastiow = 0 unless $lastiow;
	$lastirq = 0 unless $lastirq;
	$lastsirq = 0 unless $lastsirq;
	$laststeal = 0 unless $lastsirq;
	$lastguest = 0 unless $lastguest;

	if($user >= $lastuser && $nice >= $lastnice && $sys >= $lastsys && $idle >= $lastidle && $iow >= $lastiow && $irq >= $lastirq && $sirq >= $lastsirq && $steal >= $laststeal && $guest >= $lastguest) {
		my $user_ = $user - $lastuser;
		my $nice_ = $nice - $lastnice;
		my $sys_ = $sys - $lastsys;
		my $idle_ = $idle - $lastidle;
		my $iow_ = $iow - $lastiow;
		my $irq_ = $irq - $lastirq;
		my $sirq_ = $sirq - $lastsirq;
		my $steal_ = $steal - $laststeal;
		my $guest_ = $guest - $lastguest;
		my $total = $user_ + $nice_ + $sys_ + $idle_ + $iow_ + $irq_ + $sirq_ + $steal_ + $guest_;
		$user = ($user_ * 100) / $total;
		$nice = ($nice_ * 100) / $total;
		$sys = ($sys_ * 100) / $total;
		$idle = ($idle_ * 100) / $total;
		$iow = ($iow_ * 100) / $total;
		$irq = ($irq_ * 100) / $total;
		$sirq = ($sirq_ * 100) / $total;
		$steal = ($steal_ * 100) / $total;
		$guest = ($guest_ * 100) / $total;
	} else {
		$user = "nan";
		$nice = "nan";
		$sys = "nan";
		$idle = "nan";
		$iow = "nan";
		$irq = "nan";
		$sirq = "nan";
		$steal = "nan";
		$guest = "nan";
	}

	$rrdata .= ":$user:$nice:$sys:$idle:$iow:$irq:$sirq:$steal:$guest:$cs:$dentry:$file:$inode:$forks:$vforks:$val03:$val04:$val05";
	RRDs::update($KERN_RRD, $rrdata);
	if($opt_d eq "all" || $debug) {
		logger("$myself: $rrdata");
	}
	my $err = RRDs::error;
	logger("ERROR: while updating $KERN_RRD: $err") if $err;
}

# PROC graph
# ----------------------------------------------------------------------------
sub proc_init {
	my $myself = (caller(0))[3];

	my $info;
	my @ds;
	my @tmp;
	my $n;

	if(!grep {$_ eq $os} ("Linux", "FreeBSD")) {
		logger("$myself is not supported by your operating system ($os).");
		return;
	}

	if(-e $PROC_RRD) {
		$info = RRDs::info($PROC_RRD);
		for my $key (keys %$info) {
			if(index($key, 'ds[') == 0) {
				if(index($key, '.type') != -1) {
#					print("$key\n");
					push(@ds, substr($key, 3, index($key, ']') - 3));
				}
			}
		}
#		foreach(@ds) {
#			print($_ . "\n");
#		}
#		print(scalar(@ds) . "\n");
		if(scalar(@ds) / 9 != $PROC_MAX) {
			logger("Detected size mismatch between \$PROC_MAX ($PROC_MAX) and $PROC_RRD (" . scalar(@ds) / 9 . "). Resizing it accordingly. All historic data will be lost.");
			unlink($PROC_RRD);
		}
	}

	if(!(-e $PROC_RRD)) {
		logger("Creating '$PROC_RRD' file.");
		for($n = 0; $n < $PROC_MAX; $n++) {
			push(@tmp, "DS:proc" . $n . "_user:GAUGE:120:0:100");
			push(@tmp, "DS:proc" . $n . "_nice:GAUGE:120:0:100");
			push(@tmp, "DS:proc" . $n . "_sys:GAUGE:120:0:100");
			push(@tmp, "DS:proc" . $n . "_idle:GAUGE:120:0:100");
			push(@tmp, "DS:proc" . $n . "_iow:GAUGE:120:0:100");
			push(@tmp, "DS:proc" . $n . "_irq:GAUGE:120:0:100");
			push(@tmp, "DS:proc" . $n . "_sirq:GAUGE:120:0:100");
			push(@tmp, "DS:proc" . $n . "_steal:GAUGE:120:0:100");
			push(@tmp, "DS:proc" . $n . "_guest:GAUGE:120:0:100");
		}
		eval {
			RRDs::create($PROC_RRD,
				"--step=60",
				@tmp,
				"RRA:AVERAGE:0.5:1:1440",
				"RRA:AVERAGE:0.5:30:336",
				"RRA:AVERAGE:0.5:60:744",
				"RRA:AVERAGE:0.5:1440:365",
				"RRA:MIN:0.5:1:1440",
				"RRA:MIN:0.5:30:336",
				"RRA:MIN:0.5:60:744",
				"RRA:MIN:0.5:1440:365",
				"RRA:MAX:0.5:1:1440",
				"RRA:MAX:0.5:30:336",
				"RRA:MAX:0.5:60:744",
				"RRA:MAX:0.5:1440:365",
				"RRA:LAST:0.5:1:1440",
				"RRA:LAST:0.5:30:336",
				"RRA:LAST:0.5:60:744",
				"RRA:LAST:0.5:1440:365",
			);
		};
		my $err = RRDs::error;
		if($@ || $err) {
			logger("$@") unless !$@;
			if($err) {
				logger("ERROR: while creating $PROC_RRD: $err");
				if($err eq "RRDs::error") {
					logger("... is the RRDtool Perl package installed?");
				}
			}
			return;
		}
	}

	our %proc_hist = ();
	push(@graphs, "proc_update");
	if($opt_d eq "all" || $debug) {
		logger("$myself: Ok");
	}
}

sub proc_update {
	my $myself = (caller(0))[3];
	my ($debug) = @_;

	my @proc;
	my $total;
	
	my $n;
	my @lastproc;

	my @p;
	my @l;
	my $rrdata = "N";

	# Read last processor usage data
	my $str;
	for($n = 0; $n < $PROC_MAX; $n++) {
		$str = "cpu" . $n;
		if($proc_hist{$str}) {
			push(@lastproc, $proc_hist{$str});
		}
	}

	if($os eq "Linux") {
		open(IN, "/proc/stat");
		while(<IN>) {
			for($n = 0; $n < $PROC_MAX; $n++) {
				$str = "cpu" . $n;
				if(/^cpu$n /) {
					chomp($_);
					$proc_hist{$str} = $_;
					push(@proc, $_);
				}
			}
		}
		close(IN);
	} elsif($os eq "FreeBSD") {
		my $cptimes;
		my @tmp;
		my $from;
		my $to;
		my $ncpu = `sysctl -n hw.ncpu`;
		open(IN, "sysctl -n kern.cp_times |");
		my @data = split(' ', <IN>);
		close(IN);
		chomp($ncpu);
		for($n = 0; $n < $PROC_MAX; $n++) {
			$str = "cpu" . $n;
			$from = $n * 5;
			$to = $from + 4;
			@tmp = @data[$from..$to];
			@tmp[0, 1, 2, 3, 4] = @tmp[0, 1, 2, 4, 3];
			$cptimes = join(' ', @tmp);
			chomp($cptimes);
			$cptimes = $str . " " . $cptimes;
			$proc_hist{$str} = $cptimes;
			push(@proc, $cptimes);
		}
	}

	my @deltas;
	for($n = 0; $n < $PROC_MAX; $n++) {
		if($proc[$n]) {
			@p = split(' ', $proc[$n]);
			@l = split(' ', $lastproc[$n]);
			@deltas = (
				$p[1] - $l[1],	# user
				$p[2] - $l[2],	# nice
				$p[3] - $l[3],	# sys
				$p[4] - $l[4],	# idle
				$p[5] - $l[5],	# iow
				$p[6] - $l[6],	# irq
				$p[7] - $l[7],	# sirq
				$p[8] - $l[8],	# steal
				$p[9] - $l[9],	# guest
			);
			$total = $deltas[0] + $deltas[1] + $deltas[2] + $deltas[3] + $deltas[4] + $deltas[5] + $deltas[6] + $deltas[7] + $deltas[8];

			undef(@p);
			push(@p, $deltas[0] ? ($deltas[0] * 100) / $total : 0);
			push(@p, $deltas[1] ? ($deltas[1] * 100) / $total : 0);
			push(@p, $deltas[2] ? ($deltas[2] * 100) / $total : 0);
			push(@p, $deltas[3] ? ($deltas[3] * 100) / $total : 0);
			push(@p, $deltas[4] ? ($deltas[4] * 100) / $total : 0);
			push(@p, $deltas[5] ? ($deltas[5] * 100) / $total : 0);
			push(@p, $deltas[6] ? ($deltas[6] * 100) / $total : 0);
			push(@p, $deltas[7] ? ($deltas[7] * 100) / $total : 0);
			push(@p, $deltas[8] ? ($deltas[8] * 100) / $total : 0);
			$proc[$n] = join(' ', @p);
		} else {
			$proc[$n] = join(' ', (0, 0, 0, 0, 0, 0, 0, 0, 0));
		}
	}

	for($n = 0; $n < $PROC_MAX; $n++) {
		@p = split(' ', $proc[$n]);
		$rrdata .= ":$p[0]:$p[1]:$p[2]:$p[3]:$p[4]:$p[5]:$p[6]:$p[7]:$p[8]";
	}
	RRDs::update($PROC_RRD, $rrdata);
	if($opt_d eq "all" || $debug) {
		logger("$myself: $rrdata");
	}
	my $err = RRDs::error;
	logger("ERROR: while updating $PROC_RRD: $err") if $err;
}

# HPTEMP graph
# ----------------------------------------------------------------------------
sub hptemp_init {
	my $myself = (caller(0))[3];

	# save the contents of 'hplog -t' since only 'root' is able to run it
	# it also checks if 'hplog' does exists.
	if(!open(IN, "hplog -t |")) {
		logger("$myself: unable to execute 'hplog'. $!");
		return;
	}
	my @data = <IN>;
	close(IN);
	open(OUT, "> $BASE_DIR/cgi-bin/monitorix.hplog");
	print(OUT @data);
	close(OUT);

	if(!(-e $HPTEMP_RRD)) {
		logger("Creating '$HPTEMP_RRD' file.");
		eval {
			RRDs::create($HPTEMP_RRD,
				"--step=60",
				"DS:hptemp1_1:GAUGE:120:0:100",
				"DS:hptemp1_2:GAUGE:120:0:100",
				"DS:hptemp1_3:GAUGE:120:0:100",
				"DS:hptemp1_4:GAUGE:120:0:100",
				"DS:hptemp1_5:GAUGE:120:0:100",
				"DS:hptemp1_6:GAUGE:120:0:100",
				"DS:hptemp1_7:GAUGE:120:0:100",
				"DS:hptemp1_8:GAUGE:120:0:100",
				"DS:hptemp2_1:GAUGE:120:0:100",
				"DS:hptemp2_2:GAUGE:120:0:100",
				"DS:hptemp2_3:GAUGE:120:0:100",
				"DS:hptemp2_4:GAUGE:120:0:100",
				"DS:hptemp2_5:GAUGE:120:0:100",
				"DS:hptemp2_6:GAUGE:120:0:100",
				"DS:hptemp3_1:GAUGE:120:0:100",
				"DS:hptemp3_2:GAUGE:120:0:100",
				"DS:hptemp3_3:GAUGE:120:0:100",
				"DS:hptemp3_4:GAUGE:120:0:100",
				"DS:hptemp3_5:GAUGE:120:0:100",
				"DS:hptemp3_6:GAUGE:120:0:100",
				"RRA:AVERAGE:0.5:1:1440",
				"RRA:AVERAGE:0.5:30:336",
				"RRA:AVERAGE:0.5:60:744",
				"RRA:AVERAGE:0.5:1440:365",
				"RRA:MIN:0.5:1:1440",
				"RRA:MIN:0.5:30:336",
				"RRA:MIN:0.5:60:744",
				"RRA:MIN:0.5:1440:365",
				"RRA:MAX:0.5:1:1440",
				"RRA:MAX:0.5:30:336",
				"RRA:MAX:0.5:60:744",
				"RRA:MAX:0.5:1440:365",
				"RRA:LAST:0.5:1:1440",
				"RRA:LAST:0.5:30:336",
				"RRA:LAST:0.5:60:744",
				"RRA:LAST:0.5:1440:365",
			);
		};
		my $err = RRDs::error;
		if($@ || $err) {
			logger("$@") unless !$@;
			if($err) {
				logger("ERROR: while creating $HPTEMP_RRD: $err");
				if($err eq "RRDs::error") {
					logger("... is the RRDtool Perl package installed?");
				}
			}
			return;
		}
	}

	push(@graphs, "hptemp_update");
	if($opt_d eq "all" || $debug) {
		logger("$myself: Ok");
	}
}

sub hptemp_update {
	my $myself = (caller(0))[3];
	my ($debug) = @_;

	my @hptemp1;
	my @hptemp2;
	my @hptemp3;

	my $l;
	my $n;
	my $rrdata = "N";

	open(IN, "hplog -t |");
	my @data = <IN>;
	close(IN);
	my $str;
	for($l = 0; $l < scalar(@data); $l++) {
		$_ = @data[$l];
		foreach my $t (@HPTEMP_1) {
			$str = sprintf("%2d", $t);
			if(/^$str  /) {
				my $temp = substr($_, 47, 3);
				chomp($temp);
				push(@hptemp1, int($temp));
			}
		}
		foreach my $t (@HPTEMP_2) {
			$str = sprintf("%2d", $t);
			if(/^$str  /) {
				my $temp = substr($_, 47, 3);
				chomp($temp);
				push(@hptemp2, int($temp));
			}
		}
		foreach my $t (@HPTEMP_3) {
			$str = sprintf("%2d", $t);
			if(/^$str  /) {
				my $temp = substr($_, 47, 3);
				chomp($temp);
				push(@hptemp3, int($temp));
			}
		}
	}
	for($n = 0; $n < 8; $n++) {
		$hptemp1[$n] = 0 unless $hptemp1[$n];
		$rrdata .= ":$hptemp1[$n]";
	}
	for($n = 0; $n < 6; $n++) {
		$hptemp2[$n] = 0 unless $hptemp2[$n];
		$rrdata .= ":$hptemp2[$n]";
	}
	for($n = 0; $n < 6; $n++) {
		$hptemp3[$n] = 0 unless $hptemp3[$n];
		$rrdata .= ":$hptemp3[$n]";
	}

	RRDs::update($HPTEMP_RRD, $rrdata);
	if($opt_d eq "all" || $debug) {
		logger("$myself: $rrdata");
	}
	my $err = RRDs::error;
	logger("ERROR: while updating $HPTEMP_RRD: $err") if $err;
}

# LMSENS graph
# ----------------------------------------------------------------------------
sub lmsens_init {
	my $myself = (caller(0))[3];

	if(!(-e $LMSENS_RRD)) {
		logger("Creating '$LMSENS_RRD' file.");
		eval {
			RRDs::create($LMSENS_RRD,
				"--step=60",
				"DS:lmsens_mb0:GAUGE:120:0:100",
				"DS:lmsens_mb1:GAUGE:120:0:100",
				"DS:lmsens_cpu0:GAUGE:120:0:100",
				"DS:lmsens_cpu1:GAUGE:120:0:100",
				"DS:lmsens_cpu2:GAUGE:120:0:100",
				"DS:lmsens_cpu3:GAUGE:120:0:100",
				"DS:lmsens_fan0:GAUGE:120:0:U",
				"DS:lmsens_fan1:GAUGE:120:0:U",
				"DS:lmsens_fan2:GAUGE:120:0:U",
				"DS:lmsens_fan3:GAUGE:120:0:U",
				"DS:lmsens_fan4:GAUGE:120:0:U",
				"DS:lmsens_fan5:GAUGE:120:0:U",
				"DS:lmsens_fan6:GAUGE:120:0:U",
				"DS:lmsens_fan7:GAUGE:120:0:U",
				"DS:lmsens_fan8:GAUGE:120:0:U",
				"DS:lmsens_core0:GAUGE:120:0:100",
				"DS:lmsens_core1:GAUGE:120:0:100",
				"DS:lmsens_core2:GAUGE:120:0:100",
				"DS:lmsens_core3:GAUGE:120:0:100",
				"DS:lmsens_core4:GAUGE:120:0:100",
				"DS:lmsens_core5:GAUGE:120:0:100",
				"DS:lmsens_core6:GAUGE:120:0:100",
				"DS:lmsens_core7:GAUGE:120:0:100",
				"DS:lmsens_core8:GAUGE:120:0:100",
				"DS:lmsens_core9:GAUGE:120:0:100",
				"DS:lmsens_core10:GAUGE:120:0:100",
				"DS:lmsens_core11:GAUGE:120:0:100",
				"DS:lmsens_core12:GAUGE:120:0:100",
				"DS:lmsens_core13:GAUGE:120:0:100",
				"DS:lmsens_core14:GAUGE:120:0:100",
				"DS:lmsens_core15:GAUGE:120:0:100",
				"DS:lmsens_volt0:GAUGE:120:U:U",
				"DS:lmsens_volt1:GAUGE:120:U:U",
				"DS:lmsens_volt2:GAUGE:120:U:U",
				"DS:lmsens_volt3:GAUGE:120:U:U",
				"DS:lmsens_volt4:GAUGE:120:U:U",
				"DS:lmsens_volt5:GAUGE:120:U:U",
				"DS:lmsens_volt6:GAUGE:120:U:U",
				"DS:lmsens_volt7:GAUGE:120:U:U",
				"DS:lmsens_volt8:GAUGE:120:U:U",
				"DS:lmsens_volt9:GAUGE:120:U:U",
				"DS:lmsens_volt10:GAUGE:120:U:U",
				"DS:lmsens_volt11:GAUGE:120:U:U",
				"DS:lmsens_gpu0:GAUGE:120:0:100",
				"DS:lmsens_gpu1:GAUGE:120:0:100",
				"DS:lmsens_gpu2:GAUGE:120:0:100",
				"DS:lmsens_gpu3:GAUGE:120:0:100",
				"DS:lmsens_gpu4:GAUGE:120:0:100",
				"DS:lmsens_gpu5:GAUGE:120:0:100",
				"DS:lmsens_gpu6:GAUGE:120:0:100",
				"DS:lmsens_gpu7:GAUGE:120:0:100",
				"DS:lmsens_gpu8:GAUGE:120:0:100",
				"RRA:AVERAGE:0.5:1:1440",
				"RRA:AVERAGE:0.5:30:336",
				"RRA:AVERAGE:0.5:60:744",
				"RRA:AVERAGE:0.5:1440:365",
				"RRA:MIN:0.5:1:1440",
				"RRA:MIN:0.5:30:336",
				"RRA:MIN:0.5:60:744",
				"RRA:MIN:0.5:1440:365",
				"RRA:MAX:0.5:1:1440",
				"RRA:MAX:0.5:30:336",
				"RRA:MAX:0.5:60:744",
				"RRA:MAX:0.5:1440:365",
				"RRA:LAST:0.5:1:1440",
				"RRA:LAST:0.5:30:336",
				"RRA:LAST:0.5:60:744",
				"RRA:LAST:0.5:1440:365",
			);
		};
		my $err = RRDs::error;
		if($@ || $err) {
			logger("$@") unless !$@;
			if($err) {
				logger("ERROR: while creating $LMSENS_RRD: $err");
				if($err eq "RRDs::error") {
					logger("... is the RRDtool Perl package installed?");
				}
			}
			return;
		}
	}

	push(@graphs, "lmsens_update");
	if($opt_d eq "all" || $debug) {
		logger("$myself: Ok");
	}
}

sub get_nvidia_data {
	my ($gpu) = @_;
	my $total = 0;
	my $used = 0;
	my $mem = 0;
	my $cpu = 0;
	my $temp = 0;
	my $check_mem = 0;
	my $check_cpu = 0;
	my $check_temp = 0;

	open(IN, "nvidia-smi -q -i $gpu -d MEMORY,UTILIZATION,TEMPERATURE |");
	my @data = <IN>;
	close(IN);
	for($l = 0; $l < scalar(@data); $l++) {
		$_ = @data[$l];
		if(/Memory Usage/) {
			$check_mem = 1;
			next;
		}
		if($check_mem) {	
			if(/Total/) {
				my (undef, $tmp) = split(':', $_);
				if($tmp eq "\n") {
					$l++;
					$tmp = $data[$l];
				}
				my ($value, undef) = split(' ', $tmp);
				$value =~ s/[-]/./;
				$value =~ s/[^0-9.]//g;
				if(int($value) > 0) {
					$total = int($value);
				}
			}
			if(/Used/) {
				my (undef, $tmp) = split(':', $_);
				if($tmp eq "\n") {
					$l++;
					$tmp = $data[$l];
				}
				my ($value, undef) = split(' ', $tmp);
				$value =~ s/[-]/./;
				$value =~ s/[^0-9.]//g;
				if(int($value) > 0) {
					$used = int($value);
				}
				$check_mem = 0;
			}
		}

		if(/Utilization/) {
			$check_cpu = 1;
			next;
		}
		if($check_cpu) {	
			if(/Gpu/) {
				my (undef, $tmp) = split(':', $_);
				if($tmp eq "\n") {
					$l++;
					$tmp = $data[$l];
				}
				my ($value, undef) = split(' ', $tmp);
				$value =~ s/[-]/./;
				$value =~ s/[^0-9.]//g;
				if(int($value) > 0) {
					$cpu = int($value);
				}
			}
			if(/Memory/) {
				my (undef, $tmp) = split(':', $_);
				if($tmp eq "\n") {
					$l++;
					$tmp = $data[$l];
				}
				my ($value, undef) = split(' ', $tmp);
				$value =~ s/[-]/./;
				$value =~ s/[^0-9.]//g;
				if(int($value) > 0) {
					$mem = int($value);
				}
			}
			$check_cpu = 0;
		}

		if(/Temperature/) {
			$check_temp = 1;
			next;
		}
		if($check_temp) {	
			if(/Gpu/) {
				my (undef, $tmp) = split(':', $_);
				if($tmp eq "\n") {
					$l++;
					$tmp = $data[$l];
				}
				my ($value, undef) = split(' ', $tmp);
				$value =~ s/[-]/./;
				$value =~ s/[^0-9.]//g;
				if(int($value) > 0) {
					$temp = int($value);
				}
			}
			$check_temp = 0;
		}
	}
	# NVIDIA driver v285.+ not supported (needs new output parsing).
	# This is to avoid a divide by zero message.
	if($total) {
		$mem = ($used * 100) / $total;
	} else {
		$mem = $used = $total = 0;
	}
	return join(" ", $mem, $cpu, $temp);
}

sub lmsens_update {
	my $myself = (caller(0))[3];
	my ($debug) = @_;

	my @mb;
	my @cpu;
	my @fan;
	my @core;
	my @volt;
	my @gpu;

	my $l;
	my $n;
	my $rrdata = "N";

	if($os eq "Linux") {
		if(scalar(%SENSORS_LIST)) {
	  		open(IN, "sensors |");
			my @data = <IN>;
			close(IN);
			my $str;
			for($l = 0; $l < scalar(@data); $l++) {
				$_ = @data[$l];
				for($n = 0; $n < 2; $n++) {
					$str = "MB" . $n;
					$mb[$n] = 0 unless $mb[$n];
					if($SENSORS_LIST{$str} && (/^$SENSORS_LIST{$str}:/) && (!/RPM/)) {
						my (undef, $tmp) = split(':', $_);
						if($tmp eq "\n") {
							$l++;
							$tmp = $data[$l];
						}
						my ($value, undef) = split(' ', $tmp);
						$mb[$n] = int($value);
					}
				}
				for($n = 0; $n < 4; $n++) {
					$str = "CPU" . $n;
					$cpu[$n] = 0 unless $cpu[$n];
					if($SENSORS_LIST{$str} && (/^$SENSORS_LIST{$str}:/) && (!/RPM/)) {
						my (undef, $tmp) = split(':', $_);
						if($tmp eq "\n") {
							$l++;
							$tmp = $data[$l];
						}
						my ($value, undef) = split(' ', $tmp);
						$cpu[$n] = int($value);
					}
				}
				for($n = 0; $n < 9; $n++) {
					$str = "FAN" . $n;
					$fan[$n] = 0 unless $fan[$n];
					if($SENSORS_LIST{$str} && (/^$SENSORS_LIST{$str}:/) && (/RPM/)) {
						my (undef, $tmp) = split(':', $_);
						if($tmp eq "\n") {
							$l++;
							$tmp = $data[$l];
						}
						my ($value, undef) = split(' ', $tmp);
						$fan[$n] = int($value);
					}
				}
				for($n = 0; $n < 16; $n++) {
					$str = "CORE" . $n;
					$core[$n] = 0 unless $core[$n];
					if($SENSORS_LIST{$str} && (/^$SENSORS_LIST{$str}:/) && (!/RPM/)) {
						my (undef, $tmp) = split(':', $_);
						if($tmp eq "\n") {
							$l++;
							$tmp = $data[$l];
						}
						my ($value, undef) = split(' ', $tmp);
						$core[$n] = int($value);
					}
				}
				for($n = 0; $n < 12; $n++) {
					$str = "VOLT" . $n;
					$volt[$n] = 0 unless $volt[$n];
					if($SENSORS_LIST{$str} && (/^$SENSORS_LIST{$str}:/) && (!/RPM/)) {
						my (undef, $tmp) = split(':', $_);
						if($tmp eq "\n") {
							$l++;
							$tmp = $data[$l];
						}
						my ($value, undef) = split(' ', $tmp);
						$volt[$n] = $value;
					}
				}
			}
			for($n = 0; $n < 9; $n++) {
				$str = "GPU" . $n;
				$gpu[$n] = 0 unless $gpu[$n];
				if($SENSORS_LIST{$str} && $SENSORS_LIST{$str} eq "nvidia") {
					(undef, undef, $gpu[$n]) = split(' ', get_nvidia_data($n));
					if(!$gpu[$n]) {
						# attempt to get data using the old driver version
	  					open(IN, "nvidia-smi -g $n |");
						my @data = <IN>;
						close(IN);
						for($l = 0; $l < scalar(@data); $l++) {
							$_ = @data[$l];
							if(/Temperature/) {
								my (undef, $tmp) = split(':', $_);
								if($tmp eq "\n") {
									$l++;
									$tmp = $data[$l];
								}
								my ($value, undef) = split(' ', $tmp);
								$value =~ s/[-]/./;
								$value =~ s/[^0-9.]//g;
								if(int($value) > 0) {
									$gpu[$n] = int($value);
								}
							}
						}
					}
				}
			}
		}
		for($n = 0; $n < scalar(@mb); $n++) {
			$rrdata .= ":$mb[$n]";
		}
		for($n = 0; $n < scalar(@cpu); $n++) {
			$rrdata .= ":$cpu[$n]";
		}
		for($n = 0; $n < scalar(@fan); $n++) {
			$rrdata .= ":$fan[$n]";
		}
		for($n = 0; $n < scalar(@core); $n++) {
			$rrdata .= ":$core[$n]";
		}
		for($n = 0; $n < scalar(@volt); $n++) {
			$rrdata .= ":$volt[$n]";
		}
		for($n = 0; $n < scalar(@gpu); $n++) {
			$rrdata .= ":$gpu[$n]";
		}
	}

	RRDs::update($LMSENS_RRD, $rrdata);
	if($opt_d eq "all" || $debug) {
		logger("$myself: $rrdata");
	}
	my $err = RRDs::error;
	logger("ERROR: while updating $LMSENS_RRD: $err") if $err;
}

# NVIDIA graph
# ----------------------------------------------------------------------------
sub nvidia_init {
	my $myself = (caller(0))[3];

	if(!(-e $NVIDIA_RRD)) {
		logger("Creating '$NVIDIA_RRD' file.");
		eval {
			RRDs::create($NVIDIA_RRD,
				"--step=60",
				"DS:nvidia_temp0:GAUGE:120:0:U",
				"DS:nvidia_temp1:GAUGE:120:0:U",
				"DS:nvidia_temp2:GAUGE:120:0:U",
				"DS:nvidia_temp3:GAUGE:120:0:U",
				"DS:nvidia_temp4:GAUGE:120:0:U",
				"DS:nvidia_temp5:GAUGE:120:0:U",
				"DS:nvidia_temp6:GAUGE:120:0:U",
				"DS:nvidia_temp7:GAUGE:120:0:U",
				"DS:nvidia_temp8:GAUGE:120:0:U",
				"DS:nvidia_gpu0:GAUGE:120:0:100",
				"DS:nvidia_gpu1:GAUGE:120:0:100",
				"DS:nvidia_gpu2:GAUGE:120:0:100",
				"DS:nvidia_gpu3:GAUGE:120:0:100",
				"DS:nvidia_gpu4:GAUGE:120:0:100",
				"DS:nvidia_gpu5:GAUGE:120:0:100",
				"DS:nvidia_gpu6:GAUGE:120:0:100",
				"DS:nvidia_gpu7:GAUGE:120:0:100",
				"DS:nvidia_gpu8:GAUGE:120:0:100",
				"DS:nvidia_mem0:GAUGE:120:0:100",
				"DS:nvidia_mem1:GAUGE:120:0:100",
				"DS:nvidia_mem2:GAUGE:120:0:100",
				"DS:nvidia_mem3:GAUGE:120:0:100",
				"DS:nvidia_mem4:GAUGE:120:0:100",
				"DS:nvidia_mem5:GAUGE:120:0:100",
				"DS:nvidia_mem6:GAUGE:120:0:100",
				"DS:nvidia_mem7:GAUGE:120:0:100",
				"DS:nvidia_mem8:GAUGE:120:0:100",
				"RRA:AVERAGE:0.5:1:1440",
				"RRA:AVERAGE:0.5:30:336",
				"RRA:AVERAGE:0.5:60:744",
				"RRA:AVERAGE:0.5:1440:365",
				"RRA:MIN:0.5:1:1440",
				"RRA:MIN:0.5:30:336",
				"RRA:MIN:0.5:60:744",
				"RRA:MIN:0.5:1440:365",
				"RRA:MAX:0.5:1:1440",
				"RRA:MAX:0.5:30:336",
				"RRA:MAX:0.5:60:744",
				"RRA:MAX:0.5:1440:365",
				"RRA:LAST:0.5:1:1440",
				"RRA:LAST:0.5:30:336",
				"RRA:LAST:0.5:60:744",
				"RRA:LAST:0.5:1440:365",
			);
		};
		my $err = RRDs::error;
		if($@ || $err) {
			logger("$@") unless !$@;
			if($err) {
				logger("ERROR: while creating $NVIDIA_RRD: $err");
				if($err eq "RRDs::error") {
					logger("... is the RRDtool Perl package installed?");
				}
			}
			return;
		}
	}

	push(@graphs, "nvidia_update");
	if($opt_d eq "all" || $debug) {
		logger("$myself: Ok");
	}
}

sub nvidia_update {
	my $myself = (caller(0))[3];
	my ($debug) = @_;

	my @temp;
	my @gpu;
	my @mem;
	my @data;
	my $utilization;

	my $l;
	my $n;
	my $rrdata = "N";

	for($n = 0; $n < 9; $n++) {
		$temp[$n] = 0;
		$gpu[$n] = 0;
		$mem[$n] = 0;
		if($n < $NVIDIA_MAX) {
			($mem[$n], $gpu[$n], $temp[$n]) = split(' ', get_nvidia_data($n));
			if(!$temp[$n] && !$gpu[$n] && !$mem[$n]) {
				# attempt to get data using the old driver version
				$utilization = 0;
	  			open(IN, "nvidia-smi -g $n |");
				@data = <IN>;
				close(IN);
				for($l = 0; $l < scalar(@data); $l++) {
					$_ = @data[$l];
					if(/Temperature/) {
						my (undef, $tmp) = split(':', $_);
						if($tmp eq "\n") {
							$l++;
							$tmp = $data[$l];
						}
						my ($value, undef) = split(' ', $tmp);
						$value =~ s/[-]/./;
						$value =~ s/[^0-9.]//g;
						if(int($value) > 0) {
							$temp[$n] = int($value);
						}
					}
					if(/Utilization/) {
						$utilization = 1;
					}
					if($utilization == 1) {
						if(/GPU/) {
							my (undef, $tmp) = split(':', $_);
							if($tmp eq "\n") {
								$l++;
								$tmp = $data[$l];
							}
							my ($value, undef) = split(' ', $tmp);
							$value =~ s/[-]/./;
							$value =~ s/[^0-9.]//g;
							if(int($value) > 0) {
								$gpu[$n] = int($value);
							}
						}
						if(/Memory/) {
							my (undef, $tmp) = split(':', $_);
							if($tmp eq "\n") {
								$l++;
								$tmp = $data[$l];
							}
							my ($value, undef) = split(' ', $tmp);
							$value =~ s/[-]/./;
							$value =~ s/[^0-9.]//g;
							if(int($value) > 0) {
								$mem[$n] = int($value);
							}
						}
					}
				}
			}
		}
	}

	for($n = 0; $n < scalar(@temp); $n++) {
		$rrdata .= ":$temp[$n]";
	}
	for($n = 0; $n < scalar(@gpu); $n++) {
		$rrdata .= ":$gpu[$n]";
	}
	for($n = 0; $n < scalar(@mem); $n++) {
		$rrdata .= ":$mem[$n]";
	}

	RRDs::update($NVIDIA_RRD, $rrdata);
	if($opt_d eq "all" || $debug) {
		logger("$myself: $rrdata");
	}
	my $err = RRDs::error;
	logger("ERROR: while updating $NVIDIA_RRD: $err") if $err;
}

# DISK graph
# ----------------------------------------------------------------------------
sub disk_init {
	my $myself = (caller(0))[3];

	if(!(-e $DISK_RRD)) {
		logger("Creating '$DISK_RRD' file.");
		eval {
			RRDs::create($DISK_RRD,
				"--step=60",
				"DS:disk_hd0_temp:GAUGE:120:0:100",
				"DS:disk_hd0_smart1:GAUGE:120:0:U",
				"DS:disk_hd0_smart2:GAUGE:120:0:U",
				"DS:disk_hd1_temp:GAUGE:120:0:100",
				"DS:disk_hd1_smart1:GAUGE:120:0:U",
				"DS:disk_hd1_smart2:GAUGE:120:0:U",
				"DS:disk_hd2_temp:GAUGE:120:0:100",
				"DS:disk_hd2_smart1:GAUGE:120:0:U",
				"DS:disk_hd2_smart2:GAUGE:120:0:U",
				"DS:disk_hd3_temp:GAUGE:120:0:100",
				"DS:disk_hd3_smart1:GAUGE:120:0:U",
				"DS:disk_hd3_smart2:GAUGE:120:0:U",
				"DS:disk_hd4_temp:GAUGE:120:0:100",
				"DS:disk_hd4_smart1:GAUGE:120:0:U",
				"DS:disk_hd4_smart2:GAUGE:120:0:U",
				"DS:disk_hd5_temp:GAUGE:120:0:100",
				"DS:disk_hd5_smart1:GAUGE:120:0:U",
				"DS:disk_hd5_smart2:GAUGE:120:0:U",
				"DS:disk_hd6_temp:GAUGE:120:0:100",
				"DS:disk_hd6_smart1:GAUGE:120:0:U",
				"DS:disk_hd6_smart2:GAUGE:120:0:U",
				"DS:disk_hd7_temp:GAUGE:120:0:100",
				"DS:disk_hd7_smart1:GAUGE:120:0:U",
				"DS:disk_hd7_smart2:GAUGE:120:0:U",
				"RRA:AVERAGE:0.5:1:1440",
				"RRA:AVERAGE:0.5:30:336",
				"RRA:AVERAGE:0.5:60:744",
				"RRA:AVERAGE:0.5:1440:365",
				"RRA:MIN:0.5:1:1440",
				"RRA:MIN:0.5:30:336",
				"RRA:MIN:0.5:60:744",
				"RRA:MIN:0.5:1440:365",
				"RRA:MAX:0.5:1:1440",
				"RRA:MAX:0.5:30:336",
				"RRA:MAX:0.5:60:744",
				"RRA:MAX:0.5:1440:365",
				"RRA:LAST:0.5:1:1440",
				"RRA:LAST:0.5:30:336",
				"RRA:LAST:0.5:60:744",
				"RRA:LAST:0.5:1440:365",
			);
		};
		my $err = RRDs::error;
		if($@ || $err) {
			logger("$@") unless !$@;
			if($err) {
				logger("ERROR: while creating $DISK_RRD: $err");
				if($err eq "RRDs::error") {
					logger("... is the RRDtool Perl package installed?");
				}
			}
			return;
		}
	}

	push(@graphs, "disk_update");
	if($opt_d eq "all" || $debug) {
		logger("$myself: Ok");
	}
}

sub disk_update {
	my $myself = (caller(0))[3];
	my ($debug) = @_;

	my @temp;
	my @smart1;
	my @smart2;

	my $n;
	my $rrdata = "N";

	if(scalar(@DISK_LIST)) {
		my $d;
		for($n = 0; $n < 8; $n++) {
			$temp[$n] = 0;
			$smart1[$n] = 0;
			$smart2[$n] = 0;
			if($DISK_LIST[$n]) {
				$d = $DISK_LIST[$n];
	  			open(IN, "smartctl -A $d |");
				while(<IN>) {
					if(/^  5/ && /Reallocated_Sector_Ct/) {
						my @tmp = split(' ', $_);
						$smart1[$n] = $tmp[9];
						chomp($smart1[$n]);
					}
					if(/^194/ && /Temperature_Celsius/) {
						my @tmp = split(' ', $_);
						$temp[$n] = $tmp[9];
						chomp($temp[$n]);
					}
					if(/^197/ && /Current_Pending_Sector/) {
						my @tmp = split(' ', $_);
						$smart2[$n] = $tmp[9];
						chomp($smart2[$n]);
					}
					if(/^Current Drive Temperature: /) {
						my @tmp = split(' ', $_);
						$temp[$n] = $tmp[3] unless $temp[$n];
						chomp($temp[$n]);
					}
				}
				close(IN);
				$temp[$n] = `hddtemp -wqn $DISK_LIST[$n]` unless $temp[$n];
				chomp($temp[$n]);
			}
		}
	}
	for($n = 0; $n < 8; $n++) {
		$rrdata .= ":$temp[$n]";
		$rrdata .= ":$smart1[$n]";
		$rrdata .= ":$smart2[$n]";
	}

	RRDs::update($DISK_RRD, $rrdata);
	if($opt_d eq "all" || $debug) {
		logger("$myself: $rrdata");
	}
	my $err = RRDs::error;
	logger("ERROR: while updating $DISK_RRD: $err") if $err;
}

# FS graph
# ----------------------------------------------------------------------------
sub is_in_diskstats {
	my ($root) = @_;
	my @data;
	my $major;
	my $dev;
	my @line;

	open(IN, "/proc/diskstats");
	@data = <IN>;
	close(IN);
	foreach(@data) {
		($major, undef, $dev) = split(' ', $_);
		if($root =~ m/^$dev/ && $root ne $dev) {
			@line = split(' ', $_);
		}
		last unless $root ne $dev;
	}
	if($root eq $dev) {
		return $dev unless @line;
		if($major eq $line[0]) {
			# if the length is the same it means that this name
			# is the root device (not a partition)
			if(length($root) eq length($line[2])) {
				return $dev;
			} else {
				return $line[2];
			}
		}
	}
	return 0;
}

sub is_luks {
	my ($root) = @_;
	my $dev;

	if($root =~ m/luks/) {
		$root =~ s/luks-//;
		$root = `blkid -t UUID=$root | awk -F ":" '{ print \$1 }'`;
		chomp($root);
		$root =~ s/^.*dev\///;		# remove the /dev/ prefix
		$root =~ s/^.*mapper\///;	# remove the mapper/ prefix
		return $root;
	}
	return $dev;
}

sub fs_init {
	my $myself = (caller(0))[3];

	if(!(-e $FS_RRD)) {
		logger("Creating '$FS_RRD' file.");
		eval {
			RRDs::create($FS_RRD,
				"--step=60",
				"DS:fs_root:GAUGE:120:0:100",
				"DS:fs_swap:GAUGE:120:0:100",
				"DS:fs_mnt1:GAUGE:120:0:100",
				"DS:fs_mnt2:GAUGE:120:0:100",
				"DS:fs_mnt3:GAUGE:120:0:100",
				"DS:fs_mnt4:GAUGE:120:0:100",
				"DS:fs_mnt5:GAUGE:120:0:100",
				"DS:fs_mnt6:GAUGE:120:0:100",
				"DS:fs_mnt7:GAUGE:120:0:100",
				"DS:fs_mnt8:GAUGE:120:0:100",
				"DS:fs_mnt9:GAUGE:120:0:100",
				"DS:fs_read_cnt:COUNTER:600:0:U",
				"DS:fs_write_cnt:COUNTER:600:0:U",
				"DS:fs_read_sec:COUNTER:600:0:U",
				"DS:fs_write_sec:COUNTER:600:0:U",
				"RRA:AVERAGE:0.5:1:1440",
				"RRA:AVERAGE:0.5:30:336",
				"RRA:AVERAGE:0.5:60:744",
				"RRA:AVERAGE:0.5:1440:365",
				"RRA:MIN:0.5:1:1440",
				"RRA:MIN:0.5:30:336",
				"RRA:MIN:0.5:60:744",
				"RRA:MIN:0.5:1440:365",
				"RRA:MAX:0.5:1:1440",
				"RRA:MAX:0.5:30:336",
				"RRA:MAX:0.5:60:744",
				"RRA:MAX:0.5:1440:365",
				"RRA:LAST:0.5:1:1440",
				"RRA:LAST:0.5:30:336",
				"RRA:LAST:0.5:60:744",
				"RRA:LAST:0.5:1440:365",
			);
		};
		my $err = RRDs::error;
		if($@ || $err) {
			logger("$@") unless !$@;
			if($err) {
				logger("ERROR: while creating $FS_RRD: $err");
				if($err eq "RRDs::error") {
					logger("... is the RRDtool Perl package installed?");
				}
			}
			return;
		}
	}

	# This tries to find out the physical device name where the / fs resides
	eval {
		alarm $TIMEOUT;
		open(IN, "df -P / |");
		while(<IN>) {
			if(/dev/ && /\//) {
				($root_disk) = split(' ', $_);
				last;
			}
		}
		close(IN);
		alarm 0;
		chomp($root_disk);
	};

	if($os eq "Linux" && $kernel_branch > 2.4) {
		my $lvm;
		my $lvm_disk;
		my $is_md;
		my $found;

		# check for device names using symbolic links
		# e.g. /dev/disk/by-uuid/db312d12-0da6-44e5-a354-4c82118f4b66
		if(-l $root_disk) {
			$link = readlink($root_disk);
			$root_disk = abs_path(dirname($root_disk) . "/" . $link);
			chomp($root_disk);
		}


		# check if device is using EVMS <http://evms.sourceforge.net/>
		if($root_disk =~ m/\/dev\/evms\//) {
			$root_disk = `evms_query disks $root_disk`;
			if($found = is_in_diskstats($root_disk)) {
				$root_disk = $found;
				goto devname_found;
			}
		}

		$root_disk =~ s/^.*dev\///;	# remove the /dev/ prefix
		$root_disk =~ s/^.*mapper\///;	# remove the mapper/ prefix


		# check if the device is under a crypt LUKS (encrypted fs)
		if($dev = is_luks($root_disk)) {
			$root_disk = $dev;
		}


		# do exists in /proc/diskstats?
		if($found = is_in_diskstats($root_disk)) {
			$root_disk = $found;
			goto devname_found;
		}


		# check if the device is in a LVM
		$lvm = $root_disk;
		$lvm =~ s/-.*//;
		if($lvm ne $root_disk) {	# probably LVM
			if(system("pvs >/dev/null 2>&1") == 0 && $lvm) {
				$lvm_disk = `pvs --noheadings | grep $lvm | awk -F " " '{ print \$1 }' | tail -1`;
				chomp($lvm_disk);
				$lvm_disk =~ s/^.*dev\///;	# remove the /dev/ prefix
				$lvm_disk =~ s/^.*mapper\///;	# remove the mapper/ prefix
				if(!($lvm_disk =~ m/md/)) {
					if($lvm_disk =~ m/cciss/) {
						# LVM over a CCISS disk (/dev/cciss/c0d0)
						$root_disk = $lvm_disk;
						chomp($root_disk);
					} elsif($dev = is_luks($lvm_disk)) {
						$root_disk = $dev;
					} else {
						# LVM over any direct disk (/dev/hda1)
						$root_disk = $lvm_disk;
						chomp($root_disk);
					}
				} else {
					# LVM over Linux RAID combination (/dev/md1)
					$root_disk = $lvm_disk;
					chomp($root_disk);
				}
			}
		}
	} elsif($os eq "FreeBSD") {
		$root_disk =~ s/^.*dev\///;	# remove the /dev/ prefix
#		$root_disk =~ s/.*\///;		# removes /dev/
		$root_disk =~ s/...$//;		# removes part number
	}

	# do exists in /proc/diskstats?
	if($found = is_in_diskstats($root_disk)) {
		$root_disk = $found;
	}

devname_found:
	our $fs_hist = 0;
	logger("$myself: Detected physical device name for / in '$root_disk'.") unless !$opt_d;

	push(@graphs, "fs_update");
	if($opt_d eq "all" || $debug) {
		logger("$myself: Ok");
	}
}

sub fs_update {
	my $myself = (caller(0))[3];
	my ($debug) = @_;

	my @tmp;

	my $n;
	my $rrdata = "N";

	eval {
		alarm $TIMEOUT;
		open(IN, "df -P / |");
		while(<IN>) {
			if(/dev/ && /\//) {
				@tmp = split(' ', $_);
				last;
			}
		}
		close(IN);
		alarm 0;
	};
	my (undef, undef, $root_used, $root_free) = @tmp;

	my $swap_used;
	my $swap_free;
	if($os eq "Linux") {
		open(IN, "free |");
		while(<IN>) {
			if(/^Swap:\s+\d+\s+(\d+)\s+(\d+)$/) {
				$swap_used = $1;
				$swap_free = $2;
			}
		}
		close(IN);
	} elsif($os eq "FreeBSD") {
		open(IN, "swapinfo -k |");
		while(<IN>) {
			if(/^.*?\s+\d+\s+(\d+)\s+(\d+)\s+\d+\%$/) {
				$swap_used = $1;
				$swap_free = $2;
			}
		}
		close(IN);
	} elsif($os eq "OpenBSD") {
		open(IN, "pstat -sk |");
		while(<IN>) {
			if(/^swap_device\s+\d+\s+(\d+)\s+(\d+) /) {
				$swap_used = $1;
				$swap_free = $2;
			}
		}
		close(IN);
	}

	chomp($root_used, $root_free, $swap_used, $swap_free);
	my $root_pcnt = ($root_used * 100) / ($root_used + $root_free);
	my $swap_pcnt = 0;
	$swap_pcnt = ($swap_used * 100) / ($swap_used + $swap_free) unless $swap_used + $swap_free == 0; # prevents a division by 0 if swap device is not used

	# FS alert
	if($ENABLE_ALERTS eq "Y") {
		if(!$ALERT_ROOTFS_THRESHOLD || $root_pcnt < $ALERT_ROOTFS_THRESHOLD) {
			$fs_hist = 0;
		} else {
			if(!$fs_hist) {
				$fs_hist = time;
			}
			if($fs_hist > 0 && (time - $fs_hist) > $ALERT_ROOTFS_TIMEINTVL) {
				if(-x $ALERT_ROOTFS_SCRIPT) {
					system($ALERT_ROOTFS_SCRIPT . " " . $ALERT_ROOTFS_TIMEINTVL . " " . $ALERT_ROOTFS_THRESHOLD . " " . $root_pcnt);
				}
				$fs_hist = time;
			}
		}
	}

	$rrdata .= ":$root_pcnt:$swap_pcnt";
	my @mnt_used;
	my @mnt_free;
	my @mnt_pcnt;
	for($n = 0; $n < 9; $n++) {
		$mnt_used[$n] = 0;
		$mnt_free[$n] = 0;
		$mnt_pcnt[$n] = 0;
		if($n < scalar(@FS_LIST)) {
			undef(@tmp);
			eval {
				alarm $TIMEOUT;
				open(IN, "df -P $FS_LIST[$n] |");
				while(<IN>) {
					if(/ $FS_LIST[$n]$/) {
						@tmp = split(' ', $_);
						last;
					}
				}
				close(IN);
				alarm 0;
			};
			if(@tmp) {
				(undef, undef, $mnt_used[$n], $mnt_free[$n]) = @tmp;
				$mnt_pcnt[$n] = ($mnt_used[$n] * 100) / ($mnt_used[$n] + $mnt_free[$n]);
			}
		}
		$rrdata .= ":$mnt_pcnt[$n]";
	}

	my $read_cnt = 0;
	my $read_sec = 0;
	my $write_cnt = 0;
	my $write_sec = 0;
	if($os eq "Linux") {
		undef(@tmp);
		if($kernel_branch > 2.4) {
			my @tmp_p;
			open(IN, "/proc/diskstats");
			while(<IN>) {
				if(/$root_disk /) {
					@tmp = split(' ', $_);
					last;
				}
				if(/$root_disk_p /) {
					@tmp_p = split(' ', $_);
				}
			}
			close(IN);
			@tmp = @tmp_p unless @tmp;
			(undef, undef, undef, $read_cnt, undef, $read_sec, undef, $write_cnt, undef, $write_sec) = @tmp;
		} else {
			my $io;
			open(IN, "/proc/stat");
			while(<IN>) {
				if(/^disk_io/) {
					(undef, undef, $io) = split(':', $_);
					last;
				}
			}
			close(IN);
			(undef, $read_cnt, $read_sec, $write_cnt, $write_sec) = split(',', $io);
			$write_sec =~ s/\).*$//;
		}
	} elsif($os eq "FreeBSD") {
		@tmp = split(' ', `iostat -xI $root_disk | grep -w $root_disk`);
		if(@tmp) {
			(undef, $read_cnt, $write_cnt, $read_sec, $write_sec) = @tmp;
			$read_cnt = int($read_cnt);
			$write_cnt = int($write_cnt);
			$read_sec = int($read_sec);
			$write_sec = int($write_sec);
		} else {
			@tmp = split(' ', `iostat -dI | tail -1`);
			(undef, $read_cnt, $read_sec) = @tmp;
			$write_cnt = "";
			$write_sec = "";
			chomp($read_sec);
			$read_sec = int($read_sec);
		}
	} elsif($os eq "OpenBSD") {
		@tmp = split(' ', `iostat -DI | tail -1`);
		($read_cnt, $read_sec) = @tmp;
		$write_cnt = "";
		$write_sec = "";
		chomp($read_sec);
		$read_sec = int($read_sec);
	}

	chomp($read_cnt, $read_sec, $write_cnt, $write_sec);
	$rrdata .= ":$read_cnt:$write_cnt:$read_sec:$write_sec";
	RRDs::update($FS_RRD, $rrdata);
	if($opt_d eq "all" || $debug) {
		logger("$myself: $rrdata");
	}
	my $err = RRDs::error;
	logger("ERROR: while updating $FS_RRD: $err") if $err;
}

# NET graph
# ----------------------------------------------------------------------------
sub net_init {
	my $myself = (caller(0))[3];

	if(!(-e $NET_RRD)) {
		logger("Creating '$NET_RRD' file.");
		eval {
			RRDs::create($NET_RRD,
				"--step=60",
				"DS:net0_bytes_in:COUNTER:120:0:U",
				"DS:net0_bytes_out:COUNTER:120:0:U",
				"DS:net0_packs_in:COUNTER:120:0:U",
				"DS:net0_packs_out:COUNTER:120:0:U",
				"DS:net0_error_in:COUNTER:120:0:U",
				"DS:net0_error_out:COUNTER:120:0:U",
				"DS:net1_bytes_in:COUNTER:120:0:U",
				"DS:net1_bytes_out:COUNTER:120:0:U",
				"DS:net1_packs_in:COUNTER:120:0:U",
				"DS:net1_packs_out:COUNTER:120:0:U",
				"DS:net1_error_in:COUNTER:120:0:U",
				"DS:net1_error_out:COUNTER:120:0:U",
				"DS:net2_bytes_in:COUNTER:120:0:U",
				"DS:net2_bytes_out:COUNTER:120:0:U",
				"DS:net2_packs_in:COUNTER:120:0:U",
				"DS:net2_packs_out:COUNTER:120:0:U",
				"DS:net2_error_in:COUNTER:120:0:U",
				"DS:net2_error_out:COUNTER:120:0:U",
				"DS:net3_bytes_in:COUNTER:120:0:U",
				"DS:net3_bytes_out:COUNTER:120:0:U",
				"DS:net3_packs_in:COUNTER:120:0:U",
				"DS:net3_packs_out:COUNTER:120:0:U",
				"DS:net3_error_in:COUNTER:120:0:U",
				"DS:net3_error_out:COUNTER:120:0:U",
				"DS:net4_bytes_in:COUNTER:120:0:U",
				"DS:net4_bytes_out:COUNTER:120:0:U",
				"DS:net4_packs_in:COUNTER:120:0:U",
				"DS:net4_packs_out:COUNTER:120:0:U",
				"DS:net4_error_in:COUNTER:120:0:U",
				"DS:net4_error_out:COUNTER:120:0:U",
				"DS:net5_bytes_in:COUNTER:120:0:U",
				"DS:net5_bytes_out:COUNTER:120:0:U",
				"DS:net5_packs_in:COUNTER:120:0:U",
				"DS:net5_packs_out:COUNTER:120:0:U",
				"DS:net5_error_in:COUNTER:120:0:U",
				"DS:net5_error_out:COUNTER:120:0:U",
				"DS:net6_bytes_in:COUNTER:120:0:U",
				"DS:net6_bytes_out:COUNTER:120:0:U",
				"DS:net6_packs_in:COUNTER:120:0:U",
				"DS:net6_packs_out:COUNTER:120:0:U",
				"DS:net6_error_in:COUNTER:120:0:U",
				"DS:net6_error_out:COUNTER:120:0:U",
				"DS:net7_bytes_in:COUNTER:120:0:U",
				"DS:net7_bytes_out:COUNTER:120:0:U",
				"DS:net7_packs_in:COUNTER:120:0:U",
				"DS:net7_packs_out:COUNTER:120:0:U",
				"DS:net7_error_in:COUNTER:120:0:U",
				"DS:net7_error_out:COUNTER:120:0:U",
				"DS:net8_bytes_in:COUNTER:120:0:U",
				"DS:net8_bytes_out:COUNTER:120:0:U",
				"DS:net8_packs_in:COUNTER:120:0:U",
				"DS:net8_packs_out:COUNTER:120:0:U",
				"DS:net8_error_in:COUNTER:120:0:U",
				"DS:net8_error_out:COUNTER:120:0:U",
				"DS:net9_bytes_in:COUNTER:120:0:U",
				"DS:net9_bytes_out:COUNTER:120:0:U",
				"DS:net9_packs_in:COUNTER:120:0:U",
				"DS:net9_packs_out:COUNTER:120:0:U",
				"DS:net9_error_in:COUNTER:120:0:U",
				"DS:net9_error_out:COUNTER:120:0:U",
				"RRA:AVERAGE:0.5:1:1440",
				"RRA:AVERAGE:0.5:30:336",
				"RRA:AVERAGE:0.5:60:744",
				"RRA:AVERAGE:0.5:1440:365",
				"RRA:MIN:0.5:1:1440",
				"RRA:MIN:0.5:30:336",
				"RRA:MIN:0.5:60:744",
				"RRA:MIN:0.5:1440:365",
				"RRA:MAX:0.5:1:1440",
				"RRA:MAX:0.5:30:336",
				"RRA:MAX:0.5:60:744",
				"RRA:MAX:0.5:1440:365",
				"RRA:LAST:0.5:1:1440",
				"RRA:LAST:0.5:30:336",
				"RRA:LAST:0.5:60:744",
				"RRA:LAST:0.5:1440:365",
			);
		};
		my $err = RRDs::error;
		if($@ || $err) {
			logger("$@") unless !$@;
			if($err) {
				logger("ERROR: while creating $NET_RRD: $err");
				if($err eq "RRDs::error") {
					logger("... is the RRDtool Perl package installed?");
				}
			}
			return;
		}
	}

	push(@graphs, "net_update");
	if($opt_d eq "all" || $debug) {
		logger("$myself: Ok");
	}
}

sub net_update {
	my $myself = (caller(0))[3];
	my ($debug) = @_;

	my @net_bytes_in;
	my @net_bytes_out;
	my @net_packs_in;
	my @net_packs_out;
	my @net_error_in;
	my @net_error_out;

	my $n;
	my $rrdata = "N";

	for($n = 0; $n < 10 ; $n++) {
		$net_bytes_in[$n] = 0;
		$net_bytes_out[$n] = 0;
		$net_packs_in[$n] = 0;
		$net_packs_out[$n] = 0;
		$net_error_in[$n] = 0;
		$net_error_out[$n] = 0;
		if($n < scalar(@NET_LIST)) {
			if($os eq "Linux") {
				open(IN, "/proc/net/dev");
				my $dev;
				while(<IN>) {
					($dev, $data) = split(':', $_);
					$_ = $dev;
					if(/$NET_LIST[$n]/) {
						($net_bytes_in[$n], $net_packs_in[$n], $net_error_in[$n], undef, undef, undef, undef, undef, $net_bytes_out[$n], $net_packs_out[$n], $net_error_out[$n]) = split(' ', $data);
						last;
					}
				}
				close(IN);
			} elsif($os eq "FreeBSD") {
				open(IN, "netstat -nibd |");
				while(<IN>) {
					if(/Link/ && /$NET_LIST[$n]/) {
						# Idrop column added in 8.0
						if($kernel_branch > 7.2) {
							(undef, undef, undef, undef, $net_packs_in[$n], $net_error_in[$n], undef, $net_bytes_in[$n], $net_packs_out[$n], $net_error_out[$n], $net_bytes_out[$n]) = split(' ', $_);
						} else {
							(undef, undef, undef, undef, $net_packs_in[$n], $net_error_in[$n], $net_bytes_in[$n], $net_packs_out[$n], $net_error_out[$n], $net_bytes_out[$n]) = split(' ', $_);
						}
						last;
					}
				}
				close(IN);
			} elsif($os eq "OpenBSD") {
				open(IN, "netstat -nibd |");
				while(<IN>) {
					if(/Link/ && /^$NET_LIST[$n]/) {
						(undef, undef, undef, undef, $net_bytes_in[$n], $net_bytes_out[$n]) = split(' ', $_);
						$net_packs_in[$n] = 0;
						$net_error_in[$n] = 0;
						$net_packs_out[$n] = 0;
						$net_error_out[$n] = 0;
						last;
					}
				}
				close(IN);
			}
		}
		chomp($net_bytes_in[$n],
			$net_bytes_out[$n],
			$net_packs_in[$n],
			$net_packs_out[$n],
			$net_error_in[$n],
			$net_error_out[$n]);
		$rrdata .= ":$net_bytes_in[$n]:$net_bytes_out[$n]:$net_packs_in[$n]:$net_packs_out[$n]:$net_error_in[$n]:$net_error_out[$n]";
	}

	RRDs::update($NET_RRD, $rrdata);
	if($opt_d eq "all" || $debug) {
		logger("$myself: $rrdata");
	}
	my $err = RRDs::error;
	logger("ERROR: while updating $NET_RRD: $err") if $err;
}

# SERV graph
# ----------------------------------------------------------------------------
sub serv_init {
	my $myself = (caller(0))[3];

	if(!(-e $SERV_RRD)) {
		logger("Creating '$SERV_RRD' file.");
		eval {
			RRDs::create($SERV_RRD,
				"--step=300",
				"DS:serv_i_ssh:GAUGE:600:0:U",
				"DS:serv_i_ftp:GAUGE:600:0:U",
				"DS:serv_i_telnet:GAUGE:600:0:U",
				"DS:serv_i_imap:GAUGE:600:0:U",
				"DS:serv_i_smb:GAUGE:600:0:U",
				"DS:serv_i_fax:GAUGE:600:0:U",
				"DS:serv_i_cups:GAUGE:600:0:U",
				"DS:serv_i_pop3:GAUGE:600:0:U",
				"DS:serv_i_smtp:GAUGE:600:0:U",
				"DS:serv_i_spam:GAUGE:600:0:U",
				"DS:serv_i_virus:GAUGE:600:0:U",
				"DS:serv_i_f2b:GAUGE:600:0:U",
				"DS:serv_i_val02:GAUGE:600:0:U",
				"DS:serv_i_val03:GAUGE:600:0:U",
				"DS:serv_i_val04:GAUGE:600:0:U",
				"DS:serv_i_val05:GAUGE:600:0:U",
				"DS:serv_l_ssh:GAUGE:600:0:U",
				"DS:serv_l_ftp:GAUGE:600:0:U",
				"DS:serv_l_telnet:GAUGE:600:0:U",
				"DS:serv_l_imap:GAUGE:600:0:U",
				"DS:serv_l_smb:GAUGE:600:0:U",
				"DS:serv_l_fax:GAUGE:600:0:U",
				"DS:serv_l_cups:GAUGE:600:0:U",
				"DS:serv_l_pop3:GAUGE:600:0:U",
				"DS:serv_l_smtp:GAUGE:600:0:U",
				"DS:serv_l_spam:GAUGE:600:0:U",
				"DS:serv_l_virus:GAUGE:600:0:U",
				"DS:serv_l_f2b:GAUGE:600:0:U",
				"DS:serv_l_val02:GAUGE:600:0:U",
				"DS:serv_l_val03:GAUGE:600:0:U",
				"DS:serv_l_val04:GAUGE:600:0:U",
				"DS:serv_l_val05:GAUGE:600:0:U",
				"RRA:AVERAGE:0.5:1:288",
				"RRA:AVERAGE:0.5:6:336",
				"RRA:AVERAGE:0.5:12:744",
				"RRA:AVERAGE:0.5:288:365",
				"RRA:MIN:0.5:1:288",
				"RRA:MIN:0.5:6:336",
				"RRA:MIN:0.5:12:744",
				"RRA:MIN:0.5:288:365",
				"RRA:MAX:0.5:1:288",
				"RRA:MAX:0.5:6:336",
				"RRA:MAX:0.5:12:744",
				"RRA:MAX:0.5:288:365",
				"RRA:LAST:0.5:1:288",
				"RRA:LAST:0.5:6:336",
				"RRA:LAST:0.5:12:744",
				"RRA:LAST:0.5:288:365",
			);
		};
		my $err = RRDs::error;
		if($@ || $err) {
			logger("$@") unless !$@;
			if($err) {
				logger("ERROR: while creating $SERV_RRD: $err");
				if($err eq "RRDs::error") {
					logger("... is the RRDtool Perl package installed?");
				}
			}
			return;
		}
	}

	# Since 2.4.1 new values are used (val_01) to support 'fail2ban' hits.
	# These new values need to be renamed.
	RRDs::tune($SERV_RRD,
		"--data-source-rename=serv_i_val01:serv_i_f2b",
		"--data-source-rename=serv_l_val01:serv_l_f2b",
	);

	our %serv_hist = ();
	push(@graphs, "serv_update");
	if($opt_d eq "all" || $debug) {
		logger("$myself: Ok");
	}
}

sub serv_update {
	my $myself = (caller(0))[3];
	my ($debug) = @_;

	my $ssh = 0;
	my $ftp = 0;
	my $telnet = 0;
	my $imap = 0;
	my $smb = 0;
	my $fax = 0;
	my $cups = 0;
	my $pop3 = 0;
	my $smtp = 0;
	my $spam = 0;
	my $virus = 0;
	my $f2b = 0;
	my $val02 = 0;
	my $val03 = 0;
	my $val04 = 0;
	my $val05 = 0;

	my $date;
	my $rrdata = "N";

	# This graph is refreshed only every 5 minutes
	my (undef, $min) = localtime(time);
	if($min % 5) {
		return;
	}

	if(-r $SECURE_LOG) {
		$date = strftime("%b %e", localtime);
		open(IN, "$SECURE_LOG");
		while(<IN>) {
			if(/^$date/) {
				if(/ sshd\[/ && /Accepted /) {
					$ssh++;
				}
				if($os eq "Linux") {
					if(/START: pop3/) {
						$pop3++;
					}
					if(/START: ftp/ ||
					  (/ proftpd\[/ && /Login successful./)) {
						$ftp++;
					}
					if(/START: telnet/) {
						$telnet++;
					}
				} elsif($os eq "FreeBSD") {

					if(/login:/ && /login from /) {
						$telnet++;
					}
				}
			}
		}
		close(IN);
	}

	if(-r $IMAP_LOG) {
		$IMAP_DATE_LOG_FORMAT = $IMAP_DATE_LOG_FORMAT || "%b %d";
		$date_dovecot = strftime($IMAP_DATE_LOG_FORMAT, localtime);
		$date_uw = strftime("%b %e %T", localtime);
		open(IN, "$IMAP_LOG");
		while(<IN>) {
			# UW-IMAP log
			if(/$date_uw/) {
				if(/ imapd\[/ && / Login user=/) {
					$imap++;
				}
			}
			# Dovecot log
			if(/$date_dovecot /) {
				if(/ imap-login: / && / Login: /) {
					$imap++;
				}
				if(/ pop3-login: / && / Login: /) {
					$pop3++;
				}
			}
		}
		close(IN);
	}

	my @data;
	my $start = 0;
	open(IN, "smbstatus -L 2>/dev/null |");
	while(<IN>) {
		if(/^----------/) {
			$start = 1;
			next;
		}
		if($start) {
			chomp($_);
			push(@data, $_) unless !$_;
		}
	}
	close(IN);
	$smb_L = scalar(@data);
	undef(@data);
	$start = 0;
	open(IN, "smbstatus -S 2>/dev/null |");
	while(<IN>) {
		if(/^----------/) {
			$start = 1;
			next;
		}
		if($start) {
			chomp($_);
			push(@data, $_) unless !$_;
		}
	}
	close(IN);
	$smb_S = scalar(@data);
	$smb = $smb_L + $smb_S;

	if(-r $HYLAFAX_LOG) {
		$date = strftime("%m/%d/%y", localtime);
		open(IN, "$HYLAFAX_LOG");
		while(<IN>) {
			if(/^$date/ && /SEND/) {
				$fax++;
			}
		}
		close(IN);
	}

	if(-r $CUPS_LOG) {
		$date = strftime("%d/%b/%Y", localtime);
		open(IN, "$CUPS_LOG");
		while(<IN>) {
			if(/\[$date:/) {
				$cups++;
			}
		}
		close(IN);
	}

	if(-r $FAIL2BAN_LOG) {
		$date = strftime("%Y-%m-%d", localtime);
		open(IN, $FAIL2BAN_LOG);
		while(<IN>) {
			if(/^$date/ && / fail2ban/ && / WARNING / && / Ban /) {
				$f2b++;
			}
		}
		close(IN);
	}

	if(-r $MAIL_LOG) {
		$date = strftime("%b %e", localtime);
		open(IN, "$MAIL_LOG");
		while(<IN>) {
			if(/^$date/) {
				if(/to=/ && /stat(us)?=sent/i) {
					$smtp++;	
				}
				if(/MailScanner/ && /Spam Checks:/ && /Found/ && /spam messages/) {
					$spam++;
				}
				if(/MailScanner/ && /Virus Scanning:/ && /Found/ && /viruses/) {
					$virus++;
				}
			}
		}
		close(IN);
	}

	$date = strftime("%Y-%m-%d", localtime);
	if(-r "$CG_LOGDIR/$date.log") {
		open(IN, "$CG_LOGDIR/$date.log");
		while(<IN>) {
			if(/DEQUEUER \[\d+\] (LOCAL\(.+\) delivered|SMTP.+ relayed)\:/) {
				$smtp++;
			}
			if(/IMAP/ && / connected from /) {
				$imap++;
			}
			if(/POP/ && / connected from /) {
				$pop3++;
			}
		}
		close(IN);
	}

	if(-r $SPAMASSASSIN_LOG) {
		$date = strftime("%b %e", localtime);
		open(IN, $SPAMASSASSIN_LOG);
		while(<IN>) {
			if(/^$date/ && /spamd: identified spam/) {
				$spam++;
			}
		}
		close(IN);
	}

	if(-r $CLAMAV_LOG) {
		$date = strftime("%a %b %e", localtime);
		open(IN, $CLAMAV_LOG);
		while(<IN>) {
			if(/^$date/ && / FOUND/) {
				$virus++;
			}
		}
		close(IN);
	}

	# I data (incremental)
	$rrdata .= ":$ssh:$ftp:$telnet:$imap:$smb:$fax:$cups:$pop3:$smtp:$spam:$virus:$f2b:$val02:$val03:$val04:$val05";

	# L data (load)
	my $l_ssh = 0;
	my $l_ftp = 0;
	my $l_telnet = 0;
	my $l_imap = 0;
	my $l_smb = 0;
	my $l_fax = 0;
	my $l_cups = 0;
	my $l_pop3 = 0;
	my $l_smtp = 0;
	my $l_spam = 0;
	my $l_virus = 0;
	my $l_f2b = 0;
	my $l_val02 = 0;
	my $l_val03 = 0;
	my $l_val04 = 0;
	my $l_val05 = 0;

	$l_ssh = $ssh - $serv_hist{'ssh'};
	$l_ssh = 0 unless $l_ssh != $ssh;
	$l_ssh /= 300;
	$serv_hist{'ssh'} = $ssh;

	$l_ftp = $ftp - $serv_hist{'ftp'};
	$l_ftp = 0 unless $l_ftp != $ftp;
	$l_ftp /= 300;
	$serv_hist{'ftp'} = $ftp;

	$l_telnet = $telnet - $serv_hist{'telnet'};
	$l_telnet = 0 unless $l_telnet != $telnet;
	$l_telnet /= 300;
	$serv_hist{'telnet'} = $telnet;

	$l_imap = $imap - $serv_hist{'imap'};
	$l_imap = 0 unless $l_imap != $imap;
	$l_imap /= 300;
	$serv_hist{'imap'} = $imap;

	$l_smb = $smb - $serv_hist{'smb'};
	$l_smb = 0 unless $l_smb != $smb;
	$l_smb /= 300;
	$serv_hist{'smb'} = $smb;

	$l_fax = $fax - $serv_hist{'fax'};
	$l_fax = 0 unless $l_fax != $fax;
	$l_fax /= 300;
	$serv_hist{'fax'} = $fax;

	$l_cups = $cups - $serv_hist{'cups'};
	$l_cups = 0 unless $l_cups != $cups;
	$l_cups /= 300;
	$serv_hist{'cups'} = $cups;

	$l_pop3 = $pop3 - $serv_hist{'pop3'};
	$l_pop3 = 0 unless $l_pop3 != $pop3;
	$l_pop3 /= 300;
	$serv_hist{'pop3'} = $pop3;

	$l_smtp = $smtp - $serv_hist{'smtp'};
	$l_smtp = 0 unless $l_smtp != $smtp;
	$l_smtp /= 300;
	$serv_hist{'smtp'} = $smtp;

	$l_spam = $spam - $serv_hist{'spam'};
	$l_spam = 0 unless $l_spam != $spam;
	$l_spam /= 300;
	$serv_hist{'spam'} = $spam;

	$l_virus = $virus - $serv_hist{'virus'};
	$l_virus = 0 unless $l_virus != $virus;
	$l_virus /= 300;
	$serv_hist{'virus'} = $virus;

	$l_f2b = $f2b - $serv_hist{'f2b'};
	$l_f2b = 0 unless $l_f2b != $f2b;
	$l_f2b /= 300;
	$serv_hist{'f2b'} = $f2b;

	$l_val02 = $val02 - $serv_hist{'val02'};
	$l_val02 = 0 unless $l_val02 != $val02;
	$l_val02 /= 300;
	$serv_hist{'val02'} = $val02;

	$l_val03 = $val03 - $serv_hist{'val03'};
	$l_val03 = 0 unless $l_val03 != $val03;
	$l_val03 /= 300;
	$serv_hist{'val03'} = $val03;

	$l_val04 = $val04 - $serv_hist{'val04'};
	$l_val04 = 0 unless $l_val04 != $val04;
	$l_val04 /= 300;
	$serv_hist{'val04'} = $val04;

	$l_val05 = $val05 - $serv_hist{'val05'};
	$l_val05 = 0 unless $l_val05 != $val05;
	$l_val05 /= 300;
	$serv_hist{'val05'} = $val05;

	$rrdata .= ":$l_ssh:$l_ftp:$l_telnet:$l_imap:$l_smb:$l_fax:$l_cups:$l_pop3:$l_smtp:$l_spam:$l_virus:$l_f2b:$l_val02:$l_val03:$l_val04:$l_val05";
	RRDs::update($SERV_RRD, $rrdata);
	if($opt_d eq "all" || $debug) {
		logger("$myself: $rrdata");
	}
	my $err = RRDs::error;
	logger("ERROR: while updating $SERV_RRD: $err") if $err;
}

# MAIL graph
# ----------------------------------------------------------------------------
sub mail_init {
	my $myself = (caller(0))[3];

	if(!(-e $MAIL_RRD)) {
		logger("Creating '$MAIL_RRD' file.");
		eval {
			RRDs::create($MAIL_RRD,
				"--step=60",
				"DS:mail_in:GAUGE:120:0:U",
				"DS:mail_out:GAUGE:120:0:U",
				"DS:mail_recvd:GAUGE:120:0:U",
				"DS:mail_delvd:GAUGE:120:0:U",
				"DS:mail_bytes_recvd:GAUGE:120:0:U",
				"DS:mail_bytes_delvd:GAUGE:120:0:U",
				"DS:mail_rejtd:GAUGE:120:0:U",
				"DS:mail_spam:GAUGE:120:0:U",
				"DS:mail_virus:GAUGE:120:0:U",
				"DS:mail_bouncd:GAUGE:120:0:U",
				"DS:mail_queued:GAUGE:120:0:U",
				"DS:mail_discrd:GAUGE:120:0:U",
				"DS:mail_held:GAUGE:120:0:U",
				"DS:mail_forwrd:GAUGE:120:0:U",
				"DS:mail_queues:GAUGE:120:0:U",
				"DS:mail_val01:COUNTER:120:0:U",
				"DS:mail_val02:COUNTER:120:0:U",
				"DS:mail_val03:COUNTER:120:0:U",
				"DS:mail_val04:COUNTER:120:0:U",
				"DS:mail_val05:COUNTER:120:0:U",
				"DS:mail_val06:GAUGE:120:0:U",
				"DS:mail_val07:GAUGE:120:0:U",
				"DS:mail_val08:GAUGE:120:0:U",
				"DS:mail_val09:GAUGE:120:0:U",
				"DS:mail_val10:GAUGE:120:0:U",
				"RRA:AVERAGE:0.5:1:1440",
				"RRA:AVERAGE:0.5:30:336",
				"RRA:AVERAGE:0.5:60:744",
				"RRA:AVERAGE:0.5:1440:365",
				"RRA:MIN:0.5:1:1440",
				"RRA:MIN:0.5:30:336",
				"RRA:MIN:0.5:60:744",
				"RRA:MIN:0.5:1440:365",
				"RRA:MAX:0.5:1:1440",
				"RRA:MAX:0.5:30:336",
				"RRA:MAX:0.5:60:744",
				"RRA:MAX:0.5:1440:365",
				"RRA:LAST:0.5:1:1440",
				"RRA:LAST:0.5:30:336",
				"RRA:LAST:0.5:60:744",
				"RRA:LAST:0.5:1440:365",
			);
		};
		my $err = RRDs::error;
		if($@ || $err) {
			logger("$@") unless !$@;
			if($err) {
				logger("ERROR: while creating $MAIL_RRD: $err");
				if($err eq "RRDs::error") {
					logger("... is the RRDtool Perl package installed?");
				}
			}
			return;
		}
	}

	# Since 2.5.0 there is support for more message status.
	# Some values need to be renamed.
	RRDs::tune($MAIL_RRD,
		"--data-source-rename=mail_mta_val10:mail_bouncd",
		"--data-source-rename=mail_mta_val12:mail_discrd",
		"--data-source-rename=mail_mta_val13:mail_held",
		"--data-source-rename=mail_mta_val14:mail_forwrd",
		"--data-source-rename=mail_mta_val15:mail_queues",
	);

	our $mail_hist = 0;
	push(@graphs, "mail_update");
	if($opt_d eq "all" || $debug) {
		logger("$myself: Ok");
	}
}

sub mail_update {
	my $myself = (caller(0))[3];
	my ($debug) = @_;

	my $in_conn;
	my $out_conn;
	my $recvd;
	my $delvd;
	my $bytes_recvd;
	my $bytes_delvd;
	my $rejected;
	my $queued;
	my $spam;
	my $virus;
	my $gl_records;
	my $gl_greylisted;
	my $gl_whitelisted;
	my @mta_h = (0) x 15;
	my @mta = (0) x 15;
	my @gen = (0) x 10;

	my $n;
	my $mail_log_seekpos;
	my $mail_log_size;
	my $sa_log_seekpos;
	my $sa_log_size;
	my $clamav_log_seekpos;
	my $clamav_log_size;
	my $rrdata = "N";

	# Read last MAIL data from historic
	($mail_log_seekpos, $sa_log_seekpos, $clamav_log_seekpos, @mta_h[0..15-1], @gen[0..10-1]) = split(';', $mail_hist);
	$mail_log_seekpos = defined($mail_log_seekpos) ? int($mail_log_seekpos) : 0;
	$sa_log_seekpos = defined($sa_log_seekpos) ? int($sa_log_seekpos) : 0;
	$clamav_log_seekpos = defined($clamav_log_seekpos) ? int($clamav_log_seekpos) : 0;

	$recvd = $delvd = $bytes_recvd = $bytes_delvd = 0;
	$in_conn = $out_conn = $rejected = 0;
	$bounced = $discarded = $held = $forwarded = 0;
	$queued = $queues = 0;
	if(lc($MAIL_MTA) eq "sendmail") {
		if(open(IN, "mailstats -P |")) {
			while(<IN>) {
				if(/^ T\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/) {
					$recvd = $1;
					$bytes_recvd = $2;
					$delvd = $3;
					$bytes_delvd = $4;
				}
				if(/^ C\s+(\d+)\s+(\d+)\s+(\d+)/) {
					$in_conn = $1;
					$out_conn = $2;
					$rejected = $3;
				}
			}
			close(IN);
			$bytes_recvd *= 1024;
			$bytes_delvd *= 1024;
		}
		if(open(IN, "mailq |")) {
			while(<IN>) {
				my ($tmp) = ($_ =~ m/^\w{14}[ *X-]\s*(\d{1,8}) /);
				$queues += $tmp if $tmp;
				if(/^\s+Total requests: (\d+)$/) {
					$queued = $1;
				}
			}
			close(IN);
		}
	} elsif(lc($MAIL_MTA) eq "postfix") {
		my @data;

		for my $path (split /:/, $ENV{PATH}) {
			if(-f "$path/pflogsumm" && -x _) {
				open(IN, "pflogsumm -d today -h 0 -u 0 --smtpd_stats --no_bounce_detail --no_deferral_detail --no_reject_detail --no_no_msg_size --no_smtpd_warnings $MAIL_LOG 2>/dev/null |");
			}
			if(-f "$path/pflogsumm.pl" && -x _) {
				open(IN, "pflogsumm.pl -d today -h 0 -u 0 --smtpd_stats --no_bounce_detail --no_deferral_detail --no_reject_detail --no_no_msg_size --no_smtpd_warnings $MAIL_LOG 2>/dev/null |");
			}
		}
		@data = <IN>;
		close(IN);
		foreach(@data) {
			if(/^\s*(\d{1,7})([ km])\s*received$/) {
				$recvd = $1;
				$recvd = $1 * 1024 if $2 eq "k";
				$recvd = $1 * 1024 * 1024 if $2 eq "m";
			}
			if(/^\s*(\d{1,7})([ km])\s*delivered$/) {
				$delvd = $1;
				$delvd = $1 * 1024 if $2 eq "k";
				$delvd = $1 * 1024 * 1024 if $2 eq "m";
			}
			if(/^\s*(\d{1,7})([ km])\s*forwarded$/) {
				$forwarded = $1;
				$forwarded = $1 * 1024 if $2 eq "k";
				$forwarded = $1 * 1024 * 1024 if $2 eq "m";
			}
			if(/^\s*(\d{1,7})([ km])\s*bounced$/) {
				$bounced = $1;
				$bounced = $1 * 1024 if $2 eq "k";
				$bounced = $1 * 1024 * 1024 if $2 eq "m";
			}
			if(/^\s*(\d{1,7})([ km])\s*rejected \(/) {
				$rejected = $1;
				$rejected = $1 * 1024 if $2 eq "k";
				$rejected = $1 * 1024 * 1024 if $2 eq "m";
			}
			if(/^\s*(\d{1,7})([ km])\s*held/) {
				$held = $1;
				$held = $1 * 1024 if $2 eq "k";
				$held = $1 * 1024 * 1024 if $2 eq "m";
			}
			if(/^\s*(\d{1,7})([ km])\s*discarded \(/) {
				$discarded = $1;
				$discarded = $1 * 1024 if $2 eq "k";
				$discarded = $1 * 1024 * 1024 if $2 eq "m";
			}
			if(/^\s*(\d{1,7})([ km])\s*bytes received$/) {
				$bytes_recvd = $1;
				$bytes_recvd = $1 * 1024 if $2 eq "k";
				$bytes_recvd = $1 * 1024 * 1024 if $2 eq "m";
			}
			if(/^\s*(\d{1,7})([ km])\s*bytes delivered$/) {
				$bytes_delvd = $1;
				$bytes_delvd = $1 * 1024 if $2 eq "k";
				$bytes_delvd = $1 * 1024 * 1024 if $2 eq "m";
			}
		}
		if(open(IN, "mailq |")) {
			while(<IN>) {
				if(/^-- (\d+) Kbytes in (\d+) Request/) {
					$queues = $1;
					$queued = $2;
				}  
			}
			close(IN);
		}
	}

	$gl_records = $gl_greylisted = $gl_whitelisted = 0;
	if(lc($MAIL_GREYLIST) eq "milter-greylist") {
		if(-r $MILTER_GL) {
			open(IN, $MILTER_GL);
			if(!seek(IN, -80, 2)) {
				logger("Couldn't seek to the end ($MILTER_GL): $!");
				return;
			}
			while(<IN>) {
				if(/^# Summary:\s+(\d+) records,\s+(\d+) greylisted,\s+(\d+) whitelisted$/) {
					$gl_records = $1;
					$gl_greylisted = $2;
					$gl_whitelisted = $3;
				}
			}
			close(IN);
		}
	}

	$spam = $virus = 0;
	if(-r $MAIL_LOG) {
		open(IN, $MAIL_LOG);
		if(!seek(IN, 0, 2)) {
			logger("Couldn't seek to the end ($MAIL_LOG): $!");
			return;
		}
		$mail_log_size = tell(IN);
		if($mail_log_size < $mail_log_seekpos) {
			$mail_log_seekpos = 0;
		}
		if(!seek(IN, $mail_log_seekpos, 0)) {
			logger("Couldn't seek to $mail_log_seekpos ($MAIL_LOG): $!");
			return;
		}
		while(<IN>) {
			my @line;
			if(/MailScanner/ && /Spam Checks:/ && /Found/ && /spam messages/) {
				@line = split(' ', $_);
				$spam += int($line[8]);
			}
			if(/MailScanner/ && /Virus Scanning:/ && /Found/ && /viruses/) {
				@line = split(' ', $_);
				$virus += int($line[8]);
			}
		}
		close(IN);
	}

	if(-r $SPAMASSASSIN_LOG) {
		$date = strftime("%b %e", localtime);
		open(IN, $SPAMASSASSIN_LOG);
		if(!seek(IN, 0, 2)) {
			logger("Couldn't seek to the end ($SPAMASSASSIN_LOG): $!");
			return;
		}
		$sa_log_size = tell(IN);
		if($sa_log_size < $sa_log_seekpos) {
			$sa_log_seekpos = 0;
		}
		if(!seek(IN, $sa_log_seekpos, 0)) {
			logger("Couldn't seek to $sa_log_seekpos ($SPAMASSASSIN_LOG): $!");
			return;
		}
		while(<IN>) {
			if(/^$date/ && /spamd: identified spam/) {
				$spam++;
			}
		}
		close(IN);
	}

	if(-r $CLAMAV_LOG) {
		$date = strftime("%a %b %e", localtime);
		open(IN, $CLAMAV_LOG);
		if(!seek(IN, 0, 2)) {
			logger("Couldn't seek to the end ($CLAMAV_LOG): $!");
			return;
		}
		$clamav_log_size = tell(IN);
		if($clamav_log_size < $clamav_log_seekpos) {
			$clamav_log_seekpos = 0;
		}
		if(!seek(IN, $clamav_log_seekpos, 0)) {
			logger("Couldn't seek to $clamav_log_seekpos ($CLAMAV_LOG): $!");
			return;
		}
		while(<IN>) {
			if(/^$date/ && / FOUND/) {
				$virus++;
			}
		}
		close(IN);
	}

	$mta[0] = int($in_conn) - $mta_h[0];
	$mta[0] = 0 unless $mta[0] != int($in_conn);
	$mta[0] /= 60;
	$mta_h[0] = int($in_conn);

	$mta[1] = int($out_conn) - $mta_h[1];
	$mta[1] = 0 unless $mta[1] != int($out_conn);
	$mta[1] /= 60;
	$mta_h[1] = int($out_conn);

	$mta[2] = int($recvd) - $mta_h[2];
	$mta[2] = 0 unless $mta[2] != int($recvd);
	$mta[2] /= 60;
	$mta_h[2] = int($recvd);

	$mta[3] = int($delvd) - $mta_h[3];
	$mta[3] = 0 unless $mta[3] != int($delvd);
	$mta[3] /= 60;
	$mta_h[3] = int($delvd);

	$mta[4] = int($bytes_recvd) - $mta_h[4];
	$mta[4] = 0 unless $mta[4] != int($bytes_recvd);
	$mta[4] /= 60;
	$mta_h[4] = int($bytes_recvd);

	$mta[5] = int($bytes_delvd) - $mta_h[5];
	$mta[5] = 0 unless $mta[5] != int($bytes_delvd);
	$mta[5] /= 60;
	$mta_h[5] = int($bytes_delvd);

	$mta[6] = int($rejected) - $mta_h[6];
	$mta[6] = 0 unless $mta[6] != int($rejected);
	$mta[6] /= 60;
	$mta_h[6] = int($rejected);

	# avoid initial spike
	$mta[7] = int($spam) unless !$mta_h[7];
	$mta_h[7] = int($spam) unless $mta_h[7];
	$mta[7] /= 60;

	# avoid initial spike
	$mta[8] = int($virus) unless !$mta_h[8];
	$mta_h[8] = int($virus) unless $mta_h[8];
	$mta[8] /= 60;

	$mta[9] = int($bouncd) - $mta_h[9];
	$mta[9] = 0 unless $mta[9] != int($bouncd);
	$mta[9] /= 60;
	$mta_h[9] = int($bouncd);

	$mta[10] = int($queued);

	$mta[11] = int($discrd) - $mta_h[11];
	$mta[11] = 0 unless $mta[11] != int($discrd);
	$mta[11] /= 60;
	$mta_h[11] = int($discrd);

	$mta[12] = int($held) - $mta_h[12];
	$mta[12] = 0 unless $mta[12] != int($held);
	$mta[12] /= 60;
	$mta_h[12] = int($held);

	$mta[13] = int($forwrd) - $mta_h[13];
	$mta[13] = 0 unless $mta[13] != int($forwrd);
	$mta[13] /= 60;
	$mta_h[13] = int($forwrd);

	$mta[14] = int($queues);

	$gen[6] = int($gl_records);
	$gen[7] = int($gl_greylisted);
	$gen[8] = int($gl_whitelisted);

	$mail_hist = join(";", $mail_log_size, $sa_log_size, $clamav_log_size, @mta_h, @gen);
	for($n = 0; $n < 15; $n++) {
		$rrdata .= ":" . $mta[$n];
	}
	for($n = 0; $n < 10; $n++) {
		$rrdata .= ":" . $gen[$n];
	}

	RRDs::update($MAIL_RRD, $rrdata);
	if($opt_d eq "all" || $debug) {
		logger("$myself: $rrdata");
	}
	my $err = RRDs::error;
	logger("ERROR: while updating $MAIL_RRD: $err") if $err;
}

# PORT graph
# ----------------------------------------------------------------------------
sub port_init {
	my $myself = (caller(0))[3];

	my $info;
	my @ds;
	my @tmp;
	my @data;
	my $n;
	my $p;

	if(-e $PORT_RRD) {
		$info = RRDs::info($PORT_RRD);
		for my $key (keys %$info) {
			if(index($key, 'ds[') == 0) {
				if(index($key, '.type') != -1) {
#					print("$key\n");
					push(@ds, substr($key, 3, index($key, ']') - 3));
				}
			}
		}
#		foreach(@ds) {
#			print($_ . "\n");
#		}
#		print(scalar(@ds) . "\n");
		if(scalar(@ds) / 2 != $PORT_MAX) {
			logger("Detected size mismatch between \$PORT_MAX ($PORT_MAX) and $PORT_RRD (" . scalar(@ds) / 2 . "). Resizing it accordingly. All historic data will be lost.");
			unlink($PORT_RRD);
		}
	}

	if(!(-e $PORT_RRD)) {
		logger("Creating '$PORT_RRD' file.");
		for($n = 0; $n < $PORT_MAX; $n++) {
			push(@tmp, "DS:port" . $n . "_in:COUNTER:120:0:U");
			push(@tmp, "DS:port" . $n . "_out:COUNTER:120:0:U");
		}
		eval {
			RRDs::create($PORT_RRD,
				"--step=60",
				@tmp,
				"RRA:AVERAGE:0.5:1:1440",
				"RRA:AVERAGE:0.5:30:336",
				"RRA:AVERAGE:0.5:60:744",
				"RRA:AVERAGE:0.5:1440:365",
				"RRA:MIN:0.5:1:1440",
				"RRA:MIN:0.5:30:336",
				"RRA:MIN:0.5:60:744",
				"RRA:MIN:0.5:1440:365",
				"RRA:MAX:0.5:1:1440",
				"RRA:MAX:0.5:30:336",
				"RRA:MAX:0.5:60:744",
				"RRA:MAX:0.5:1440:365",
				"RRA:LAST:0.5:1:1440",
				"RRA:LAST:0.5:30:336",
				"RRA:LAST:0.5:60:744",
				"RRA:LAST:0.5:1440:365",
			);
		};
		my $err = RRDs::error;
		if($@ || $err) {
			logger("$@") unless !$@;
			if($err) {
				logger("ERROR: while creating $PORT_RRD: $err");
				if($err eq "RRDs::error") {
					logger("... is the RRDtool Perl package installed?");
				}
			}
			return;
		}
	}

	if($os eq "Linux") {
		# remove the changed ports or those that no longer exist
		open(IN, "iptables -nxvL INPUT --line-numbers | grep MONITORIX_IN |");
		@data = <IN>;
		close(IN);
		my $rule;
		my $num;
		my $prot;
		my $port;
		my $exist;
		my @line;
		foreach my $d (@data) {
			$exist = 0;
			($rule, undef, undef, $name, $prot, undef, undef, undef, undef, undef, undef, $port) = split(' ', $d);
			(undef, $port) = split(':', $port);
			for($n = 0; $n < $PORT_MAX; $n++) {
				if($port eq $PORT_LIST[$n] && $prot eq lc($PORT_PROT[$n])) {
					$exist = 1;
					last;
				}
			}
			if(!$exist) {
				logger("removing unused iptables rule: port=$port,$prot");
				(undef, undef, $num) = split('_', $name);
				$name = "MONITORIX_IN_$num";
				open(IN, "iptables -nxvL INPUT --line-numbers | grep -w $name 2>/dev/null |");
				@line = <IN>;
				close(IN);
				($rule) = split(' ', $line[0]);
				system("iptables -D INPUT $rule");
				system("iptables -F $name");
				system("iptables -X $name");

				$name = "MONITORIX_OUT_$num";
				open(IN, "iptables -nxvL OUTPUT --line-numbers | grep -w $name 2>/dev/null |");
				@line = <IN>;
				close(IN);
				($rule) = split(' ', $line[0]);
				system("iptables -D OUTPUT $rule");
				system("iptables -F $name");
				system("iptables -X $name");
			}
		}

		# set the current defined ports
		for($n = 0; $n < $PORT_MAX; $n++) {
			if($PORT_LIST[$n]) {
				open(IN, "iptables -nxvL INPUT --line-numbers | grep MONITORIX_IN | grep -w dpt:$PORT_LIST[$n] 2>/dev/null |");
				@line = <IN>;
				close(IN);
				(undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, $num) = split(' ', $line[0]);
				chomp($num);
				if($num ne "dpt:$PORT_LIST[$n]") {
					$p = lc($PORT_PROT[$n]);
					$p = "all" unless $PORT_PROT[$n];
					system("iptables -N MONITORIX_IN_$n 2>/dev/null");
					system("(iptables -L INPUT | grep -q MONITORIX_IN_$n) || iptables -I INPUT -p $p --dport $PORT_LIST[$n] -j MONITORIX_IN_$n -c 0 0");
				}
				open(IN, "iptables -nxvL OUTPUT --line-numbers | grep MONITORIX_OUT | grep -w spt:$PORT_LIST[$n] 2>/dev/null |");
				@line = <IN>;
				close(IN);
				(undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, $num) = split(' ', $line[0]);
				chomp($num);
				if($num ne "spt:$PORT_LIST[$n]") {
					$p = lc($PORT_PROT[$n]);
					$p = "all" unless $PORT_PROT[$n];
					system("iptables -N MONITORIX_OUT_$n 2>/dev/null");
					system("(iptables -L OUTPUT | grep -q MONITORIX_OUT_$n) || iptables -I OUTPUT -p $p --sport $PORT_LIST[$n] -j MONITORIX_OUT_$n -c 0 0");
				}
			}
		}
	}
	if($os eq "FreeBSD" || $os eq "OpenBSD") {
		# remove the changed ports or those that no longer exist
		open(IN, "ipfw show $PORT_RULE 2>/dev/null | grep \"from any to me dst-port\" |");
		@data = <IN>;
		close(IN);
		my $num;
		my $rule;
		my $prot;
		my $port;
		my $exist;
		my @line;
		foreach my $d (@data) {
			$exist = 0;
			($rule, undef, undef, undef, $prot, undef, undef, undef, undef, undef, $port) = split(' ', $d);
			chomp($port);
			for($n = 0; $n < $PORT_MAX; $n++) {
				if($rule eq $PORT_RULE && $port eq $PORT_LIST[$n] && $prot eq lc($PORT_PROT[$n])) {
					$exist = 1;
					last;
				}
			}
			if(!$exist) {
				logger("removing unused ipfw rule: port=$port,$prot");
				system("ipfw delete $PORT_RULE >/dev/null");
			}
		}

		# set the current defined ports
		for($n = 0; $n < $PORT_MAX; $n++) {
			if($PORT_LIST[$n]) {
				open(IN, "ipfw show $PORT_RULE 2>/dev/null | grep \"from any to me dst-port $PORT_LIST[$n]\$\" |");
				@line = <IN>;
				close(IN);
				($rule, undef, undef, undef, undef, undef, undef, undef, undef, undef, $num) = split(' ', $line[0]);
				chomp($num);
				if($rule ne $PORT_RULE && $num ne $PORT_LIST[$n]) {
					$p = lc($PORT_PROT[$n]);
					$p = "ip" unless $PORT_PROT[$n];
					system("ipfw -q add $PORT_RULE count $p from any to me $PORT_LIST[$n]");
				}
				open(IN, "ipfw show $PORT_RULE 2>/dev/null | grep \"from me $PORT_LIST[$n] to any\$\" |");
				@line = <IN>;
				close(IN);
				($rule, undef, undef, undef, undef, undef, undef, $num) = split(' ', $line[0]);
				chomp($num);
				if($rule ne $PORT_RULE && $num ne $PORT_LIST[$n]) {
					$p = lc($PORT_PROT[$n]);
					$p = "ip" unless $PORT_PROT[$n];
					system("ipfw -q add $PORT_RULE count $p from me $PORT_LIST[$n] to any");
				}
			}
		}
	}

	push(@graphs, "port_update");
	if($opt_d eq "all" || $debug) {
		logger("$myself: Ok");
	}
}

sub port_update {
	my $myself = (caller(0))[3];
	my ($debug) = @_;

	my @in;
	my @out;

	my $n;
	my $rrdata = "N";

	if($os eq "Linux") {
		open(IN, "iptables -nxvL INPUT |");
		while(<IN>) {
			for($n = 0; $n < $PORT_MAX; $n++) {
				$in[$n] = 0 unless $in[$n];
				if(/ MONITORIX_IN_$n /) {
					my (undef, $bytes) = split(' ', $_);
					chomp($bytes);
					$in[$n] = $bytes;
				}
			}
		}
		close(IN);
		open(IN, "iptables -nxvL OUTPUT |");
		while(<IN>) {
			for($n = 0; $n < $PORT_MAX; $n++) {
				$out[$n] = 0 unless $out[$n];
				if(/ MONITORIX_OUT_$n /) {
					my (undef, $bytes) = split(' ', $_);
					chomp($bytes);
					$out[$n] = $bytes;
				}
			}
		}
		close(IN);
	}
	if($os eq "FreeBSD" || $os eq "OpenBSD") {
		open(IN, "ipfw show $PORT_RULE 2>/dev/null |");
		while(<IN>) {
			for($n = 0; $n < $PORT_MAX; $n++) {
				$in[$n] = 0 unless $in[$n];
				if(/ from any to me dst-port $PORT_LIST[$n]$/) {
					my (undef, undef, $bytes) = split(' ', $_);
					chomp($bytes);
					$in[$n] = $bytes;
				}
				$out[$n] = 0 unless $out[$n];
				if(/ from me $PORT_LIST[$n] to any$/) {
					my (undef, undef, $bytes) = split(' ', $_);
					chomp($bytes);
					$out[$n] = $bytes;
				}
			}
		}
		close(IN);
	}

	for($n = 0; $n < $PORT_MAX; $n++) {
		$rrdata .= ":$in[$n]:$out[$n]";
	}
	RRDs::update($PORT_RRD, $rrdata);
	if($opt_d eq "all" || $debug) {
		logger("$myself: $rrdata");
	}
	my $err = RRDs::error;
	logger("ERROR: while updating $PORT_RRD: $err") if $err;
}

# USER graph
# ----------------------------------------------------------------------------
sub user_init {
	my $myself = (caller(0))[3];

	if(!(-e $USER_RRD)) {
		logger("Creating '$USER_RRD' file.");
		eval {
			RRDs::create($USER_RRD,
				"--step=60",
				"DS:user_sys:GAUGE:120:0:U",
				"DS:user_smb:GAUGE:120:0:U",
				"DS:user_mac:GAUGE:120:0:U",
				"DS:user_val1:GAUGE:120:0:U",
				"DS:user_val2:GAUGE:120:0:U",
				"DS:user_val3:GAUGE:120:0:U",
				"DS:user_val4:GAUGE:120:0:U",
				"DS:user_val5:GAUGE:120:0:U",
				"RRA:AVERAGE:0.5:1:1440",
				"RRA:AVERAGE:0.5:30:336",
				"RRA:AVERAGE:0.5:60:744",
				"RRA:AVERAGE:0.5:1440:365",
				"RRA:MIN:0.5:1:1440",
				"RRA:MIN:0.5:30:336",
				"RRA:MIN:0.5:60:744",
				"RRA:MIN:0.5:1440:365",
				"RRA:MAX:0.5:1:1440",
				"RRA:MAX:0.5:30:336",
				"RRA:MAX:0.5:60:744",
				"RRA:MAX:0.5:1440:365",
				"RRA:LAST:0.5:1:1440",
				"RRA:LAST:0.5:30:336",
				"RRA:LAST:0.5:60:744",
				"RRA:LAST:0.5:1440:365",
			);
		};
		my $err = RRDs::error;
		if($@ || $err) {
			logger("$@") unless !$@;
			if($err) {
				logger("ERROR: while creating $USER_RRD: $err");
				if($err eq "RRDs::error") {
					logger("... is the RRDtool Perl package installed?");
				}
			}
			return;
		}
	}

	push(@graphs, "user_update");
	if($opt_d eq "all" || $debug) {
		logger("$myself: Ok");
	}
}

sub user_update {
	my $myself = (caller(0))[3];
	my ($debug) = @_;

	my $sys;
	my $smb;
	my $mac;

	my @data;
	my $rrdata = "N";

	open(IN, "who -q |");
	while(<IN>) {
		if(/^#/) {
			my @tmp = split('=', $_);
			$sys = $tmp[scalar(@tmp) - 1];
			chomp($sys);
			$sys = int($sys);
			last;
		}
	}
	close(IN);

	my $start = 0;
	undef(@data);
	open(IN, "smbstatus -b 2>/dev/null |");
	while(<IN>) {
		if(/^----------/) {
			$start = 1;
			next;
		}
		if($start) {
			chomp($_);
			push(@data, $_) unless !$_;
		}
	}
	close(IN);
	$smb = scalar(@data);

	open(IN, "macusers 2>/dev/null |");
	@data = <IN>;
	close(IN);
	$mac = scalar(@data) - 1;
	$mac = 0 unless @data;

	$rrdata .= ":$sys:$smb:$mac:0:0:0:0:0";
	RRDs::update($USER_RRD, $rrdata);
	if($opt_d eq "all" || $debug) {
		logger("$myself: $rrdata");
	}
	my $err = RRDs::error;
	logger("ERROR: while updating $USER_RRD: $err") if $err;
}

# APACHE graph
# ----------------------------------------------------------------------------
sub apache_init {
	my $myself = (caller(0))[3];

	my $info;
	my @ds;
	my @tmp;
	my $n;

	if(!defined(@APACHE_LIST)) {
		logger("$myself: ERROR: missing or not defined APACHE_LIST option.");
		return 0;
	}

	if(-e $APACHE_RRD) {
		$info = RRDs::info($APACHE_RRD);
		for my $key (keys %$info) {
			if(index($key, 'ds[') == 0) {
				if(index($key, '.type') != -1) {
#					print("$key\n");
					push(@ds, substr($key, 3, index($key, ']') - 3));
				}
			}
		}
#		foreach(@ds) {
#			print($_ . "\n");
#		}
#		print(scalar(@ds) . "\n");
		if(scalar(@ds) / 5 != scalar(@APACHE_LIST)) {
			logger("Detected size mismatch between \@APACHE_LIST (" . scalar(@APACHE_LIST) . ") and $APACHE_RRD (" . scalar(@ds) / 5 . "). Resizing it accordingly. All historic data will be lost.");
			unlink($APACHE_RRD);
		}
	}

	if(!(-e $APACHE_RRD)) {
		logger("Creating '$APACHE_RRD' file.");
		for($n = 0; $n < scalar(@APACHE_LIST); $n++) {
			push(@tmp, "DS:apache" . $n . "_acc:GAUGE:120:0:U");
			push(@tmp, "DS:apache" . $n . "_kb:GAUGE:120:0:U");
			push(@tmp, "DS:apache" . $n . "_cpu:GAUGE:120:0:U");
			push(@tmp, "DS:apache" . $n . "_busy:GAUGE:120:0:U");
			push(@tmp, "DS:apache" . $n . "_idle:GAUGE:120:0:U");
		}
		eval {
			RRDs::create($APACHE_RRD,
				"--step=60",
				@tmp,
				"RRA:AVERAGE:0.5:1:1440",
				"RRA:AVERAGE:0.5:30:336",
				"RRA:AVERAGE:0.5:60:744",
				"RRA:AVERAGE:0.5:1440:365",
				"RRA:MIN:0.5:1:1440",
				"RRA:MIN:0.5:30:336",
				"RRA:MIN:0.5:60:744",
				"RRA:MIN:0.5:1440:365",
				"RRA:MAX:0.5:1:1440",
				"RRA:MAX:0.5:30:336",
				"RRA:MAX:0.5:60:744",
				"RRA:MAX:0.5:1440:365",
				"RRA:LAST:0.5:1:1440",
				"RRA:LAST:0.5:30:336",
				"RRA:LAST:0.5:60:744",
				"RRA:LAST:0.5:1440:365",
			);
		};
		my $err = RRDs::error;
		if($@ || $err) {
			logger("$@") unless !$@;
			if($err) {
				logger("ERROR: while creating $APACHE_RRD: $err");
				if($err eq "RRDs::error") {
					logger("... is the RRDtool Perl package installed?");
				}
			}
			return;
		}
	}

	# Since 2.5.0 first 5 values belong to the first Apache being monitored.
	# These values need to be renamed.
	RRDs::tune($APACHE_RRD,
		"--data-source-rename=apache_acc:apache0_acc",
		"--data-source-rename=apache_kb:apache0_kb",
		"--data-source-rename=apache_cpu:apache0_cpu",
		"--data-source-rename=apache_busy:apache0_busy",
		"--data-source-rename=apache_idle:apache0_idle",
	);

	our %apache_hist = ();
	push(@graphs, "apache_update");
	if($opt_d eq "all" || $debug) {
		logger("$myself: Ok");
	}
}

sub apache_update {
	my $myself = (caller(0))[3];
	my ($debug) = @_;

	my $acc;
	my $kb;
	my $cpu;
	my $busy;
	my $idle;

	my $str;
	my $rrdata = "N";

	my $n = 0;
	foreach(@APACHE_LIST) {
		my $URL = $_ . "/server-status?auto";
		my $ua = LWP::UserAgent->new(timeout => 30);
		my $response = $ua->request(HTTP::Request->new('GET', $URL));

		foreach(split('\n', $response->content)) {
			if(/^Total Accesses:\s+(\d+)$/) {
				$str = $n . "acc";
				$acc = $1 - $apache_hist{$str};
				$acc = 0 unless $acc != $1;
				$acc /= 60;
				$apache_hist{$str} = $1;
				next;
			}
			if(/^Total kBytes:\s+(\d+)$/) {
				$str = $n . "kb";
				$kb = $1 - $apache_hist{$str};
				$kb = 0 unless $kb != $1;
				$apache_hist{$str} = $1;
				next;
			}
			if(/^CPULoad:\s+(\d*\.\d+)$/) {
#				$str = $n . "cpu";
#				$cpu = abs($1 - $apache_hist{$str});
#				$cpu = 0 unless $cpu != $1;
#				$apache_hist{$str} = $1;
				$cpu = abs($1);
				next;
			}
			if(/^BusyWorkers:\s+(\d+)/ || /^BusyServers:\s+(\d+)/) {
				$busy = int($1);
				next;
			}
			if(/^IdleWorkers:\s+(\d+)/ || /^IdleServers:\s+(\d+)/) {
				$idle = int($1);
				last;
			}
		}
		$rrdata .= ":$acc:$kb:$cpu:$busy:$idle";
		$n++;
	}

	RRDs::update($APACHE_RRD, $rrdata);
	if($opt_d eq "all" || $debug) {
		logger("$myself: $rrdata");
	}
	my $err = RRDs::error;
	logger("ERROR: while updating $APACHE_RRD: $err") if $err;
}

# NGINX graph
# ----------------------------------------------------------------------------
sub nginx_init {
	my $myself = (caller(0))[3];


	if(!(-e $NGINX_RRD)) {
		logger("Creating '$NGINX_RRD' file.");
		eval {
			RRDs::create($NGINX_RRD,
				"--step=60",
				"DS:nginx_requests:GAUGE:120:0:U",
				"DS:nginx_total:GAUGE:120:0:U",
				"DS:nginx_reading:GAUGE:120:0:U",
				"DS:nginx_writing:GAUGE:120:0:U",
				"DS:nginx_waiting:GAUGE:120:0:U",
				"DS:nginx_bytes_in:GAUGE:120:0:U",
				"DS:nginx_bytes_out:GAUGE:120:0:U",
				"RRA:AVERAGE:0.5:1:1440",
				"RRA:AVERAGE:0.5:30:336",
				"RRA:AVERAGE:0.5:60:744",
				"RRA:AVERAGE:0.5:1440:365",
				"RRA:MIN:0.5:1:1440",
				"RRA:MIN:0.5:30:336",
				"RRA:MIN:0.5:60:744",
				"RRA:MIN:0.5:1440:365",
				"RRA:MAX:0.5:1:1440",
				"RRA:MAX:0.5:30:336",
				"RRA:MAX:0.5:60:744",
				"RRA:MAX:0.5:1440:365",
				"RRA:LAST:0.5:1:1440",
				"RRA:LAST:0.5:30:336",
				"RRA:LAST:0.5:60:744",
				"RRA:LAST:0.5:1440:365",
			);
		};
		my $err = RRDs::error;
		if($@ || $err) {
			logger("$@") unless !$@;
			if($err) {
				logger("ERROR: while creating $NGINX_RRD: $err");
				if($err eq "RRDs::error") {
					logger("... is the RRDtool Perl package installed?");
				}
			}
			return;
		}
	}

	if(!defined($NGINX_PORT)) {
		logger("$myself: ERROR: undefined NGINX_PORT option.");
		return 0;
	}

	if($os eq "Linux") {
		system("iptables -N NGINX_IN 2>/dev/null");
		system("(iptables -L INPUT | grep -q NGINX_IN) || iptables -I INPUT -p tcp --dport $NGINX_PORT -j NGINX_IN -c 0 0");
		system("iptables -N NGINX_OUT 2>/dev/null");
		system("(iptables -L OUTPUT | grep -q NGINX_OUT) || iptables -I OUTPUT -p tcp --sport $NGINX_PORT -j NGINX_OUT -c 0 0");
	}
	if($os eq "FreeBSD" || $os eq "OpenBSD") {
		system("ipfw delete $NGINX_RULE 2>/dev/null");
		system("ipfw -q add $NGINX_RULE count tcp from me $NGINX_PORT to any");
		system("ipfw -q add $NGINX_RULE count tcp from any to me $NGINX_PORT");
	}

	# Since 2.4.0 these two values need to be converted to GAUGE.
	RRDs::tune($NGINX_RRD,
		"--data-source-type=nginx_bytes_in:GAUGE",
	);
	RRDs::tune($NGINX_RRD,
		"--data-source-type=nginx_bytes_out:GAUGE",
	);

	our %nginx_hist = ();
	push(@graphs, "nginx_update");
	if($opt_d eq "all" || $debug) {
		logger("$myself: Ok");
	}
}

sub nginx_update {
	my $myself = (caller(0))[3];
	my ($debug) = @_;

	my $reqs;
	my $tot;
	my $reads;
	my $writes;
	my $waits;
	my $in;
	my $out;
	my $val;

	my $URL = "http://127.0.0.1:" . $NGINX_PORT . "/nginx_status";
	my $ua = LWP::UserAgent->new(timeout => 30);
	my $response = $ua->request(HTTP::Request->new('GET', $URL));
	my $rrdata = "N";

	foreach(split('\n', $response->content)) {
		if(/^Active connections:\s+(\d+)/) {
			chomp($1);
			$tot = $1;
			next;
		}
		if(/^Reading:\s+(\d+).*Writing:\s+(\d+).*Waiting:\s+(\d+)/) {
			$reads = $1;
			$writes = $2;
			$waits = $3;
		}
		if(/^\s+(\d+)\s+(\d+)\s+(\d+)/) {
			chomp($3);
			$reqs = $3 - $nginx_hist{'requests'};
			$reqs = 0 unless $reqs != $3;
			$nginx_hist{'requests'} = $3;
		}
	}

	if($os eq "Linux") {
		open(IN, "iptables -nxvL INPUT |");
		while(<IN>) {
			if(/ NGINX_IN /) {
				(undef, $val) = split(' ', $_);
				chomp($val);
				$in = $val - $nginx_hist{'in'};
				$in = 0 unless $in != $val;
				$nginx_hist{'in'} = $val;
				$in /= 60;
				last;
			}
		}
		close(IN);
		open(IN, "iptables -nxvL OUTPUT |");
		while(<IN>) {
			if(/ NGINX_OUT /) {
				(undef, $val) = split(' ', $_);
				chomp($val);
				$out = $val - $nginx_hist{'out'};
				$out = 0 unless $out != $val;
				$nginx_hist{'out'} = $val;
				$out /= 60;
				last;
			}
		}
		close(IN);
	}
	if($os eq "FreeBSD" || $os eq "OpenBSD") {
		open(IN, "ipfw show $NGINX_RULE 2>/dev/null |");
		while(<IN>) {
			if(/ from any to me dst-port $NGINX_PORT$/) {
				(undef, undef, $val) = split(' ', $_);
				chomp($val);
				$in = $val - $nginx_hist{'in'};
				$in = 0 unless $in != $val;
				$nginx_hist{'in'} = $val;
				$in /= 60;
			}
			if(/ from me $NGINX_PORT to any$/) {
				(undef, undef, $val) = split(' ', $_);
				chomp($val);
				$out = $val - $nginx_hist{'out'};
				$out = 0 unless $out != $val;
				$nginx_hist{'out'} = $val;
				$out /= 60;
			}
		}
		close(IN);
	}

	$rrdata .= ":$reqs:$tot:$reads:$writes:$waits:$in:$out";
	RRDs::update($NGINX_RRD, $rrdata);
	if($opt_d eq "all" || $debug) {
		logger("$myself: $rrdata");
	}
	my $err = RRDs::error;
	logger("ERROR: while updating $NGINX_RRD: $err") if $err;
}

# LIGHTTPD graph
# ----------------------------------------------------------------------------
sub lighttpd_init {
	my $myself = (caller(0))[3];

	my $info;
	my @ds;
	my @tmp;
	my $n;

	if(!defined(@LIGHTTPD_LIST)) {
		logger("$myself: ERROR: missing or not defined LIGHTTPD_LIST option.");
		return 0;
	}

	if(-e $LIGHTTPD_RRD) {
		$info = RRDs::info($LIGHTTPD_RRD);
		for my $key (keys %$info) {
			if(index($key, 'ds[') == 0) {
				if(index($key, '.type') != -1) {
#					print("$key\n");
					push(@ds, substr($key, 3, index($key, ']') - 3));
				}
			}
		}
#		foreach(@ds) {
#			print($_ . "\n");
#		}
#		print(scalar(@ds) . "\n");
		if(scalar(@ds) / 9 != scalar(@LIGHTTPD_LIST)) {
			logger("Detected size mismatch between \@LIGHTTPD_LIST (" . scalar(@LIGHTTPD_LIST) . ") and $LIGHTTPD_RRD (" . scalar(@ds) / 9 . "). Resizing it accordingly. All historic data will be lost.");
			unlink($LIGHTTPD_RRD);
		}
	}

	if(!(-e $LIGHTTPD_RRD)) {
		logger("Creating '$LIGHTTPD_RRD' file.");
		for($n = 0; $n < scalar(@LIGHTTPD_LIST); $n++) {
			push(@tmp, "DS:lighttpd" . $n . "_acc:GAUGE:120:0:U");
			push(@tmp, "DS:lighttpd" . $n . "_kb:GAUGE:120:0:U");
			push(@tmp, "DS:lighttpd" . $n . "_busy:GAUGE:120:0:U");
			push(@tmp, "DS:lighttpd" . $n . "_idle:GAUGE:120:0:U");
			push(@tmp, "DS:lighttpd" . $n . "_val01:GAUGE:120:0:U");
			push(@tmp, "DS:lighttpd" . $n . "_val02:GAUGE:120:0:U");
			push(@tmp, "DS:lighttpd" . $n . "_val03:GAUGE:120:0:U");
			push(@tmp, "DS:lighttpd" . $n . "_val04:GAUGE:120:0:U");
			push(@tmp, "DS:lighttpd" . $n . "_val05:GAUGE:120:0:U");
		}
		eval {
			RRDs::create($LIGHTTPD_RRD,
				"--step=60",
				@tmp,
				"RRA:AVERAGE:0.5:1:1440",
				"RRA:AVERAGE:0.5:30:336",
				"RRA:AVERAGE:0.5:60:744",
				"RRA:AVERAGE:0.5:1440:365",
				"RRA:MIN:0.5:1:1440",
				"RRA:MIN:0.5:30:336",
				"RRA:MIN:0.5:60:744",
				"RRA:MIN:0.5:1440:365",
				"RRA:MAX:0.5:1:1440",
				"RRA:MAX:0.5:30:336",
				"RRA:MAX:0.5:60:744",
				"RRA:MAX:0.5:1440:365",
				"RRA:LAST:0.5:1:1440",
				"RRA:LAST:0.5:30:336",
				"RRA:LAST:0.5:60:744",
				"RRA:LAST:0.5:1440:365",
			);
		};
		my $err = RRDs::error;
		if($@ || $err) {
			logger("$@") unless !$@;
			if($err) {
				logger("ERROR: while creating $LIGHTTPD_RRD: $err");
				if($err eq "RRDs::error") {
					logger("... is the RRDtool Perl package installed?");
				}
			}
			return;
		}
	}

	our %lighttpd_hist = ();
	push(@graphs, "lighttpd_update");
	if($opt_d eq "all" || $debug) {
		logger("$myself: Ok");
	}
}

sub lighttpd_update {
	my $myself = (caller(0))[3];
	my ($debug) = @_;

	my $acc;
	my $kb;
	my $busy;
	my $idle;

	my $str;
	my $rrdata = "N";

	my $n = 0;
	foreach(@LIGHTTPD_LIST) {
		my $URL = $_ . "/server-status?auto";
		my $ua = LWP::UserAgent->new(timeout => 30);
		my $response = $ua->request(HTTP::Request->new('GET', $URL));

		foreach(split('\n', $response->content)) {
			if(/^Total Accesses:\s+(\d+)$/) {
				$str = $n . "acc";
				$acc = $1 - $lighttpd_hist{$str};
				$acc = 0 unless $acc != $1;
				$acc /= 60;
				$lighttpd_hist{$str} = $1;
				next;
			}
			if(/^Total kBytes:\s+(\d+)$/) {
				$str = $n . "kb";
				$kb = $1 - $lighttpd_hist{$str};
				$kb = 0 unless $kb != $1;
				$lighttpd_hist{$str} = $1;
				next;
			}
			if(/^BusyServers:\s+(\d+)/) {
				$busy = int($1);
				next;
			}
			if(/^IdleServers:\s+(\d+)/) {
				$idle = int($1);
				last;
			}
		}
		$rrdata .= ":$acc:$kb:$busy:$idle:0:0:0:0:0";
		$n++;
	}

	RRDs::update($LIGHTTPD_RRD, $rrdata);
	if($opt_d eq "all" || $debug) {
		logger("$myself: $rrdata");
	}
	my $err = RRDs::error;
	logger("ERROR: while updating $LIGHTTPD_RRD: $err") if $err;
}

# MYSQL graph
# ----------------------------------------------------------------------------
sub mysql_init {
	my $myself = (caller(0))[3];

	my $info;
	my @ds;
	my @tmp;
	my $num;
	my $n;

	$MYSQL_CONN_TYPE = $MYSQL_CONN_TYPE || "Host";
	if(lc($MYSQL_CONN_TYPE) eq "host") {
		if(!@MYSQL_HOST_LIST) {
			logger("$myself: ERROR: invalid value or not defined MYSQL_HOST_LIST option.");
			return 0;
		}
		if(!@MYSQL_PORT_LIST) {
			logger("$myself: ERROR: invalid value or not defined MYSQL_PORT_LIST option.");
			return 0;
		}
		if(!@MYSQL_USER_LIST) {
			logger("$myself: ERROR: invalid value or not defined MYSQL_USER_LIST option.");
			return 0;
		}
		if(!@MYSQL_PASS_LIST) {
			logger("$myself: ERROR: invalid value or not defined MYSQL_PASS_LIST option.");
			return 0;
		}
	} elsif(lc($MYSQL_CONN_TYPE) eq "socket") {
		if(!@MYSQL_SOCK_LIST) {
			logger("$myself: ERROR: invalid value or not defined MYSQL_SOCK_LIST option.");
			return 0;
		}
	} else {
		logger("$myself: ERROR: invalid value in MYSQL_CONN_TYPE option.");
		return 0;
	}

	if(-e $MYSQL_RRD) {
		$info = RRDs::info($MYSQL_RRD);
		for my $key (keys %$info) {
			if(index($key, 'ds[') == 0) {
				if(index($key, '.type') != -1) {
#					print("$key\n");
					push(@ds, substr($key, 3, index($key, ']') - 3));
				}
			}
		}
#		foreach(@ds) {
#			print($_ . "\n");
#		}
#		print(scalar(@ds) . "\n");
		if(lc($MYSQL_CONN_TYPE) eq "host") {
			$num = scalar(@MYSQL_HOST_LIST);
			if(scalar(@ds) / 38 != scalar(@MYSQL_HOST_LIST)) {
				logger("Detected size mismatch between \@MYSQL_HOST_LIST (" . scalar(@MYSQL_HOST_LIST) . ") and $MYSQL_RRD (" . scalar(@ds) / 38 . "). Resizing it accordingly. All historic data will be lost.");
				unlink($MYSQL_RRD);
			}
		}
		if(lc($MYSQL_CONN_TYPE) eq "socket") {
			$num = scalar(@MYSQL_SOCK_LIST);
			if(scalar(@ds) / 38 != scalar(@MYSQL_SOCK_LIST)) {
				logger("Detected size mismatch between \@MYSQL_SOCK_LIST (" . scalar(@MYSQL_SOCK_LIST) . ") and $MYSQL_RRD (" . scalar(@ds) / 38 . "). Resizing it accordingly. All historic data will be lost.");
				unlink($MYSQL_RRD);
			}
		}
	}

	if(!(-e $MYSQL_RRD)) {
		logger("Creating '$MYSQL_RRD' file.");
		for($n = 0; $n < $num; $n++) {
			push(@tmp, "DS:mysql" . $n . "_queries:GAUGE:120:0:U");
			push(@tmp, "DS:mysql" . $n . "_sq:GAUGE:120:0:U");
			push(@tmp, "DS:mysql" . $n . "_tchr:GAUGE:120:0:100");
			push(@tmp, "DS:mysql" . $n . "_qcu:GAUGE:120:0:100");
			push(@tmp, "DS:mysql" . $n . "_ot:GAUGE:120:0:U");
			push(@tmp, "DS:mysql" . $n . "_conns_u:GAUGE:120:0:100");
			push(@tmp, "DS:mysql" . $n . "_conns:GAUGE:120:0:U");
			push(@tmp, "DS:mysql" . $n . "_tlw:GAUGE:120:0:U");
			push(@tmp, "DS:mysql" . $n . "_kbu:GAUGE:120:0:100");
			push(@tmp, "DS:mysql" . $n . "_innbu:GAUGE:120:0:100");
			push(@tmp, "DS:mysql" . $n . "_csel:GAUGE:120:0:U");
			push(@tmp, "DS:mysql" . $n . "_ccom:GAUGE:120:0:U");
			push(@tmp, "DS:mysql" . $n . "_cdel:GAUGE:120:0:U");
			push(@tmp, "DS:mysql" . $n . "_cins:GAUGE:120:0:U");
			push(@tmp, "DS:mysql" . $n . "_cinss:GAUGE:120:0:U");
			push(@tmp, "DS:mysql" . $n . "_cupd:GAUGE:120:0:U");
			push(@tmp, "DS:mysql" . $n . "_crep:GAUGE:120:0:U");
			push(@tmp, "DS:mysql" . $n . "_creps:GAUGE:120:0:U");
			push(@tmp, "DS:mysql" . $n . "_crol:GAUGE:120:0:U");
			push(@tmp, "DS:mysql" . $n . "_acli:GAUGE:120:0:U");
			push(@tmp, "DS:mysql" . $n . "_acon:GAUGE:120:0:U");
			push(@tmp, "DS:mysql" . $n . "_brecv:GAUGE:120:0:U");
			push(@tmp, "DS:mysql" . $n . "_bsent:GAUGE:120:0:U");
			push(@tmp, "DS:mysql" . $n . "_val01:GAUGE:120:0:U");
			push(@tmp, "DS:mysql" . $n . "_val02:GAUGE:120:0:U");
			push(@tmp, "DS:mysql" . $n . "_val03:GAUGE:120:0:U");
			push(@tmp, "DS:mysql" . $n . "_val04:GAUGE:120:0:U");
			push(@tmp, "DS:mysql" . $n . "_val05:GAUGE:120:0:U");
			push(@tmp, "DS:mysql" . $n . "_val06:GAUGE:120:0:U");
			push(@tmp, "DS:mysql" . $n . "_val07:GAUGE:120:0:U");
			push(@tmp, "DS:mysql" . $n . "_val08:GAUGE:120:0:U");
			push(@tmp, "DS:mysql" . $n . "_val09:GAUGE:120:0:U");
			push(@tmp, "DS:mysql" . $n . "_val10:GAUGE:120:0:U");
			push(@tmp, "DS:mysql" . $n . "_val11:GAUGE:120:0:U");
			push(@tmp, "DS:mysql" . $n . "_val12:GAUGE:120:0:U");
			push(@tmp, "DS:mysql" . $n . "_val13:GAUGE:120:0:U");
			push(@tmp, "DS:mysql" . $n . "_val14:GAUGE:120:0:U");
			push(@tmp, "DS:mysql" . $n . "_val15:GAUGE:120:0:U");
		}
		eval {
			RRDs::create($MYSQL_RRD,
				"--step=60",
				@tmp,
				"RRA:AVERAGE:0.5:1:1440",
				"RRA:AVERAGE:0.5:30:336",
				"RRA:AVERAGE:0.5:60:744",
				"RRA:AVERAGE:0.5:1440:365",
				"RRA:MIN:0.5:1:1440",
				"RRA:MIN:0.5:30:336",
				"RRA:MIN:0.5:60:744",
				"RRA:MIN:0.5:1440:365",
				"RRA:MAX:0.5:1:1440",
				"RRA:MAX:0.5:30:336",
				"RRA:MAX:0.5:60:744",
				"RRA:MAX:0.5:1440:365",
				"RRA:LAST:0.5:1:1440",
				"RRA:LAST:0.5:30:336",
				"RRA:LAST:0.5:60:744",
				"RRA:LAST:0.5:1440:365",
			);
		};
		my $err = RRDs::error;
		if($@ || $err) {
			logger("$@") unless !$@;
			if($err) {
				logger("ERROR: while creating $MYSQL_RRD: $err");
				if($err eq "RRDs::error") {
					logger("... is the RRDtool Perl package installed?");
				}
			}
			return;
		}
	}

	# Since 2.5.0 first NN values belong to the first MySQL being monitored.
	# These values need to be renamed.
	RRDs::tune($MYSQL_RRD,
		"--data-source-rename=mysql_queries:mysql0_queries",
		"--data-source-rename=mysql_slow_queries:mysql0_sq",
		"--data-source-rename=mysql_tcache_hit_r:mysql0_tchr",
		"--data-source-rename=mysql_qcache_usage:mysql0_qcu",
		"--data-source-rename=mysql_opened_tbl:mysql0_ot",
		"--data-source-rename=mysql_conns_u:mysql0_conns_u",
		"--data-source-rename=mysql_conns:mysql0_conns",
		"--data-source-rename=mysql_tlocks_w:mysql0_tlw",
		"--data-source-rename=mysql_key_buf_u:mysql0_kbu",
		"--data-source-rename=mysql_innodb_buf_u:mysql0_innbu",
		"--data-source-rename=mysql_com_select:mysql0_csel",
		"--data-source-rename=mysql_com_commit:mysql0_ccom",
		"--data-source-rename=mysql_com_delete:mysql0_cdel",
		"--data-source-rename=mysql_com_insert:mysql0_cins",
		"--data-source-rename=mysql_com_insert_s:mysql0_cinss",
		"--data-source-rename=mysql_com_update:mysql0_cupd",
		"--data-source-rename=mysql_com_replace:mysql0_crep",
		"--data-source-rename=mysql_com_replace_s:mysql0_creps",
		"--data-source-rename=mysql_com_rollback:mysql0_crol",
		"--data-source-rename=mysql_aborted_cli:mysql0_acli",
		"--data-source-rename=mysql_aborted_conn:mysql0_acon",
		"--data-source-rename=mysql_bytes_recv:mysql0_brecv",
		"--data-source-rename=mysql_bytes_sent:mysql0_bsent",
		"--data-source-rename=mysql_val01:mysql0_val01",
		"--data-source-rename=mysql_val02:mysql0_val02",
		"--data-source-rename=mysql_val03:mysql0_val03",
		"--data-source-rename=mysql_val04:mysql0_val04",
		"--data-source-rename=mysql_val05:mysql0_val05",
		"--data-source-rename=mysql_val06:mysql0_val06",
		"--data-source-rename=mysql_val07:mysql0_val07",
		"--data-source-rename=mysql_val08:mysql0_val08",
		"--data-source-rename=mysql_val09:mysql0_val09",
		"--data-source-rename=mysql_val10:mysql0_val10",
		"--data-source-rename=mysql_val11:mysql0_val11",
		"--data-source-rename=mysql_val12:mysql0_val12",
		"--data-source-rename=mysql_val13:mysql0_val13",
		"--data-source-rename=mysql_val14:mysql0_val14",
		"--data-source-rename=mysql_val15:mysql0_val15",
	);

	our %mysql_hist = ();
	push(@graphs, "mysql_update");
	if($opt_d eq "all" || $debug) {
		logger("$myself: Ok");
	}
}

sub mysql_update {
	my $myself = (caller(0))[3];
	my ($debug) = @_;

	my $num;
	my $n = 0;
	my $rrdata = "N";

	my $print_error = 0;
	if($opt_d eq "all" || $debug) {
		$print_error = 1;
	}

	$MYSQL_CONN_TYPE = $MYSQL_CONN_TYPE || "Host";
	if(lc($MYSQL_CONN_TYPE) eq "host") {
		$num = scalar(@MYSQL_HOST_LIST);
	}
	if(lc($MYSQL_CONN_TYPE) eq "socket") {
		$num = scalar(@MYSQL_SOCK_LIST);
	}

	for($n = 0; $n < $num; $n++) {
		my $host = $MYSQL_HOST_LIST[$n];
		my $port = $MYSQL_PORT_LIST[$n];
		my $user = $MYSQL_USER_LIST[$n];
		my $pass = $MYSQL_PASS_LIST[$n];
		my $sock = $MYSQL_SOCK_LIST[$n];
		my $dbh;
		if(lc($MYSQL_CONN_TYPE) eq "host") {
			unless ($host && $port && $user && $pass) {
				logger("$myself: ERROR: undefined configuration.");
				next;
			}
			$dbh = DBI->connect(
				"DBI:mysql:host=$host;port=$port",
				$user,
				$pass,
				{ PrintError => $print_error, }
			) or logger("$myself: Cannot connect to MySQL '$host:$port'.") and next;
		}
		if(lc($MYSQL_CONN_TYPE) eq "socket") {
			unless ($sock) {
				logger("$myself: ERROR: undefined configuration.");
				next;
			}
			$dbh = DBI->connect(
				"DBI:mysql:mysql_socket=$sock",
				{ PrintError => $print_error, }
			) or logger("$myself: Cannot connect to MySQL '$sock'.") and next;
		}

		# SHOW STATUS
		my $aborted_clients = 0;
		my $aborted_connects = 0;
		my $connections = 0;
		my $connections_real = 0;
		my $innodb_buffer_pool_pages_free = 0;
		my $innodb_buffer_pool_pages_total = 0;
		my $key_blocks_used = 0;
		my $key_blocks_unused = 0;
		my $max_used_connections = 0;
		my $qcache_free_memory = 0;
		my $qcache_hits = 0;
		my $qcache_inserts = 0;
		my $queries = 0;
		my $opened_tables = 0;
		my $slow_queries = 0;
		my $table_locks_waited = 0;
		my $threads_created = 0;
		my $sql = "show status";
		my $sth = $dbh->prepare($sql);
		$sth->execute;
		while((my $name, $value) = $sth->fetchrow_array) {
			if($name eq "Aborted_clients") {
				$aborted_clients = $value - $mysql_hist{'aborted_clients'};
				$aborted_clients = 0 unless $aborted_clients != $value;
				$aborted_clients /= 60;
				$mysql_hist{'aborted_clients'} = $value;
			}
			if($name eq "Aborted_connects") {
				$aborted_connects = $value - $mysql_hist{'aborted_connects'};
				$aborted_connects = 0 unless $aborted_connects != $value;
				$aborted_connects /= 60;
				$mysql_hist{'aborted_connects'} = $value;
			}
			if($name eq "Connections") {
				$connections_real = int($value);
				$connections = $value - $mysql_hist{'connections'};
				$connections = 0 unless $connections != $value;
				$connections /= 60;
				$mysql_hist{'connections'} = $value;
			}
			if($name eq "Innodb_buffer_pool_pages_free") {
				$innodb_buffer_pool_pages_free = int($value);
			}
			if($name eq "Innodb_buffer_pool_pages_total") {
				$innodb_buffer_pool_pages_total = int($value);
			}
			if($name eq "Key_blocks_unused") {
				$key_blocks_unused = int($value);
			}
			if($name eq "Key_blocks_used") {
				$key_blocks_used = int($value);
			}
			if($name eq "Max_used_connections") {
				$max_used_connections = int($value);
			}
			if($name eq "Opened_tables") {
				$opened_tables = $value - $mysql_hist{'opened_tables'};
				$opened_tables = 0 unless $opened_tables != $value;
				$opened_tables /= 60;
				$mysql_hist{'opened_tables'} = $value;
			}
			if($name eq "Qcache_free_memory") {
				$qcache_free_memory = int($value);
			}
			if($name eq "Qcache_hits") {
				$qcache_hits = int($value);
			}
			if($name eq "Qcache_inserts") {
				$qcache_inserts = int($value);
			}
			if($name eq "Queries") {
				$queries = $value - $mysql_hist{'queries'};
				$queries = 0 unless $queries != $value;
				$queries /= 60;
				$mysql_hist{'queries'} = $value;
			}
			if($name eq "Slow_queries") {
				$slow_queries = $value - $mysql_hist{'slow_queries'};
				$slow_queries = 0 unless $slow_queries != $value;
				$slow_queries /= 60;
				$mysql_hist{'slow_queries'} = $value;
			}
			if($name eq "Table_locks_waited") {
				$table_locks_waited = $value - $mysql_hist{'table_locks_waited'};
				$table_locks_waited = 0 unless $table_locks_waited != $value;
				$table_locks_waited /= 60;
				$mysql_hist{'table_locks_waited'} = $value;
			}
			if($name eq "Threads_created") {
				$threads_created = int($value);
			}
		}
		$sth->finish;

		# SHOW GLOBAL STATUS
		my $bytes_received = 0;
		my $bytes_sent = 0;
		my $com_commit = 0;
		my $com_delete = 0;
		my $com_insert = 0;
		my $com_insert_s = 0;
		my $com_replace = 0;
		my $com_replace_s = 0;
		my $com_rollback = 0;
		my $com_select = 0;
		my $com_update = 0;
		my $sql = "show global status";
		my $sth = $dbh->prepare($sql);
		$sth->execute;
		while((my $name, $value) = $sth->fetchrow_array) {
			if($name eq "Bytes_received") {
				$bytes_received = $value - $mysql_hist{'bytes_received'};
				$bytes_received = 0 unless $bytes_received != $value;
				$bytes_received /= 60;
				$mysql_hist{'bytes_received'} = $value;
			}
			if($name eq "Bytes_sent") {
				$bytes_sent = $value - $mysql_hist{'bytes_sent'};
				$bytes_sent = 0 unless $bytes_sent != $value;
				$bytes_sent /= 60;
				$mysql_hist{'bytes_sent'} = $value;
			}
			if($name eq "Com_commit") {
				$com_commit = $value - $mysql_hist{'com_commit'};
				$com_commit = 0 unless $com_commit != $value;
				$com_commit /= 60;
				$mysql_hist{'com_commit'} = $value;
			}
			if($name eq "Com_delete") {
				$com_delete = $value - $mysql_hist{'com_delete'};
				$com_delete = 0 unless $com_delete != $value;
				$com_delete /= 60;
				$mysql_hist{'com_delete'} = $value;
			}
			if($name eq "Com_insert") {
				$com_insert = $value - $mysql_hist{'com_insert'};
				$com_insert = 0 unless $com_insert != $value;
				$com_insert /= 60;
				$mysql_hist{'com_insert'} = $value;
			}
			if($name eq "Com_insert_select") {
				$com_insert_s = $value - $mysql_hist{'com_insert_s'};
				$com_insert_s = 0 unless $com_insert_s != $value;
				$com_insert_s /= 60;
				$mysql_hist{'com_insert_s'} = $value;
			}
			if($name eq "Com_replace") {
				$com_replace = $value - $mysql_hist{'com_replace'};
				$com_replace = 0 unless $com_replace != $value;
				$com_replace /= 60;
				$mysql_hist{'com_replace'} = $value;
			}
			if($name eq "Com_replace_select") {
				$com_replace_s = $value - $mysql_hist{'com_replace_s'};
				$com_replace_s = 0 unless $com_replace_s != $value;
				$com_replace_s /= 60;
				$mysql_hist{'com_replace_s'} = $value;
			}
			if($name eq "Com_rollback") {
				$com_rollback = $value - $mysql_hist{'com_rollback'};
				$com_rollback = 0 unless $com_rollback != $value;
				$com_rollback /= 60;
				$mysql_hist{'com_rollback'} = $value;
			}
			if($name eq "Com_select") {
				$com_select = $value - $mysql_hist{'com_select'};
				$com_select = 0 unless $com_select != $value;
				$com_select /= 60;
				$mysql_hist{'com_select'} = $value;
			}
			if($name eq "Com_update") {
				$com_update = $value - $mysql_hist{'com_update'};
				$com_update = 0 unless $com_update != $value;
				$com_update /= 60;
				$mysql_hist{'com_update'} = $value;
			}
		}
		$sth->finish;

		# SHOW VARIABLES
		my $query_cache_size = 0;
		my $max_connections = 0;
		my $sql = "show variables";
		my $sth = $dbh->prepare($sql);
		$sth->execute;
		while((my $name, $value) = $sth->fetchrow_array) {
			if($name eq "max_connections") {
				$max_connections = int($value);
			}
			if($name eq "query_cache_size") {
				$query_cache_size = int($value);
			}
		}
		$sth->finish;
		$dbh->disconnect;

		my $tcache_hit_rate = 0;
		my $qcache_usage = 0;
		my $connections_usage = 0;
		my $key_buffer_usage = 0;
		my $innodb_buffer_pool_usage = 0;

		$tcache_hit_rate = (1 - ($threads_created / $connections_real)) * 100
			unless !$connections_real;
		$qcache_usage = (1 - ($qcache_free_memory / $query_cache_size)) * 100
			unless !$query_cache_size;
		$connections_usage = ($max_used_connections / $max_connections) * 100
			unless !$max_connections;
		$key_buffer_usage = ($key_blocks_used / ($key_blocks_used + $key_blocks_unused)) * 100
			unless !($key_blocks_used + $key_blocks_unused);
		$innodb_buffer_pool_usage = (1 - ($innodb_buffer_pool_pages_free / $innodb_buffer_pool_pages_total)) * 100
			unless !$innodb_buffer_pool_pages_total;

		$connections_usage = $connections_usage > 100 ? 100 : $connections_usage;

		$rrdata .= ":$queries:$slow_queries:$tcache_hit_rate:$qcache_usage:$opened_tables:$connections_usage:$connections:$table_locks_waited:$key_buffer_usage:$innodb_buffer_pool_usage:$com_select:$com_commit:$com_delete:$com_insert:$com_insert_s:$com_update:$com_replace:$com_replace_s:$com_rollback:$aborted_clients:$aborted_connects:$bytes_received:$bytes_sent:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0";
	}

	RRDs::update($MYSQL_RRD, $rrdata);
	if($opt_d eq "all" || $debug) {
		logger("$myself: $rrdata");
	}
	my $err = RRDs::error;
	logger("ERROR: while updating $MYSQL_RRD: $err") if $err;
}

# SQUID graph
# ----------------------------------------------------------------------------
sub squid_init {
	my $myself = (caller(0))[3];

	if(!(-e $SQUID_RRD)) {
		logger("Creating '$SQUID_RRD' file.");
		eval {
			RRDs::create($SQUID_RRD,
				"--step=60",
				"DS:squid_g1_1:GAUGE:120:0:U",
				"DS:squid_g1_2:GAUGE:120:0:U",
				"DS:squid_g1_3:GAUGE:120:0:U",
				"DS:squid_g1_4:GAUGE:120:0:U",
				"DS:squid_g1_5:GAUGE:120:0:U",
				"DS:squid_g1_6:GAUGE:120:0:U",
				"DS:squid_g1_7:GAUGE:120:0:U",
				"DS:squid_g1_8:GAUGE:120:0:U",
				"DS:squid_g1_9:GAUGE:120:0:U",
				"DS:squid_g2_1:GAUGE:120:0:U",
				"DS:squid_g2_2:GAUGE:120:0:U",
				"DS:squid_g2_3:GAUGE:120:0:U",
				"DS:squid_g2_4:GAUGE:120:0:U",
				"DS:squid_g2_5:GAUGE:120:0:U",
				"DS:squid_g2_6:GAUGE:120:0:U",
				"DS:squid_g2_7:GAUGE:120:0:U",
				"DS:squid_g2_8:GAUGE:120:0:U",
				"DS:squid_g2_9:GAUGE:120:0:U",
				"DS:squid_rq_1:GAUGE:120:0:U",
				"DS:squid_rq_2:GAUGE:120:0:U",
				"DS:squid_rq_3:GAUGE:120:0:U",
				"DS:squid_rq_4:GAUGE:120:0:U",
				"DS:squid_rq_5:GAUGE:120:0:U",
				"DS:squid_rq_6:GAUGE:120:0:U",
				"DS:squid_rq_7:GAUGE:120:0:U",
				"DS:squid_rq_8:GAUGE:120:0:U",
				"DS:squid_rq_9:GAUGE:120:0:U",
				"DS:squid_m_1:GAUGE:120:0:U",
				"DS:squid_m_2:GAUGE:120:0:U",
				"DS:squid_m_3:GAUGE:120:0:U",
				"DS:squid_m_4:GAUGE:120:0:U",
				"DS:squid_m_5:GAUGE:120:0:U",
				"DS:squid_ic_1:GAUGE:120:0:U",
				"DS:squid_ic_2:GAUGE:120:0:U",
				"DS:squid_ic_3:GAUGE:120:0:U",
				"DS:squid_ic_4:GAUGE:120:0:U",
				"DS:squid_ic_5:GAUGE:120:0:U",
				"DS:squid_io_1:GAUGE:120:0:U",
				"DS:squid_io_2:GAUGE:120:0:U",
				"DS:squid_io_3:GAUGE:120:0:U",
				"DS:squid_io_4:GAUGE:120:0:U",
				"DS:squid_io_5:GAUGE:120:0:U",
				"DS:squid_s_1:GAUGE:120:0:U",
				"DS:squid_s_2:GAUGE:120:0:U",
				"DS:squid_s_3:GAUGE:120:0:U",
				"DS:squid_s_4:GAUGE:120:0:U",
				"DS:squid_s_5:GAUGE:120:0:U",
				"DS:squid_tc_1:GAUGE:120:0:U",
				"DS:squid_tc_2:GAUGE:120:0:U",
				"DS:squid_tc_3:GAUGE:120:0:U",
				"DS:squid_ts_1:GAUGE:120:0:U",
				"DS:squid_ts_2:GAUGE:120:0:U",
				"DS:squid_ts_3:GAUGE:120:0:U",
				"RRA:AVERAGE:0.5:1:1440",
				"RRA:AVERAGE:0.5:30:336",
				"RRA:AVERAGE:0.5:60:744",
				"RRA:AVERAGE:0.5:1440:365",
				"RRA:MIN:0.5:1:1440",
				"RRA:MIN:0.5:30:336",
				"RRA:MIN:0.5:60:744",
				"RRA:MIN:0.5:1440:365",
				"RRA:MAX:0.5:1:1440",
				"RRA:MAX:0.5:30:336",
				"RRA:MAX:0.5:60:744",
				"RRA:MAX:0.5:1440:365",
				"RRA:LAST:0.5:1:1440",
				"RRA:LAST:0.5:30:336",
				"RRA:LAST:0.5:60:744",
				"RRA:LAST:0.5:1440:365",
			);
		};
		my $err = RRDs::error;
		if($@ || $err) {
			logger("$@") unless !$@;
			if($err) {
				logger("ERROR: while creating $SQUID_RRD: $err");
				if($err eq "RRDs::error") {
					logger("... is the RRDtool Perl package installed?");
				}
			}
			return;
		}
	}

	our %squid_hist = ();
	push(@graphs, "squid_update");
	if($opt_d eq "all" || $debug) {
		logger("$myself: Ok");
	}
}

sub squid_update {
	my $myself = (caller(0))[3];
	my ($debug) = @_;

	my %g1 = ();
	my %g2 = ();
	my $seek_pos;
	my $logsize;
	my @data;
	my $all;
	my $value;
	my $g_result;
	my $g_status;

	my $rq_client_http_req;
	my $rq_client_http_hit;
	my $rq_server_http_req;
	my $rq_server_ftp_req;
	my $rq_server_other_req;
	my $rq_aborted_req;
	my $rq_swap_files_cleaned;
	my $rq_unlink_requests;

	my $tc_client_http_in;
	my $tc_client_http_out;
	my $ts_server_all_in;
	my $ts_server_all_out;

	my $m_alloc;
	my $m_inuse;

	my $ic_requests;
	my $ic_hits;
	my $ic_misses;

	my $io_http;
	my $io_ftp;
	my $io_gopher;
	my $io_wais;

	my $s_entries;
	my $s_maximum;
	my $s_current;

	my $n;
	my $rrdata = "N";

	$seek_pos = $squid_hist{'seek_pos'};
	$seek_pos = defined($seek_pos) ? int($seek_pos) : 0;
	open(IN, $SQUID_LOG);
	if(!seek(IN, 0, 2)) {
		logger("Couldn't seek to the end ($SQUID_LOG): $!");
		return;
	}
	$logsize = tell(IN);
	if($logsize < $seek_pos) {
		$seek_pos = 0;
	}
	if(!seek(IN, $seek_pos, 0)) {
		logger("Couldn't seek to $seek_pos ($SQUID_LOG): $!");
		return;
	}
	if(defined($squid_hist{'seek_pos'})) {	# avoid initial spike
		while(<IN>) {
			(undef, undef, undef, $value) = split(' ', $_);
			($g_result, $g_status) = split('/', $value);
			$g1{$g_result}++;
			$g1{$g_status}++;
			$g2{$g_result}++;
			$g2{$g_status}++;
		}
	}
	close(IN);
	foreach(@SQUID_GRAPH_1) {
		$rrdata .= ":";
		$rrdata .= defined($g1{$_}) ? int($g1{$_}) : 0;
	}
	foreach(@SQUID_GRAPH_2) {
		$rrdata .= ":";
		$rrdata .= defined($g2{$_}) ? int($g2{$_}) : 0;
	}
	$squid_hist{'seek_pos'} = $logsize;

	open(IN, "$SQUID_CMD mgr:counters |");
	while(<IN>) {
		if(/^client_http\.requests = (\d+)$/) {
			$rq_client_http_req = $1 - $squid_hist{'rq_client_http_req'};
			$rq_client_http_req = 0 unless $rq_client_http_req != $1;
			$rq_client_http_req /= 60;
			$squid_hist{'rq_client_http_req'} = $1;
			next;
		}
		if(/^client_http\.hits = (\d+)$/) {
			$rq_client_http_hit = $1 - $squid_hist{'rq_client_http_hit'};
			$rq_client_http_hit = 0 unless $rq_client_http_hit != $1;
			$rq_client_http_hit /= 60;
			$squid_hist{'rq_client_http_hit'} = $1;
			next;
		}
		if(/^client_http\.kbytes_in = (\d+)$/) {
			$tc_client_http_in = $1 - $squid_hist{'tc_client_http_in'};
			$tc_client_http_in = 0 unless $tc_client_http_in != $1;
			$tc_client_http_in *= 1024;
			$tc_client_http_in /= 60;
			$squid_hist{'tc_client_http_in'} = $1;
			next;
		}
		if(/^client_http\.kbytes_out = (\d+)$/) {
			$tc_client_http_out = $1 - $squid_hist{'tc_client_http_out'};
			$tc_client_http_out = 0 unless $tc_client_http_out != $1;
			$tc_client_http_out *= 1024;
			$tc_client_http_out /= 60;
			$squid_hist{'tc_client_http_out'} = $1;
			next;
		}
		if(/^server\.all\.kbytes_in = (\d+)$/) {
			$ts_server_all_in = $1 - $squid_hist{'ts_server_all_in'};
			$ts_server_all_in = 0 unless $ts_server_all_in != $1;
			$ts_server_all_in *= 1024;
			$ts_server_all_in /= 60;
			$squid_hist{'ts_server_all_in'} = $1;
			next;
		}
		if(/^server\.all\.kbytes_out = (\d+)$/) {
			$ts_server_all_out = $1 - $squid_hist{'ts_server_all_out'};
			$ts_server_all_out = 0 unless $ts_server_all_out != $1;
			$ts_server_all_out *= 1024;
			$ts_server_all_out /= 60;
			$squid_hist{'ts_server_all_out'} = $1;
			next;
		}
		if(/^server\.http\.requests = (\d+)$/) {
			$rq_server_http_req = $1 - $squid_hist{'rq_server_http_req'};
			$rq_server_http_req = 0 unless $rq_server_http_req != $1;
			$rq_server_http_req /= 60;
			$squid_hist{'rq_server_http_req'} = $1;
			next;
		}
		if(/^server\.ftp\.requests = (\d+)$/) {
			$rq_server_ftp_req = $1 - $squid_hist{'rq_server_ftp_req'};
			$rq_server_ftp_req = 0 unless $rq_server_ftp_req != $1;
			$rq_server_ftp_req /= 60;
			$squid_hist{'rq_server_ftp_req'} = $1;
			next;
		}
		if(/^server\.other\.requests = (\d+)$/) {
			$rq_server_other_req = $1 - $squid_hist{'rq_server_other_req'};
			$rq_server_other_req = 0 unless $rq_server_other_req != $1;
			$rq_server_other_req /= 60;
			$squid_hist{'rq_server_other_req'} = $1;
			next;
		}
		if(/^unlink\.requests = (\d+)$/) {
			$rq_unlink_requests = $1 - $squid_hist{'rq_unlink_requests'};
			$rq_unlink_requests = 0 unless $rq_unlink_requests != $1;
			$rq_unlink_requests /= 60;
			$squid_hist{'rq_unlink_requests'} = $1;
			next;
		}
		if(/^swap\.files_cleaned = (\d+)$/) {
			$rq_swap_files_cleaned = $1 - $squid_hist{'rq_swap_files_cleaned'};
			$rq_swap_files_cleaned = 0 unless $rq_swap_files_cleaned != $1;
			$rq_swap_files_cleaned /= 60;
			$squid_hist{'rq_swap_files_cleaned'} = $1;
			next;
		}
		if(/^aborted_requests = (\d+)$/) {
			$rq_aborted_req = $1 - $squid_hist{'rq_aborted_req'};
			$rq_aborted_req = 0 unless $rq_aborted_req != $1;
			$rq_aborted_req /= 60;
			$squid_hist{'rq_aborted_req'} = $1;
			last;
		}
	}
	close(IN);
	$rrdata .= ":$rq_client_http_req:$rq_client_http_hit:$rq_server_http_req:$rq_server_ftp_req:$rq_server_other_req:$rq_aborted_req:$rq_swap_files_cleaned:$rq_unlink_requests:0";

	open(IN, "$SQUID_CMD mgr:mem |");
	while(<IN>) {
		if(/^Total      /) {
			(undef, undef, $m_alloc, undef, undef, undef, undef, $m_inuse) = split(' ', $_);
			chomp($m_alloc);
			chomp($m_inuse);
			$m_alloc;
			$m_inuse;
			last;
		}
	}
	close(IN);
	$rrdata .= ":$m_alloc:$m_inuse:0:0:0";

	open(IN, "$SQUID_CMD mgr:ipcache |");
	while(<IN>) {
		if(/^IPcache Requests:\s+(\d+)$/) {
			$ic_requests = $1 - $squid_hist{'ic_requests'};
			$ic_requests = 0 unless $ic_requests != $1;
			$ic_requests /= 60;
			$squid_hist{'ic_requests'} = $1;
			next;
		}
		if(/^IPcache Hits:\s+(\d+)$/) {
			$ic_hits = $1 - $squid_hist{'ic_hits'};
			$ic_hits = 0 unless $ic_hits != $1;
			$ic_hits /= 60;
			$squid_hist{'ic_hits'} = $1;
			next;
		}
		if(/^IPcache Misses:\s+(\d+)$/) {
			$ic_misses = $1 - $squid_hist{'ic_misses'};
			$ic_misses = 0 unless $ic_misses != $1;
			$ic_misses /= 60;
			$squid_hist{'ic_misses'} = $1;
			last;
		}
	}
	close(IN);
	$rrdata .= ":$ic_requests:$ic_hits:$ic_misses:0:0";

	open(IN, "$SQUID_CMD mgr:io |");
	@data = <IN>;
	close(IN);
	$all = join('', @data);
	$all =~ s/\n/ /g;
	($value) = ($all =~ m/ HTTP I\/O number of reads.*?(\d+)/g);
	chomp($value);
	$io_http = $value - $squid_hist{'io_http'};
	$io_http = 0 unless $io_http != $value;
	$io_http /= 60;
	$squid_hist{'io_http'} = $value;
	($value) = ($all =~ m/ FTP I\/O number of reads.*?(\d+)/g);
	chomp($value);
	$io_ftp = $value - $squid_hist{'io_ftp'};
	$io_ftp = 0 unless $io_ftp != $value;
	$io_ftp /= 60;
	$squid_hist{'io_ftp'} = $value;
	($value) = ($all =~ m/ Gopher I\/O number of reads.*?(\d+)/g);
	chomp($value);
	$io_gopher = $value - $squid_hist{'io_gopher'};
	$io_gopher = 0 unless $io_gopher != $value;
	$io_gopher /= 60;
	$squid_hist{'io_gopher'} = $value;
	($value) = ($all =~ m/ WAIS I\/O number of reads.*?(\d+)/g);
	chomp($value);
	$io_wais = $value - $squid_hist{'io_wais'};
	$io_wais = 0 unless $io_wais != $value;
	$io_wais /= 60;
	$squid_hist{'io_wais'} = $value;
	$rrdata .= ":$io_http:$io_ftp:$io_gopher:$io_wais:0";

	open(IN, "$SQUID_CMD mgr:storedir |");
	while(<IN>) {
		if(/^Store Entries\s+:\s+(\d+)$/) {
			$s_entries = $1;
			next;
		}
		if(/^Maximum Swap Size\s+:\s+(\d+)/) {
			$s_maximum = $1;
			next;
		}
		if(/^Current Store Swap Size\s*:\s+(\d+)/) {
			$s_current = $1;
			last;
		}
	}
	close(IN);
	$rrdata .= ":$s_entries:$s_maximum:$s_current:0:0";
	$rrdata .= ":$tc_client_http_in:$tc_client_http_out:0";
	$rrdata .= ":$ts_server_all_in:$ts_server_all_out:0";
	RRDs::update($SQUID_RRD, $rrdata);
	if($opt_d eq "all" || $debug) {
		logger("$myself: $rrdata");
	}
	my $err = RRDs::error;
	logger("ERROR: while updating $SQUID_RRD: $err") if $err;
}

# NFSS graph
# ----------------------------------------------------------------------------
sub nfss_init {
	my $myself = (caller(0))[3];

	if(!(-e $NFSS_RRD)) {
		logger("Creating '$NFSS_RRD' file.");
		eval {
			RRDs::create($NFSS_RRD,
				"--step=60",
				"DS:nfss_0:COUNTER:120:0:U",
				"DS:nfss_1:COUNTER:120:0:U",
				"DS:nfss_2:COUNTER:120:0:U",
				"DS:nfss_3:COUNTER:120:0:U",
				"DS:nfss_4:COUNTER:120:0:U",
				"DS:nfss_5:COUNTER:120:0:U",
				"DS:nfss_6:COUNTER:120:0:U",
				"DS:nfss_7:COUNTER:120:0:U",
				"DS:nfss_8:COUNTER:120:0:U",
				"DS:nfss_9:COUNTER:120:0:U",
				"DS:nfss_10:COUNTER:120:0:U",
				"DS:nfss_11:COUNTER:120:0:U",
				"DS:nfss_12:COUNTER:120:0:U",
				"DS:nfss_13:COUNTER:120:0:U",
				"DS:nfss_14:COUNTER:120:0:U",
				"DS:nfss_15:COUNTER:120:0:U",
				"DS:nfss_16:COUNTER:120:0:U",
				"DS:nfss_17:COUNTER:120:0:U",
				"DS:nfss_18:COUNTER:120:0:U",
				"DS:nfss_19:COUNTER:120:0:U",
				"DS:nfss_20:COUNTER:120:0:U",
				"DS:nfss_21:COUNTER:120:0:U",
				"DS:nfss_22:COUNTER:120:0:U",
				"DS:nfss_23:COUNTER:120:0:U",
				"DS:nfss_24:COUNTER:120:0:U",
				"DS:nfss_25:COUNTER:120:0:U",
				"DS:nfss_26:COUNTER:120:0:U",
				"DS:nfss_27:COUNTER:120:0:U",
				"DS:nfss_28:COUNTER:120:0:U",
				"DS:nfss_29:COUNTER:120:0:U",
				"DS:nfss_30:COUNTER:120:0:U",
				"DS:nfss_31:COUNTER:120:0:U",
				"DS:nfss_32:COUNTER:120:0:U",
				"DS:nfss_33:COUNTER:120:0:U",
				"DS:nfss_34:COUNTER:120:0:U",
				"DS:nfss_35:COUNTER:120:0:U",
				"DS:nfss_36:COUNTER:120:0:U",
				"DS:nfss_37:COUNTER:120:0:U",
				"DS:nfss_38:COUNTER:120:0:U",
				"DS:nfss_39:COUNTER:120:0:U",
				"DS:nfss_40:COUNTER:120:0:U",
				"DS:nfss_41:COUNTER:120:0:U",
				"DS:nfss_42:COUNTER:120:0:U",
				"DS:nfss_43:COUNTER:120:0:U",
				"DS:nfss_44:COUNTER:120:0:U",
				"DS:nfss_45:COUNTER:120:0:U",
				"DS:nfss_46:COUNTER:120:0:U",
				"DS:nfss_47:COUNTER:120:0:U",
				"DS:nfss_48:COUNTER:120:0:U",
				"DS:nfss_49:COUNTER:120:0:U",
				"DS:nfss_rc_1:COUNTER:120:0:U",
				"DS:nfss_rc_2:COUNTER:120:0:U",
				"DS:nfss_rc_3:COUNTER:120:0:U",
				"DS:nfss_rc_4:COUNTER:120:0:U",
				"DS:nfss_rc_5:COUNTER:120:0:U",
				"DS:nfss_fh_1:COUNTER:120:0:U",
				"DS:nfss_fh_2:COUNTER:120:0:U",
				"DS:nfss_fh_3:COUNTER:120:0:U",
				"DS:nfss_fh_4:COUNTER:120:0:U",
				"DS:nfss_fh_5:COUNTER:120:0:U",
				"DS:nfss_io_1:COUNTER:120:0:U",
				"DS:nfss_io_2:COUNTER:120:0:U",
				"DS:nfss_io_3:COUNTER:120:0:U",
				"DS:nfss_th_0:COUNTER:120:0:U",
				"DS:nfss_th_1:COUNTER:120:0:U",
				"DS:nfss_th_2:COUNTER:120:0:U",
				"DS:nfss_th_3:COUNTER:120:0:U",
				"DS:nfss_th_4:COUNTER:120:0:U",
				"DS:nfss_th_5:COUNTER:120:0:U",
				"DS:nfss_th_6:COUNTER:120:0:U",
				"DS:nfss_th_7:COUNTER:120:0:U",
				"DS:nfss_th_8:COUNTER:120:0:U",
				"DS:nfss_th_9:COUNTER:120:0:U",
				"DS:nfss_th_10:COUNTER:120:0:U",
				"DS:nfss_net_1:COUNTER:120:0:U",
				"DS:nfss_net_2:COUNTER:120:0:U",
				"DS:nfss_net_3:COUNTER:120:0:U",
				"DS:nfss_net_4:COUNTER:120:0:U",
				"DS:nfss_net_5:COUNTER:120:0:U",
				"DS:nfss_rpc_1:COUNTER:120:0:U",
				"DS:nfss_rpc_2:COUNTER:120:0:U",
				"DS:nfss_rpc_3:COUNTER:120:0:U",
				"DS:nfss_rpc_4:COUNTER:120:0:U",
				"DS:nfss_rpc_5:COUNTER:120:0:U",
				"RRA:AVERAGE:0.5:1:1440",
				"RRA:AVERAGE:0.5:30:336",
				"RRA:AVERAGE:0.5:60:744",
				"RRA:AVERAGE:0.5:1440:365",
				"RRA:MIN:0.5:1:1440",
				"RRA:MIN:0.5:30:336",
				"RRA:MIN:0.5:60:744",
				"RRA:MIN:0.5:1440:365",
				"RRA:MAX:0.5:1:1440",
				"RRA:MAX:0.5:30:336",
				"RRA:MAX:0.5:60:744",
				"RRA:MAX:0.5:1440:365",
				"RRA:LAST:0.5:1:1440",
				"RRA:LAST:0.5:30:336",
				"RRA:LAST:0.5:60:744",
				"RRA:LAST:0.5:1440:365",
			);
		};
		my $err = RRDs::error;
		if($@ || $err) {
			logger("$@") unless !$@;
			if($err) {
				logger("ERROR: while creating $NFSS_RRD: $err");
				if($err eq "RRDs::error") {
					logger("... is the RRDtool Perl package installed?");
				}
			}
			return;
		}
	}

	push(@graphs, "nfss_update");
	if($opt_d eq "all" || $debug) {
		logger("$myself: Ok");
	}
}

sub nfss_update {
	my $myself = (caller(0))[3];
	my ($debug) = @_;

	my @rc;
	my @fh;
	my @io;
	my @th;
	my @net;
	my @rpc;
	my @nfss;

	my $n;
	my $rrdata = "N";

	if($os eq "Linux") {
		open(IN, "/proc/net/rpc/nfsd");
		while(<IN>) {
			if(/^rc\s+(\d+)\s+(\d+)\s+(\d+)$/) {
				@rc = ($1, $2, $3);
			}
			if(/^fh\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)$/) {
				@fh = ($1, $2, $3, $4, $5);
			}
			if(/^io\s+(\d+)\s+(\d+)$/) {
				@io = ($1, $2);
			}
			if(/^th /) {
				my @tmp = split(' ', $_);
				(undef, undef, @th) = @tmp;
			}
			if(/^net\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)$/) {
				@net = ($1, $2, $3, $4);
			}
			if(/^rpc\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)$/) {
				@rpc = ($1, $2, $3, $4, $5);
			}
			if(/^proc$NFSS_VERSION /) {
				my @tmp = split(' ', $_);
				(undef, undef, @nfss) = @tmp;
			}
		}
		close(IN);
	} elsif($os eq "FreeBSD" || $os eq "OpenBSD") {
		# this part must be finished!!!! XXX
	}

	for($n = 0; $n < 50; $n++) {
		if(!defined($nfss[$n])) {
			$nfss[$n] = 0;
		}
		$rrdata .= ":" . $nfss[$n];
	}
	$rrdata .= ":$rc[0]:$rc[1]:$rc[2]:0:0";
	$rrdata .= ":$fh[0]:$fh[1]:$fh[2]:$fh[3]:$fh[4]";
	$rrdata .= ":$io[0]:$io[1]:0";
	for($n = 0; $n < 11; $n++) {
		$rrdata .= ":" . int($th[$n]);
	}
	$rrdata .= ":$net[0]:$net[1]:$net[2]:$net[3]:0";
	$rrdata .= ":$rpc[0]:$rpc[1]:$rpc[2]:$rpc[3]:$rpc[4]";
	RRDs::update($NFSS_RRD, $rrdata);
	if($opt_d eq "all" || $debug) {
		logger("$myself: $rrdata");
	}
	my $err = RRDs::error;
	logger("ERROR: while updating $NFSS_RRD: $err") if $err;
}

# NFSC graph
# ----------------------------------------------------------------------------
sub nfsc_init {
	my $myself = (caller(0))[3];

	if(!(-e $NFSC_RRD)) {
		logger("Creating '$NFSC_RRD' file.");
		eval {
			RRDs::create($NFSC_RRD,
				"--step=60",
				"DS:nfsc_0:COUNTER:120:0:U",
				"DS:nfsc_1:COUNTER:120:0:U",
				"DS:nfsc_2:COUNTER:120:0:U",
				"DS:nfsc_3:COUNTER:120:0:U",
				"DS:nfsc_4:COUNTER:120:0:U",
				"DS:nfsc_5:COUNTER:120:0:U",
				"DS:nfsc_6:COUNTER:120:0:U",
				"DS:nfsc_7:COUNTER:120:0:U",
				"DS:nfsc_8:COUNTER:120:0:U",
				"DS:nfsc_9:COUNTER:120:0:U",
				"DS:nfsc_10:COUNTER:120:0:U",
				"DS:nfsc_11:COUNTER:120:0:U",
				"DS:nfsc_12:COUNTER:120:0:U",
				"DS:nfsc_13:COUNTER:120:0:U",
				"DS:nfsc_14:COUNTER:120:0:U",
				"DS:nfsc_15:COUNTER:120:0:U",
				"DS:nfsc_16:COUNTER:120:0:U",
				"DS:nfsc_17:COUNTER:120:0:U",
				"DS:nfsc_18:COUNTER:120:0:U",
				"DS:nfsc_19:COUNTER:120:0:U",
				"DS:nfsc_20:COUNTER:120:0:U",
				"DS:nfsc_21:COUNTER:120:0:U",
				"DS:nfsc_22:COUNTER:120:0:U",
				"DS:nfsc_23:COUNTER:120:0:U",
				"DS:nfsc_24:COUNTER:120:0:U",
				"DS:nfsc_25:COUNTER:120:0:U",
				"DS:nfsc_26:COUNTER:120:0:U",
				"DS:nfsc_27:COUNTER:120:0:U",
				"DS:nfsc_28:COUNTER:120:0:U",
				"DS:nfsc_29:COUNTER:120:0:U",
				"DS:nfsc_30:COUNTER:120:0:U",
				"DS:nfsc_31:COUNTER:120:0:U",
				"DS:nfsc_32:COUNTER:120:0:U",
				"DS:nfsc_33:COUNTER:120:0:U",
				"DS:nfsc_34:COUNTER:120:0:U",
				"DS:nfsc_35:COUNTER:120:0:U",
				"DS:nfsc_36:COUNTER:120:0:U",
				"DS:nfsc_37:COUNTER:120:0:U",
				"DS:nfsc_38:COUNTER:120:0:U",
				"DS:nfsc_39:COUNTER:120:0:U",
				"DS:nfsc_40:COUNTER:120:0:U",
				"DS:nfsc_41:COUNTER:120:0:U",
				"DS:nfsc_42:COUNTER:120:0:U",
				"DS:nfsc_43:COUNTER:120:0:U",
				"DS:nfsc_44:COUNTER:120:0:U",
				"DS:nfsc_45:COUNTER:120:0:U",
				"DS:nfsc_46:COUNTER:120:0:U",
				"DS:nfsc_47:COUNTER:120:0:U",
				"DS:nfsc_48:COUNTER:120:0:U",
				"DS:nfsc_49:COUNTER:120:0:U",
				"DS:nfsc_rpc_1:COUNTER:120:0:U",
				"DS:nfsc_rpc_2:COUNTER:120:0:U",
				"DS:nfsc_rpc_3:COUNTER:120:0:U",
				"DS:nfsc_rpc_4:COUNTER:120:0:U",
				"DS:nfsc_rpc_5:COUNTER:120:0:U",
				"RRA:AVERAGE:0.5:1:1440",
				"RRA:AVERAGE:0.5:30:336",
				"RRA:AVERAGE:0.5:60:744",
				"RRA:AVERAGE:0.5:1440:365",
				"RRA:MIN:0.5:1:1440",
				"RRA:MIN:0.5:30:336",
				"RRA:MIN:0.5:60:744",
				"RRA:MIN:0.5:1440:365",
				"RRA:MAX:0.5:1:1440",
				"RRA:MAX:0.5:30:336",
				"RRA:MAX:0.5:60:744",
				"RRA:MAX:0.5:1440:365",
				"RRA:LAST:0.5:1:1440",
				"RRA:LAST:0.5:30:336",
				"RRA:LAST:0.5:60:744",
				"RRA:LAST:0.5:1440:365",
			);
		};
		my $err = RRDs::error;
		if($@ || $err) {
			logger("$@") unless !$@;
			if($err) {
				logger("ERROR: while creating $NFSC_RRD: $err");
				if($err eq "RRDs::error") {
					logger("... is the RRDtool Perl package installed?");
				}
			}
			return;
		}
	}

	push(@graphs, "nfsc_update");
	if($opt_d eq "all" || $debug) {
		logger("$myself: Ok");
	}
}

sub nfsc_update {
	my $myself = (caller(0))[3];
	my ($debug) = @_;

	my @rpc;
	my @nfsc;

	my $n;
	my $rrdata = "N";

	if($os eq "Linux") {
		open(IN, "/proc/net/rpc/nfs");
		while(<IN>) {
			if(/^rpc\s+(\d+)\s+(\d+)\s+(\d+)$/) {
				@rpc = ($1, $2, $3);
			}
			if(/^proc$NFSC_VERSION /) {
				my @tmp = split(' ', $_);
				(undef, undef, @nfsc) = @tmp;
			}
		}
		close(IN);
	} elsif($os eq "FreeBSD" || $os eq "OpenBSD") {
		# this part must be finished!!!! XXX
	}

	for($n = 0; $n < 50; $n++) {
		if(!defined($nfsc[$n])) {
			$nfsc[$n] = 0;
		}
		$rrdata .= ":" . $nfsc[$n];
	}

	$rrdata .= ":$rpc[0]:$rpc[1]:$rpc[2]:0:0";
	RRDs::update($NFSC_RRD, $rrdata);
	if($opt_d eq "all" || $debug) {
		logger("$myself: $rrdata");
	}
	my $err = RRDs::error;
	logger("ERROR: while updating $NFSC_RRD: $err") if $err;
}

# NTP graph
# ----------------------------------------------------------------------------
sub ntp_init {
	my $myself = (caller(0))[3];

	my $info;
	my @ds;
	my @tmp;
	my $n;

	if(-e $NTP_RRD) {
		$info = RRDs::info($NTP_RRD);
		for my $key (keys %$info) {
			if(index($key, 'ds[') == 0) {
				if(index($key, '.type') != -1) {
#					print("$key\n");
					push(@ds, substr($key, 3, index($key, ']') - 3));
				}
			}
		}
#		foreach(@ds) {
#			print($_ . "\n");
#		}
#		print(scalar(@ds) . "\n");
		if(scalar(@ds) / 14 != scalar(@NTP_HOST_LIST)) {
			logger("Detected size mismatch between \@NTP_HOST_LIST (" . scalar(@NTP_HOST_LIST) . ") and $NTP_RRD (" . scalar(@ds) / 14 . "). Resizing it accordingly. All historic data will be lost.");
			unlink($NTP_RRD);
		}
	}

	if(!(-e $NTP_RRD)) {
		logger("Creating '$NTP_RRD' file.");
		for($n = 0; $n < scalar(@NTP_HOST_LIST); $n++) {
			push(@tmp, "DS:ntp" . $n . "_del:GAUGE:120:U:U");
			push(@tmp, "DS:ntp" . $n . "_off:GAUGE:120:U:U");
			push(@tmp, "DS:ntp" . $n . "_jit:GAUGE:120:U:U");
			push(@tmp, "DS:ntp" . $n . "_str:GAUGE:120:0:U");
			push(@tmp, "DS:ntp" . $n . "_c01:GAUGE:120:0:U");
			push(@tmp, "DS:ntp" . $n . "_c02:GAUGE:120:0:U");
			push(@tmp, "DS:ntp" . $n . "_c03:GAUGE:120:0:U");
			push(@tmp, "DS:ntp" . $n . "_c04:GAUGE:120:0:U");
			push(@tmp, "DS:ntp" . $n . "_c05:GAUGE:120:0:U");
			push(@tmp, "DS:ntp" . $n . "_c06:GAUGE:120:0:U");
			push(@tmp, "DS:ntp" . $n . "_c07:GAUGE:120:0:U");
			push(@tmp, "DS:ntp" . $n . "_c08:GAUGE:120:0:U");
			push(@tmp, "DS:ntp" . $n . "_c09:GAUGE:120:0:U");
			push(@tmp, "DS:ntp" . $n . "_c10:GAUGE:120:0:U");
		}
		eval {
			RRDs::create($NTP_RRD,
				"--step=60",
				@tmp,
				"RRA:AVERAGE:0.5:1:1440",
				"RRA:AVERAGE:0.5:30:336",
				"RRA:AVERAGE:0.5:60:744",
				"RRA:AVERAGE:0.5:1440:365",
				"RRA:MIN:0.5:1:1440",
				"RRA:MIN:0.5:30:336",
				"RRA:MIN:0.5:60:744",
				"RRA:MIN:0.5:1440:365",
				"RRA:MAX:0.5:1:1440",
				"RRA:MAX:0.5:30:336",
				"RRA:MAX:0.5:60:744",
				"RRA:MAX:0.5:1440:365",
				"RRA:LAST:0.5:1:1440",
				"RRA:LAST:0.5:30:336",
				"RRA:LAST:0.5:60:744",
				"RRA:LAST:0.5:1440:365",
			);
		};
		my $err = RRDs::error;
		if($@ || $err) {
			logger("$@") unless !$@;
			if($err) {
				logger("ERROR: while creating $NTP_RRD: $err");
				if($err eq "RRDs::error") {
					logger("... is the RRDtool Perl package installed?");
				}
			}
			return;
		}
	}

	push(@graphs, "ntp_update");
	if($opt_d eq "all" || $debug) {
		logger("$myself: Ok");
	}
}

sub ntp_update {
	my $myself = (caller(0))[3];
	my ($debug) = @_;

	my @data;
	my $del;
	my $off;
	my $jit;
	my $str;
	my $cod;

	my $n;
	my $rrdata = "N";

	my $e = 0;
	foreach(@NTP_HOST_LIST) {
		open(IN, "ntpq -pn $_ |");
		@data = <IN>;
		close(IN);
		$cod = $str = $del = $off = $jit = 0;
		foreach(@data) {
			if(/^\*/) {
				(undef, $cod, $str, undef, undef, undef, undef, $del, $off, $jit) = split(' ', $_);
				$cod =~ s/\.//g;
				chomp($jit);
				last;
			}
		}
		$del = 0 unless defined($del);
		$off = 0 unless defined($off);
		$jit = 0 unless defined($jit);
		$str = 0 unless defined($str);
		$del /= 1000;
		$off /= 1000;
		$jit /= 1000;
		$rrdata .= ":$del:$off:$jit:$str";
		foreach my $i (@NTP_CODE_LIST[$e]) {
			for($n = 0; $n < 10; $n++) {
				if($cod eq @$i[$n]) {
					$rrdata .= ":1";
				} else {
					$rrdata .= ":0";
				}
			}
		}
		$e++;
	}

	RRDs::update($NTP_RRD, $rrdata);
	if($opt_d eq "all" || $debug) {
		logger("$myself: $rrdata");
	}
	my $err = RRDs::error;
	logger("ERROR: while updating $NTP_RRD: $err") if $err;
}

# FAIL2BAN graph
# ----------------------------------------------------------------------------
sub fail2ban_init {
	my $myself = (caller(0))[3];

	my $info;
	my @ds;
	my @tmp;
	my $n;

	if(-e $FAIL2BAN_RRD) {
		$info = RRDs::info($FAIL2BAN_RRD);
		for my $key (keys %$info) {
			if(index($key, 'ds[') == 0) {
				if(index($key, '.type') != -1) {
#					print("$key\n");
					push(@ds, substr($key, 3, index($key, ']') - 3));
				}
			}
		}
#		foreach(@ds) {
#			print($_ . "\n");
#		}
#		print(scalar(@ds) . "\n");
		if(scalar(@ds) / 9 != scalar(@FAIL2BAN_LIST)) {
			logger("Detected size mismatch between \@FAIL2BAN_LIST (" . scalar(@FAIL2BAN_LIST) . ") and $FAIL2BAN_RRD (" . scalar(@ds) / 9 . "). Resizing it accordingly. All historic data will be lost.");
			unlink($FAIL2BAN_RRD);
		}
	}

	if(!(-e $FAIL2BAN_RRD)) {
		logger("Creating '$FAIL2BAN_RRD' file.");
		for($n = 0; $n < scalar(@FAIL2BAN_LIST); $n++) {
			push(@tmp, "DS:fail2ban" . $n . "_j1:GAUGE:120:0:U");
			push(@tmp, "DS:fail2ban" . $n . "_j2:GAUGE:120:0:U");
			push(@tmp, "DS:fail2ban" . $n . "_j3:GAUGE:120:0:U");
			push(@tmp, "DS:fail2ban" . $n . "_j4:GAUGE:120:0:U");
			push(@tmp, "DS:fail2ban" . $n . "_j5:GAUGE:120:0:U");
			push(@tmp, "DS:fail2ban" . $n . "_j6:GAUGE:120:0:U");
			push(@tmp, "DS:fail2ban" . $n . "_j7:GAUGE:120:0:U");
			push(@tmp, "DS:fail2ban" . $n . "_j8:GAUGE:120:0:U");
			push(@tmp, "DS:fail2ban" . $n . "_j9:GAUGE:120:0:U");
		}
		eval {
			RRDs::create($FAIL2BAN_RRD,
				"--step=60",
				@tmp,
				"RRA:AVERAGE:0.5:1:1440",
				"RRA:AVERAGE:0.5:30:336",
				"RRA:AVERAGE:0.5:60:744",
				"RRA:AVERAGE:0.5:1440:365",
				"RRA:MIN:0.5:1:1440",
				"RRA:MIN:0.5:30:336",
				"RRA:MIN:0.5:60:744",
				"RRA:MIN:0.5:1440:365",
				"RRA:MAX:0.5:1:1440",
				"RRA:MAX:0.5:30:336",
				"RRA:MAX:0.5:60:744",
				"RRA:MAX:0.5:1440:365",
				"RRA:LAST:0.5:1:1440",
				"RRA:LAST:0.5:30:336",
				"RRA:LAST:0.5:60:744",
				"RRA:LAST:0.5:1440:365",
			);
		};
		my $err = RRDs::error;
		if($@ || $err) {
			logger("$@") unless !$@;
			if($err) {
				logger("ERROR: while creating $FAIL2BAN_RRD: $err");
				if($err eq "RRDs::error") {
					logger("... is the RRDtool Perl package installed?");
				}
			}
			return;
		}
	}

	our $fail2ban_hist = 0;
	push(@graphs, "fail2ban_update");
	if($opt_d eq "all" || $debug) {
		logger("$myself: Ok");
	}
}

sub fail2ban_update {
	my $myself = (caller(0))[3];
	my ($debug) = @_;

	my $seek_pos;
	my $logsize;
	my @jails;

	my $n;
	my $str;
	my $rrdata = "N";

	if(! -r $FAIL2BAN_LOG) {
		logger("Couldn't find file $FAIL2BAN_LOG: $!");
		return;
	}

	$seek_pos = $fail2ban_hist;
	$seek_pos = defined($seek_pos) ? int($seek_pos) : 0;
	open(IN, $FAIL2BAN_LOG);
	if(!seek(IN, 0, 2)) {
		logger("Couldn't seek to the end ($FAIL2BAN_LOG): $!");
		return;
	}
	$logsize = tell(IN);
	if($logsize < $seek_pos) {
		$seek_pos = 0;
	}
	if(!seek(IN, $seek_pos, 0)) {
		logger("Couldn't seek to $seek_pos ($FAIL2BAN_LOG): $!");
		return;
	}
	if($fail2ban_hist > 0) {	# avoid initial spike
		$date = strftime("%Y-%m-%d", localtime);
		while(<IN>) {
			if(/^$date/) {
				my $e = 0;
				while($e < scalar(@FAIL2BAN_LIST)) {
					foreach my $i (@FAIL2BAN_LIST[$e]) {
						my $e2 = 0;
						foreach my $j (@$i) {
							($str = $j) =~ s/\[/\\[/;
							$str =~ s/\]/\\]/;
							$jails[$e][$e2] = 0 unless defined $jails[$e][$e2];
							if(/ $str Ban /) {
								$jails[$e][$e2]++;
							}
							$e2++;
						}
					}
					$e++;
				}
			}
		}
	}
	close(IN);

	my $e = 0;
	while($e < scalar(@FAIL2BAN_LIST)) {
		for($n = 0; $n < 9; $n++) {
			$jails[$e][$n] = 0 unless defined $jails[$e][$n];
			$rrdata .= ":" . $jails[$e][$n];
		}
		$e++;
	}

	$fail2ban_hist = $logsize;

	RRDs::update($FAIL2BAN_RRD, $rrdata);
	if($opt_d eq "all" || $debug) {
		logger("$myself: $rrdata");
	}
	my $err = RRDs::error;
	logger("ERROR: while updating $FAIL2BAN_RRD: $err") if $err;
}

# ICECAST graph
# ----------------------------------------------------------------------------
sub icecast_init {
	my $myself = (caller(0))[3];

	my $info;
	my @ds;
	my @tmp;
	my $n;

	if(-e $ICECAST_RRD) {
		$info = RRDs::info($ICECAST_RRD);
		for my $key (keys %$info) {
			if(index($key, 'ds[') == 0) {
				if(index($key, '.type') != -1) {
#					print("$key\n");
					push(@ds, substr($key, 3, index($key, ']') - 3));
				}
			}
		}
#		foreach(@ds) {
#			print($_ . "\n");
#		}
#		print(scalar(@ds) . "\n");
		if(scalar(@ds) / 36 != scalar(@ICECAST_URL_LIST)) {
			logger("Detected size mismatch between \@ICECAST_URL_LIST (" . scalar(@ICECAST_URL_LIST) . ") and $ICECAST_RRD (" . scalar(@ds) / 36 . "). Resizing it accordingly. All historic data will be lost.");
			unlink($ICECAST_RRD);
		}
	}

	if(!(-e $ICECAST_RRD)) {
		logger("Creating '$ICECAST_RRD' file.");
		for($n = 0; $n < scalar(@ICECAST_URL_LIST); $n++) {
			push(@tmp, "DS:icecast" . $n . "_mp0_ls:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp0_br:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp0_v0:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp0_v1:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp1_ls:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp1_br:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp1_v0:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp1_v1:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp2_ls:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp2_br:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp2_v0:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp2_v1:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp3_ls:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp3_br:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp3_v0:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp3_v1:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp4_ls:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp4_br:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp4_v0:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp4_v1:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp5_ls:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp5_br:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp5_v0:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp5_v1:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp6_ls:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp6_br:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp6_v0:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp6_v1:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp7_ls:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp7_br:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp7_v0:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp7_v1:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp8_ls:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp8_br:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp8_v0:GAUGE:120:0:U");
			push(@tmp, "DS:icecast" . $n . "_mp8_v1:GAUGE:120:0:U");
		}
		eval {
			RRDs::create($ICECAST_RRD,
				"--step=60",
				@tmp,
				"RRA:AVERAGE:0.5:1:1440",
				"RRA:AVERAGE:0.5:30:336",
				"RRA:AVERAGE:0.5:60:744",
				"RRA:AVERAGE:0.5:1440:365",
				"RRA:MIN:0.5:1:1440",
				"RRA:MIN:0.5:30:336",
				"RRA:MIN:0.5:60:744",
				"RRA:MIN:0.5:1440:365",
				"RRA:MAX:0.5:1:1440",
				"RRA:MAX:0.5:30:336",
				"RRA:MAX:0.5:60:744",
				"RRA:MAX:0.5:1440:365",
				"RRA:LAST:0.5:1:1440",
				"RRA:LAST:0.5:30:336",
				"RRA:LAST:0.5:60:744",
				"RRA:LAST:0.5:1440:365",
			);
		};
		my $err = RRDs::error;
		if($@ || $err) {
			logger("$@") unless !$@;
			if($err) {
				logger("ERROR: while creating $ICECAST_RRD: $err");
				if($err eq "RRDs::error") {
					logger("... is the RRDtool Perl package installed?");
				}
			}
			return;
		}
	}

	push(@graphs, "icecast_update");
	if($opt_d eq "all" || $debug) {
		logger("$myself: Ok");
	}
}

sub icecast_update {
	my $myself = (caller(0))[3];
	my ($debug) = @_;

	my @ls;
	my @br;

	my $n;
	my $rrdata = "N";

	my $e = 0;
	foreach(@ICECAST_URL_LIST) {
		my $ua = LWP::UserAgent->new(timeout => 30);
		my $response = $ua->request(HTTP::Request->new('GET', $_));
		my $data = $response->content;

		$data =~ s/\n//g;
		undef(@ls);
		undef(@br);
		foreach my $i (@ICECAST_MP_LIST[$e]) {
			foreach(@$i) {
				my $m = "Mount Point " . $_;
				my ($b) = ($data =~ m/$m.*?<tr><td>Bitrate:<\/td><td class=\"streamdata\">(\d*?)<\/td><\/tr>/g);
				my ($l) = ($data =~ m/$m.*?<tr><td>Current Listeners:<\/td><td class=\"streamdata\">(\d*?)<\/td>/g);
				$b = 0 unless defined($b);
				$l = 0 unless defined($l);
				push(@ls, $l);
				push(@br, $b);
			}
			for($n = 0; $n < 9; $n++) {
				$ls[$n] = 0 unless defined($ls[$n]);
				$br[$n] = 0 unless defined($br[$n]);
				$rrdata .= ":" . $ls[$n];
				$rrdata .= ":" . $br[$n];
				$rrdata .= ":" . "0";
				$rrdata .= ":" . "0";
			}
		}
		$e++;
	}

	RRDs::update($ICECAST_RRD, $rrdata);
	if($opt_d eq "all" || $debug) {
		logger("$myself: $rrdata");
	}
	my $err = RRDs::error;
	logger("ERROR: while updating $ICECAST_RRD: $err") if $err;
}

# INT graph
# ----------------------------------------------------------------------------
sub int_init {
	my $myself = (caller(0))[3];

	if(!(-e $INT_RRD)) {
		logger("Creating '$INT_RRD' file.");
		eval {
			RRDs::create($INT_RRD,
				"--step=60",
				"DS:int_0:COUNTER:120:0:U",
				"DS:int_1:COUNTER:120:0:U",
				"DS:int_2:COUNTER:120:0:U",
				"DS:int_3:COUNTER:120:0:U",
				"DS:int_4:COUNTER:120:0:U",
				"DS:int_5:COUNTER:120:0:U",
				"DS:int_6:COUNTER:120:0:U",
				"DS:int_7:COUNTER:120:0:U",
				"DS:int_8:COUNTER:120:0:U",
				"DS:int_9:COUNTER:120:0:U",
				"DS:int_10:COUNTER:120:0:U",
				"DS:int_11:COUNTER:120:0:U",
				"DS:int_12:COUNTER:120:0:U",
				"DS:int_13:COUNTER:120:0:U",
				"DS:int_14:COUNTER:120:0:U",
				"DS:int_15:COUNTER:120:0:U",
				"DS:int_16:COUNTER:120:0:U",
				"DS:int_17:COUNTER:120:0:U",
				"DS:int_18:COUNTER:120:0:U",
				"DS:int_19:COUNTER:120:0:U",
				"DS:int_20:COUNTER:120:0:U",
				"DS:int_21:COUNTER:120:0:U",
				"DS:int_22:COUNTER:120:0:U",
				"DS:int_23:COUNTER:120:0:U",
				"DS:int_24:COUNTER:120:0:U",
				"DS:int_25:COUNTER:120:0:U",
				"DS:int_26:COUNTER:120:0:U",
				"DS:int_27:COUNTER:120:0:U",
				"DS:int_28:COUNTER:120:0:U",
				"DS:int_29:COUNTER:120:0:U",
				"DS:int_30:COUNTER:120:0:U",
				"DS:int_31:COUNTER:120:0:U",
				"DS:int_32:COUNTER:120:0:U",
				"DS:int_33:COUNTER:120:0:U",
				"DS:int_34:COUNTER:120:0:U",
				"DS:int_35:COUNTER:120:0:U",
				"DS:int_36:COUNTER:120:0:U",
				"DS:int_37:COUNTER:120:0:U",
				"DS:int_38:COUNTER:120:0:U",
				"DS:int_39:COUNTER:120:0:U",
				"DS:int_40:COUNTER:120:0:U",
				"DS:int_41:COUNTER:120:0:U",
				"DS:int_42:COUNTER:120:0:U",
				"DS:int_43:COUNTER:120:0:U",
				"DS:int_44:COUNTER:120:0:U",
				"DS:int_45:COUNTER:120:0:U",
				"DS:int_46:COUNTER:120:0:U",
				"DS:int_47:COUNTER:120:0:U",
				"DS:int_48:COUNTER:120:0:U",
				"DS:int_49:COUNTER:120:0:U",
				"DS:int_50:COUNTER:120:0:U",
				"DS:int_51:COUNTER:120:0:U",
				"DS:int_52:COUNTER:120:0:U",
				"DS:int_53:COUNTER:120:0:U",
				"DS:int_54:COUNTER:120:0:U",
				"DS:int_55:COUNTER:120:0:U",
				"DS:int_56:COUNTER:120:0:U",
				"DS:int_57:COUNTER:120:0:U",
				"DS:int_58:COUNTER:120:0:U",
				"DS:int_59:COUNTER:120:0:U",
				"DS:int_60:COUNTER:120:0:U",
				"DS:int_61:COUNTER:120:0:U",
				"DS:int_62:COUNTER:120:0:U",
				"DS:int_63:COUNTER:120:0:U",
				"DS:int_64:COUNTER:120:0:U",
				"DS:int_65:COUNTER:120:0:U",
				"DS:int_66:COUNTER:120:0:U",
				"DS:int_67:COUNTER:120:0:U",
				"DS:int_68:COUNTER:120:0:U",
				"DS:int_69:COUNTER:120:0:U",
				"DS:int_70:COUNTER:120:0:U",
				"DS:int_71:COUNTER:120:0:U",
				"DS:int_72:COUNTER:120:0:U",
				"DS:int_73:COUNTER:120:0:U",
				"DS:int_74:COUNTER:120:0:U",
				"DS:int_75:COUNTER:120:0:U",
				"DS:int_76:COUNTER:120:0:U",
				"DS:int_77:COUNTER:120:0:U",
				"DS:int_78:COUNTER:120:0:U",
				"DS:int_79:COUNTER:120:0:U",
				"DS:int_80:COUNTER:120:0:U",
				"DS:int_81:COUNTER:120:0:U",
				"DS:int_82:COUNTER:120:0:U",
				"DS:int_83:COUNTER:120:0:U",
				"DS:int_84:COUNTER:120:0:U",
				"DS:int_85:COUNTER:120:0:U",
				"DS:int_86:COUNTER:120:0:U",
				"DS:int_87:COUNTER:120:0:U",
				"DS:int_88:COUNTER:120:0:U",
				"DS:int_89:COUNTER:120:0:U",
				"DS:int_90:COUNTER:120:0:U",
				"DS:int_91:COUNTER:120:0:U",
				"DS:int_92:COUNTER:120:0:U",
				"DS:int_93:COUNTER:120:0:U",
				"DS:int_94:COUNTER:120:0:U",
				"DS:int_95:COUNTER:120:0:U",
				"DS:int_96:COUNTER:120:0:U",
				"DS:int_97:COUNTER:120:0:U",
				"DS:int_98:COUNTER:120:0:U",
				"DS:int_99:COUNTER:120:0:U",
				"DS:int_100:COUNTER:120:0:U",
				"DS:int_101:COUNTER:120:0:U",
				"DS:int_102:COUNTER:120:0:U",
				"DS:int_103:COUNTER:120:0:U",
				"DS:int_104:COUNTER:120:0:U",
				"DS:int_105:COUNTER:120:0:U",
				"DS:int_106:COUNTER:120:0:U",
				"DS:int_107:COUNTER:120:0:U",
				"DS:int_108:COUNTER:120:0:U",
				"DS:int_109:COUNTER:120:0:U",
				"DS:int_110:COUNTER:120:0:U",
				"DS:int_111:COUNTER:120:0:U",
				"DS:int_112:COUNTER:120:0:U",
				"DS:int_113:COUNTER:120:0:U",
				"DS:int_114:COUNTER:120:0:U",
				"DS:int_115:COUNTER:120:0:U",
				"DS:int_116:COUNTER:120:0:U",
				"DS:int_117:COUNTER:120:0:U",
				"DS:int_118:COUNTER:120:0:U",
				"DS:int_119:COUNTER:120:0:U",
				"DS:int_120:COUNTER:120:0:U",
				"DS:int_121:COUNTER:120:0:U",
				"DS:int_122:COUNTER:120:0:U",
				"DS:int_123:COUNTER:120:0:U",
				"DS:int_124:COUNTER:120:0:U",
				"DS:int_125:COUNTER:120:0:U",
				"DS:int_126:COUNTER:120:0:U",
				"DS:int_127:COUNTER:120:0:U",
				"DS:int_128:COUNTER:120:0:U",
				"DS:int_129:COUNTER:120:0:U",
				"DS:int_130:COUNTER:120:0:U",
				"DS:int_131:COUNTER:120:0:U",
				"DS:int_132:COUNTER:120:0:U",
				"DS:int_133:COUNTER:120:0:U",
				"DS:int_134:COUNTER:120:0:U",
				"DS:int_135:COUNTER:120:0:U",
				"DS:int_136:COUNTER:120:0:U",
				"DS:int_137:COUNTER:120:0:U",
				"DS:int_138:COUNTER:120:0:U",
				"DS:int_139:COUNTER:120:0:U",
				"DS:int_140:COUNTER:120:0:U",
				"DS:int_141:COUNTER:120:0:U",
				"DS:int_142:COUNTER:120:0:U",
				"DS:int_143:COUNTER:120:0:U",
				"DS:int_144:COUNTER:120:0:U",
				"DS:int_145:COUNTER:120:0:U",
				"DS:int_146:COUNTER:120:0:U",
				"DS:int_147:COUNTER:120:0:U",
				"DS:int_148:COUNTER:120:0:U",
				"DS:int_149:COUNTER:120:0:U",
				"DS:int_150:COUNTER:120:0:U",
				"DS:int_151:COUNTER:120:0:U",
				"DS:int_152:COUNTER:120:0:U",
				"DS:int_153:COUNTER:120:0:U",
				"DS:int_154:COUNTER:120:0:U",
				"DS:int_155:COUNTER:120:0:U",
				"DS:int_156:COUNTER:120:0:U",
				"DS:int_157:COUNTER:120:0:U",
				"DS:int_158:COUNTER:120:0:U",
				"DS:int_159:COUNTER:120:0:U",
				"DS:int_160:COUNTER:120:0:U",
				"DS:int_161:COUNTER:120:0:U",
				"DS:int_162:COUNTER:120:0:U",
				"DS:int_163:COUNTER:120:0:U",
				"DS:int_164:COUNTER:120:0:U",
				"DS:int_165:COUNTER:120:0:U",
				"DS:int_166:COUNTER:120:0:U",
				"DS:int_167:COUNTER:120:0:U",
				"DS:int_168:COUNTER:120:0:U",
				"DS:int_169:COUNTER:120:0:U",
				"DS:int_170:COUNTER:120:0:U",
				"DS:int_171:COUNTER:120:0:U",
				"DS:int_172:COUNTER:120:0:U",
				"DS:int_173:COUNTER:120:0:U",
				"DS:int_174:COUNTER:120:0:U",
				"DS:int_175:COUNTER:120:0:U",
				"DS:int_176:COUNTER:120:0:U",
				"DS:int_177:COUNTER:120:0:U",
				"DS:int_178:COUNTER:120:0:U",
				"DS:int_179:COUNTER:120:0:U",
				"DS:int_180:COUNTER:120:0:U",
				"DS:int_181:COUNTER:120:0:U",
				"DS:int_182:COUNTER:120:0:U",
				"DS:int_183:COUNTER:120:0:U",
				"DS:int_184:COUNTER:120:0:U",
				"DS:int_185:COUNTER:120:0:U",
				"DS:int_186:COUNTER:120:0:U",
				"DS:int_187:COUNTER:120:0:U",
				"DS:int_188:COUNTER:120:0:U",
				"DS:int_189:COUNTER:120:0:U",
				"DS:int_190:COUNTER:120:0:U",
				"DS:int_191:COUNTER:120:0:U",
				"DS:int_192:COUNTER:120:0:U",
				"DS:int_193:COUNTER:120:0:U",
				"DS:int_194:COUNTER:120:0:U",
				"DS:int_195:COUNTER:120:0:U",
				"DS:int_196:COUNTER:120:0:U",
				"DS:int_197:COUNTER:120:0:U",
				"DS:int_198:COUNTER:120:0:U",
				"DS:int_199:COUNTER:120:0:U",
				"DS:int_200:COUNTER:120:0:U",
				"DS:int_201:COUNTER:120:0:U",
				"DS:int_202:COUNTER:120:0:U",
				"DS:int_203:COUNTER:120:0:U",
				"DS:int_204:COUNTER:120:0:U",
				"DS:int_205:COUNTER:120:0:U",
				"DS:int_206:COUNTER:120:0:U",
				"DS:int_207:COUNTER:120:0:U",
				"DS:int_208:COUNTER:120:0:U",
				"DS:int_209:COUNTER:120:0:U",
				"DS:int_210:COUNTER:120:0:U",
				"DS:int_211:COUNTER:120:0:U",
				"DS:int_212:COUNTER:120:0:U",
				"DS:int_213:COUNTER:120:0:U",
				"DS:int_214:COUNTER:120:0:U",
				"DS:int_215:COUNTER:120:0:U",
				"DS:int_216:COUNTER:120:0:U",
				"DS:int_217:COUNTER:120:0:U",
				"DS:int_218:COUNTER:120:0:U",
				"DS:int_219:COUNTER:120:0:U",
				"DS:int_220:COUNTER:120:0:U",
				"DS:int_221:COUNTER:120:0:U",
				"DS:int_222:COUNTER:120:0:U",
				"DS:int_223:COUNTER:120:0:U",
				"DS:int_224:COUNTER:120:0:U",
				"DS:int_225:COUNTER:120:0:U",
				"DS:int_226:COUNTER:120:0:U",
				"DS:int_227:COUNTER:120:0:U",
				"DS:int_228:COUNTER:120:0:U",
				"DS:int_229:COUNTER:120:0:U",
				"DS:int_230:COUNTER:120:0:U",
				"DS:int_231:COUNTER:120:0:U",
				"DS:int_232:COUNTER:120:0:U",
				"DS:int_233:COUNTER:120:0:U",
				"DS:int_234:COUNTER:120:0:U",
				"DS:int_235:COUNTER:120:0:U",
				"DS:int_236:COUNTER:120:0:U",
				"DS:int_237:COUNTER:120:0:U",
				"DS:int_238:COUNTER:120:0:U",
				"DS:int_239:COUNTER:120:0:U",
				"DS:int_240:COUNTER:120:0:U",
				"DS:int_241:COUNTER:120:0:U",
				"DS:int_242:COUNTER:120:0:U",
				"DS:int_243:COUNTER:120:0:U",
				"DS:int_244:COUNTER:120:0:U",
				"DS:int_245:COUNTER:120:0:U",
				"DS:int_246:COUNTER:120:0:U",
				"DS:int_247:COUNTER:120:0:U",
				"DS:int_248:COUNTER:120:0:U",
				"DS:int_249:COUNTER:120:0:U",
				"DS:int_250:COUNTER:120:0:U",
				"DS:int_251:COUNTER:120:0:U",
				"DS:int_252:COUNTER:120:0:U",
				"DS:int_253:COUNTER:120:0:U",
				"DS:int_254:COUNTER:120:0:U",
				"DS:int_255:COUNTER:120:0:U",
				"RRA:AVERAGE:0.5:1:1440",
				"RRA:AVERAGE:0.5:30:336",
				"RRA:AVERAGE:0.5:60:744",
				"RRA:AVERAGE:0.5:1440:365",
				"RRA:MIN:0.5:1:1440",
				"RRA:MIN:0.5:30:336",
				"RRA:MIN:0.5:60:744",
				"RRA:MIN:0.5:1440:365",
				"RRA:MAX:0.5:1:1440",
				"RRA:MAX:0.5:30:336",
				"RRA:MAX:0.5:60:744",
				"RRA:MAX:0.5:1440:365",
				"RRA:LAST:0.5:1:1440",
				"RRA:LAST:0.5:30:336",
				"RRA:LAST:0.5:60:744",
				"RRA:LAST:0.5:1440:365",
			);
		};
		my $err = RRDs::error;
		if($@ || $err) {
			logger("$@") unless !$@;
			if($err) {
				logger("ERROR: while creating $INT_RRD: $err");
				if($err eq "RRDs::error") {
					logger("... is the RRDtool Perl package installed?");
				}
			}
			return;
		}
	}

	push(@graphs, "int_update");
	if($opt_d eq "all" || $debug) {
		logger("$myself: Ok");
	}
}

sub int_update {
	my $myself = (caller(0))[3];
	my ($debug) = @_;

	my @int;

	my $n;
	my $maxints;
	my $rrdata = "N";

	if($os eq "Linux") {
		open(IN, "/proc/stat");
		while(<IN>) {
			if(/^intr/) {
				my @tmp = split(' ', $_);
				(undef, undef, @int) = @tmp;
				last;
			}
		}
		close(IN);
	} elsif($os eq "FreeBSD" || $os eq "OpenBSD") {
		open(IN, "vmstat -i |");
		my @allfields;
		my $num;
		my $name;
		my $ticks;
		$maxints = 0;
		while(<IN>) {
			if(/^\D{3}\d+/) {
				@allfields = split(' ', $_);
				$num = $allfields[0];
				$name = $allfields[1];
				$ticks = $allfields[$#allfields - 1];
				chomp($ticks);
				if($name eq "timer") {
					$num = 0;
				} else {
					$num =~ s/^\D{3}//;
					$num =~ s/://;
				}
				$int[$num] += $ticks;
				$maxints = $maxints < $num ? $num : $maxints;
			}
		}
		close(IN);
		for($n = 0; $n < $maxints; $n++) {
			$int[$n] = !$int[$n] ? 0 : $int[$n];
		}
	}

	for($n = 0; $n < scalar(@int); $n++) {
		if(($n % 256) != $n) {
			$int[$n % 256] += $int[$n];
		}
	}

	for($n = 0; $n < 256; $n++) {
		if(!defined($int[$n])) {
			$int[$n] = 0;
		}
		$rrdata .= ":" . $int[$n];
	}

	RRDs::update($INT_RRD, $rrdata);
	if($opt_d eq "all" || $debug) {
		logger("$myself: $rrdata");
	}
	my $err = RRDs::error;
	logger("ERROR: while updating $INT_RRD: $err") if $err;
}

# PC graph
# ----------------------------------------------------------------------------
sub pc_init {
	my $myself = (caller(0))[3];

	my $info;
	my @ds;
	my @tmp;
	my @data;
	my $n;
	my $p;

	if(-e $PC_RRD) {
		$info = RRDs::info($PC_RRD);
		for my $key (keys %$info) {
			if(index($key, 'ds[') == 0) {
				if(index($key, '.type') != -1) {
#					print("$key\n");
					push(@ds, substr($key, 3, index($key, ']') - 3));
				}
			}
		}
#		foreach(@ds) {
#			print($_ . "\n");
#		}
#		print(scalar(@ds) . "\n");
		if(scalar(@ds) / 2 != $PC_MAX) {
			logger("Detected size mismatch between \$PC_MAX ($PC_MAX) and $PC_RRD (" . scalar(@ds) / 2 . "). Resizing it accordingly. All historic data will be lost.");
			unlink($PC_RRD);
		}
	}

	if(!(-e $PC_RRD)) {
		logger("Creating '$PC_RRD' file.");
		for($n = 0; $n < $PC_MAX; $n++) {
			push(@tmp, "DS:pc" . $n . "_in:COUNTER:120:0:U");
			push(@tmp, "DS:pc" . $n . "_out:COUNTER:120:0:U");
		}
		eval {
			RRDs::create($PC_RRD,
				"--step=60",
				@tmp,
				"RRA:AVERAGE:0.5:1:1440",
				"RRA:AVERAGE:0.5:30:336",
				"RRA:AVERAGE:0.5:60:744",
				"RRA:AVERAGE:0.5:1440:365",
				"RRA:MIN:0.5:1:1440",
				"RRA:MIN:0.5:30:336",
				"RRA:MIN:0.5:60:744",
				"RRA:MIN:0.5:1440:365",
				"RRA:MAX:0.5:1:1440",
				"RRA:MAX:0.5:30:336",
				"RRA:MAX:0.5:60:744",
				"RRA:MAX:0.5:1440:365",
				"RRA:LAST:0.5:1:1440",
				"RRA:LAST:0.5:30:336",
				"RRA:LAST:0.5:60:744",
				"RRA:LAST:0.5:1440:365",
			);
		};
		my $err = RRDs::error;
		if($@ || $err) {
			logger("$@") unless !$@;
			if($err) {
				logger("ERROR: while creating $PC_RRD: $err");
				if($err eq "RRDs::error") {
					logger("... is the RRDtool Perl package installed?");
				}
			}
			return;
		}
	}

	if($os eq "Linux") {
		if(!$NET_GATEWAY) {
			logger("You must assign a valid ethernet interface in \$NET_GATEWAY");
			return;
		}
		# remove the changed PC or those that no longer exist (daily)
		open(IN, "iptables -nxvL FORWARD | grep _daily |");
		@data = <IN>;
		close(IN);
		my $rule;
		my $num;
		my $exist;
		foreach my $d (@data) {
			$exist = 0;
			(undef, undef, $name) = split(' ', $d);
			$name =~ s/_daily//;
			for($n = 0; $n < $PC_MAX; $n++) {
				if($name eq $PC_LIST[$n]) {
					$exist = 1;
					last;
				}
			}
			if(!$exist) {
				logger("removing unused iptables rule: pc=$name");
				$rule = system("iptables -nxvL FORWARD --line-numbers | grep -w $name" . "_daily 2>/dev/null");
				$rule = split(' ', $rule);
				system("iptables -D FORWARD $rule");
				system("iptables -F $name" . "_daily");
				system("iptables -X $name" . "_daily");
	
				$rule = system("iptables -nxvL FORWARD --line-numbers | grep -w $name" . "_total 2>/dev/null");
				$rule = split(' ', $rule);
				system("iptables -D FORWARD $rule");
				system("iptables -F $name" . "_total");
				system("iptables -X $name" . "_total");
			}
		}

		# set the current defined PC
		my $ip;
		for($n = 0; $n < $PC_MAX; $n++) {
			if($PC_LIST[$n]) {
				if(!($ip = $PC_IP[$n])) {
					if(!(gethostbyname($PC_LIST[$n]))) {
						logger("DNS problem with: ", $PC_LIST[$n]);
					}
					$ip = inet_ntoa((gethostbyname($PC_LIST[$n]))[4]);
					$ip = $ip . "/32";
				}
				undef(@data);
				open(IN, "iptables -nxvL $PC_LIST[$n]_total 2>/dev/null |");
				@data = <IN>;
				close(IN);
				if(!scalar(@data)) {
					system("iptables -N $PC_LIST[$n]_total");
					system("iptables -N $PC_LIST[$n]_daily");
					system("iptables -I FORWARD -j $PC_LIST[$n]_total");
					system("iptables -A $PC_LIST[$n]_total -s $ip -d 0/0 -o $NET_GATEWAY");
					system("iptables -A $PC_LIST[$n]_total -s 0/0 -d $ip -i $NET_GATEWAY");
					system("iptables -I FORWARD -j $PC_LIST[$n]_daily");
					system("iptables -A $PC_LIST[$n]_daily -s $ip -d 0/0 -o $NET_GATEWAY");
					system("iptables -A $PC_LIST[$n]_daily -s 0/0 -d $ip -i $NET_GATEWAY");
				}
			}
		}
	}

	push(@graphs, "pc_update");
	if($opt_d eq "all" || $debug) {
		logger("$myself: Ok");
	}
}

sub pc_update {
	my $myself = (caller(0))[3];

	my @in;
	my @out;

	my $n;
	my $ip;
	my $rrdata = "N";

	for($n = 0; $n < $PC_MAX; $n++) {
		if($PC_LIST[$n]) {
			if(!($ip = $PC_IP[$n])) {
				if(!(gethostbyname($PC_LIST[$n]))) {
					logger("DNS problem with: ", $PC_LIST[$n]);
				}
				$ip = inet_ntoa((gethostbyname($PC_LIST[$n]))[4]);
			}
			$ip =~ s/\/\d+//;
			open(IN, "iptables -nxvL $PC_LIST[$n]_total |");
			while(<IN>) {
				my (undef, $bytes, undef, undef, undef, undef, $source) = split(' ', $_);
				if($source eq $ip) {
					push(@out, $bytes);
				}
				if($source =~ /0.0.0.0/) {
					push(@in, $bytes);
				}
			}
			close(IN);
		}
	}

	for($n = 0; $n < $PC_MAX; $n++) {
		$rrdata .= ":$in[$n]:$out[$n]";
	}

	RRDs::update($PC_RRD, $rrdata);
	if($opt_d eq "all") {
		logger("$myself: $rrdata");
	}
	my $err = RRDs::error;
	logger("ERROR: while updating $PC_RRD: $err") if $err;
}

sub get_counters {
	my $in;
	my $out;

	my $n;
	my $ip;
	my $day = (localtime(time - 60))[3];

	for($n = 0; $n < $PC_MAX; $n++) {
		if($PC_LIST[$n]) {
			if(!($ip = $PC_IP[$n])) {
				if(!(gethostbyname($PC_LIST[$n]))) {
					logger("DNS problem with: ", $PC_LIST[$n]);
				}
				$ip = inet_ntoa((gethostbyname($PC_LIST[$n]))[4]);
			}
			$ip =~ s/\/\d+//;
			open(IN, "iptables -nxvL $PC_LIST[$n]_daily |");
			while(<IN>) {
				my (undef, $bytes, undef, undef, undef, undef, $source) = split(' ', $_);
				if($source eq $ip) {
					$out = $bytes;
				}
				if($source =~ /0.0.0.0/) {
					$in = $bytes;
				}
			}
			close(IN);
			if(! -w $USAGE_DIR) {
				logger("WARNING: directory '" . $USAGE_DIR ."' doesn't exists or is not writable.");
				last;
			} else {
				open(OUT, ">> " . $USAGE_DIR . $PC_LIST[$n]);
				print(OUT "$day $in $out\n");
				close(OUT);
				logger("Saved the daily traffic counter for '$PC_LIST[$n]'.") unless !$opt_d;
			}
			system("iptables -Z $PC_LIST[$n]_daily >/dev/null 2>/dev/null");
		}
	}

}

sub send_reports {
	my $myself = (caller(0))[3];
	my $n;
	my $to;

	if(! -x $REPORT_DIR . "send_reports") {
		logger("$myself: unable to find the script '" . $REPORT_DIR . "send_reports" . "'.");
		return;
	}
	logger("Sending monthly traffic reports.");
	for($n = 0; $n < $PC_MAX; $n++) {
		if($PC_LIST[$n]) {
			$to = $PC_REPORT_MAIL[$n];
			$to = $PC_DEFAULT_MAIL unless $PC_REPORT_MAIL[$n];
			logger("$myself: $PC_LIST[$n] -> $to [$PC_REPORT_LANG]");
			system("cd $REPORT_DIR ; " . $REPORT_DIR . "send_reports -h $PC_LIST[$n] -c $opt_c &");
		}
	}
}


# Main
# ----------------------------------------------------------------------------
getopts("d:vc:p:") || usage();

if($opt_v) {
	print("Monitorix version " . VERSION . " (" . RELDATE . ")\n");
	print("by Jordi Sanfeliu <jordi\@fibranet.cat>\n");
	print("http://www.monitorix.org/\n\n");
	exit(0);
}
if(!$opt_c) {
	usage();
	exit(1);
}
$opt_c = abs_path($opt_c) unless $^V lt 5.6.2;
if(!stat($opt_c)) {
	die("can't open file $opt_c.\n");
}

# get the current OS and kernel branch and check its support
my $release;
($os, undef, $release) = uname();
my ($major, $minor) = split('\.', $release);
$kernel_branch = $major . "." . $minor;
if(!grep {$_ eq $os} @suppsys) {
	die("FATAL: your operating system ($os) is not supported.\n");
}

# check configuration file syntax (and load it)
if($os eq "FreeBSD" || $os eq "OpenBSD") {
	$SIG{'CHLD'} = 'DEFAULT';
}	
if(system("perl -wc $opt_c >/dev/null 2>&1")) {
	die("FATAL: configuration file '$opt_c' had compilation errors.\n");
}
require $opt_c;


$0 = sprintf("%s %s%s%s%s",
	$^V lt 5.6.2 ? monitorix : abs_path($0),
	$opt_c ? "-c $opt_c" : "",
	$opt_p ? " -p $opt_p" : "",
	$opt_d ? " -d $opt_d" : "",
	$opt_v ? " -v" : "");

daemonize();
logger("Starting Monitorix version " . VERSION . " (pid $$).");

if($opt_p) {
	$opt_p = abs_path($opt_p);
	open(PIDFILE, "> $opt_p")
		|| die("could not open $opt_p for writing");
	print(PIDFILE "$$");
	close(PIDFILE);
}

# change to safety directory
unless(chdir("/tmp")) {
	logger("can't chdir to /tmp: $!");
	unless(chdir("/lost+found")) {
		die("Can't chdir to /lost+found: $!");
	}
}

if($opt_d) {
	if($opt_d ne "none" && $opt_d ne "all") {
		@graphs_debug = split(',', $opt_d);
		foreach my $t (@graphs_debug) {
			if(!grep {$_ eq $t} (@GRAPH_NAME)) {
				die("Invalid debug key '$t'");
			}
		}
	}
	logger("Entering in debug mode.");
	logger("Changed process name to '$0'.");
}

# save the path of the configuration file
open(OUT, "> $BASE_DIR/cgi-bin/monitorix.conf.path");
print(OUT "$opt_c\n");
close(OUT);

# initialize all enabled graphs
logger("Initializing graphs.") unless !$opt_d;

my $func;
foreach my $g (@GRAPH_NAME) {
	if($GRAPH_ENABLE{$g} eq "Y") {
		$func = $g . "_init";
		eval {&$func();};
		if($@) {
			logger("WARNING: unexpected errors in function $func()");
		}
	}
}
if($PC_LAN eq "Y") {
	pc_init();
}

if(!scalar(@graphs)) {
	logger("nothing to do, exiting.");
	exit(0);
}

# create 'index.html' file
logger("Generating the 'index.html' file.") unless !$opt_d;
create_index();
logger("Ok, done.") unless !$opt_d;

alarm(1);
while(1) {
        sleep(1);
}
