#!/usr/bin/perl
#------------------------------------------------------------------------------
#    sysusage - Full system monitoring with RRDTOOL
#    Copyright (C) 2003-2009 Gilles Darold
#
#    This program is provided WITHOUT WARRANTY of any kind, either
#    expressed or implied. It is free software, and you are welcome
#    to modify or re-distribute it under same terms of Perl itself.
#
# Author: Gilles Darold <gilles@darold.net>
#
#------------------------------------------------------------------------------
use strict qw(vars);

use Getopt::Long;
use FileHandle;
use RRDs;
use SysUsage::Sar;
use POSIX qw(locale_h);
setlocale(LC_ALL, 'C');

my $VERSION = '2.10';

$| = 1;

# Default configuration overriden by command line argument
my $CONF_FILE = '/etc/sysusage.cfg';
if (!-e $CONF_FILE) {
	$CONF_FILE = '/usr/local/etc/sysusage.cfg';
	if (!-e $CONF_FILE) {
		$CONF_FILE = '/etc/sysusage.cfg';
	}
}
my %Config    = ();
my %CMD       = ();
my @DATA      = ();
my $nfiles    = 0;
my $HELP      = 0;
my $SHOWVER   = 0;
my $VERBOSE   = 0;
my $rrds      = undef;
my $sar_opt   = '-p -A 1 5';
my $df_option = '-lkP -x tmpfs',
my $sar_version = '';

# Get command line arguments
&check_args();

# Read configuration file
&read_config();

# Check if an other instance is running
&check_running();

my $STEP = int($Config{'GENERAL'}{'INTERVAL'}*1.5);

my $sar = new SysUsage::Sar(
	'sar' => $Config{'GENERAL'}{'SAR_BIN'},
	'opt' => $sar_opt,
	'debug' => $Config{'GENERAL'}{'DEBUG'}
);
if (!defined $sar) {
	unlink("$Config{'GENERAL'}{'PID_DIR'}/sysusage.pid");
	die "ERROR: Can't instantiate SysUsage::Sar module.\n"; 
}
$sar->parseSarOutput();

# First line is the system kernel + date
my $kernel = $sar->getHeader();
unless(open(KFILE, ">$Config{'GENERAL'}{'DATA_DIR'}/kernel.txt")) {
	unlink("$Config{'GENERAL'}{'PID_DIR'}/sysusage.pid");
	die "ERROR: Can't write to file $Config{'GENERAL'}{'DATA_DIR'}/kernel.txt\n";
}
print KFILE "$kernel";
close(KFILE);

# Find what to do with the given command
foreach my $t (keys %CMD) {
	if ($t eq 'cpu') {
		&mon_cpu($t);
	} elsif ($t eq 'wait') {
		# Nothing this is done by mon_cpu
	} elsif ($t eq 'net') {
		&mon_network($t);
	} elsif ($t eq 'err') {
		&mon_net_error($t);
	} elsif ($t eq 'load') {
		&mon_load_average($t);
	} elsif ($t eq 'mem') {
		&mon_memory($t);
	} elsif ($t eq 'work') {
		&mon_memory($t);
	} elsif ($t =~ /^dev_(.*)/) {
		&mon_dev($1);
	} elsif ($t eq 'swap') {
		&mon_memory($t);
	} elsif ($t eq 'disk') {
		&mon_disk_usage($t);
	} elsif ($t eq 'share') {
		&mon_share_usage($t);
	} elsif ($t eq 'sock') {
		&mon_socket($t);
	} elsif ($t eq 'io') {
		&mon_io($t);
	} elsif ($t eq 'pswap') {
		&mon_page_swap($t);
	} elsif ($t eq 'file') {
		&mon_file($t);
	} elsif ($t =~ m#^queue_(.*)#) {
		&mon_queue($t, $1);
	} elsif ($t eq 'page') {
		&mon_paging($t);
	} elsif ($t eq 'pcrea') {
		&mon_process_created($t);
	} elsif ($t eq 'cswch') {
		&mon_cswch($t);
	} elsif ($t eq 'intr') {
		&mon_intr($t);
	} elsif ($t =~ /^proc_(.*)/) {
		&mon_process($t,$1);
	} elsif ($t =~ /^hddtemp_(.*)/) {
		&mon_hddtemp($t,$1);
	} elsif ($t =~ /^sensors_(.*)/) {
		&mon_sensors($t,$1);
	} else {
		print "ERROR: Unknown statistic type: $t.\n";
	}
}

unlink("$Config{'GENERAL'}{'PID_DIR'}/sysusage.pid");
exit 0;


####
# Function used to return CPU usage
# It return the total User + System CPU Utilization.
# This function report statistics for ALL CPU.
####
sub mon_cpu
{
	my ($type) = @_;

	my %cpuinfo = $sar->getReportType('cpu');
	foreach my $name (keys %cpuinfo) {
		my $total = $cpuinfo{$name}{'%user'} + $cpuinfo{$name}{'%system'};
print "CPU: $name => (", $cpuinfo{$name}{'%user'}, " + ", $cpuinfo{$name}{'%system'}, ") = $total\n" if ($Config{'GENERAL'}{'DEBUG'});
print "CPU: $name => iowait: ", $cpuinfo{$name}{'%iowait'}, " - steal: ", $cpuinfo{$name}{'%steal'}, " - idle: ", $cpuinfo{$name}{'%idle'}, "\n" if ($Config{'GENERAL'}{'DEBUG'});
		my $target = 'cpu';
		$target .= $name if ($name ne 'all');
		if (exists $CMD{'cpu'}) {
			if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/$target") {
				&createRRD($target);
			}
			if (!$total) {
				&insertIntDataNull($target);
			} else {
				&insertIntData($target, $total, $cpuinfo{$name}{'%user'}, $cpuinfo{$name}{'%system'});
			}
			# Just warn on total of CPU usage (all cpus)
			&send_warning($target, $type, "Cpu", $total) if ($name eq 'all');
		}
		if (exists $CMD{'wait'}) {
			my $target = 'wait';
			$target .= $name if ($name ne 'all');
			if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/$target") {
				&createRRD($target);
			}
			if (!$cpuinfo{$name}{'%iowait'} && !$cpuinfo{$name}{'%ioidle'}) {
				&insertIntDataNull($target);
			} else {
				&insertIntData($target, $cpuinfo{$name}{'%iowait'}, $cpuinfo{$name}{'%idle'}, $cpuinfo{$name}{'%steal'});
			}
			# Just warn on total of CPU wait (all cpus)
			&send_warning($target, $type, "Cpu", $cpuinfo{$name}{'%iowait'}) if ($name eq 'all');
		}
	}
}

####
# Function used to return Network usage
# It return the number of bytes per second on a given interface.
# The interface must be specified with command line option -i
####
sub mon_network
{
	my ($type) = @_;

	my %netinfo = $sar->getReportType('net');
	foreach my $name (keys %netinfo) {
		my $rxlbl = 'rxbyt';
		my $txlbl = 'txbyt';
		if (!exists($netinfo{$name}{rxbyt})) {
			$rxlbl = 'rxkB';
			$txlbl = 'txkB';
			# Revert to byte for compatibility
			$netinfo{$name}{$rxlbl} *= 1000;
			$netinfo{$name}{$txlbl} *= 1000;
		}
print "NET: $name => In: $netinfo{$name}{$rxlbl}, Out: $netinfo{$name}{txbyt}\n" if ($Config{'GENERAL'}{'DEBUG'});
		my $target = 'net_' . $name;
		if (exists $CMD{'net'}) {
			if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/$target") {
				&createRRD($target);
			}
			if (!$netinfo{$name}{$rxlbl} && !$netinfo{$name}{$txlbl}) {
				&insertIntDataNull($target);
			} else {
				&insertIntData($target, $netinfo{$name}{$rxlbl}, $netinfo{$name}{$txlbl}, 0);
			}
			&send_warning($target, $type, "Network usage on $name", $netinfo{$name}{$rxlbl}, $netinfo{$name}{$txlbl});
		}
	}
}

####
# Function used to return the load average statistics
# It return the system load average for the last minute
####
sub mon_load_average
{
	my ($type) = @_;

	my %loadinfo = $sar->getReportType('load');
print "LOAD: Queue length = $loadinfo{'runq-sz'}, Number of process = $loadinfo{'plist-sz'}, load average = $loadinfo{'ldavg-1'} / $loadinfo{'ldavg-5'} / $loadinfo{'ldavg-15'}\n" if ($Config{'GENERAL'}{'DEBUG'});
	if (exists $CMD{'load'}) {
		if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/load") {
			&createRRD('load');
		}
		if (!$loadinfo{'ldavg-1'} && !$loadinfo{'ldavg-5'} && !$loadinfo{'ldavg-15'}) {
			&insertIntDataNull('load');
		} else {
			&insertIntData('load', $loadinfo{'ldavg-1'}, $loadinfo{'ldavg-5'}, $loadinfo{'ldavg-15'});
		}
		&send_warning($type, $type, 'Load average', $loadinfo{'ldavg-1'});
	}
}

####
# Function used to return the load average statistics
# It return the system load average for the last minute
####
sub mon_dev
{
	my ($device) = @_;

	my %devinfo = $sar->getReportType('dev');

	foreach my $name (keys %devinfo) {
		next if ( ($device ne 'all') && ($device ne $name));
print "DEV $name: %util = $devinfo{$name}{'%util'}, rd_sec/s = $devinfo{$name}{'rd_sec'}, wr_sec/s = $devinfo{$name}{'wr_sec'}\n" if ($Config{'GENERAL'}{'DEBUG'});
		my $target = 'dev_';
		$target .= $name;
		if (exists $CMD{$target}) {
			if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/$target") {
				&createRRD($target);
			}
			if (!$devinfo{$name}{'%util'}) {
				&insertIntDataNull($target);
			} else {
				&insertIntData($target, $devinfo{$name}{'%util'},0,0);
			}
			&send_warning($target, 'dev', "Device $name CPU Usage", $devinfo{$name}{'%util'});
		}
	}
}

####
# Function used to return the memory usage
# It return the total Megabyte of used memory
# with and witout caching.
####
sub mon_memory
{
	my ($type) = @_;

	my %meminfo = $sar->getReportType($type);

	if (($type eq 'mem') && exists $CMD{'mem'}) {
		# Total memory in use without shared/buffer/cache
		$meminfo{kbmemused} *= 1000;
		$meminfo{kbbuffers} *= 1000;
		$meminfo{kbmemshrd} *= 1000 if (exists $meminfo{kbmemshrd});
		$meminfo{realused} = $meminfo{kbmemused} - $meminfo{kbbuffers} - $meminfo{kbcached};
		$meminfo{realused} -= $meminfo{kbmemshrd} if (exists $meminfo{kbmemshrd});
print "MEM: used => $meminfo{kbmemused} B, real used (without shared/buffer/cache) => $meminfo{realused} B\n" if ($Config{'GENERAL'}{'DEBUG'});

		if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/mem") {
			&createRRD('mem');
		}
		if (!$meminfo{realused} && !$meminfo{kbmemused}) {
			&insertIntDataNull('mem');
		} else {
			&insertIntData('mem', $meminfo{kbmemused}, $meminfo{realused}, 0);
		}
		&send_warning('mem','mem', 'Memory', $meminfo{'%memused'});
	}
	if (($type eq 'swap') && exists $CMD{'swap'}) {
print "SWAP: used => ", $meminfo{'%swpused'}, "%\n" if ($Config{'GENERAL'}{'DEBUG'});
		if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/swap") {
			&createRRD('swap');
		}
		if (!$meminfo{'%swpused'}) {
			&insertIntDataNull('swap');
		} else {
			&insertIntData('swap', $meminfo{'%swpused'}, 0, 0);
		}
		&send_warning('swap','swap', 'Swap', $meminfo{'%swpused'});
	}
	if (($type eq 'work') && exists $CMD{'work'}) {
		# Total memory in use without shared/buffer/cache
		$meminfo{kbcommit} *= 1000;
print "WORK: memory needed => $meminfo{kbcommit} B, Percent => $meminfo{'%commit'} %\n" if ($Config{'GENERAL'}{'DEBUG'});

		if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/work") {
			&createRRD('work');
		}
		if (!$meminfo{kbcommit}) {
			&insertIntDataNull('work');
		} else {
			&insertIntData('work', $meminfo{kbcommit}, 0, 0);
		}
		&send_warning('work','work', 'Memory needed for current workload', $meminfo{'%commit'});
	}
}

####
# Function used to return the disk usage
# It return the total percentage of disk use for a given
# partition.
####
sub mon_disk_usage
{
	my ($type) = @_;

	my %diskinfo = ();
	my @ret = `df $df_option | grep -v "Filesystem" | grep -v "^none"`;
	foreach my $line (@ret) {
		chomp($line);
		my ($device, $size, $used, $free, $percent, $dir) = split(/\s+/, $line);
		# Get exclude mount point from configuration
		if ($#{$CMD{$type}} == 1) {
			my @exclude = split(m#;#, $CMD{$type}[1]);
			if (grep($dir =~ m|$_|, @exclude)) {
	print "DISK: $dir => Exclusion match\n" if ($Config{'GENERAL'}{'DEBUG'});
				next;
			}
		}
		next if (!$dir);
		$percent =~ s/\%//;
		$percent ||= 0;
	print "DISK: $dir => $percent %\n" if ($Config{'GENERAL'}{'DEBUG'});
		my $target = 'disk' . $dir;
		$target =~ s#_#__#g;
		$target =~ s#/#_#g;
		if (exists $CMD{'disk'}) {
			if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/$target") {
				&createRRD($target) if (!$Config{'GENERAL'}{'DEBUG'});
			}
			if (!$percent) {
				&insertIntDataNull($target);
			} else {
				&insertIntData($target, $percent, 0, 0);
			}
			&send_warning($target, $type, "Disk space on $dir", $percent);
		}
	}
}

####
# Function used to return the disk usage on /dev/shm the
# POSIX Share Memory usage virtual device.
# It return the total percentage of disk use for this
# partition.
####
sub mon_share_usage
{
	my ($type) = @_;
#none                    515244         0    515244   0% /dev/shm

	my %diskinfo = ();
	my $line = `df -k /dev/shm | grep -v "Filesystem"`;
	chomp($line);
	my ($device, $size, $used, $free, $percent, $dir) = split(/\s+/, $line);
	$percent =~ s/\%//;
	$percent ||= 0;
	print "SHARE: $dir => Total: $size, used: $percent %\n" if ($Config{'GENERAL'}{'DEBUG'});
	if (exists $CMD{$type}) {
		if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/$type") {
			&createRRD($type);
		}
		if (!$percent) {
			&insertIntDataNull($type);
		} else {
			&insertIntData($type, $percent, 0, 0);
		}
		&send_warning($type, $type, "Posix Share memory usage", $percent);
	}
}


####
# Function used to return the running process number.
####
sub mon_process
{
	my ($type, $proc) = @_;

	my %procinfo = ();
	my $count = `ps axw | grep "$proc" | grep -v "grep" | awk '{print \$5}' | wc -l`;
	chomp($count);
	$count =~ s/^\s+//g;
	$procinfo{$proc} = $count || 0;
print "PROC: $proc => $procinfo{$proc} process\n" if ($Config{'GENERAL'}{'DEBUG'});
	my $target = 'proc_' . $proc;
	if (exists $CMD{"$target"}) {
		if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/$target") {
			&createRRD($target);
		}
		if (!$procinfo{$proc}) {
			&insertIntDataNull($target);
		} else {
			&insertIntData($target, $procinfo{$proc}, 0, 0);
		}
		&send_warning($type, $type, "Number of $proc process", $procinfo{$proc});
	}


}

####
# Function used to return the running process number.
####
sub mon_queue
{
	my ($type, $dir) = @_;

	if (-d $dir) {
		use File::Find;
		sub wanted {
			$nfiles++ if (-f); # Skip directory
		}
		find(\&wanted, $dir);
print "QUEUE: $dir => $nfiles files\n" if ($Config{'GENERAL'}{'DEBUG'});
		my $target = 'queue' . $dir;
		$target =~ s/_/__/g;
		$target =~ s/\//_/g;
		if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/$target") {
			&createRRD($target);
		}
		if (!$nfiles) {
			&insertIntDataNull($target);
		} else {
			&insertIntData($target, $nfiles, 0, 0);
		}
		&send_warning($type, $type, "Number of files in $dir", $nfiles);
	}
}


####
# Function used to return the total number of sockets in use.
####
sub mon_socket
{
	my ($type) = @_;

	my %sockinfo = $sar->getReportType('sock');
print "SOCK: total => $sockinfo{totsck}, tcp => $sockinfo{tcpsck}, udp => $sockinfo{udpsck}, raw => $sockinfo{rawsck}\n" if ($Config{'GENERAL'}{'DEBUG'});
	if (exists $CMD{'sock'}) {
		if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/sock") {
			&createRRD('sock');
		}
		if (!$sockinfo{totsck} && !$sockinfo{tcpsck} && !$sockinfo{udpsck}) {
			&insertIntDataNull('sock');
		} else {
			&insertIntData('sock', $sockinfo{totsck}, $sockinfo{tcpsck}, $sockinfo{udpsck});
		}
		&send_warning($type, $type, "Socket usage", $sockinfo{tosck});
	}

}

####
# Function used to return the number read and write IO request.
####
sub mon_io
{
	my ($type) = @_;

	my %ioinfo = $sar->getReportType('io');
print "IO: read => $ioinfo{'rtps'}, write => $ioinfo{'wtps'}, block read => $ioinfo{'bread'}, block write => $ioinfo{'bwrtn'}\n" if ($Config{'GENERAL'}{'DEBUG'});
	if (exists $CMD{'io'}) {
		if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/io") {
			&createRRD('io');
		}
		if (!$ioinfo{'rtps'} && !$ioinfo{'wtps'}) {
			&insertIntDataNull('io');
		} else {
			&insertIntData('io', $ioinfo{'rtps'}, $ioinfo{'wtps'}, 0)
		}
		&send_warning($type, $type, "I/O usage", $ioinfo{'bread'}, $ioinfo{'bwrtn'});
		if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/bio") {
			&createRRD('bio');
		}
		if (!$ioinfo{'bread'} && !$ioinfo{'bwrtn'}) {
			&insertIntDataNull('bio');
		} else {
			&insertIntData('bio', $ioinfo{'bread'}, $ioinfo{'bwrtn'}, 0);
		}
		&send_warning($type, $type, "I/O block usage", $ioinfo{'bread'}, $ioinfo{'bwrtn'});
	}

}

####
# Function used to return the context switches usage
####
sub mon_cswch
{
	my ($type) = @_;

	my %info = $sar->getReportType('cswch');
	# No file in use percentage, so compute if
print "CSWCH: Context Switch => $info{'cswch'}/s\n" if ($Config{'GENERAL'}{'DEBUG'});
	if (exists $CMD{'cswch'}) {
		if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/$type") {
			&createRRD($type);
		}
		if (!$info{'cswch'}) {
			&insertIntDataNull($type);
		} else {
			&insertIntData($type, $info{'cswch'}, 0, 0);
		}
		&send_warning($type, $type, "Context Switches usage", $info{'cswch'});
	}
}

####
# Function used to return the percentage of open file
# regarding file-max setting.
####
sub mon_file
{
        my ($type) = @_;

        my %fileinfo = $sar->getReportType('file');

        my $target = 'file';
        if (!exists $fileinfo{'%file-sz'}) {
		if (!exists $fileinfo{'file-sz'}) {
			$fileinfo{'file-sz'} = $fileinfo{'file-nr'} || 0;
		}
                $fileinfo{'%file-sz'} = $fileinfo{'file-sz'};
                $target = 'filen';
print "FILE: opened => ", $fileinfo{'%file-sz'}, "\n" if ($Config{'GENERAL'}{'DEBUG'});
        } else {
print "FILE: used => ", $fileinfo{'%file-sz'}, "%\n" if ($Config{'GENERAL'}{'DEBUG'});
	}

        if (exists $CMD{'file'}) {
                if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/$target") {
                        &createRRD($target);
                }
                if (!$fileinfo{'%file-sz'}) {
                        &insertIntDataNull($target);
                } else {
                        &insertIntData($target, $fileinfo{'%file-sz'}, 0, 0);
                }
                &send_warning($type, $type, "Open file usage", $fileinfo{'%file-sz'});
        }
}

####
# Function used to return the interrupts usage
####
sub mon_intr
{
	my ($type) = @_;

	my %info = $sar->getReportType('intr');
	# No file in use percentage, so compute if
print "INTR: Interrupts => $info{'sum'}{'intr'}/s\n" if ($Config{'GENERAL'}{'DEBUG'});
	if (exists $CMD{'intr'}) {
		if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/$type") {
			&createRRD($type);
		}
		if (!$info{'sum'}{'intr'}) {
			&insertIntDataNull($type);
		} else {
			&insertIntData($type, $info{'sum'}{'intr'}, 0, 0);
		}
		&send_warning($type, $type, "Interrupts usage", $info{'sum'}{'intr'});
	}
}


####
# Function used to return the number of error received and
# transmitted on a given network interface. The interface
# must be specified with command line option -i
####
sub mon_net_error
{
	my ($type) = @_;

	my %errinfo = $sar->getReportType('err');
	foreach my $name (keys %errinfo) {
print "ERR: $name => errin: $errinfo{$name}{rxerr}, errout: $errinfo{$name}{txerr}, dropin: $errinfo{$name}{rxdrop}, dropout: $errinfo{$name}{txdrop}, coll: $errinfo{$name}{coll}\n" if ($Config{'GENERAL'}{'DEBUG'});
		my $target = 'err_' . $name;
		if (exists $CMD{'err'}) {
			if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/$target") {
				&createRRD($target);
			}
			if (!$errinfo{$name}{rxerr} && !$errinfo{$name}{txerr}) {
				&insertIntDataNull($target);
			} else {
				&insertIntData($target, $errinfo{$name}{rxerr}, $errinfo{$name}{txerr}, 0);
			}
			&send_warning($target, $type, "Bad packet on $name", $errinfo{$name}{rxerr}, $errinfo{$name}{txerr});
			$target = 'drop_' . $name;
			if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/$target") {
				&createRRD($target);
			}
			if (!$errinfo{$name}{rxdrop} && !$errinfo{$name}{txdrop}) {
				&insertIntDataNull($target);
			} else {
				&insertIntData($target, $errinfo{$name}{rxdrop}, $errinfo{$name}{txdrop}, 0);
			}
			&send_warning($target, $type, "Dropped packet on $name", $errinfo{$name}{rxdrop}, $errinfo{$name}{txdrop});
			$target = 'coll_' . $name;
			if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/$target") {
				&createRRD($target);
			}
			if (!$errinfo{$name}{coll}) {
				&insertIntDataNull($target);
			} else {
				&insertIntData($target, $errinfo{$name}{coll}, 0, 0);
			}
			&send_warning($target, $type, "Collision packet on $name", $errinfo{$name}{coll});
		}
	}
}

####
# Function used to return the number of process created per second
####
sub mon_process_created
{
	my ($type) = @_;

	my %process = $sar->getReportType('pcrea');
print "PROCESS/SEC: $process{proc}/s\n" if ($Config{'GENERAL'}{'DEBUG'});
	if (exists $CMD{'pcrea'}) {
		if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/pcrea") {
			&createRRD('pcrea');
		}
		if (!$process{proc}) {
			&insertIntDataNull('pcrea');
		} else {
			&insertIntData('pcrea', $process{proc}, 0, 0);
		}
	}
	&send_warning($type, $type, "Process creation", $process{proc});
}

####
# Function used to return the paging statistics.
# It return the number of blocks paged in from disk per second
# and the number of blocks paged out to disk per second.
####
sub mon_paging
{
	my ($type) = @_;

	my %pageinfo = $sar->getReportType('page');
print "PAGIN: page in => $pageinfo{pgpgin}, page out => $pageinfo{pgpgout}\n" if ($Config{'GENERAL'}{'DEBUG'});
	if (exists $CMD{'page'}) {
		if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/page") {
			&createRRD('page');
		}
		if (!$pageinfo{pgpgin} && !$pageinfo{pgpgout}) {
			&insertIntDataNull('page');
		} else {
			&insertIntData('page', $pageinfo{pgpgin}, $pageinfo{pgpgout}, 0);
		}
		&send_warning($type, $type, "I/O page usage", $pageinfo{pgpgin}, $pageinfo{pgpgout});
	}

}

####
# Function used to return the swapping statistics.
# It return the number of swap pages the system brought in per second
# and the number of swap pages the system brought out per second.
####
sub mon_page_swap
{
	my ($type) = @_;

	my %pswapinfo = $sar->getReportType('pswap');
print "PAGE SWAP: page in => $pswapinfo{pswpin}, page out => $pswapinfo{pswpout}\n" if ($Config{'GENERAL'}{'DEBUG'});
	if (exists $CMD{'pswap'}) {
		if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/pswap") {
			&createRRD('pswap');
		}
		if (!$pswapinfo{'pswpin'} && !$pswapinfo{'pswpout'}) {
			&insertIntDataNull('pswap');
		} else {
			&insertIntData('pswap', $pswapinfo{'pswpin'}, $pswapinfo{'pswpout'}, 0);
		}
		&send_warning($type, $type, "Swap page usage", $pswapinfo{'pswpin'}, $pswapinfo{'pswpout'});
	}
}


####
# Function used to return the hard drive temperature using hddtemp.
####
sub mon_hddtemp
{
	my ($type, $device) = @_;

	my $temperature = `$Config{'GENERAL'}{'HDDTEMP_BIN'} -n $device`;
	chomp($temperature);
	$temperature ||= 0;
print "HDDTEMP: $device => $temperature\n" if ($Config{'GENERAL'}{'DEBUG'});
	my $target = 'hddtemp_' . $device;
	$target =~ s/\//_/g;
	if (exists $CMD{"hddtemp"}) {
		if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/$target") {
			&createRRD($target);
		}
		if (!$temperature) {
			&insertIntDataNull($target);
		} else {
			&insertIntData($target, $temperature, 0, 0);
		}
		&send_warning($type, $type, "Hard drive temperature of $device", $temperature);
	}
}


####
# Function used to return the temperature records return by sensors.
####
sub mon_sensors
{
	my ($type, $pattern) = @_;

	my @temperature = ();
	open(SENSORS, "$Config{'GENERAL'}{'SENSORS_BIN'} |");
	while (my $l = <SENSORS> ) {
		chomp($l);
		if ($l =~ /($pattern)([^:]*):\s+([^\s]+)/) {
			my $tmp = $3;
			$tmp =~ s/[^0-9\-\.]//g;
			$tmp = int($tmp);
			if ($tmp) {
				push(@temperature, $tmp);
				last if ($#temperature == 1);
			}
		}
	}
	close(SENSORS);
	push(@temperature, 0) while ($#temperature <= 1);
	if ($Config{'GENERAL'}{'DEBUG'}) {
		for (my $i = 0; $i <= $#temperature; $i++) {
print "SENSORS: $pattern-$i => $temperature[$i]\n";
		}
	}
	my $target = 'sensors_' . $pattern;
	if (exists $CMD{"$target"}) {
		$target =~ s/\s/_/g;
		if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/$target") {
			&createRRD($target);
		}
		if (!$temperature[0]) {
			&insertIntDataNull($target);
		} else {
			&insertIntData($target, $temperature[0] || 0, $temperature[1] || 0, 0);
		}
		if ($pattern !~ /\bfan\d*\b/i) {
			&send_warning($type, $type, $pattern, @temperature);
		} else {
			&send_warning($type, $type, $pattern, $temperature[0]);
		}
	}
}

####
# Function used to return command line usage 
####
sub usage
{
	print qq{
Usage: sysusage [-c conf_file] [-h|--help] [-v]

	-c conf_file : Path to configuration file. Default: /etc/sysusage.cfg
	-d|--debug   : Verbose output without writing anything
	-h|--help    : Output this message and exit
	-v           : Display sysusage version

};
	exit(1);
}

####
# Function used to check command line argument and configuration
####
sub check_args
{
        GetOptions(
                "c=s"   => \$CONF_FILE,
                "d!"    => \$VERBOSE,
                "debug!"    => \$VERBOSE,
                "h!"    => \$HELP,
		"help!" => \$HELP,
		"v!"    => \$SHOWVER
        ) || &usage();
        &usage() if ($HELP);

	if ($SHOWVER) {
		print "Sysusage v$VERSION\n";
		exit 0;
	}

	# Check configuration
	if (! -f $CONF_FILE) {
		print "ERROR: Configuration file $CONF_FILE doesn't exists.\n";
		&usage();
	}

}

sub insertIntDataNull
{
        my ($target) = @_;

        &debug("insertIntDataNull: update $Config{'GENERAL'}{'DATA_DIR'}/$target N:0:0:0\n");
	return 1 if ($Config{'GENERAL'}{'DEBUG'});

        RRDs::update("$Config{'GENERAL'}{'DATA_DIR'}/$target", "N:0:0:0");
        my $e = RRDs::error();
	if ($e) {
		unlink("$Config{'GENERAL'}{'PID_DIR'}/sysusage.pid");
		die "ERROR: Cannot update $Config{'GENERAL'}{'DATA_DIR'}/$target: $e\n";
	}

        return(1);
}

sub insertIntData
{
        my ($target, $in, $out, $other) = @_;

	$in ||= 0;
	$out ||= 0;
	$other ||= 0;
        &debug("insertData(): update $Config{'GENERAL'}{'DATA_DIR'}/$target N:$in:$out:$other\n");
	return 1 if ($Config{'GENERAL'}{'DEBUG'});

        RRDs::update("$Config{'GENERAL'}{'DATA_DIR'}/$target", "N:$in:$out:$other");
        my $e = RRDs::error();
	if ($e) {
		unlink("$Config{'GENERAL'}{'PID_DIR'}/sysusage.pid");
		die "ERROR: Cannot update $Config{'GENERAL'}{'DATA_DIR'}/$target: $e\n";
	}

        return(1);
}

sub createRRD
{
        my ($target) = @_;

	# We store 3 values 
        &debug("createRRD(): Creating RRD database file $Config{'GENERAL'}{'DATA_DIR'}/$target\n");
	return 1 if ($Config{'GENERAL'}{'DEBUG'});

        my @args = ("$Config{'GENERAL'}{'DATA_DIR'}/$target", '-s', $Config{'GENERAL'}{'INTERVAL'},
                "DS:A:GAUGE:$STEP:U:U",
                "DS:B:GAUGE:$STEP:U:U",
                "DS:C:GAUGE:$STEP:U:U",
                "RRA:AVERAGE:0.5:1:" . int(180000/$Config{'GENERAL'}{'INTERVAL'}),
                "RRA:AVERAGE:0.5:" . int(1800/$Config{'GENERAL'}{'INTERVAL'})  . ":700",
                "RRA:AVERAGE:0.5:" . int(7200/$Config{'GENERAL'}{'INTERVAL'})  . ":775",
                "RRA:AVERAGE:0.5:" . int(86400/$Config{'GENERAL'}{'INTERVAL'}) . ":797",
                "RRA:MAX:0.5:1:" . int(180000/$Config{'GENERAL'}{'INTERVAL'}),
                "RRA:MAX:0.5:" . int(1800/$Config{'GENERAL'}{'INTERVAL'})   . ":700",
                "RRA:MAX:0.5:" . int(7200/$Config{'GENERAL'}{'INTERVAL'})   . ":775",
                "RRA:MAX:0.5:" .  int(86400/$Config{'GENERAL'}{'INTERVAL'}) . ":797"
        );
        RRDs::create(@args);
        my $e = RRDs::error();
	if ($e) {
		unlink("$Config{'GENERAL'}{'PID_DIR'}/sysusage.pid");
		die "ERROR: Cannot create rrdtool database $Config{'GENERAL'}{'DATA_DIR'}/$target: $e\n";
	}

}

sub debug
{
	my ($str) = @_;

	if ($Config{'GENERAL'}{'DEBUG'}) {
		print $str;
	}
}

sub check_running
{
	if (-e "$Config{'GENERAL'}{'PID_DIR'}/sysusage.pid") {
		my @ret = `ps -ef | grep sysusage | grep -v grep`;
		if ($#ret > 0) {
			print qq{
An other instance of sysusage is running. Please wait...
exiting...
};
		} else {
			print "Found $Config{'GENERAL'}{'PID_DIR'}/sysusage.pid and no instance of sysusage is running.\nRemoving it...\n";
			unlink("$Config{'GENERAL'}{'PID_DIR'}/sysusage.pid");
		}
		exit 0;
	} else {
		open(PIDF, ">$Config{'GENERAL'}{'PID_DIR'}/sysusage.pid") or die "Error: can't write into $Config{'GENERAL'}{'PID_DIR'}/sysusage.pid\n";
		print PIDF "$$";
		close(PIDF);
	}
}


sub send_warning
{
	my ($target, $type, $title, @vals) = @_;

	return if (!$Config{'ALARM'}{'WARN_MODE'});

	# Some time range may not send alarms
	my ($sec , $min, $hour, @other) = localtime(time);
	$min = "0$min" if ($min < 10);
	$hour = "0$hour" if ($hour < 10);
	for (my $i = 0; $i <= $#{$Config{'GENERAL'}{'SKIP_BEGIN'}}; $i++) {
		if ( $Config{'GENERAL'}{'SKIP_BEGIN'}[$i] <= $Config{'GENERAL'}{'SKIP_END'}[$i] ) {
			return if ( ("$hour$min" >= $Config{'GENERAL'}{'SKIP_BEGIN'}[$i]) && ("$hour$min" <= $Config{'GENERAL'}{'SKIP_END'}[$i]) );
		} elsif ( $Config{'GENERAL'}{'SKIP_BEGIN'}[$i] > $Config{'GENERAL'}{'SKIP_END'}[$i] ) {
			return if ( ("$hour$min" >= $Config{'GENERAL'}{'SKIP_BEGIN'}[$i]) && ("$hour$min" < 2400) || ("$hour$min" <= $Config{'GENERAL'}{'SKIP_END'}[$i]) && ("$hour$min" > 0));
		}
	}

	my $nagios_cmd = '';
	if ($Config{'ALARM'}{'NAGIOS'}) {
		$nagios_cmd = "-n $Config{'ALARM'}{'NAGIOS'}";
	}
	my $smtp_cmd = '';
	if ($Config{'ALARM'}{'SMTP'}) {
		$smtp_cmd = "-s $Config{'ALARM'}{'SMTP'} -f $Config{'ALARM'}{'FROM'} -d $Config{'ALARM'}{'TO'}";
	}
	if (exists $CMD{$type}) {
		if (defined $CMD{$type}[0] && ($CMD{$type}[0] ne '') && ($CMD{$type}[0] > 0)) {
			my $alertsent = 0;
			foreach (@vals) {
				if ($_ >= $CMD{$type}[0]) {

print "$Config{'ALARM'}{'ALARM_PROG'} -t \"$title\" -c $_ -v $CMD{$type}[0] -l $Config{'ALARM'}{'UPPER_LEVEL'} $smtp_cmd -r $target $nagios_cmd &\n" if ($Config{'GENERAL'}{'DEBUG'});
					system("$Config{'ALARM'}{'ALARM_PROG'} -t \"$title\" -c $_ -v $CMD{$type}[0] -l $Config{'ALARM'}{'UPPER_LEVEL'} $smtp_cmd -r $target $nagios_cmd &") if (!$Config{'GENERAL'}{'DEBUG'});
					$alertsent = 1;
					last;
				}
			}
			if ($nagios_cmd && !$alertsent) {
print "$Config{'ALARM'}{'ALARM_PROG'} -t \"OK - Running...\" -l 0 -r $target $nagios_cmd &\n" if ($Config{'GENERAL'}{'DEBUG'});
				system("$Config{'ALARM'}{'ALARM_PROG'} -t \"OK - Running...\" -l 0 -r $target $nagios_cmd &") if (!$Config{'GENERAL'}{'DEBUG'});
			}
		}
		if (defined $CMD{$type}[1] && ($CMD{$type}[1] ne '')) {
			foreach (@vals) {
				if ($_ <= $CMD{$type}[1]) {
print "$Config{'ALARM'}{'ALARM_PROG'} -t \"$title\" -c $_ -v $CMD{$type}[1] -l $Config{'ALARM'}{'LOWER_LEVEL'} $smtp_cmd -r $target $nagios_cmd &\n" if ($Config{'GENERAL'}{'DEBUG'});
					system("$Config{'ALARM'}{'ALARM_PROG'} -t \"$title\" -c $_ -v $CMD{$type}[1] -l $Config{'ALARM'}{'LOWER_LEVEL'} $smtp_cmd -r $target $nagios_cmd &") if (!$Config{'GENERAL'}{'DEBUG'});
					last;
				}
			}
		}
	}
}

####
# Function used to load sysusage configuration
####
sub read_config
{
	my $part = '';

	unless(open(CONF, "$CONF_FILE")) {
		die "ERROR: can't open file $CONF_FILE, $!\n";
	}
	while (my $l = <CONF>) {
		chomp($l);
		$l =~ s/
//;
		# Skip empty line and comments
		next if (!$l || ($l =~ /^[\s\t]*#/));
		if ($l =~ /\[GENERAL\]/i) {
			$part = 'GENERAL';
			next;
		} elsif ($l =~ /\[ALARM\]/i) {
			$part = 'ALARM';
			next;
		} elsif ($l =~ /\[MONITOR\]/i) {
			$part = 'MONITOR';
			next;
		}
		if ($part eq 'MONITOR') {
			# type:ThresholdMax:ThresholdMin
			my ($type, $thres_max, $thres_min, $other) = split(/:/, $l);
			if ($type eq 'queue') {
				$type = "queue_$thres_max";
				$thres_max = $thres_min;
				$thres_min = '';
			} elsif ($type eq 'proc') {
				$type = "proc_$thres_max";
				$thres_max = $thres_min;
				$thres_min = $other;
			} elsif ($type eq 'dev') {
				$type = "dev_$thres_max";
				$thres_max = $thres_min;
				$thres_min = $other;
			} elsif ($type eq 'hddtemp') {
				$type = "hddtemp_$thres_max";
				$thres_max = $thres_min;
				$thres_min = $other;
			} elsif ($type eq 'sensors') {
				$type = "sensors_$thres_max";
				$thres_max = $thres_min;
				$thres_min = $other;
			} elsif ($type eq 'mem') {
				# Assume backward compatibility with
				# threshold max for memory, now in percent
				if ($thres_max && ($thres_max > 100)) {
					$thres_max = 99;
				}
			}
			push(@{$CMD{$type}}, $thres_max, $thres_min);
		} else {
			if (!$part) {
				die "ERROR: Invalid configuration file syntax, please read documentation\n";
			}
			my ($var, $val) = split(/\s*=\s*/, $l, 2);
			$Config{$part}{"\U$var\E"} = $val; 
		}
	}
	close(CONF);

	if (! -d $Config{'GENERAL'}{'DATA_DIR'}) {
		unless(mkdir($Config{'GENERAL'}{'DATA_DIR'}, 0755)) {
			print "ERROR: RRD database directory $Config{'GENERAL'}{'DATA_DIR'} doesn't exists.\n";
			&usage();
		}
	}
	if (!-x $Config{'GENERAL'}{'SAR_BIN'}) {
		print "ERROR: sar command not found at $Config{'GENERAL'}{'SAR_BIN'}\n";
		&usage();
	}
	if ($Config{'GENERAL'}{'HDDTEMP_BIN'} && !-x $Config{'GENERAL'}{'HDDTEMP_BIN'}) {
		print "ERROR: hddtemp command not found at $Config{'GENERAL'}{'HDDTEMP_BIN'}\n";
		&usage();
	}
	if ($Config{'ALARM'}{'WARN_MODE'}) {
		if (!$Config{'ALARM'}{'NAGIOS'} && (!$Config{'ALARM'}{'FROM'} || !$Config{'ALARM'}{'TO'} || !$Config{'ALARM'}{'SMTP'})) {
			print "ERROR: you must provide an SMTP server, a sender and a recipient address to send alert message\n";
			&usage();
		}
		if (!-x $Config{'ALARM'}{'ALARM_PROG'}) {
			print "ERROR: Can not execute $Config{'ALARM'}{'ALARM_PROG'}\n";
			&usage();
		}
		if ($Config{'ALARM'}{'NAGIOS'} && !-x $Config{'ALARM'}{'NAGIOS'}) {
			print "ERROR: Can not execute $Config{'ALARM'}{'NAGIOS'}\n";
			&usage();
		}
	}
	if (!$Config{'ALARM'}{'UPPER_LEVEL'}) {
		$Config{'ALARM'}{'UPPER_LEVEL'} = 1;
	}
	if (!$Config{'ALARM'}{'LOWER_LEVEL'}) {
		$Config{'ALARM'}{'LOWER_LEVEL'} = 2;
	}
	if ($Config{'GENERAL'}{'SKIP'}) {
		my $tmp = $Config{'GENERAL'}{'SKIP'} || '';
		delete $Config{'GENERAL'}{'SKIP'};
		my @timerange = split(/[\s\t]+/, $tmp);
		foreach $tmp (@timerange) {
			next if (!$tmp);
			if ($tmp =~ m#(\d{2}):(\d{2})/(\d{2}):(\d{2})#) {
				push(@{$Config{'GENERAL'}{'SKIP_BEGIN'}}, "$1$2");
				push(@{$Config{'GENERAL'}{'SKIP_END'}}, "$3$4");
			} else {
				print "ERROR: Bad time range: $tmp\n";
				print "Format must be: HH:MM/HH:MM\n";
				&usage();
			}
		}
	}
	if ($VERBOSE) {
		$Config{'GENERAL'}{'DEBUG'} = 1;
	}

	# Get the current version of sysstat for backward compatibility
	$sar_version = `$Config{'GENERAL'}{'SAR_BIN'} -V 2>&1 | grep "sysstat version"`;
	chomp($sar_version);
	print "$sar_version\n" if ($Config{'GENERAL'}{'DEBUG'});
	$sar_version =~ s/.* ([\d\.]+)$/$1/;
	$sar_version =~ s/\.//g;
	if ($sar_version < 513) {
		# Version lower than 5.1.3 do not have support for -p
		$sar_opt =~ s/-p //; 
	}
	if (exists $CMD{'work'} && ($sar_version < 815)) {
		# Version lower than 8.1.5 do not have support for amount
		# and percentage of memory needed for current workload
		delete $CMD{'work'};
	}
}


1;

__END__
