#!/usr/local/bin/perl

# OSCONFIG Should upload disk groupings (metadevices) 
# also some key things rom /etc/system, like the dnlc settings and maxusers.
# and should move all os dependent stuff into separate libs.

use Mysql;
use POSIX;
use Getopt::Std;
use IO::Select;
$|=1;

$config{CRONLOG}="/var/cron/log";
die ("Please make $config{CRONLOG} readable.\n") unless ( -r $config{CRONLOG});
$config{PS}="ps -ef";

getopts("s:t:l:") || die_usage();

($host,$database)=split /:/,$opt_s;
die_usage() unless $host and $database;

$config{TESTNAME}=$opt_t;
if (! $config{TESTNAME}) {
  print "Enter testname label (this can be anything you want, it's just to\n";
  print "help differentiate between multiple test runs on the same host)\n";
  print "(You can specify this on the command line with the -t option)\n";
  print "Label: ";
  chomp($config{TESTNAME} = <STDIN>) ;
}
die_usage() unless $config{TESTNAME};

$config{LOGDIR}=$opt_l;
if (! $config{LOGDIR}) {
    print "Enter the full path to the INN log directory.  It should contain\n";
    print "\"news\" and \"news.notice\"\n";
    print "Dir: ";
    chomp($config{LOGDIR} = <STDIN>) ;
}
die_usage() unless $config{LOGDIR};

$config{NEWSLOG}="$config{LOGDIR}/news";
$config{NOTICELOG}="$config{LOGDIR}/news.notice";


print "* Getting OS Configuration\n";
get_osconfig();

print "* Getting Disk Configuration\n";
get_diskconfig();

#print "* Getting INN Configuration\n";
#get_innconfg();

print "* Updating SQL database...\n";
update_config_db();

# start data collector
main_loop();

exit(0);


# This is currently very sun specific.
sub main_loop {
    $s=new IO::Select();

    print "* Gathering performance data..\n";

    open(IOSTAT,"/usr/bin/iostat -x 30|") || 
	die "Error running iostat: $!\n";
    
    open (VMSTAT,"/usr/bin/vmstat -S 30|") || 
	die "Error running vmstat: $!\n";

    open (CPUSTATE,"./cpustate $config{NPROCESSORS_ONLN} 30|") || 
	die "Error running cpustate: $!\n";

    open(TIMER,"tail -f $config{NOTICELOG}|") || 
	die "Error running tail -f $config{NOTICELOG}: $!\n";


    open(CRONLOG,"tail -f $config{CRONLOG}|") || 
	die "Error running tail -f $config{CRONLOG}: $!\n";

    $s->add(\*IOSTAT);
    $s->add(\*VMSTAT);
    $s->add(\*CPUSTATE);
    $s->add(\*TIMER);
    $s->add(\*CRONLOG);


    while(1) {
	foreach $fd ($s->can_read(60)) {
	    chomp($_=<$fd>);
	    run_periodic();
	    if ($fd == \*IOSTAT) {
		next if /extended disk statistics/;
		next if /disk      r\/s  w\/s/;
		($disk,$rs,$ws,$krs,$kws,$wait,$actv,$svc_t,$pw)=split /\s+/;
		if (! $device{$disk}) {
		    # the drive isnt mounted- skip it.
		    next;
		}
		insert_trend($device{$disk},"wait",$wait);
		insert_trend($device{$disk},"actv",$actv);
		insert_trend($device{$disk},"svc_t",$svc_t);
		insert_trend($device{$disk},"%w",$pw);
	    } elsif ($fd == \*VMSTAT) {
		next if /procs     memory/;
		next if /r b w   swap/;
		s/^\s*//g;
		my($r,$b,$w,$swap,$free,$si,$so,$pi,$po,$fr)=split /\s+/;
		insert_trend("memory","swap",$swap);
		insert_trend("memory","free",$free);
		insert_trend("memory","si",$si);
		insert_trend("memory","so",$so);
		insert_trend("memory","pi",$pi);
		insert_trend("memory","po",$po);
		insert_trend("memory","fr",$fr);
	    } elsif ($fd == \*CPUSTATE) {
		next if /--/;
		($val,$var)=split /% /;
		$var="%$var";
		insert_trend("cpu",$var,$val);
	    } elsif ($fd == \*TIMER) {
		next unless /ME time (\d+) (.*)$/;  
		undef %t;
		$t{time}=$1;

		@a=split /\s+/,$2;
		while(@a) {
		    $k=shift(@a);
		    $v=shift(@a);
		    ($v1,$v2)=($v=~/(\d+)\((\d+)\)/);
		    $t{$k}=$v1;
		    $t{$k . "2"}=$v2;
		}
		insert_trend("innd","%busy",
			     (($t{time}-$t{idle}) / $t{time}) * 100);
		insert_trend("innd","%artwrite",
			     ($t{artwrite} / $t{time} * 100));
		insert_trend("innd","%artlink",
			     ($t{artlink} / $t{time} * 100));
		insert_trend("innd","%hiswrite",
			     ($t{hiswrite} / $t{time} * 100));
		insert_trend("innd","%hisread",
			     ($t{hishave} / $t{time} * 100));
		insert_trend("innd","artwrite",
			     ($t{artwrite} / $t{artwrite2})) unless ($t{artwrite2}==0);
		insert_trend("innd","artlink",
			     ($t{artlink} / $t{artlink2})) unless ($t{artlink2} == 0);
		insert_trend("innd","hiswrite",
			     ($t{hiswrite} / $t{hiswrite2})) unless ($t{artwrite2} == 0);
		insert_trend("innd","hisread",
			     ($t{hishave} / $t{hishave2})) unless ($t{hishave2} == 0);
	    } elsif ($fd == \*CRONLOG) {
		if (/CMD: (.*)/) {
		    $lastcmd=$1;
		    next;
		}
		if (/^>\s+(\S+) (\d+)/) {
		    $e="($1) [$2] $lastcmd";
		    log_event("CRON: $e");
		    $cron_running{$2}=$e;
		    next;
		}
		if (/^<\s+\S+ (\d+)/) {
		    log_event("CRON: EXIT $cron_running{$1}");
		    delete $cron_running{$1};
		    next;
		}

	    } else {
		die "Data on unknown filehandle $fd\n";
	    }
	}
    }   
}

sub insert_trend {
    my ($device,$name,$value)=@_;

    my $now=time();
    $query="INSERT INTO trend_data(config_id,when,device,name,value) VALUES ($config_id,$now,'$device','$name','$value')";

    $sth=$dbh->query($query);
    die($dbh->errmsg . "\n") if $dbh->errno;
#    print " - Inserted $device/$name=$value.\n";
    print ".";
}

sub get_diskconfig {
    open(DF,"/bin/df -k|") || die "Unable to run /bin/df -k: $!\n";
    while(<DF>) {
	my ($dev,$size,$used,$avail,$capacity,$mounted)=split /\s+/;
	next if ($dev =~ /^(swap|\/proc|fd|Filesystem)/);

	($metadev)=($dev=~m:/dev/md/dsk/(d\d+):);
	if ($metadev) {
	    # solstice disksuite metadevice
	    print "* Getting metadevice config for $metadev\n";
	    open(MD,"/usr/opt/SUNWmd/sbin/metastat $metadev|") || warn "Unable to run metastat: $!\n";
	    @devs=();
	    while (<MD>) {
		($sd)=/(c\dt\dd\ds\d)/;
		push @devs,$sd if $sd;
		push @devlist,$sd if $sd;
	    }
	    close(MD);
	    $devlist{$dev}=join ',',@devs;
	} else {
	    ($sd)=($dev=~/(c\dt\dd\ds\d)/);
	    $devlist{$dev}=$sd if $sd;
	    push @devlist,$sd if $sd;
	    $device{$dev}=$dev;
	}
    }
    close(DF);

    if ($sysname eq "SunOS" and $release > 5.0) {
	my %path_to_inst;
	open(PI,"</etc/path_to_inst") ||
	    die "Can't open /etc/path_to_inst: $!\n";
	while (<PI>) {
	    ($path,$inst)=/\"(.*)\" (\d+)/;
	    $path_to_inst{$path}=$inst;
	}
	close(PI);

	foreach (@devlist) {
	    my $lp=readlink("/dev/dsk/$_");
	    $lp=~s@^.*/devices@@g; $lp=~s@:.$@@g;
	    $inst{$_}=$path_to_inst{$lp};
	    $device{"sd$path_to_inst{$lp}"}=$_;
	}
    }

    print "  Disk Config:\n";
    foreach (sort keys %devlist) { 
	my @f=();
	foreach (split ',',$devlist{$_}) {
	    push @f,"$_ (sd$inst{$_})";
	}
	print "   $_=" . (join ', ',@f) . "\n";
    }
}


sub update_config_db {
    print "  Connecting to MySQL database \"$database\" on \"$host\"\n";
    $dbh = Mysql->connect($host, $database);
    die("Error connecting to database: $!\n") unless $dbh;
    die($dbh->errmsg . "\n") if $dbh->errno;

    $query="INSERT INTO host_config(os,name,testname,start_time) VALUES ('$config{OS}','$config{HOSTNAME}','$config{TESTNAME}'," . time() . ")";
    $sth=$dbh->query($query);
    die($dbh->errmsg . "\n") if $dbh->errno;

    $config_id=$sth->insert_id;
    print "- Inserted host_config id $config_id\n";

    foreach (sort keys %config) { 
	$query="INSERT INTO config_feature(config_id,name,value,description) VALUES ($config_id,'$_','$config{$_}','$description{$_}')";
	$sth=$dbh->query($query);
	die($dbh->errmsg . "\n") if $dbh->errno;
	print " - Inserted $_\n";
    }    
}



sub die_usage {
    print STDERR "Usage: $0 -s [db server:db name] -l /path/to/logs (-t test label name)\n";
    print STDERR "  Ex.: $0 -s pulsar.sky.net:news -l /usr/lib/news/logs\n";
    exit(1);
}

# Collect some basic data about the machine.  This is very system dependent.
# Currently only solaris is fully supported.  It's not a big deal though,
# this info is largely for convenience.
sub get_osconfig {    
    chomp($config{UNAME}=`/bin/uname -a`);
    $description{UNAME}="/bin/uname -a";
    ($sysname, $nodename, $release)=split /\s+/,$config{UNAME};
    $config{OS}="$sysname $release";
    chomp($config{HOSTNAME}=`/bin/hostname`);

    sub get_posix_sysconf {
      # See what we can get out of the POSIX sysconf call.
      $config{CHILD_MAX}=sysconf(&_SC_CHILD_MAX);
      $config{OPEN_MAX}=sysconf(&_SC_OPEN_MAX);
      $config{NPROCESSORS_ONLN}=sysconf(&_NPROCESSORS_ONLN);
      $config{PHYSMEM}=sysconf(&_SC_PAGESIZE)*sysconf(&_SC_PHYS_PAGES);
     }
     eval "get_posix_sysconf()";


    if ($sysname eq "SunOS" and $release > 5.0) {
	open (SYSDEF,"/usr/sbin/sysdef|") || warn "Unable to run sysdef: $!\n";
	$flag=0;
	while (<SYSDEF>) {
	    if (/\* Tunable Parameter/) { $flag=1; next; }
	    next unless $flag;
	    last if /\* Utsname Tunables/;
	    ($num,$desc,$var)=/\s*(\d+)(.*)\((.*)\)/;
	    next unless $var;
	    $config{$var}=$num;
	    $desc=~s/^\s*//; $desc=~s/\s*$//;
	    $description{$var}=$desc;
	}
	close(SYSDEP);
	open (PRTCONF,"/usr/sbin/prtconf|") || warn "Unable to run prtconf: $!\n";
	while(<PRTCONF>) {
	    ($megs)=/Memory size: (.*) Megabytes/;
	    if ($megs) {
		$config{PHYSMEM}=$megs*1024*1024;
	    }
	}

	if (! $config{NPROCESSORS_ONLN}) {
	    my $nprocs=`psrinfo -v | grep "Status of processor" | wc -l` + 0;
	    $config{NPROCESSORS_ONLN}=$nprocs;
	    $description{NPROCESSORS_ONLN}="from psrinfo -v";
	}       
    }
}


sub log_event {
    print "LOG_EVENT: @_\n";
    
}


# Try to run this function at least once a minute.  It will take it from there.
sub run_periodic {
    $now=time();
    # things to do every 5 minutes or so
    if ($now - $lastrun > 300) {
	$lastrun=$now;
	
      $query="UPDATE host_config set end_time=$now where config_id=$config_id";
	$sth=$dbh->query($query);
	die($dbh->errmsg . "\n") if $dbh->errno;
	
	return;
    }

    # things to do every 2 minutes or so..
    if ($now - $lastrun > 120) {
    	$lastrun=$now;
	
	# incoming article rate
	chomp($count=`tail -1000 /usr/lib/news/logs/news | cut -c1-15 | uniq -c | awk '{i+=\$1} END {print i/NR}'`);
	insert_trend("news","art/sec in",$count);
		     
	# number of readers
	chomp($readers=`$config{PS} | grep nnrpd | grep -v grep | wc -l`);
	insert_trend("news","readers",$readers);
	
	return;
	
    }
    
}
