#!/usr/bin/perl -w

use strict;
use warnings;

use DBI ();
use Getopt::Long qw(GetOptions);
use noris::NetSaint;

my $NetSaint = noris::NetSaint->new;

my %Threshold;

sub manage_thresholds {
    my ( $option, $step, $value ) = @_;
    $option =~ /^(critical|warning)-(age|duration)\z/ or die;
    $Threshold{$step}{$2}{$1} = $value;
}

GetOptions(
    'kunde=s'       => \my $Kunde,
    'prozessname=s' => \my $Prozessname,
    'database=s'    =>
      \( my $DataSource = 'DBI:mysql:database=e2e;host=db1.noris.net' ),
    'db-user=s'            => \my $DbUser,
    'db-password=s'        => \my $DbPassword,
    'critical-age=f%'      => \&manage_thresholds,
    'warning-age=f%'       => \&manage_thresholds,
    'critical-duration=f%' => \&manage_thresholds,
    'warning-duration=f%'  => \&manage_thresholds,
    'sql-runtime-max=i'    => \my $SqlRuntimeMax,
    'help|?'               => sub {
        exec perldoc => -F => $0 or die "Fehler beim Aufruf von perldoc: $!";
    },
  )
  or exit noris::NetSaint::Status->new('Unknown');

die "-kunde und -prozessname mssen angegeben werden.\n"
  unless defined $Kunde && defined $Prozessname;

die "Keine Schwellenwerte definiert.\n" unless keys %Threshold;

sub get_threshold($$$) {
    my ( $type, $severity, $step ) = @_;
    $Threshold{ exists $Threshold{$step}{$type}{$severity}
          && $step }{$type}{$severity};
}

sub get_thresholds($$) {
    my ( $type, $step ) = @_;
    return
      unless defined( my $warning_threshold =
          get_threshold( $type => warning => $step ) )
      or defined( my $critical_threshold =
          get_threshold( $type => critical => $step ) );
    $warning_threshold, $critical_threshold;
}

sub check_thresholds {
    my ( $value, $message, $warning_threshold, $critical_threshold ) = @_;

    if ( defined $critical_threshold && $value > $critical_threshold ) {
        $NetSaint->update( Critical => $message );
    }
    elsif ( defined $warning_threshold && $value > $warning_threshold ) {
        $NetSaint->update( Warning => $message );
    }
    else { return '' }
    1;
}

my $dbh = do {
    no warnings 'once';
    DBI->connect( $DataSource, $DbUser, $DbPassword,
        { AutoCommit => 0, PrintError => 0, RaiseError => 1 } )
      or die "Fehler beim Aufbau der Datenbankverbindung: $DBI::errstr\n";
};

my $sth = $dbh->prepare(<<'_');
        SELECT    step.name,
                  TIME(messwert.ts),
                  UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(messwert.ts),
                  messwert.dauer/100
        FROM      kunde, messwert, prozess, prozessname, step
        WHERE     kunde.id            = prozess.kunde
	      AND messwert.step       = step.id
              AND messwert.ts = (
                SELECT MAX(m.ts) FROM messwert m WHERE m.step = step.id
                  )
              AND prozess.prozessname = prozessname.id
              AND prozess.id          = step.prozess
              AND kunde.name          = ?
              AND prozessname.name    = ?
        ORDER BY  step.nr
_
my $sql_runtime = time;
$sth->execute( $Kunde, $Prozessname );
$sql_runtime = time - $sql_runtime;
die "Prozess nicht gefunden!\n" unless $sth->rows;

my ( @messwerte, %step );
@step{ grep length, keys %Threshold } = ();

while ( my ( $step, $ts, $age, $dauer ) = $sth->fetchrow_array ) {
    if ( defined $dauer ) {
        push @messwerte, "$step = $dauer s";
        delete $step{$step};
        if ( my @age_thresholds = get_thresholds( age => $step ) ) {
            check_thresholds( $age,
                qq(Step "$step" wurde zuletzt um $ts Uhr gemessen.),
                @age_thresholds );
        }
        elsif ( my @duration_thresholds = get_thresholds( duration => $step ) )
        {
            check_thresholds(
                $dauer,
                qq(Step "$step" dauerte zuletzt (um $ts Uhr) $dauer Sekunde)
                  . ( $dauer != 1 && 'n' ) . '.',
                @duration_thresholds
            );
        }
    }
    else {
        $NetSaint->update( Critical =>
              qq(Fehler bei der letzten Messung von Step "$step" um $ts Uhr) );
    }
}

$NetSaint->update( Unknown => qq(Step "$_" wurde nicht gefunden.) )
  for keys %step;

$NetSaint->update(
    defined $SqlRuntimeMax
      && $sql_runtime > $SqlRuntimeMax
    ? ( Warning => 'Die SQL-Abfrage bentigte '
          . ( $sql_runtime == 1 ? 'eine Sekunde' : "$sql_runtime Sekunden" )
          . ' (vgl. RT#395298).' )
    : ( OK => join( ', ', @messwerte ) )
  )
  unless $NetSaint->message;

END {
    $dbh->rollback if defined $dbh;
}

__END__

=head1 NAME

check_end2end - Nagios-Plugin fr end2end-Monitoring

=head1 SYNOPSE

    check_end2end -kunde pop -prozessname 'pop main website' \
                  -warning-age =600                          \
                  -critical-duration Gesamtdauer=4.2         \
                  -sql-runtime-max 23

Gibt einen kritischen Status aus, wenn es fr irgendeinen Step keine Messwerte
gibt, die letzte Messung eines Steps fehlerhaft war (= NULL-Messwert) oder wenn
der Step "Gesamtdauer" zuletzt lnger als 4,2 Sekunden bentigte und ansonsten
eine Warnung, falls irgendein Step seit ber zehn Minuten nicht gemessen wurde
und/oder wenn die SQL-Abfrage zur Ermittlung des Ergebnisses lnger als 23
Sekunden dauerte.

=head1 NOTWENDIGE ARGUMENTE

=head2 Auswahl des zu berwachenden Prozesses

=over 4

=item -kunde NAME

Name des Kunden

=item -prozessname NAME

Name des Prozesses

=back

=head1 OPTIONEN

=head2 Auswahl der Datenquelle

=over 4

=item -database DSN

Datenbank, aus der die Werte entnommen werden sollen

=item -db-user USER

Benutzername fr die Datenbankverbindung

=item -db-password KENNWORT

Kennwort fr die Datenbankverbindung

=back

Fr diese Optionen sind jeweils sinnvolle Werte voreingestellt;
Details s. Quellcode.

=head2 Festlegung der Schwellenwerte

=over 4

=item -critical-age STEP=SEKUNDEN

=item -warning-age STEP=SEKUNDEN

maximal erlaubtes Alter der Messdaten fr den fraglichen Step

=item -critical-duration STEP=SEKUNDEN

=item -warning-duration STEP=SEKUNDEN

maximale Dauer fr den fraglichen Step

=back

Fr diese Optionen gilt:

=over 4

=item *

Mindestens eine muss angegeben werden.

=item *

Es knnen auch Sekundenbruchteile angegeben werden.

=item *

Wird ein Stepname angegeben, fr den es in der Datenbank keine Messwerte gibt,
erzeugt das einen L<C<Unknown>-Status|noris::NetSaint::Status>.

=item *

Eine Angabe mit einem leeren Step-Namen legt einen Defaultwert fr alle Steps
fest, fr die keine entsprechende Angabe explizit gemacht wird.
Steps, fr die es gar keine Messdaten gibt, werden dabei allerdings ignoriert.

=back

=over 4

=item -sql-runtime-warning Zeit

(mindestens) eine Warnung ausgeben, wenn das SQL-Statement zur Ermittlung der
Werte lnger als die in Sekunden angegebene Zeit bentigte (vgl. RT#395298)

=back

=head2 Sonstige

=over 4

=item -help

=item -?

um (nur) diese Dokumentation anzeigen zu lassen

=back

=head1 AUTOR

 Martin H. Sluka <fany@noris.net>
 fr die noris network AG
