#!__PERL__
# -*- perl -*-

# Copyright (c) 2002 by Jeff Weisberg
# Author: Jeff Weisberg <argus @ tcp4me.com>
# Date: 2002-Oct-31 15:45 (EST)
# Function: server side of graph data collector
#
# $Id: graphd.pl,v 1.10 2004/12/31 19:00:19 jaw Exp $

# read in data from argusd, save to data files
# uses a large number of file descriptors, be sure
# that the system limit is sufficient (eg. limit/ulimit openfile/maxfiles/...)


use lib('__LIBDIR__');
require "conf.pl";

package GraphData;
use GraphData;
use Sys::Syslog qw(:DEFAULT setlogsock);
use POSIX qw(:errno_h);


%obj = ();
@lru = ();

$syslog = shift @ARGV;
if( $syslog ){
    eval {
	if( defined &setlogsock && $Sys::Syslog::VERSION < 0.03 ){
	    setlogsock( 'unix' );
	}
    };
    openlog( 'graphd', 'pid ndelay', $syslog );
}
loggit( "restarted" );

while( <> ){
    chop;
    if( /^sample/ ){
	# sample T obj st val
	my(undef, $t, $name, $st, $val
	   ) = split;
	newobj( $name ) unless defined $obj{$name};
	add_sample( $name, $t, $st, $val );
	push @lru, $name;
    }
}

# It adds a precious seeing to the eye
#   -- Shakespeare, Love's Labour 's Lost
sub add_sample {
    my $name = shift;
    my $time = shift;
    my $stat = shift;
    my $valu = shift;
    my( $me, $fh, $off, $flg );

    $me = $obj{$name};
    $fh = $me->{fd};

    # updating too fast?
    return if $time - $me->{lastt} < 30;
    
    $off = $me->{sampl_index} * $SAMP_SIZE + $me->{sampl_start};
    sysseek( $fh, $off, 0 );
    syswrite($fh, pack("NfNx4",
		       $time, $valu, flags($stat)
		       ), $SAMP_SIZE);
    
    $me->{sampl_index} ++;
    $me->{sampl_index} %= $me->{sampl_nmax};
    $me->{sampl_count} ++
	unless $me->{sampl_count} >= $me->{sampl_nmax};

    # update hourly stats
    $me->{hours_min} = $valu
	if !$me->{hours_nsamp} || ($valu < $me->{hours_min});
    $me->{hours_max} = $valu
	if !$me->{hours_nsamp} || ($valu > $me->{hours_max});
    $me->{hours_sigma} += $valu;
    $me->{hours_sigm2} += $valu * $valu;
    $me->{hours_nsamp} ++;
    $me->{hours_flags} = sum_flags( $me->{hours_flags}, $stat );

    # update daily stats
    $me->{days_min} = $valu
	if !$me->{days_nsamp}  || ($valu < $me->{days_min});
    $me->{days_max} = $valu
	if !$me->{days_nsamp}  || ($valu > $me->{days_max});
    $me->{days_sigma} += $valu;
    $me->{days_sigm2} += $valu * $valu;
    $me->{days_nsamp} ++;
    $me->{days_flags} = sum_flags( $me->{days_flags}, $stat );

    # roll?
    my( @lt, @ct );
    @lt = localtime $me->{lastt};
    @ct = localtime $time;
    if( $me->{lastt} && ($lt[2] != $ct[2]) ){
	# add hour data
	my($ave, $std) = ave_sdev( $me->{hours_sigma},
				   $me->{hours_sigm2},
				   $me->{hours_nsamp});
	$off = $me->{hours_index} * $HOURS_SIZE + $me->{hours_start};
	sysseek( $fh, $off, 0 );
	syswrite($fh, pack("NffffNNx4",
			   $time,
			   $me->{hours_min}, $me->{hours_max},
			   $ave, $std, 
			   $me->{hours_nsamp},
			   $me->{hours_flags},
			   ), $HOURS_SIZE);
	$me->{hours_index} ++;
	$me->{hours_index} %= $me->{hours_nmax};
	$me->{hours_count} ++
	    unless $me->{hours_count} >= $me->{hours_nmax};

	# reset stats
	$me->{hours_min} = $me->{hours_max} = 
	    $me->{hours_nsamp} = $me->{hours_sigma} = 
	    $me->{hours_sigm2} = $me->{hours_flags} = 0;
    }

    if( $me->{lastt} && ($lt[3] != $ct[3]) ){
	# add day data
	my($ave, $std) = ave_sdev( $me->{days_sigma},
				   $me->{days_sigm2},
				   $me->{days_nsamp});
	$off = $me->{days_index} * $DAYS_SIZE + $me->{days_start};
	sysseek( $fh, $off, 0 );
	syswrite($fh, pack("NffffNNx4",
			   $time,
			   $me->{days_min}, $me->{days_max},
			   $ave, $std, 
			   $me->{days_nsamp},
			   $me->{days_flags},
			   ), $DAYS_SIZE);

	$me->{days_index} ++;
	$me->{days_index} %= $me->{days_nmax};
	$me->{days_count} ++
	    unless $me->{days_count} >= $me->{days_nmax};

	# reset stats
	$me->{days_min} = $me->{days_max} =
	    $me->{days_nsamp} = $me->{days_sigma} =
	    $me->{days_sigm2} = $me->{days_flags} = 0;
    }
    
    $me->{lastt} = $time;
    $me->write_header();
}

# Yet, for necessity of present life,
# I must show out a flag and sign of love,
# Which is indeed but sign. That you shall surely find him,
#   -- Shakespeare, Othello
sub flags {
    my $st = shift;

    {
	up => 0,
	down => 1,
	override => 2,
    }->{$st};
}

sub sum_flags {
    my $o = shift;
    my $n = flags( shift );

    return 1 if $o == 1 || $n == 1;
    return 2 if $o == 2 || $n == 2;
    0;
}

sub newobj {
    my $name = shift;
    my( $fh, $me );
    
    $me = $obj{$name} = {
	name => $name,
    };

    bless $me;
    $fh = anon_fh();
    $me->{fd} = $fh;

    my( $x, $i );
    if( -f "$::datadir/gdata/$name" ){
	$x = "+< $::datadir/gdata/$name";
	$i = 1;
    }else{
	$x = "+> $::datadir/gdata/$name";
    }
    
    for my $n (0 .. 1){
	last if open( $fh, $x );

	if( $! == EMFILE || $! == ENFILE ){
	    # try closing something

	    my $on = shift @lru;
	    if( $on && $obj{$on} ){
		loggit( "closing $on" );
		close $obj{$on}->{fd};
		delete $obj{$on};

		next;
	    }
	}
	
	loggit( "$name on too tight, won't open: $!" );
	last;
    }
    
    binmode $fh;
    if( $i ){
	$me->initstats();
    }else{
	$me->initfile();
    }
    
}

################################################################

sub initfile {
    my $me = shift;
    my $fh = $me->{fd};

    loggit( "creating data file $me->{name}" );
    $me->header_init();
    
    # pre-extend
    truncate $fh, ($me->{days_start} + $DAYS_SIZE * $me->{days_nmax});

    $me->write_header();
}

sub initstats {
    my $me = shift;
    my $fh = $me->{fd};

    my $magic = $me->read_header();

    unless( $magic eq $MAGIC){
	loggit( "corrupt data file: $me->{name}" );
	close $fh;
    }
}

sub ave_sdev {
    my $s  = shift;
    my $s2 = shift;
    my $n  = shift;
    my( $u, $x, $v );

    # things may overflow and we may try taking sqrt of a negative
    # (like if some wiseguy tries graphing uptime)

    if( $n ){
	$u = $s  / $n;
	$x = $s2 / $n - $u * $u;
    }else{
	$u = $x = $v = 0;
    }
    if( $x > 0 ){
	$v = sqrt( $x );
    }

    ($u, $v);
}


################################################################

sub loggit {
    my $msg = shift;

    eval {
	syslog( 'info', $msg )  if $syslog;
    };
    print STDERR "GRAPHD: $msg\n";
}

