#!/usr/bin/perl
#============================================================================
# Program: perfi.pl
# 
# (C) Andreas Jobs, Network Operation Center RUB, Bochum, Germany
# Some parts of the code are taken from the orginal nedi code
#
# DATE          COMMENT
#----------------------------------------------------------------------------
# 07/04/08      v0.3  first production version at noc.rub.de
# 08/04/08	v0.4  added hi-counter where necessary
# 08/05/08	v0.5  moved creation of system.rrd here

my $version = "0.4 8.Apr 2008";
$|=1;
#============================================================================
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#============================================================================
# Visit http://www.nedi.ch for more information.
#============================================================================


use strict;
use Getopt::Std;
use RRDs;

use vars qw( $i $n $p %opt %dev %int $rrdpath $rrdstep $MAX_SESSIONS $MASTER_TIMEOUT $MAX_DEV $VERBOSE );


# Get nedi base
$p = $0;
$p =~ s/(.*)\/(.*)/$1/;
if($0 eq $p){$p = "."};

$rrdpath = "$main::p/rrd";
$rrdstep = 300;		# override config settings for now

getopts('s:t:m:I:v',\%opt) or &Help();

$MAX_SESSIONS = $opt{s} || 50;
$MASTER_TIMEOUT = $opt{t} || 240;

$MAX_DEV = $opt{m} || 999999;
$VERBOSE = $opt{v} || 0;
$opt{D}  = !$VERBOSE;		# Fake -D for not printing db read stats if not on verbose mode
$opt{I}  = 1 unless $opt{I};
$opt{I}  = 1 if $opt{I} < 1;

use SNMP::Effective;
use Data::Dumper;

require "$p/inc/libmisc.pl";	# Use the miscellaneous nedi library
&misc::ReadConf();
require "$p/inc/libdb-" . lc($misc::backend) . ".pl" || die "Backend error ($misc::backend)!";


my $sysO        = '1.3.6.1.2.1.1.2.0';

my $ifidxO	= '1.3.6.1.2.1.2.2.1.1';
my $ifnamO      = '1.3.6.1.2.1.31.1.1.1.1';
my $ifadmO      = '1.3.6.1.2.1.2.2.1.7';
my $ifinoO      = '1.3.6.1.2.1.2.2.1.10';
my $ifineO      = '1.3.6.1.2.1.2.2.1.14';
my $ifotoO      = '1.3.6.1.2.1.2.2.1.16';
my $ifoteO      = '1.3.6.1.2.1.2.2.1.20';

my $ifhioO      = '1.3.6.1.2.1.31.1.1.1.6';
my $ifhooO      = '1.3.6.1.2.1.31.1.1.1.10';
my $ifhspO      = '1.3.6.1.2.1.31.1.1.1.15';

# Read already discoverd devices
&db::ReadDev();

# Collect the data
&CollectData();

exit;

# The main working routine
sub CollectData {

  my $query = SNMP::Effective->new( max_sessions => $MAX_SESSIONS, master_timeout => $MASTER_TIMEOUT );
  my $query1 = SNMP::Effective->new( max_sessions => $MAX_SESSIONS, master_timeout => $MASTER_TIMEOUT );

  foreach (keys %dev) {
    LoadSysObjData($dev{$_}{so});

    print "Adding $_ with IP:", $dev{$_}{ip}, " with sysObject: ", $dev{$_}{so}, "\n" if $VERBOSE;
    if ($_ =~ /cat6500-rz-1/) {
      print "Details for $_\n";
      print "\thc     -> ", $dev{$_}{hc}, "\n";
      print "\tifidxO -> $ifidxO\n";
      if ($dev{$_}{hc}) {
        print "\tifhioO -> $ifhioO\n";
	print "\tifhooO -> $ifhooO\n";
      } else {
        print "\tifinoO -> $ifinoO\n";
	print "\tifotoO -> $ifotoO\n";
      }
      print "\tifineO -> $ifineO\n";
      print "\tifoteO -> $ifoteO\n";
      print "\tifnamO -> $ifnamO\n";
      print "\tifadmO -> $ifadmO\n";
      print "\tso-rc  -> ", $misc::sysobj{$dev{$_}{so}}{rc}, "\n";
      print "\tso-rm  -> ", $misc::sysobj{$dev{$_}{so}}{rm}, "\n";
      print "\tso-ri  -> ", $misc::sysobj{$dev{$_}{so}}{ri}, "\n";
      print "\tso-rt  -> ", $misc::sysobj{$dev{$_}{so}}{rt}, "\n";
    }

    # Construct varbinds according to sysObject thing
    my @vars = ();
    push @vars, $ifidxO;
    push @vars, $ifineO, $ifoteO, $ifnamO, $ifadmO;
    if ($dev{$_}{hc}) {
      push @vars, $ifhioO, $ifhooO;
    } else {
      push @vars, $ifinoO, $ifotoO;
    }

    my @gvars = ();
    push @gvars, $misc::sysobj{$dev{$_}{so}}{rc} if $misc::sysobj{$dev{$_}{so}}{rc};
    push @gvars, $misc::sysobj{$dev{$_}{so}}{rm} if $misc::sysobj{$dev{$_}{so}}{rm};
    push @gvars, $misc::sysobj{$dev{$_}{so}}{ri} if $misc::sysobj{$dev{$_}{so}}{ri};
    push @gvars, $misc::sysobj{$dev{$_}{so}}{rt} if $misc::sysobj{$dev{$_}{so}}{rt};

if ($dev{$_}{hc}) {
    # add the query
    # heap may contain a ref to some device specific data
    $query->add(
	dest_host	=> $dev{$_}{ip},
	callback	=> \&StoreDataCallback,
	walk		=> \@vars,
	arg		=> {
			Community  => $dev{$_}{cm},
			Version    => 2,
			},
        heap		=> {name => $_}
	);
    $query->add(
	dest_host	=> $dev{$_}{ip},
	get		=> \@gvars
        ) if scalar @gvars;;
} else {

    # add the query
    # heap may contain a ref to some device specific data
    $query1->add(
	dest_host	=> $dev{$_}{ip},
	callback	=> \&StoreDataCallback,
	walk		=> \@vars,
	arg		=> {
			Community  => $dev{$_}{cm},
			Version    => 1,
			},
        heap		=> {name => $_}
	);
    $query1->add(
	dest_host	=> $dev{$_}{ip},
	get		=> \@gvars
        ) if scalar @gvars;;
}

  }
  $query->execute();
  #$query1->execute();
  print "Collector finished\n" if $VERBOSE;
}

###### SUBROUTINES #######
#
# This may be unnecesary until we support system.rrd too.
sub LoadSysObjData {
  my $so = $_[0];

  if(!exists $misc::sysobj{$so}){					# Load .def if not done already
    print "Loading sysobject data for $so\n" if $VERBOSE;
    if (-e "$main::p/sysobj/$so.def"){
      open  ("DEF", "$main::p/sysobj/$so.def");
    }else{
      open  ("DEF","$main::p/sysobj/other.def");
    }
    my @def = <DEF>;
    close("DEF");
    chomp @def;
    $misc::sysobj{$so}{bf} = "";
    $misc::sysobj{$so}{ty} = $so;
    $misc::sysobj{$so}{hc} = 0;
    $misc::sysobj{$so}{mv} = 0;
    foreach my $l (@def){
      if ($l !~ /^[#;]|^\s*$/){
	my @v  = split(/\s+/,$l);
	if (!defined $v[1]){$v[1] = ""}
	if ($v[0] eq "Type")		{$misc::sysobj{$so}{ty} = $v[1]}
	elsif ($v[0] eq "OS")		{$misc::sysobj{$so}{os} = $v[1]}
	elsif ($v[0] eq "Icon")		{$misc::sysobj{$so}{ic} = $v[1]}
	elsif ($v[0] eq "SNMPv")	{
	  $misc::sysobj{$so}{sp} = substr($v[1],0,1);
	  if(substr($v[1],1,2) eq 'HC'){$misc::sysobj{$so}{hc} = 128}
	}
	elsif ($v[0] eq "Serial")	{$misc::sysobj{$so}{sn} = $v[1]}
	elsif ($v[0] eq "Bimage")	{$misc::sysobj{$so}{bi} = $v[1]}
	elsif ($v[0] eq "Bridge")	{$misc::sysobj{$so}{bf} = $v[1]}
	elsif ($v[0] eq "Dispro")	{$misc::sysobj{$so}{dp} = $v[1]}

	elsif ($v[0] eq "VLnams")	{$misc::sysobj{$so}{vn} = $v[1]}
	elsif ($v[0] eq "VTPdom")	{$misc::sysobj{$so}{vd} = $v[1]}
	elsif ($v[0] eq "VTPmod")	{$misc::sysobj{$so}{vm} = $v[1]}

	elsif ($v[0] eq "IFalia")	{$misc::sysobj{$so}{al} = $v[1]}
	elsif ($v[0] eq "IFalix")	{$misc::sysobj{$so}{ax} = $v[1]}
	elsif ($v[0] eq "IFdupl")	{$misc::sysobj{$so}{du} = $v[1]}
	elsif ($v[0] eq "IFduix")	{$misc::sysobj{$so}{dx} = $v[1]}
	elsif ($v[0] eq "Halfdp")	{$misc::sysobj{$so}{hd} = $v[1]}
	elsif ($v[0] eq "Fulldp")	{$misc::sysobj{$so}{fd} = $v[1]}
	elsif ($v[0] eq "IFvlan")	{$misc::sysobj{$so}{vi} = $v[1]}
	elsif ($v[0] eq "IFvlix")	{$misc::sysobj{$so}{vx} = $v[1]}

	elsif ($v[0] eq "Modesc")	{$misc::sysobj{$so}{md} = $v[1]}
	elsif ($v[0] eq "Moclas")	{$misc::sysobj{$so}{mc} = $v[1]}
	elsif ($v[0] eq "Movalu")	{$misc::sysobj{$so}{mv} = $v[1]}
	elsif ($v[0] eq "Mostep")	{$misc::sysobj{$so}{mp} = $v[1]}
	elsif ($v[0] eq "Moslot")	{$misc::sysobj{$so}{mt} = $v[1]}
	elsif ($v[0] eq "Modhw")	{$misc::sysobj{$so}{mh} = $v[1]}
	elsif ($v[0] eq "Modsw")	{$misc::sysobj{$so}{ms} = $v[1]}
	elsif ($v[0] eq "Modfw")	{$misc::sysobj{$so}{mf} = $v[1]}
	elsif ($v[0] eq "Modser")	{$misc::sysobj{$so}{mn} = $v[1]}
	elsif ($v[0] eq "Momodl")	{$misc::sysobj{$so}{mm} = $v[1]}


	elsif ($v[0] eq "CPUutl")	{$misc::sysobj{$so}{rc} = $v[1]}
	elsif ($v[0] eq "MemCPU")	{$misc::sysobj{$so}{rm} = $v[1]}
	elsif ($v[0] eq "MemIO")	{$misc::sysobj{$so}{ri} = $v[1]}
	elsif ($v[0] eq "Temp")		{$misc::sysobj{$so}{rt} = $v[1]}
      }
    }
  print "done.\n" if $VERBOSE;
  }
}



#===================================================================
#  RRD-Processing
#===================================================================
#
#   1  device     z.B.  "cat2900-ga1s-1"
#   2  interface  z.B.  "Fa0/19"
#   3  inoct
#   4  outoct
#   5  inerr
#   6  outerr
#
#   globales  $rrdpath
#             $rrdstep
#

sub UpdateRRD {
  my ($dv,$irf,$inoct,$outoct,$inerr,$outerr) = @_;;

  my $ok;
  my $ERR;

  $irf =~ s/([^a-zA-Z0-9_.-])/"%" . uc(sprintf("%2.2x",ord($1)))/eg;
  if (-e "$rrdpath/$dv/$irf.rrd"){
    $ok = 1;
  } else {
    # check if directory present
    #
    if (!(-d "$rrdpath/$dv")) {
      if (-d "$rrdpath") {
	mkdir "$rrdpath/$dv";
      } else {
	mkdir "$rrdpath";
	mkdir "$rrdpath/$dv";
      }
    }
    my $ds = 2 * $rrdstep;
    $ok = 1;
    RRDs::create (
        "$rrdpath/$dv/$irf.rrd",
        "-s","$rrdstep",
        "DS:inoct:COUNTER:$ds:0:10000000000",
        "DS:outoct:COUNTER:$ds:0:10000000000",
        "DS:inerr:COUNTER:$ds:0:10000000000",
        "DS:outerr:COUNTER:$ds:0:10000000000",
        "RRA:AVERAGE:0.5:1:600",
        "RRA:AVERAGE:0.5:6:600",
        "RRA:AVERAGE:0.5:24:600",
        "RRA:AVERAGE:0.5:288:600",
        );
    $ERR = RRDs::error;
    $ok=0, print "Error creating RRD file $rrdpath/$dv/$irf.rrd: $ERR\n" if $ERR;
  }
  if ($ok) {
    print "$rrdpath/$dv/$irf.rrd N:$inoct:$outoct:$inerr:$outerr\n" if $VERBOSE;
    RRDs::update ( "$rrdpath/$dv/$irf.rrd","N:$inoct:$outoct:$inerr:$outerr" );
    $ERR = RRDs::error;
    print "Error updating RRD file $rrdpath/$dv/$irf.rrd: $ERR\n" if $ERR;
  } 
}

sub UpdateSystemRRD {
  my ($dv,$cpu,$mcp,$mio,$tmp) = @_;

  my $ok;
  my $ERR;

  if (-e "$rrdpath/$dv/system.rrd"){
    $ok = 1;
  }else{
    # check if directory present
    #
    if (!(-d "$rrdpath/$dv")) {
      if (-d "$rrdpath") {
	mkdir "$rrdpath/$dv";
      } else {
	mkdir "$rrdpath";
	mkdir "$rrdpath/$dv";
      }
    }
    my $ds = 2 * $rrdstep;
    $ok = 1;
    RRDs::create (
        "$rrdpath/$dv/system.rrd",
        "-s","$rrdstep",
        "DS:cpu:GAUGE:$ds:0:100",
        "DS:memcpu:GAUGE:$ds:0:U",
        "DS:memio:GAUGE:$ds:0:U",
        "DS:temp:GAUGE:$ds:-1000:1000",
        "RRA:AVERAGE:0.5:1:600",
        "RRA:AVERAGE:0.5:24:600"
        );
    $ERR = RRDs::error;
    $ok=0, print "Error creating RRD file $rrdpath/$dv/system.rrd: $ERR\n" if $ERR;
  }
  if ($ok) {
    print "$rrdpath/$dv/system.rrd N:$cpu:$mcp:$mio:$tmp\n" if $VERBOSE;
    RRDs::update ( "$rrdpath/$dv/system.rrd","N:$cpu:$mcp:$mio:$tmp" );
    $ERR = RRDs::error;
    print "Error updating RRD file $rrdpath/$dv/system.rrd: $ERR\n" if $ERR;
  }
}

# routine for storing data
sub StoreDataCallback {
  my ($host, $error) = @_;

  use vars qw ($ioc $ooc $iec $oec $ina $sta $cpu $mcp $mio $tmp);

  if ($error) {
    print ("$host failed with error: $error\n");
    return;
  }

  my $data = $host->data;
  my $name = $host->heap->{'name'};

  print "StoreDataCallback called for $name($host)\n" if $VERBOSE;

  if (defined $data->{$ifidxO}) {
#print "Updating Int for $name ", Dumper($data), "\n";
#print "Updating Int for $name ", Dumper($data), "\n" if $name =~ /ATM/;
    # Update interface data
    foreach (keys %{$data->{$ifidxO}}) {
      $ioc = exists $data->{$ifhioO}{$_} ? $data->{$ifhioO}{$_} : $data->{$ifinoO}{$_};
      $ooc = exists $data->{$ifhooO}{$_} ? $data->{$ifhooO}{$_} : $data->{$ifotoO}{$_};
      $iec = $data->{$ifineO}{$_};
      $oec = $data->{$ifoteO}{$_};
      $ina = $data->{$ifnamO}{$_};
      $sta = $data->{$ifadmO}{$_};

      &UpdateRRD($name, $ina, $ioc, $ooc, $iec, $oec);
    }
  } else {
#print "Updating Sys for $name ", Dumper($data), "\n";
#print "Updating Sys for $name ", Dumper($data), "\n" if $name =~ /ATM/;
print "Updating Sys for $name ", Dumper($data), "\n" if $name =~ /cat6500-rz-1/;
    # Update system data
    $cpu = &GetSafeValue($data->{$misc::sysobj{$dev{$name}{so}}{rc}}->{'1'});
    $mcp = &GetSafeValue($data->{$misc::sysobj{$dev{$name}{so}}{rm}}->{'1'});
    $mio = &GetSafeValue($data->{$misc::sysobj{$dev{$name}{so}}{ri}}->{'1'});
    $tmp = &GetSafeValue($data->{$misc::sysobj{$dev{$name}{so}}{rt}}->{'1'});

    &UpdateSystemRRD($name, $cpu, $mcp, $mio, $tmp);
  }

  return;
}


#===================================================================
# Display some help
#===================================================================
sub Help {
  print "\n";
  print "usage: perfi.pl <options>\n";
  print "--- a few options to play with\n";
  print "-s <num> number of SNMP sessions in parallel (Default: 50)\n";
  print "-t <num> master timeout for the complete operation (Default: 240)\n";
  print "-v       be verbose\n";
  die "Nedi's perfi $version\n";
}

sub GetSafeValue {
  return 0 unless $_[0];
  return 0 unless $_[0] =~ /^\d+$/;
  return $_[0];
}

