#!/usr/bin/perl
#------------------------------------------------------------------------------
#    sysusage - Full system monitoring with RRDTOOL
#    Copyright (C) 2003-2007 Gilles Darold - Groupe SAMSE
#
#    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.6';

$| = 1;

# Default configuration overriden by command line argument
my $CONF_FILE = '/etc/sysusage.cfg';
my %Config    = ();
my %CMD       = ();
my @DATA      = ();
my $nfiles    = 0;
my $HELP      = 0;
my $SHOWVER   = 0;
my $rrds      = undef;

# 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' => '-A 1 5',
	'debug' => $Config{'GENERAL'}{'DEBUG'}
);
if (!defined $sar) {
	unlink("$Config{'GENERAL'}{'PID_FILE'}/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_FILE'}/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 'swap') {
		# Nothing this is done by mon_memory
	} 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);
	} else {
		print STDERR "ERROR: Unknown statistic type: $t.\n";
	}
}
unlink("$Config{'GENERAL'}{'PID_FILE'}/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 STDERR "CPU: $name => (", $cpuinfo{$name}{'%user'}, " + ", $cpuinfo{$name}{'%system'}, ") = $total\n" if ($Config{'GENERAL'}{'DEBUG'});
print STDERR "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 (!$Config{'GENERAL'}{'DEBUG'});
			}
			if (!$total) {
				&insertIntDataNull($target) if (!$Config{'GENERAL'}{'DEBUG'});
			} else {
				&insertIntData($target, $total, $cpuinfo{$name}{'%user'}, $cpuinfo{$name}{'%system'}) if (!$Config{'GENERAL'}{'DEBUG'});
			}
			# Just warn on total of CPU usage (all cpus)
			&send_warning($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 (!$Config{'GENERAL'}{'DEBUG'});
			}
			if (!$cpuinfo{$name}{'%iowait'} && !$cpuinfo{$name}{'%ioidle'}) {
				&insertIntDataNull($target) if (!$Config{'GENERAL'}{'DEBUG'});
			} else {
				&insertIntData($target, $cpuinfo{$name}{'%iowait'}, $cpuinfo{$name}{'%idle'}, $cpuinfo{$name}{'%steal'}) if (!$Config{'GENERAL'}{'DEBUG'});
			}
			# Just warn on total of CPU wait (all cpus)
			&send_warning($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';
			$netinfo{$name}{$rxlbl} *= 1000;
			$netinfo{$name}{$txlbl} *= 1000;
		}
print STDERR "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 (!$Config{'GENERAL'}{'DEBUG'});
			}
			if (!$netinfo{$name}{$rxlbl} && !$netinfo{$name}{$txlbl}) {
				&insertIntDataNull($target) if (!$Config{'GENERAL'}{'DEBUG'});
			} else {
				&insertIntData($target, $netinfo{$name}{$rxlbl}, $netinfo{$name}{$txlbl}, 0) if (!$Config{'GENERAL'}{'DEBUG'});
			}
			&send_warning($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 STDERR "LOAD: Queue length = $loadinfo{'runq-sz'}, Number of process = $loadinfo{'plist-sz'}, load average = $loadinfo{'ldavg-1'}\n" if ($Config{'GENERAL'}{'DEBUG'});
	if (exists $CMD{'load'}) {
		if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/load") {
			&createRRD('load') if (!$Config{'GENERAL'}{'DEBUG'});
		}
		if (!$loadinfo{'ldavg-1'} && !$loadinfo{'plist-sz'} && !$loadinfo{'runq-sz'}) {
			&insertIntDataNull('load') if (!$Config{'GENERAL'}{'DEBUG'});
		} else {
			&insertIntData('load', $loadinfo{'ldavg-1'}, $loadinfo{'runq-sz'}, $loadinfo{'plist-sz'}) if (!$Config{'GENERAL'}{'DEBUG'});
		}
		&send_warning($type, 'Load average', $loadinfo{'ldavg-1'});
	}
}

####
# 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('mem');

	# Total memory in use without shared/buffer/cache
	$meminfo{realused} = $meminfo{kbmemused} - $meminfo{kbbuffers} - $meminfo{kbcached};
	$meminfo{realused} -= $meminfo{kbmemshrd} if (exists $meminfo{kbmemshrd});

print STDERR "MEM: used => $meminfo{kbmemused} Ko, real used (without shared/buffer/cache) => $meminfo{realused} Ko, Swap => ", $meminfo{'%swpused'}, "%\n" if ($Config{'GENERAL'}{'DEBUG'});
	if (exists $CMD{'mem'}) {
		if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/mem") {
			&createRRD('mem') if (!$Config{'GENERAL'}{'DEBUG'});
		}
		if (!$meminfo{realused} && !$meminfo{kbmemused}) {
			&insertIntDataNull('mem') if (!$Config{'GENERAL'}{'DEBUG'});
		} else {
			&insertIntData('mem', $meminfo{kbmemused}, $meminfo{realused}, 0) if (!$Config{'GENERAL'}{'DEBUG'});
		}
		&send_warning($type, 'mem', $meminfo{kbmemused}, $meminfo{realused});
	}
	if (exists $CMD{'swap'}) {
		if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/swap") {
			&createRRD('swap') if (!$Config{'GENERAL'}{'DEBUG'});
		}
		if (!$meminfo{'%swpused'}) {
			&insertIntDataNull('swap') if (!$Config{'GENERAL'}{'DEBUG'});
		} else {
			&insertIntData('swap', $meminfo{'%swpused'}, 0, 0) if (!$Config{'GENERAL'}{'DEBUG'});
		}
		&send_warning($type, 'Swap usage', $meminfo{'%swpused'});
	}
}

####
# Function used to return the disk usage
# It return the total percentage of disk use for a given
# partition. The partition device must be specified with
# command line option -d 
####
sub mon_disk_usage
{
	my ($type) = @_;

	my %diskinfo = ();
	my @ret = `df -kP | 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(m#^$dir$#, @exclude)) {
	print STDERR "DISK: $dir => Exclusion match\n" if ($Config{'GENERAL'}{'DEBUG'});
				next;
			}
		}
		next if (!$dir);
		$percent =~ s/\%//;
		$percent ||= 0;
	print STDERR "DISK: $dir => $percent %\n" if ($Config{'GENERAL'}{'DEBUG'});
		my $target = 'disk' . $dir;
		$target =~ s#/#_#g;
		if (exists $CMD{'disk'}) {
			if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/$target") {
				&createRRD($target) if (!$Config{'GENERAL'}{'DEBUG'});
			}
			if (!$percent) {
				&insertIntDataNull($target) if (!$Config{'GENERAL'}{'DEBUG'});
			} else {
				&insertIntData($target, $percent, 0, 0) if (!$Config{'GENERAL'}{'DEBUG'});
			}
			&send_warning($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 STDERR "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 (!$Config{'GENERAL'}{'DEBUG'});
		}
		if (!$percent) {
			&insertIntDataNull($type) if (!$Config{'GENERAL'}{'DEBUG'});
		} else {
			&insertIntData($type, $percent, 0, 0) if (!$Config{'GENERAL'}{'DEBUG'});
		}
		&send_warning($type, "Posix Share memory usage", $percent);
	}
}


####
# Function used to return the running process number.
# The monitored process must be specified with command
# line option -d
####
sub mon_process
{
	my ($type, $proc) = @_;

	my %procinfo = ();
	my $count = `ps axw | grep "$proc" | grep -v "grep" | wc -l`;
	chomp($count);
	$count =~ s/^\s+//g;
	$procinfo{$proc} = $count || 0;
print STDERR "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 (!$Config{'GENERAL'}{'DEBUG'});
		}
		if (!$procinfo{$proc}) {
			&insertIntDataNull($target) if (!$Config{'GENERAL'}{'DEBUG'});
		} else {
			&insertIntData($target, $procinfo{$proc}, 0, 0) if (!$Config{'GENERAL'}{'DEBUG'});
		}
		&send_warning($type, "Number of $proc process", $procinfo{$proc});
	}


}

####
# Function used to return the running process number.
# The monitored process must be specified with command
# line option -d
####
sub mon_queue
{
	my ($type, $dir) = @_;

	if (-d $dir) {
		use File::Find;
		sub wanted {
			$nfiles++ if (-f); # Skip directory
		}
		find(\&wanted, $dir);
print STDERR "QUEUE: $dir => $nfiles files\n" if ($Config{'GENERAL'}{'DEBUG'});
		my $target = 'queue' . $dir;
		$target =~ s/\//_/g;
		if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/$target") {
			&createRRD($target) if (!$Config{'GENERAL'}{'DEBUG'});
		}
		if (!$nfiles) {
			&insertIntDataNull($target) if (!$Config{'GENERAL'}{'DEBUG'});
		} else {
			&insertIntData($target, $nfiles, 0, 0) if (!$Config{'GENERAL'}{'DEBUG'});
		}
		&send_warning($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 STDERR "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 (!$Config{'GENERAL'}{'DEBUG'});
		}
		if (!$sockinfo{totsck} && !$sockinfo{tcpsck} && !$sockinfo{udpsck}) {
			&insertIntDataNull('sock') if (!$Config{'GENERAL'}{'DEBUG'});
		} else {
			&insertIntData('sock', $sockinfo{totsck}, $sockinfo{tcpsck}, $sockinfo{udpsck}) if (!$Config{'GENERAL'}{'DEBUG'});
		}
		&send_warning($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 STDERR "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 (!$Config{'GENERAL'}{'DEBUG'});
		}
		if (!$ioinfo{'rtps'} && !$ioinfo{'wtps'}) {
			&insertIntDataNull('io') if (!$Config{'GENERAL'}{'DEBUG'});
		} else {
			&insertIntData('io', $ioinfo{'rtps'}, $ioinfo{'wtps'}, 0) if (!$Config{'GENERAL'}{'DEBUG'});
		}
		&send_warning($type, "I/O usage", $ioinfo{'bread'}, $ioinfo{'bwrtn'});
		if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/bio") {
			&createRRD('bio') if (!$Config{'GENERAL'}{'DEBUG'});
		}
		if (!$ioinfo{'bread'} && !$ioinfo{'bwrtn'}) {
			&insertIntDataNull('bio') if (!$Config{'GENERAL'}{'DEBUG'});
		} else {
			&insertIntData('bio', $ioinfo{'bread'}, $ioinfo{'bwrtn'}, 0) if (!$Config{'GENERAL'}{'DEBUG'});
		}
		&send_warning($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 STDERR "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 (!$Config{'GENERAL'}{'DEBUG'});
		}
		if (!$info{'cswch'}) {
			&insertIntDataNull($type) if (!$Config{'GENERAL'}{'DEBUG'});
		} else {
			&insertIntData($type, $info{'cswch'}, 0, 0) if (!$Config{'GENERAL'}{'DEBUG'});
		}
		&send_warning($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 $filemax = 0;
        if (-f '/proc/sys/fs/file-max') {
                $filemax = `cat /proc/sys/fs/file-max`;
                chomp($filemax);
        }
        # No file in use percentage, so compute if
        my $target = 'file';
        if (!exists $fileinfo{'%file-sz'} && $filemax) {
                $fileinfo{'%file-sz'} = int(($fileinfo{'file-sz'}*100)/$filemax);
        } elsif (!exists $fileinfo{'%file-sz'}) {
                $fileinfo{'%file-sz'} = $fileinfo{'file-sz'};
                $target = 'filen';
        }
print STDERR "FILE: filemax => $filemax, used => ", $fileinfo{'%file-sz'}, "%\n" if ($Config{'GENERAL'}{'DEBUG'});
        if (exists $CMD{'file'}) {
                if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/$target") {
                        &createRRD($target) if (!$Config{'GENERAL'}{'DEBUG'});
                }
                if (!$fileinfo{'%file-sz'}) {
                        &insertIntDataNull($target) if (!$Config{'GENERAL'}{'DEBUG'});
                } else {
                        &insertIntData($target, $fileinfo{'%file-sz'}, 0, 0) if (!$Config{'GENERAL'}{'DEBUG'});
                }
                &send_warning($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 STDERR "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 (!$Config{'GENERAL'}{'DEBUG'});
		}
		if (!$info{'intr'}) {
			&insertIntDataNull($type) if (!$Config{'GENERAL'}{'DEBUG'});
		} else {
			&insertIntData($type, $info{'sum'}{'intr'}, 0, 0) if (!$Config{'GENERAL'}{'DEBUG'});
		}
		&send_warning($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 STDERR "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 (!$Config{'GENERAL'}{'DEBUG'});
			}
			if (!$errinfo{$name}{rxerr} && !$errinfo{$name}{txerr}) {
				&insertIntDataNull($target) if (!$Config{'GENERAL'}{'DEBUG'});
			} else {
				&insertIntData($target, $errinfo{$name}{rxerr}, $errinfo{$name}{txerr}, 0) if (!$Config{'GENERAL'}{'DEBUG'});
			}
			&send_warning($type, "Bad packet on $name", $errinfo{$name}{rxerr}, $errinfo{$name}{txerr});
			$target = 'drop_' . $name;
			if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/$target") {
				&createRRD($target) if (!$Config{'GENERAL'}{'DEBUG'});
			}
			if (!$errinfo{$name}{rxdrop} && !$errinfo{$name}{txdrop}) {
				&insertIntDataNull($target) if (!$Config{'GENERAL'}{'DEBUG'});
			} else {
				&insertIntData($target, $errinfo{$name}{rxdrop}, $errinfo{$name}{txdrop}, 0) if (!$Config{'GENERAL'}{'DEBUG'});
			}
			&send_warning($type, "Dropped packet on $name", $errinfo{$name}{rxdrop}, $errinfo{$name}{txdrop});
			$target = 'coll_' . $name;
			if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/$target") {
				&createRRD($target) if (!$Config{'GENERAL'}{'DEBUG'});
			}
			if (!$errinfo{$name}{coll}) {
				&insertIntDataNull($target) if (!$Config{'GENERAL'}{'DEBUG'});
			} else {
				&insertIntData($target, $errinfo{$name}{coll}, 0, 0) if (!$Config{'GENERAL'}{'DEBUG'});
			}
			&send_warning($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 STDERR "PROCESS/SEC: $process{proc}/s\n" if ($Config{'GENERAL'}{'DEBUG'});
	if (exists $CMD{'pcrea'}) {
		if (!-e "$Config{'GENERAL'}{'DATA_DIR'}/pcrea") {
			&createRRD('pcrea') if (!$Config{'GENERAL'}{'DEBUG'});
		}
		if (!$process{proc}) {
			&insertIntDataNull('pcrea') if (!$Config{'GENERAL'}{'DEBUG'});
		} else {
			&insertIntData('pcrea', $process{proc}, 0, 0) if (!$Config{'GENERAL'}{'DEBUG'});
		}
	}
	&send_warning($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 STDERR "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 (!$Config{'GENERAL'}{'DEBUG'});
		}
		if (!$pageinfo{pgpgin} && !$pageinfo{pgpgout}) {
			&insertIntDataNull('page') if (!$Config{'GENERAL'}{'DEBUG'});
		} else {
			&insertIntData('page', $pageinfo{pgpgin}, $pageinfo{pgpgout}, 0) if (!$Config{'GENERAL'}{'DEBUG'});
		}
		&send_warning($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 STDERR "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 (!$Config{'GENERAL'}{'DEBUG'});
		}
		if (!$pswapinfo{'pswpin'} && !$pswapinfo{'pswpout'}) {
			&insertIntDataNull('pswap') if (!$Config{'GENERAL'}{'DEBUG'});
		} else {
			&insertIntData('pswap', $pswapinfo{'pswpin'}, $pswapinfo{'pswpout'}, 0) if (!$Config{'GENERAL'}{'DEBUG'});
		}
		&send_warning($type, "Swap page usage", $pswapinfo{'pswpin'}, $pswapinfo{'pswpout'});
	}
}


####
# 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
	-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,
                "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 STDERR "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");
        RRDs::update("$Config{'GENERAL'}{'DATA_DIR'}/$target", "N:0:0:0");
        my $e = RRDs::error();
	if ($e) {
		unlink("$Config{'GENERAL'}{'PID_FILE'}/sysusage.pid");
		die "ERROR: Cannot update $Config{'GENERAL'}{'DATA_DIR'}/$target: $e\n";
	}

        return(1);
}

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

        &debug("insertData(): Data received ok... comparing\n");
        &debug("insertData(): update $Config{'GENERAL'}{'DATA_DIR'}/$target N:$in:$out:$other\n");
        &debug("Update $Config{'GENERAL'}{'DATA_DIR'}/$target N:$in:$out:$other\n");
        RRDs::update("$Config{'GENERAL'}{'DATA_DIR'}/$target", "N:$in:$out:$other");
        my $e = RRDs::error();
	if ($e) {
		unlink("$Config{'GENERAL'}{'PID_FILE'}/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");
        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_FILE'}/sysusage.pid");
		die "ERROR: Cannot create rrdtool database $Config{'GENERAL'}{'DATA_DIR'}/$target: $e\n";
	}

}

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

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

sub check_running
{
	if (-e "$Config{'GENERAL'}{'PID_FILE'}/sysusage.pid") {
		if (`ps -ef | grep sysusage | grep -v grep`) {
			print STDERR "Found $Config{'GENERAL'}{'PID_FILE'}/sysusage.pid. No other instance of sysusage is running.\nRemoving it...\n";
			unlink("$Config{'GENERAL'}{'PID_FILE'}/sysusage.pid");
		} else {
			print STDERR qq{
An other instance of sysusage is running...
File $Config{'GENERAL'}{'PID_FILE'}/sysusage.pid already exists. Remove it if necessary.
exiting...
};
		}
		exit 0;
	} else {
		open(PIDF, ">$Config{'GENERAL'}{'PID_FILE'}/sysusage.pid") or die "Error: can't write into $Config{'GENERAL'}{'PID_FILE'}/sysusage.pid\n";
		print PIDF "$$";
		close(PIDF);
	}
}


sub send_warning
{
	my ($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);
	if ( $Config{'GENERAL'}{'SKIP_BEGIN'} && $Config{'GENERAL'}{'SKIP_END'} ) {
		return if ( ("$hour$min" > $Config{'GENERAL'}{'SKIP_BEGIN'}) || ("$hour$min" < $Config{'GENERAL'}{'SKIP_END'}) );
	}

	if (exists $CMD{$type}) {
		if (defined $CMD{$type}[0] && ($CMD{$type}[0] ne '') && ($CMD{$type}[0] > 0)) {
			foreach (@vals) {
				if ($_ >= $CMD{$type}[0]) {
print STDERR "$Config{'ALARM'}{'ALARM_PROG'} -t \"$title\" -c $_ -v $CMD{$type}[0] -s $Config{'ALARM'}{'SMTP'} -f $Config{'ALARM'}{'FROM'} -d $Config{'ALARM'}{'TO'} &" if ($Config{'GENERAL'}{'DEBUG'});
					system("$Config{'ALARM'}{'ALARM_PROG'} -t \"$title\" -c $_ -v $CMD{$type}[0] -s $Config{'ALARM'}{'SMTP'} -f $Config{'ALARM'}{'FROM'} -d $Config{'ALARM'}{'TO'} &") if (!$Config{'GENERAL'}{'DEBUG'});
					last;
				}
			}
		}
		if (defined $CMD{$type}[1] && ($CMD{$type}[1] ne '')) {
			foreach (@vals) {
				if ($_ <= $CMD{$type}[1]) {
print STDERR "$Config{'ALARM'}{'ALARM_PROG'} -t \"$title\" -c $_ -v $CMD{$type}[1] -s $Config{'ALARM'}{'SMTP'} -f $Config{'ALARM'}{'FROM'} -d $Config{'ALARM'}{'TO'} &" if ($Config{'GENERAL'}{'DEBUG'});
					system("$Config{'ALARM'}{'ALARM_PROG'} -t \"$title\" -c $_ -v $CMD{$type}[1] -s $Config{'ALARM'}{'SMTP'} -f $Config{'ALARM'}{'FROM'} -d $Config{'ALARM'}{'TO'} &") 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;
			}
			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 STDERR "ERROR: RRD database directory $Config{'GENERAL'}{'DATA_DIR'} doesn't exists.\n";
			&usage();
		}
	}
	if (!-x $Config{'GENERAL'}{'SAR_BIN'}) {
		print STDERR "ERROR: sar command not found at $Config{'GENERAL'}{'SAR_BIN'}\n";
		&usage();
	}
	if ($Config{'ALARM'}{'WARN_MODE'} && (!$Config{'ALARM'}{'FROM'} || !$Config{'ALARM'}{'TO'} || !$Config{'ALARM'}{'SMTP'})) {
		print STDERR "ERROR: you must provide an SMTP server, a sender and a recipient address to send alert message\n";
		&usage();
	}
	if ($Config{'ALARM'}{'WARN_MODE'} && !-x $Config{'ALARM'}{'ALARM_PROG'}) {
		print STDERR "ERROR: Can not execute $Config{'ALARM'}{'ALARM_PROG'}\n";
		&usage();
	}
	if ($Config{'GENERAL'}{'SKIP'}) {
		my $tmp = $Config{'GENERAL'}{'SKIP'};
		delete $Config{'GENERAL'}{'SKIP'};
		if ($tmp =~ m#(\d{2}:\d{2})/(\d{2}:\d{2})#) {
			$Config{'GENERAL'}{'SKIP_BEGIN'} = $1;
			$Config{'GENERAL'}{'SKIP_END'} = $2;
			$Config{'GENERAL'}{'SKIP_BEGIN'} =~ s/://;
			$Config{'GENERAL'}{'SKIP_END'} =~ s/://;
		} else {
			print STDERR "ERROR: Bad time range: $Config{'GENERAL'}{'SKIP'}\n";
			print STDERR "Format must be: HH:MM/HH:MM\n";
			&usage();
		}
	}


}


1;

__END__
