#!/usr/bin/perl
use warnings;
use strict;
$|=1;

my $opt_f = "/proc/stat"; # file to read from
my $opt_n; # num cpu for SMP support
my $opt_C; # one specific cpu/core

my $opt_A; # cache file
my $opt_a = 300; # cache file age limit sec

my $opt_s = 5; # presleep base
my $opt_S = 5; # presleep rand

my $opt_t = 10; # measurement base
my $opt_T = 0;  # measurement rand

my $opt_v = 0;

my $opt_i = ""; # min idle, (cpureg;warn;crit;)+

use Time::HiRes qw ( time );

use Getopt::Long qw(:config no_ignore_case bundling);
GetOptions(
        'f=s' => \$opt_f,
        'n=i' => \$opt_n,
        'A=s' => \$opt_A,
        'a=i' => \$opt_a,
        'C=s' => \$opt_C,
        's=s' => \$opt_s,
        'S=s' => \$opt_S,
        't=s' => \$opt_t,
        'T=s' => \$opt_T,
        'i=s' => \$opt_i,
        'v'   => \$opt_v,
        ) or die "funnyargs";

#die "no interface" unless length $opt_i;
die "incompatible n+C" if defined $opt_n && defined $opt_C;
($opt_t, $opt_T,) = (0,0,) if defined $opt_A && length $opt_A;

my %lev = ();
if (length $opt_i) {
	while ($opt_i =~ s/^([^;]+);(\d+);(\d+);//) {
		$lev{$1} = [$2,$3,];
	}
}

my $ps = 0;
if ($opt_s + $opt_S) {
	$ps = ($opt_s + ( $opt_S ? rand($opt_S) : 0 ));
	select undef,undef,undef, $ps;
}

my ($msg, $perf,);
my $ec = 3;
my (@W, @C,);
my $rc = eval { 

	my $d1;
	if ($opt_t + $opt_T) {
		$d1 = &do_cpus($opt_f, $opt_n, $opt_C,); 
		select(undef,undef,undef, ($opt_t + ( $opt_T ? rand($opt_T) : 0 )));
	} elsif (defined $opt_A && length $opt_A) {
		$d1 = do $opt_A;

	}

	my $d2 = &do_cpus($opt_f, $opt_n, $opt_C,); 
	if (defined $opt_A && length $opt_A) {
		my $tf = $opt_A.".tmp";
		unlink $tf if -e $tf;
		open TF, ">", $tf;
		use Data::Dumper;
		print TF Dumper($d2);
		close TF;
		unlink $opt_A if $opt_A;
		rename $tf, $opt_A or die "rename: $!";
	}

	die "no d1" unless defined $d1;
	my $t1 = delete $d1->{_time};
	my $t2 = delete $d2->{_time};
	my $dt = $t2-$t1;
	die "stale d1" unless $dt < $opt_a;

	my @p = ();
	for my $c (sort keys %$d1) {
		my $D1 = $d1->{$c};
		die "no d2 $c" unless exists $d2->{$c};
		my $D2 = $d2->{$c};

		my @dp1 = split " ", $D1;
		die "no dp1 for $c" unless @dp1;
		my @dp2 = split " ", $D2;
		die "no dp2 for $c" unless @dp2;

		die "length mismatch" unless scalar @dp1 == scalar @dp2;
	
		my ($i, $u,);	
		my @d = ();
		for (0..$#dp1) {
			my $dd = $dp2[$_]-$dp1[$_];
			if ($_ == 3) {
				$i = $dd;
			} else {
				$u += $dd;
			}
			$d[$_] = int(($dd/$dt)+0.5);
		}
		if (defined $i) {
			my $ip = ( $i / ($i+$u) ) * 100; 
			for my $p (sort keys %lev) {
				if ($c =~ /^$p$/) {
					my ($W, $C,) = @{$lev{$p}};
					if ($C > $ip) {
						push @C, sprintf "%s idle %.1f < $C", $c, $ip;
					} elsif ($W > $ip) {
						push @W, sprintf "%s idle %.1f < $W", $c, $ip;
					}
				}
			}
		}

		push @p, sprintf("%s=%s", $c, join(";",@d));
	}

	die "no perf" unless @p;
	$perf = join " ", @p;
	$perf .= sprintf " dt=%.5fs", $dt;
	$perf .= sprintf " ps=%.5fs", $ps;
	$msg = sprintf "%i datasets", scalar @p;
	return "ok";
};


if (defined $rc && $rc eq 'ok') {
	$msg ||= sprintf "%i perfbytes", length $perf;
	$ec = 0;
	if (@W) {
		$msg = sprintf "WARN(%s) %s", join(", ", @W), $msg;
		$ec = 1;
	}
	if (@C) {
		$msg = sprintf "CRIT(%s) %s", join(", ", @C), $msg;
		$ec = 2;
	}
} else {
	$ec = 3;
	if ($@) {
		chomp $@;
		$msg = "EXCEPTION: '$@'";
	} else {
		$msg = "UNKNOWNRC: '$rc'";
	}
}
unless (defined $msg && length $msg) {
	$msg = "NOMESSAGE";
	$ec = 3;
}

$perf ||= "";

my $st = {
		0 => 'OK',
		1 => 'WARNING',
		2 => 'CRITICAL',
		3 => 'UNKNOWN',
	}->{$ec}||"EC$ec";

printf "%s - %s|%s\n", $st, $msg, $perf;

exit $ec;


sub do_cpus($$$) {
	my ($fn, $cn, $cp,) = @_;

	unless (defined $cp && length $cp) {
	       $cp = "cpu";
       		if (defined $cn && $cn) {
	 		$cp .= "[".join("",0..($cn-1))."]?";
		} else {
			$cp .= '\d*';
		}
	}		

	die "'$fn' does not exist" unless -e $fn;
	die "'$fn' not a file" unless -f $fn;
	open IF, "<", $fn or die "open($fn): $!";

	my $d = {
		_time => time,
		};
	while (<IF>) {
		s/[\s\r\n]+/ /g;
		s/(^ | $)//g;

		if (/^(intr|ctxt|processes|$cp) (\d+(?:\s\d+)*)$/) {
			my ($k, $v,) = ($1,$2,);
			die "collision for $k" if exists $d->{$k};
			$v =~ s/ .*//g if $k eq "intr";
			$d->{$k} = $v;
		}
	}
	close IF;

	die "no cpu data found" unless scalar keys %$d;

	return $d;
}

