#!/usr/bin/perl
#
# Monitorix - A lightweight system monitoring tool.
# Copyright (C) 2005-2011 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#

require 5.006;

no strict "vars";
no warnings "once";
#use warnings;
use LWP::UserAgent;
use IO::Socket;
use Getopt::Long;
use MIME::Lite;
use POSIX;
use RRDs;
use Cwd 'abs_path';


# 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.1.0";
use constant RELDATE	=> "09-Mar-2011";

my $pid;
my ($opt_config, $opt_debug, $opt_version);
my @graphs;
my $root_disk;
my $root_disk_p;
my $os;
my $kernel_branch;

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

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

sub HUP_handler {
	my ($signal) = @_;
	my $myself = (caller(0))[3];
	logger("$myself: received SIG$signal. Reopening log file.");
	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: $!");
	}
}

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

	if($sec == 0) {
		foreach my $g (@graphs) {
			logger("$myself: calling $g()") unless !$opt_debug;
			eval {&$g();};
			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("/tmp")) {
		die("Can't chdir to /tmp: $!");
	}
	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: $!");
	}
	$pid = $$;
}

sub usage {
	print(STDERR << "EOF");
usage: monitorix -c|--config <config_file> [-d|--debug] [-v|--version]

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_debug;
	}

	open(OUT, "> $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="$BASE_URL/monitorixico.png">
 </head>
 <body bgcolor="$bgcolor" text="#888888" vlink="#888888" link="#888888">
  <center>
  <p>
  <br>
  <font face="Verdana, Tahoma, 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

	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/xxx/$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++) {
					$gname = "_" . $g;
					print(OUT "              <option value='" . $gname . $n . "'>" . $rgraphs{$gname} . " " . $PORT_LIST[$n] . " (" . $PORT_NAME[$n] . ")" . "</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="day">
            <font color="$title_fore_color"><b>Daily&nbsp;</b></font>
        </td>
        <td bgcolor="$title_back_color">
          <input type="radio" name="when" value="week">
            <font color="$title_fore_color"><b>Weekly&nbsp;</b></font>
        </td>
        <td bgcolor="$title_back_color">
          <input type="radio" name="when" value="month">
            <font color="$title_fore_color"><b>Monthly&nbsp;</b></font>
        </td>
        <td bgcolor="$title_back_color">
          <input type="radio" name="when" value="year">
            <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");
}

sub system_update {
	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");
		my @tmp = split(' ', <IN>);
		close(IN);
		($load1, $load5, $load15, my $tmp1) = @tmp;
		($nprun, $npslp) = split('\/', $tmp1);
		chomp($npslp, $nprun);
		$nproc = $npslp + $nprun;

		open(IN, "/proc/meminfo");
		while(<IN>) {
			if(/^MemTotal:/) {
				my @tmp = split(' ', $_);
				$mtotl = $tmp[1];
			}
			if(/^Buffers:/) {
				my @tmp = split(' ', $_);
				$mbuff = $tmp[1];
			}
			if(/^Cached:/) {
				my @tmp = split(' ', $_);
				$mcach = $tmp[1];
			}
			if(/^MemFree:/) {
				my @tmp = split(' ', $_);
				$mfree = $tmp[1];
			}
		}
		close(IN);
		$macti = $minac = "";
	} elsif($os eq "FreeBSD") {
		my $page_size;
		open(IN, "sysctl -n vm.loadavg |");
		my @tmp = split(' ', <IN>);
		close(IN);
		(undef, $load1, $load5, $load15) = @tmp;
		open(IN, "sysctl vm.vmtotal | grep 'Processes' |");
		@tmp = split(' ', <IN>);
		close(IN);
		(undef, undef, $nprun, undef, undef, undef, undef, undef, undef, undef, $npslp) = @tmp;
		$npslp =~ s/\)//;
		chomp($npslp, $nprun);
		$nproc = $npslp + $nprun;
		$mtotl = `sysctl -n hw.realmem`;
		$mbuff = `sysctl -n vfs.bufspace`;
		$mcach = `sysctl -n vm.stats.vm.v_cache_count`;
		$mfree = `sysctl vm.vmtotal | grep "Free Memory" | awk -F ":" '{ print \$2 }' | sed s'/K//'`;

		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;
	}

	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);
	my $err = RRDs::error;
	print("ERROR: while updating $SYSTEM_RRD: $err\n") 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_val01:GAUGE:120:0:100",
			"DS:kern_val02:GAUGE:120:0:100",
			"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;
		}
	}
	our %kernel_hist = ();

	push(@graphs, "kern_update");
}

sub kern_update {
	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 $val01;
	my $val02;
	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'} = $_;
			}
			if(/^ctxt/) {
				my (undef, $data) = split(' ', $_);
				# avoid initial spike
				$cs = int($data) unless !$kernel_hist{'cs'};
				$kernel_hist{'cs'} = int($data) unless $kernel_hist{'cs'};
				last;
			}
		}
		close(IN);
		open(IN, "/proc/sys/fs/dentry-state");
			{
			my ($max, $un) = split(' ', <IN>);
			$dentry = ($un * 100) / $max;
			}
		close(IN);
		open(IN, "/proc/sys/fs/file-nr");
			{
			my ($nr, undef, $max) = split(' ', <IN>);
			$file = ($nr * 100) / $max;
			}
		close(IN);
		open(IN, "/proc/sys/fs/inode-nr");
			{
			my ($max, $un) = split(' ', <IN>);
			$inode = ($un * 100) / $max;
			}
		close(IN);
	} elsif($os eq "FreeBSD") {
		my $max;
		my $num;
		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);
		my $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'};

		open(IN, "sysctl -n kern.maxfiles |");
			$max = <IN>;
			chomp($max);
		close(IN);
		open(IN, "sysctl -n kern.openfiles |");
			$num = <IN>;
			chomp($num);
		close(IN);
		$file = ($num * 100) / $max;

		open(IN, "sysctl -n kern.maxvnodes |");
			$max = <IN>;
			chomp($max);
		close(IN);
		open(IN, "sysctl -n vfs.numvnodes |");
			$num = <IN>;
			chomp($num);
		close(IN);
		$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:$val01:$val02:$val03:$val04:$val05";
	RRDs::update($KERN_RRD, $rrdata);
	my $err = RRDs::error;
	print("ERROR: while updating $KERN_RRD: $err\n") if $err;
}

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

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

	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 . "). 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");
}

sub proc_update {
	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);
	my $err = RRDs::error;
	print("ERROR: while updating $PROC_RRD: $err\n") if $err;
}

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

	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;
		}
	}
	# save the contents of 'hplog -t' since only 'root' is able to run it
	open(IN, "hplog -t |");
	my @data = <IN>;
	close(IN);
	open(OUT, "> $BASE_DIR/cgi-bin/monitorix.hplog");
	print(OUT @data);
	close(OUT);
	push(@graphs, "hptemp_update");
}

sub hptemp_update {
	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);
	my $err = RRDs::error;
	print("ERROR: while updating $HPTEMP_RRD: $err\n") 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");
}

sub lmsens_update {
	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") {
	  				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);
	my $err = RRDs::error;
	print("ERROR: while updating $LMSENS_RRD: $err\n") 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");
}

sub nvidia_update {
	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) {
			$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 eq 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);
	my $err = RRDs::error;
	print("ERROR: while updating $NVIDIA_RRD: $err\n") 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");
}

sub disk_update {
	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);
	my $err = RRDs::error;
	print("ERROR: while updating $DISK_RRD: $err\n") if $err;
}

# FS graph
# ----------------------------------------------------------------------------
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 finds out the name of the disk drive where the root filesystem is
	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_cciss;
		my $is_md;
		my $found = 0;
	
		$root_disk_p = $root_disk;
		if($root_disk =~ m/cciss/) {
			$root_disk =~ s/[0-9]$//;
			$root_disk =~ s/[0-9]$//;
			$root_disk =~ s/.$//;
			$is_cciss = 1;
		}
		if($root_disk =~ m/\/dev\/evms\//) {
			$root_disk = `evms_query disks $root_disk`;
			$found = 1;
		}
		if(!$found) {
		if(stat("/proc/mdstat")) {
			my $tmp;
			my $md_root_disk = $root_disk;
			$md_root_disk =~ s/\/dev\///;
			$md_root_disk =~ s/.*\///;
			$tmp = `grep -w $md_root_disk /proc/mdstat | awk -F " " '{ print \$1 }'`;
			chomp($tmp);
			if($tmp eq $md_root_disk) {
				$root_disk = $md_root_disk;
				$is_md = 1;
			}
		} else {
			$root_disk =~ s/.$//;
		}
		$root_disk =~ s/.*\///;
		$root_disk_p =~ s/.*\///;
	
		$lvm = $root_disk;
		$lvm =~ s/-.*//;
		if($lvm ne $root_disk) {	# probably LVM
			system("pvs >/dev/null 2>&1");
			if($? == 0 && $lvm) {
				$lvm_disk = `pvs --noheadings | grep $lvm | awk -F " " '{ print \$1 }' | tail -1`;
				chomp($lvm_disk);
				if(!($lvm_disk =~ m/md/)) {
					if(!($lvm_disk =~ m/cciss/)) {
						# LVM over any direct disk (/dev/hda1)
						$root_disk = `echo $lvm_disk | sed s'/.\$//'`;
						chomp($root_disk);
					} else {
						# LVM over a CCISS disk (/dev/cciss/c0d0)
						$root_disk = `echo $lvm_disk | sed s'/.\$//'`;
						chomp($root_disk);
						$root_disk = `echo $root_disk | sed s'/.\$//'`;
						chomp($root_disk);
					}
				} else {
					# LVM over RAID combination (/dev/md1)
					$root_disk = $lvm_disk;
					chomp($root_disk);
				}
				chomp($root_disk);
				$root_disk =~ s/.*\///;
				chomp($root_disk);
			}
		} else {
			if(!$is_cciss && !$is_md) {
				$root_disk =~ s/[0-9]$//;
				$root_disk =~ s/[0-9]$//;
			}

		}
		}
	} elsif($os eq "FreeBSD") {
		$root_disk =~ s/.*\///;		# removes /dev/
		$root_disk =~ s/...$//;		# removes part number
	}
	our $fs_hist = 0;

	push(@graphs, "fs_update");
}

sub fs_update {
	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") {
		@tmp = split(' ', `free | grep Swap`);
		(undef, undef, $swap_used, $swap_free) = @tmp;
	} elsif($os eq "FreeBSD") {
		@tmp = split(' ', `swapinfo -k | tail -1`);
		(undef, undef, $swap_used, $swap_free) = @tmp;
	}

	chomp($root_used, $root_free, $swap_used, $swap_free);
	my $root_pcnt = ($root_used * 100) / ($root_used + $root_free);
	my $swap_pcnt = ($swap_used * 100) / ($swap_used + $swap_free);

	# 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)) {
			eval {
				alarm $TIMEOUT;
				open(IN, "df -P $FS_LIST[$n] |");
				while(<IN>) {
					if(/ $FS_LIST[$n]$/) {
						@tmp = split(' ', $_);
						last;
					}
				}
				close(IN);
				alarm 0;
			};
			(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 {
			open(IN, "/proc/stat");
			while(<IN>) {
				if(/disk_io/) {
					my (undef, undef, $tmp) = split(':', $_);
					last;
				}
			}
			close(IN);
			(undef, $read_cnt, $read_sec, $write_cnt, $write_sec) = split(',', $tmp);
			$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);
		}
	}

	chomp($read_cnt, $read_sec, $write_cnt, $write_sec);
	$rrdata .= ":$read_cnt:$write_cnt:$read_sec:$write_sec";
	RRDs::update($FS_RRD, $rrdata);
	my $err = RRDs::error;
	print("ERROR: while updating $FS_RRD: $err\n") 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");
}

sub net_update {
	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]/) {
						(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);
			}
		}
		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);
	my $err = RRDs::error;
	print("ERROR: while updating $NET_RRD: $err\n") 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_val01: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_val01: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;
		}
	}
	our %serv_hist = ();
	push(@graphs, "serv_update");
}

sub serv_update {
	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 $val01 = 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) {
		$date_dovecot = strftime("%b %d", localtime);
		$date_uw = strftime("%b %e %T", localtime);
		open(IN, "$IMAP_LOG");
		while(<IN>) {
			# UW-IMAP log
			if(/$date/) {
				if(/ imapd\[/ && / Login user=/) {
					$imap++;
				}
			}
			# Dovecot log
			if(/^dovecot: $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 $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);
	}

	# I data (incremental)
	$rrdata .= ":$ssh:$ftp:$telnet:$imap:$smb:$fax:$cups:$pop3:$smtp:$spam:$virus:$val01:$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_val01 = 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_val01 = $val01 - $serv_hist{'val01'};
	$l_val01 = 0 unless $l_val01 != $val01;
	$l_val01 /= 300;
	$serv_hist{'val01'} = $val01;

	$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_val01:$l_val02:$l_val03:$l_val04:$l_val05";
	RRDs::update($SERV_RRD, $rrdata);
	my $err = RRDs::error;
	print("ERROR: while updating $SERV_RRD: $err\n") 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_mta_val10:GAUGE:120:0:U",
			"DS:mail_queued:GAUGE:120:0:U",
			"DS:mail_mta_val12:GAUGE:120:0:U",
			"DS:mail_mta_val13:GAUGE:120:0:U",
			"DS:mail_mta_val14:GAUGE:120:0:U",
			"DS:mail_mta_val15: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;
		}
	}
	our $mail_hist = 0;

	push(@graphs, "mail_update");
}

sub mail_update {
	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 $seek_pos;
	my $logsize;
	my $rrdata = "N";

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

	$recvd = $delvd = $bytes_recvd = $bytes_delvd = 0;
	$in_conn = $out_conn = $rejected = 0;
	$queued = 0;
	if($MAIL_MTA eq "sendmail") {
		if(open(IN, "mailstats -P |")) {
			while(<IN>) {
				if(/^ T/) {
					my @line = split(' ', $_);
					(undef, $recvd, $bytes_recvd, $delvd, $bytes_delvd) = @line;
				}
				if(/^ C/) {
					my @line = split(' ', $_);
					(undef, $in_conn, $out_conn, $rejected) = @line;
				}
			}
			close(IN);
			$bytes_recvd *= 1024;
			$bytes_delvd *= 1024;
		}
		if(open(IN, "mailq |")) {
			while(<IN>) {
				if(/Total requests:/) {
					my @line = split(' ', $_);
					(undef, undef, $queued) = @line;
				}
			}
			close(IN);
		}
	}

	$gl_records = $gl_greylisted = $gl_whitelisted = 0;
	if($MAIL_GREYLIST eq "milter-greylist") {
		if(-r $MILTER_GL) {
			open(IN, $MILTER_GL);
			seek(IN, -80, 2) or die "Couldn't seek to the end ($MILTER_GL): $!\n";
			while(<IN>) {
				if(/^# Summary:/) {
					(undef, undef, $gl_records, undef, $gl_greylisted, undef, $gl_whitelisted) = split(' ', $_);
				}
			}
			close(IN);
		}
	}

	$spam = $virus = 0;
	if(-r $MAIL_LOG) {
		open(IN, $MAIL_LOG);
		seek(IN, 0, 2) or die "Couldn't seek to the end ($MAIL_LOG): $!\n";
		$logsize = tell(IN);
		if($logsize < $seek_pos) {
			$seek_pos = 0;
		}
		seek(IN, $seek_pos, 0) or die "Couldn't seek to $seek_pos ($MAIL_LOG): $!\n";
		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);
	}

	$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[10] = int($queued);

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

	$mail_hist = join(" ", $logsize, @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);
	my $err = RRDs::error;
	print("ERROR: while updating $MAIL_RRD: $err\n") 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 . "). 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;
		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";
				$rule = system("iptables -nxvL INPUT --line-numbers | grep -w $name 2>/dev/null");
				$rule = split(' ', $rule);
				system("iptables -D INPUT $rule");
				system("iptables -F $name");
				system("iptables -X $name");

				$name = "MONITORIX_OUT_$num";
				$rule = system("iptables -nxvL OUTPUT --line-numbers | grep -w $name 2>/dev/null");
				$rule = split(' ', $rule);
				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]) {
				$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");
				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");
			}
		}
	}
	push(@graphs, "port_update");
}

sub port_update {
	my @in;
	my @out;

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

	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);

	for($n = 0; $n < $PORT_MAX; $n++) {
		$rrdata .= ":$in[$n]:$out[$n]";
	}
	RRDs::update($PORT_RRD, $rrdata);
	my $err = RRDs::error;
	print("ERROR: while updating $PORT_RRD: $err\n") 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");
}

sub user_update {
	my $sys;
	my $smb;
	my $mac;

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

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

	my $start = 0;
	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);
	my $err = RRDs::error;
	print("ERROR: while updating $USER_RRD: $err\n") if $err;
}

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

	if(!(-e $APACHE_RRD)) {
		logger("Creating '$APACHE_RRD' file.");
		eval {
			RRDs::create($APACHE_RRD,
			"--step=60",
			"DS:apache_acc:GAUGE:120:0:U",
			"DS:apache_kb:GAUGE:120:0:U",
			"DS:apache_cpu:GAUGE:120:0:U",
			"DS:apache_busy:GAUGE:120:0:U",
			"DS:apache_idle: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 $APACHE_RRD: $err");
				if($err eq "RRDs::error") {
					logger("... is the RRDtool Perl package installed?");
				}
			}
			return;
		}
	}

	if(!defined($APACHE_PORT)) {
		logger("$myself: ERROR: undefined APACHE_PORT option.");
		return 0;
	}
	our %apache_hist = ();

	push(@graphs, "apache_update");
}

sub apache_update {
	my $acc;
	my $kb;
	my $cpu;
	my $busy;
	my $idle;

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

	foreach (split('\n', $response->content)) {
		my ($desc, $data) = split(':', $_);
		if($desc eq "Total Accesses") {
			chomp($data);
			$data =~ s/^\s+//;
			$acc = $data - $apache_hist{'acc'};
			$acc = 0 unless $acc != $data;
			$acc /= 60;
			$apache_hist{'acc'} = $data;
			next;
		}
		if($desc eq "Total kBytes") {
			chomp($data);
			$data =~ s/^\s+//;
			$kb = $data - $apache_hist{'kb'};
			$kb = 0 unless $kb != $data;
			$apache_hist{'kb'} = $data;
			next;
		}
		if($desc eq "CPULoad") {
			chomp($data);
			$data =~ s/^\s+//;
#			$cpu = abs($data - $apache_hist{'cpu'});
#			$cpu = 0 unless $cpu != $data;
#			$apache_hist{'cpu'} = $data;
			$cpu = abs($data);
			next;
		}
		if($desc eq "BusyWorkers") {
			chomp($data);
			$busy = int($data);
			next;
		}
		if($desc eq "IdleWorkers") {
			chomp($data);
			$idle = int($data);
			last;
		}
	}

	$rrdata .= ":$acc:$kb:$cpu:$busy:$idle";
	RRDs::update($APACHE_RRD, $rrdata);
	my $err = RRDs::error;
	print("ERROR: while updating $APACHE_RRD: $err\n") 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:COUNTER:120:0:U",
			"DS:nginx_bytes_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 $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");
	}

	our $nginx_hist = 0;
	push(@graphs, "nginx_update");
}

sub nginx_update {
	my $reqs;
	my $tot;
	my $reads;
	my $writes;
	my $waits;
	my $in;
	my $out;

	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)) {
		my ($desc, $data) = split(':', $_);
		if($desc eq "Active connections") {
			chomp($data);
			$data =~ s/\s+//g;
			$tot = $data;
			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;
			$reqs = 0 unless $reqs != $3;
			$nginx_hist = $3;
		}
	}

	open(IN, "iptables -nxvL INPUT |");
	while(<IN>) {
		if(/ NGINX_IN /) {
			(undef, $in) = split(' ', $_);
			chomp($in);
			last;
		}
	}
	close(IN);
	open(IN, "iptables -nxvL OUTPUT |");
	while(<IN>) {
		if(/ NGINX_OUT /) {
			(undef, $out) = split(' ', $_);
			chomp($out);
			last;
		}
	}
	close(IN);

	$rrdata .= ":$reqs:$tot:$reads:$writes:$waits:$in:$out";
	RRDs::update($NGINX_RRD, $rrdata);
	my $err = RRDs::error;
	print("ERROR: while updating $NGINX_RRD: $err\n") 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");
}

sub int_update {
	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") {
		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];
		}
	}
	my $rrdata = "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);
	my $err = RRDs::error;
	print("ERROR: while updating $INT_RRD: $err\n") 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 . "). 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";
				}
				system("iptables -N $PC_LIST[$n]_total 2>/dev/null");
				if(!($? >> 8)) {
					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");
}

sub pc_update {
	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);
	my $err = RRDs::error;
	print("ERROR: while updating $PC_RRD: $err\n") 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_debug;
			}
			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("Start 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_config &");
		}
	}
}


# Main
# ----------------------------------------------------------------------------
GetOptions(
	"config=s"	=> \$opt_config,
	"debug"		=> \$opt_debug,
	"version"	=> \$opt_version,
	);

if($opt_version) {
	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_config) {
	usage();
	exit(1);
}
$opt_config = abs_path($opt_config) unless $^V lt 5.6.2;
if(!stat($opt_config)) {
	die("can't open file $opt_config.\n");
}

# check configuration file syntax
if(system("perl -wc $opt_config >/dev/null 2>&1")) {
	print("FATAL: configuration file '$opt_config' had compilation errors.\n");
	exit(1);
}
# load configuration file
require $opt_config;

# get the current OS and kernel branch and check support
my $release;
($os, undef, $release) = uname();
my ($major, $minor) = split('\.', $release);
$kernel_branch = $major . "." . $minor;
if($os ne "Linux" && $os ne "FreeBSD") {
	print("FATAL: your OS ($os) is not yet supported.\n");
	exit(1);
}

$0 = sprintf("%s %s%s%s",
	$^V lt 5.6.2 ? monitorix : abs_path($0),
	$opt_config ? "-c $opt_config" : "",
	$opt_debug ? " -d" : "",
	$opt_version ? " -v" : "");
daemonize();
logger("Starting Monitorix version " . VERSION . " (pid $pid).");

if($opt_debug) {
	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_config\n");
close(OUT);

# initialize all enabled graphs
logger("Initializing and checking all graphs.") unless !$opt_debug;

my $func;
foreach my $g (@GRAPH_NAME) {
	if($GRAPH_ENABLE{$g} eq "Y") {
		$func = $g . "_init";
		eval {&$func();};
		if($@) {
			logger("WARNING: undefined 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_debug;
create_index();
logger("Ok, done.") unless !$opt_debug;

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