#!/usr/bin/perl

BEGIN {
    (my $dir = $0) =~ s/\/?[^\/]+$//;
    eval "use lib '$dir/lib';";
    die $@ if($@);
};

use strict;
use warnings;
use Time::HiRes qw( gettimeofday tv_interval sleep );
use POSIX qw( :sys_wait_h setsid );
use Getopt::Long;
use Data::Dumper;
use vars qw($config_file $debug $status_file $interface $port $config
$status $update);

use Resmon::Config;
use Resmon::ExtComm;
use Resmon::Status;

GetOptions(
    "i=s" => \$interface,
    "p=i" => \$port,
    "c=s" => \$config_file,
    "d"   => \$debug,
    "f=s" => \$status_file,
    "u"   => \$update,
);

if ($update) {
    use Resmon::Updater;
    (my $resmondir = $0) =~ s/\/?[^\/]+$//;
    exit(Resmon::Updater::update($debug, $resmondir));
}

$config_file ||= "$0.conf";
die "Cannot open configuration file: $config_file" unless (-r $config_file);

sub configure {
    $config = Resmon::Config->new($config_file);
    $config->{statusfile} = $status_file if($status_file);
    $config->{port} = $port if($port);
    $config->{interface} = $interface if($interface);
}

configure();

my $sighup = 0;
sub sighup_handler { $sighup = 1; }
$SIG{'HUP'} = \&sighup_handler;

my $sigint = 0;
sub sigint_handler { $sigint = 1; }
$SIG{'INT'} = \&sigint_handler;

my $rmlast = undef;
sub wait_interval {
    $rmlast = [gettimeofday] unless defined($rmlast);
    my $elapsed = $config->{interval} - tv_interval($rmlast);
    if($elapsed > 0) {
        sleep($elapsed);
    }
    $rmlast = [gettimeofday];
}

sub reap_zombies {
    my $kid;
    do {
        $kid = waitpid(-1, WNOHANG);
    } while $kid > 0;
}

unless($debug) {
    fork && exit;
    setsid;
    open(STDIN, "</dev/null");
    open(STDOUT, ">/dev/null");
    open(STDERR, ">/dev/null");
    fork && exit;
}

my $list = [];
$status = Resmon::Status->new($config->{statusfile});
$status->open();
$status->serve_http_on($config->{interface}, $config->{port},
        $config->{authuser}, $config->{authpass}, $config->{hostsallow})
    if($config->{port});

while(1) {
    while(my($module_name, $mod_configs) = each %{$config->{Module}}) {
        while(my($check_name, $monitor_obj) = each %$mod_configs) {
            my $check_metrics = {};
            my $starttime = [gettimeofday];
            # Get old status if it hasn't expired
            $check_metrics = $monitor_obj->get_cached_metrics();
            # Otherwise, run the check
            if (!$check_metrics) {
                my $timeout = $monitor_obj->{'check_timeout'} ||
                    $config->{'timeout'};
                alarm($timeout);
                my $handler;
                eval {
                    local $SIG{ALRM} = sub { die "alarm\n" };
                    if ($check_name eq "*") {
                        $check_metrics = $monitor_obj->wildcard_handler;
                    } else {
                        $check_metrics = {
                            $check_name => $monitor_obj->handler
                        };
                    }
                };
                alarm 0;
                # Store the last metrics for use by fresh_status_msg later
                $monitor_obj->cache_metrics($check_metrics);
            };
            my $checkproblem = $@;
            if($checkproblem) {
                chomp $checkproblem;
                if ($checkproblem eq "alarm") {
                    $checkproblem = "Check timeout";
                }
                $check_metrics = {
                    $check_name => {"error" => ["$checkproblem", "s"]}
                };
                Resmon::ExtComm::clean_up;
            }
            foreach my $name (keys %$check_metrics) {
                my $results = {
                    last_runtime_seconds => sprintf("%.6f",
                        tv_interval($starttime)),
                    metric => $check_metrics->{$name}
                };
                $status->store($module_name,$name, $results);
                $status->write($module_name,$name, $results->{'metric'},
                    $debug);
            }
        }
    }
    $status->close();
    die "Exiting.\n" if($sigint);
    if ($sighup) {
        # Reload configuration (and modules) on SIGHUP
        $sighup = 0;
        print STDERR "Reloading modules\n";
        $config = Resmon::Config->new($config_file);
        # Needed to ensure any removed modules do not continue to show in the
        # web interface
        $status->clear();
    } else {
        reap_zombies();
        wait_interval();
        reap_zombies();
    }
    die "Exiting.\n" if($sigint);
    print "\n---- ".localtime(time)."----------\n"
    unless $status->open();
}
