#!/usr/local/bin/perl

# na_snmp - process SNMP data from a NetApp
# Copyright (C) 1998  Daniel Quinlan
#
# 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., 675 Mass Ave, Cambridge, MA 02139, USA.

$ENV{'PATH'} = "/usr/local/bin:/usr/bin:/bin";
$ENV{'MIBFILE'} = "/usr/local/share/snmp/mibs/netapp.mib";

$prog = $0;
$prog =~ s@.*/@@;

use Getopt::Long;
&GetOptions("help", "dump", "s:i", "sysstat:i", "df", "if", "quota",
	    "v", "verbose", "cpu");

# Quota Fields, for major versions 4 and 5
sub QF4_Type ()		{0;}
sub QF4_ID ()		{1;}
sub QF4_KBytesUsed ()	{2;}
sub QF4_KBytesLimit ()	{3;}
sub QF4_FilesUsed ()	{4;}
sub QF4_FilesLimit ()	{5;}
sub QF4_QuotaSpec ()	{6;}
#
sub QF5_Type ()		{0;}
sub QF5_ID ()		{1;}
sub QF5_Volume ()	{2;}
sub QF5_Tree ()		{3;}
sub QF5_KBytesUsed ()	{4;}
sub QF5_KBytesLimit ()	{5;}
sub QF5_FilesUsed ()	{6;}
sub QF5_FilesLimit ()	{7;}
sub QF5_QuotaSpec ()	{8;}
#
sub QF_Type ()		{($QF == 4) ? &QF4_Type        : &QF5_Type; }
sub QF_ID ()		{($QF == 4) ? &QF4_ID          : &QF5_ID; }
sub QF_KBytesUsed ()	{($QF == 4) ? &QF4_KBytesUsed  : &QF5_KBytesUsed; }
sub QF_KBytesLimit ()	{($QF == 4) ? &QF4_KBytesLimit : &QF5_KBytesLimit; }
sub QF_FilesUsed ()	{($QF == 4) ? &QF4_FilesUsed   : &QF5_FilesUsed; }
sub QF_FilesLimit ()	{($QF == 4) ? &QF4_FilesLimit  : &QF5_FilesLimit; }
sub QF_QuotaSpec ()	{($QF == 4) ? &QF4_QuotaSpec   : &QF5_QuotaSpec; }

if ($opt_help) {
    &usage;
    exit 0;
}

if ($ARGV[0]) {
    $host = $ARGV[0];
}
else {
    &usage;
    exit 1;
}

sub usage {
    print <<EOF;
usage: $prog [options] filer
 --verbose | -v      be more verbose
 --help              print this help
 --sysstat=n | -s n  report filer statistics every n seconds (15 is default)
 --df                summarize free disk space
 --if                summarize inode use
 --quota             report quotas
 --cpu               report CPU usage (for logging)

This program requires that the CMU SNMP tools be installed and working
correctly.
EOF
}

if (defined ($opt_sysstat) || defined ($opt_s)) {
    my $interval = 15;
    my $count = 0;
    my $first = 1;
    my $query = join(' ', "cpu.cpuUpTime.0", "cpu.cpuBusyTime.0",
		     "misc.miscNfsOps.0", "misc.miscNfsOps.0",
		     "misc.miscNetRcvdKB.0", "misc.miscNetSentKB.0");

    $ENV{'PREFIX'} = ".iso.org.dod.internet.private.enterprises.netapp.netapp1.sysStat";

    if ($opt_sysstat) {
	$interval = $opt_sysstat;
    }
    elsif ($opt_s) {
	$interval = $opt_s;
    }

    if ($interval < 1) {
	die("$prog: sysstat interval must be greater than 0\n");
    }

    for (;;) {
	if ($count-- == 0) {
	    print " CPU    NFS      Net kB/s\n";
	    print "                 in   out\n";
	    $count = 20;
	}

	open (SNMPGET, "snmpget -v 1 $host public $query |");
	while (<SNMPGET>) {
	    /cpuUpTime\.0 = Timeticks: \((\d+)\)/
		&& ($cpuUpTime2 = $1);
	    /cpuBusyTime\.0 = Timeticks: \((\d+)\)/
		&& ($cpuBusyTime2 = $1);
	    /miscNfsOps\.0 = (\d+)/
		&& ($miscNfsOps2 = $1);
	    /miscNetRcvdKB\.0 = (\d+)/
		&& ($miscNetRcvdKB2 = $1);
	    /miscNetSentKB\.0 = (\d+)/
		&& ($miscNetSentKB2 = $1);
	}
	close (SNMPGET);

	if ($first) {
	    $first = 0;
	}
	else {
	    $cpu = int (($cpuBusyTime2 - $cpuBusyTime1) /
			($cpuUpTime2 - $cpuUpTime1) * 100);
	    $NfsOps = int (($miscNfsOps2 - $miscNfsOps1) / $interval);

	    # work-around
	    if ($miscNetRcvdKB2 < $miscNetRcvdKB1) {
		if ($opt_v || $opt_verbose) {
		    print "$prog: Net in counter flipped\n";
		}
		$miscNetRcvdKB1 -= (2**32 / 1024);
	    }
	    if ($miscNetSentKB2 < $miscNetSentKB1) {
		if ($opt_v || $opt_verbose) {
		    print "$prog: Net out counter flipped\n";
		}
		$miscNetSentKB1 -= (2**32 / 1024);
	    }
	    $NetIn = int (($miscNetRcvdKB2 - $miscNetRcvdKB1) / $interval);
	    $NetOut = int (($miscNetSentKB2 - $miscNetSentKB1) / $interval);

	    printf "%3d%% %6d   %5d %5d\n", $cpu, $NfsOps, $NetIn, $NetOut;
	}

	$cpuBusyTime1 = $cpuBusyTime2;
	$cpuUpTime1 = $cpuUpTime2;
	$miscNfsOps1 = $miscNfsOps2;
	$miscNetRcvdKB1 = $miscNetRcvdKB2;
	$miscNetSentKB1 = $miscNetSentKB2;

	sleep $interval;
    }
}

if ($opt_dump) {
    $ENV{'PREFIX'} = ".iso.org.dod.internet.private.enterprises";
    open (SNMPWALK, "snmpwalk -v 1 $host public netapp |");
    while (<SNMPWALK>) {
	print $_;
    }
    close (SNMPWALK);
}

if ($opt_df || $opt_if) {
    my $min, $max, $i;
    my $query = "";

    $ENV{'PREFIX'} = ".iso.org.dod.internet.private.enterprises.netapp.netapp1.filesys.dfTable.dfEntry";

    open (SNMPWALK, "snmpwalk -v 1 $host public dfIndex |");
    while (<SNMPWALK>) {
	/dfTable.dfEntry.dfIndex.(\d+) = (\d+)/ && do {
	    $dfIndex[$1] = $2;
	    if (!defined($min)) {
		$min = $1;
	    }
	    $max = $1;
	};
    }
    close (SNMPWALK);

    for ($i = $min; $i <= $max; $i++) {
	$query .= join(' ', "dfEntry.dfFileSys.$i",
		      "dfEntry.dfKBytesTotal.$i",
		      "dfEntry.dfKBytesUsed.$i",
		      "dfEntry.dfKBytesAvail.$i",
		      "dfEntry.dfPerCentKBytesCapacity.$i",
		      "dfEntry.dfInodesUsed.$i",
		      "dfEntry.dfInodesFree.$i",
		      "dfEntry.dfPerCentInodeCapacity.$i",
		      "dfEntry.dfMountedOn.$i ");
    }
    $query =~ s/ $//;

    $ENV{'PREFIX'} = ".iso.org.dod.internet.private.enterprises.netapp.netapp1.filesys.dfTable";

    open (SNMPGET, "snmpget -v 1 $host public $query |");
    while (<SNMPGET>) {
	#print STDERR $_;
	/dfFileSys.(\d+) = "(\S+)"/
	    && ($dfFileSys[$1] = $2);
	/dfKBytesTotal.(\d+) = (\d+)/
	    && ($dfKBytesTotal[$1] = $2);
	/dfKBytesUsed.(\d+) = (\d+)/
	    && ($dfKBytesUsed[$1] = $2);
	/dfKBytesAvail.(\d+) = (-?\d+)/
	    && ($dfKBytesAvail[$1] = $2);
	/dfPerCentKBytesCapacity.(\d+) = (\d+)/
	    && ($dfPerCentKBytesCapacity[$1] = $2);
	/dfInodesUsed.(\d+) = (\d+)/
	    && ($dfInodesUsed[$1] = $2);
	/dfInodesFree.(\d+) = (\d+)/
	    && ($dfInodesFree[$1] = $2);
	/dfPerCentInodeCapacity.(\d+) = (\d+)/
	    && ($dfPerCentInodeCapacity[$1] = $2);
	/dfMountedOn.(\d+) = "(\S+)"/
	    && ($dfMountedOn[$1] = $2);
    }
    close (SNMPGET);

    if ($opt_df) {
	print "Filesystem     kbytes       used      avail capacity  Mounted on\n";
	for ($i = $min; $i <= $max; $i++) {
	    printf "%-10s %10d %10d %10d   %3d%%    %s\n",
	      $dfFileSys[$i],
	      $dfKBytesTotal[$i],
	      $dfKBytesUsed[$i],
	      $dfKBytesAvail[$i],
	      $dfPerCentKBytesCapacity[$i],
	      $dfMountedOn[$i];
  	}
    }
    if ($opt_if) {
	print "Filesystem      iused      ifree  %iused  Mounted on\n";
	printf "%-10s %10d %10d   %3d%%   %s\n",
	  $dfFileSys[1],
	  $dfInodesUsed[1],
	  $dfInodesFree[1],
	  $dfPerCentInodeCapacity[1],
	  $dfMountedOn[1];
    }
}

if ($opt_quota) {
    local $vtable_size = 0;
    local @entries, @tmp;

    $ENV{'PREFIX'} = ".iso.org.dod.internet.private.enterprises.netapp.netapp1";

    # this should really use the qvStateTable
    open (SNMPGET, "snmpget -v 1 $host public quota.quotaState.0 |");
    while (<SNMPGET>) {
	/quotaState\.0 = quotaStateOff/
	    && die("$prog: quotas are off.\n");
	/quotaState\.0 = quotaStateInit/
	    && die("$prog: quotas are initializing.\n");
    }
    close (SNMPGET);

    open (SNMPWALK, "snmpwalk -v 1 $host public quota |");
    while (<SNMPWALK>) {
#	print $_;
	# get volume names
	if (/qvStateTable.qvStateEntry.qvStateName.([0-9]+) = \"(.*)\"/) {
	    $volume_names[$1] = $2;
	}

	# get entries
        if (/qrVTable\.qrVEntry\.qrVType\.([0-9]+)\.([0-9]+) = qrVType([A-Za-z]+)\([0-9]+\)/) {
	    $entries[$2][&QF5_Type] = $3;
	    $entries[$2][&QF5_Volume] = $volume_names[$1];
	}
	if (/qrVTable\.qrVEntry\.qrVId\.([0-9]+)\.([0-9]+) = ([0-9]+)/) {
	    $entries[$2][&QF5_ID] = $3;
	}	    
	if (/qrVTable\.qrVEntry\.qrVKBytesUsed\.([0-9]+)\.([0-9]+) = ([0-9]+)/) {
	    $entries[$2][&QF5_KBytesUsed] = $3;
	}
	if (/qrVTable\.qrVEntry\.qrVKBytesLimit\.([0-9]+)\.([0-9]+) = ([0-9]+)/) {
	    $entries[$2][&QF5_KBytesLimit] = $3;
	}
	if (/qrVTable\.qrVEntry\.qrVFilesUsed\.([0-9]+)\.([0-9]+) = ([0-9]+)/) {
	    $entries[$2][&QF5_FilesUsed] = $3;
	}
	if (/qrVTable\.qrVEntry\.qrVFilesLimit\.([0-9]+)\.([0-9]+) = (-?[0-9]+)/) {
	    $entries[$2][&QF5_FilesLimit] = $3;
	}
	# broken???
	if (/qrVTable\.qrVEntry\.qrVPathName\.([0-9]+)\.([0-9]+) = \"(.*)\"/) {
	    $entries[$2][&QF5_QuotaSpec] = $3;
	}
#	if (/qrVTable\.qrVEntry\.qrVVolume\.([0-9]+)\.([0-9]+) = ([0-9]+)/) {
#	    $entries[$2][&QF5_Volume] = $3;
#	}
    }
    print <<'EOF';
                                        K-Bytes          Files
Type       ID    Volume    Tree     Used    Limit    Used    Limit   Quota Specifier
----- --------  -------- --------  ------- ------- -------- -------- -----------------
EOF
    for ($i = 1; $i <= $#entries; $i++) {
	if ($entries[$i][&QF5_Type] eq "User") {
	    if (getpwuid($entries[$i][&QF5_ID])) {
		$entries[$i][&QF5_ID] = (getpwuid($entries[$i][&QF5_ID]))[0];
	    }
	}
	if ($entries[$i][&QF5_Type] eq "UserDefault") {
	    $entries[$i][&QF5_Type] = "User";
	    $entries[$i][&QF5_ID] = "*";
	}
	$entries[$i][&QF5_Type] = lcfirst($entries[$i][&QF5_Type]);

	# NetApp bug 8704: tree information is missing from SNMP implementation
	if (! $entries[$i][&QF5_Tree]) {
	    $entries[$i][&QF5_Tree] = "xxx";
	}
	if ($entries[$i][&QF5_FilesLimit] = -1) {
#	    $entries[$i][&QF5_FilesLimit] = "-";
	    $entries[$i][&QF5_FilesLimit] = "";
	}

	printf ("%-5s %-8s  %-4s %12.12s  %7s %7s %8s %8s %-22s\n",
		@{ $entries[$i] }[0..8]);
    }
}

if ($opt_cpu) {
    $ENV{'PREFIX'} = ".iso.org.dod.internet.private.enterprises.netapp.netapp1.sysStat.cpu";

    $query .= 'cpuUpTime.0 cpuBusyTime.0 cpuIdleTime.0';

    open (SNMPGET, "snmpget -v 1 $host public $query |");
    while (<SNMPGET>) {
	/cpuUpTime\.0 = Timeticks: \(([0-9]+)\)/ && ($cpuUp = $1);
	/cpuBusyTime\.0 = Timeticks: \(([0-9]+)\)/ && ($cpuBusy = $1);
	/cpuIdleTime\.0 = Timeticks: \(([0-9]+)\)/ && ($cpuIdle = $1);
    }
    close (SNMPGET);
    $time = time();
    print "time:$time\tup:$cpuUp\tbusy:$cpuBusy\tidle:$cpuIdle\n";
}
