#!/usr/bin/perl

our $VERSION = "0.0.0";
$VERSION = eval $VERSION;

use Modern::Perl;
use Getopt::Long 2.39;
use File::Basename qw(basename);

# application context
my $app = { };

# anonymous pod2usage sub
my $pod2usage = sub {
    require Pod::Usage;
	Pod::Usage::pod2usage(@_);
};

BEGIN {
    # set alarm to kill script if it takes too long to finish
    $SIG{ALRM} = sub { die "" };
    alarm 60;
}

END {
    say "ERROR: " . $app->{error} if $app->{error};

    # cancel alarm
    alarm 0;
}


### Main Processing Section ###

MAIN:
{
    # parse command line
    parse_cmdline(@ARGV);

    # get memory usage
    my $cpu_usage = get_cpu_usage();

    # print cacti string
    print hash2cacti($cpu_usage);
}


### Subroutines ###

sub parse_cmdline {
    my @argv = @_;

    # option parser
    my $parser = Getopt::Long::Parser->new;
    $parser->configure('no_ignore_case');

    # option specifications
    my %opts;
    my @specs = (
        "debug",                    # optional, debug
        "help|h",                   # optional, help info
        "manual|m",                 # optional, complete manual
        "version|V",                # optional, version info
    );
	
    # parse options
    $pod2usage->(
        -verbose => 0,
        -exitval => 2,
        -message => " ",
    ) unless $parser->getoptionsfromarray(\@argv, \%opts, @specs);

    print_version() if $opts{version};
    print_manual()  if $opts{manual};
    print_help()    if $opts{help};

    # store args and opts in context
    $app->{args} = \@argv;
    $app->{opts} = \%opts;

    return 1;
};

sub get_cpu_usage {
    # determine operating system
    my $OS = $^O;
    debug("Operating system: $OS");

    if ($OS eq 'linux') {
        return get_linux_cpu_usage();
    }

    $app->{error} = "Unsupported operating system: $OS";
    exit(1);
}

sub get_linux_cpu_usage {
    my $proc_stat = "/proc/stat";

    my %cpu   = ();    # initial cpu jiffies
    my %usage = ();    # calculated cpu usage

    for (my $i=0; $i <= 1; $i++) {
        sleep $i;
        debug("Reading $proc_stat");
        my $file = new IO::File("$proc_stat") or $app->{error} = "Could not open $proc_stat" and exit(1);

        while (my $line = $file->getline()) {
            if ($line =~ m/^cpu[0-9]\s+[0-9]+/) {
                chomp($line);
                debug($line);

                my @jiffies = split(/\s+/, $line);
                my $id = substr($jiffies[0], 3);

                my ($user_time, $nice_time, $system_time, $idle_time, $io_wait, $irq, $soft_irq, $steal, $guest) = @jiffies[1, 2, 3, 4, 5, 6, 7, 8, 9];

                my $system_all_time  = $system_time + $irq + $soft_irq;
                my $idle_all_time    = $idle_time + $io_wait;
                my $virtual_all_time = $steal + $guest;
                my $total_time       = $user_time + $nice_time + $system_all_time + $idle_all_time + $virtual_all_time;

                if (not defined $cpu{$id}) {
                    $cpu{$id}{user_time       } = $user_time        ;
                    $cpu{$id}{nice_time       } = $nice_time        ;
                    $cpu{$id}{system_all_time } = $system_all_time  ;
                    $cpu{$id}{virtual_all_time} = $virtual_all_time ;
                    $cpu{$id}{total_time      } = $total_time       ;
                } else {
                    my $delta_total            = $total_time       - $cpu{$id}{total_time};
                    my $delta_user_time        = $user_time        - $cpu{$id}{user_time};
                    my $delta_nice_time        = $nice_time        - $cpu{$id}{nice_time};
                    my $delta_system_all_time  = $system_all_time  - $cpu{$id}{system_all_time};
                    my $delta_virtual_all_time = $virtual_all_time - $cpu{$id}{virtual_all_time};

                    my $usage = sprintf("%.2f", ($delta_user_time + $delta_nice_time + $delta_system_all_time + $delta_virtual_all_time) / $delta_total * 100);

                    $usage{'cpu'.$id} = $usage;
                }
            }
        }

        $file->close;
    }

    return \%usage;
}

sub hash2cacti {
    my $href = shift;
    my $result;

    $result = join(" ", map { "$_:$href->{$_}" } sort keys %$href);
	$result = "CACTI | $result\n" if $app->{opts}{debug};

    return $result;
}

sub print_version {
    say STDERR basename($0) . " v" . main->VERSION();
    exit(0);
}

sub print_manual {
    alarm 0;
    $pod2usage->(
        -verbose => 2,
        -exitval => 1,
    );
};

sub print_help {
    $pod2usage->(
        -verbose => 0,
        -exitval => 1,
    );
};

sub debug {
    my $string = shift;
    say "DEBUG | $string" if $app->{opts}{debug};
}

__END__


=head1 NAME

cpu2cacti - Query CPU usage for Cacti

=head1 SYNOPSIS

B<cpu2cacti> [options]

=over 2

=item Options:

  -h --help       Help info
  -m --manual     Manpages
  -V --version    Version info
  -d --debug      Debugging

=back

See `cpu2cacti --manual` for the full documentation.

=head1 DESCRIPTION

This program queries CPU statistics for this host. The output is formatted to use with Cacti.

=head1 OPTIONS

=over 4

=item B<-h, --help>

Displays a brief summary of cpu2cacti's options.

=item B<-m, --manual>

Displays cpu2cacti's full documentation.

=item B<-V, --version>

Display the version of cpu2cacti.

=item B<-d, --debug>

Displays debug information.

=back

=head1 AUTHOR

Jurgen van den Hurk, E<lt>jvdhurk@tilburguniversity.eduE<gt>

=head1 LICENSE AND COPYRIGHT

Copyright 2017 Tilburg University. All rights reserved.

This program is free software; you may redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut
