#!/usr/local/bin/perl
# =============================================================================
# realifmon.pl - Realtime monitor of host interfaces
# -----------------------------------------------------------------------------
$main::VERSION = '1.04';
# -----------------------------------------------------------------------------

=head1 NAME

realifmon.pl - Realtime monitor of host interfaces

=head1 SYNOPSIS

    $ realifmon.pl [-c COMMUNITY_NAME] [-w WAIT] [-x REGEXP] HOST

    COMMUNITY_NAME ... SNMP Community Name. Omitting uses 'public'.
    WAIT           ... Interval seconds. Default is 5.
    REGEXP         ... Specify regular expression for pickup IFs by name.
    HOST           ... Target hosts to check.

    This program is for devices which can deal SNMP version 2c.

=head1 DESCRIPTION

This program shows realtime traffic throughput of interfaces of a host on your
console with using C<snmpwalk()> and callbacking.

=head1 NOTE

This script is a sample of C<Net::SNMP::Util>.
This program is for devices which can deal SNMP version 2c.

=cut


use strict;
use warnings;
use Getopt::Std;
use Term::ANSIScreen qw/:color :screen :constants/;
use Net::SNMP::Util;

my %opt;
getopts('hv:c:w:x:', \%opt);
my $host = shift @ARGV;

sub HELP_MESSAGE {
    print "Usage: $0 [-c COMMUNITY_NAME] [-w WAIT] [-x REGEXP] HOST\n";
    exit 1;
}
HELP_MESSAGE() if ( !$host || $opt{h} );

my ($wait,$regexp) = ($opt{w}||5, $opt{x}? qr/$opt{x}/: '');
my $console = Term::ANSIScreen->new();
local $| = 1;

# make session
my ($ses, $err) = Net::SNMP->session(
    -hostname  => $host,
    -version   => "2",
    -community => ($opt{c} || "public")
);
die "[ERROR] $err\n" unless defined $ses;

# main loop
my (%pdata, %cdata);  # flag, previous and current octets data
my $first = 1;
while ( 1 ){
    %cdata = ();
    (my $ret, $err) = snmpwalk(
        snmp => $ses,
        oids => {
            sysUpTime => '1.3.6.1.2.1.1.3',
            ifTable => [
                '1.3.6.1.2.1.31.1.1.1.1',  # [0] ifName
                '1.3.6.1.2.1.2.2.1.7',     # [1] ifAdminStatus
                '1.3.6.1.2.1.2.2.1.8',     # [2] ifOperStatus
                '1.3.6.1.2.1.31.1.1.1.6',  # [3] ifHCInOctets
                '1.3.6.1.2.1.31.1.1.1.10', # [4] ifHCOutOctets
                '1.3.6.1.2.1.31.1.1.1.15', # [5] ifHighSpeed
            ] },
        -mycallback => sub {
            my ($s, $host, $key, $val) = @_;
            return 1 if $key ne 'ifTable';
            my $name = $val->[0][1];
            return 0 if ( $regexp && $name !~ /$regexp/ );
            # storing current octets data
            $cdata{$name}{t} = time;
            $cdata{$name}{i} = $val->[3][1];
            $cdata{$name}{o} = $val->[4][1];
            return 1;
        }
    );
    die "[ERROR] $err\n" unless $ret;

    # header
    $console->Cls();
    $console->Cursor(0, 0);

    printf "%s, up %s - %s\n\n",
        BOLD.$host.CLEAR, $ret->{sysUpTime}{0}, scalar(localtime(time));

    # matrix
    printf "%s%-30s (%-10s) %2s %2s %10s %10s %10s%s\n",
        UNDERSCORE, qw/ifName ifIndex Ad Op BW(Mbps) InBps(M) OutBps(M)/, CLEAR;

    my $iftable = $ret->{ifTable};
    foreach my $i ( sort { $a <=> $b } keys %{$iftable->[1]} )
    {
        my ($name, $astat, $ostat, $bw)
            = map { $iftable->[$_]{$i} } qw( 0 1 2 5 );
        if ( $first ){
            printf "%-30s (%-10d) %2d %2d %10.1f %10s %10s\n",
                $name, $i, $astat, $ostat, $bw/1000, '-', '-';
            next;   # skip first
        }

        # calculate (k)bps
        my $td = $cdata{$name}{t} - $pdata{$name}{t};
        my ($inbps, $outbps) = map {
            my $delta = $cdata{$name}{$_} - $pdata{$name}{$_};
            $delta<0? 0: $delta / $td / 1000; # Kbps
        } qw( i o );

        printf "%-30s (%-10d) %2d %2d %10.1f %10.1f %10.1f\n",
            $name, $i, $astat, $ostat, map { $_/1000 } ($bw, $inbps, $outbps);
    }

    %pdata = %cdata;
    $first = 0;
    sleep $wait;
}

__END__


=head1 EXAMPLES

Simply way to check host with specifying community name is;

    example% realifmon.pl -c max-heart luminous.precures.local

Checking traffics throughtput for every 10 seconds;

    example% realifmon.pl -c max-heart -w 10 black.precures.local

If you want check only ports of slot #2 when the device has IFs which ifNames
are set like "X/Y";

    example% realifmon.pl -c max-heart -r '2/\d+$' white.precures.local


=head1 REQUIREMENTS

C<Net::SNMP>, C<Net::SNMP::Util>


=head1 AUTHOR

t.onodera, C<< <cpan :: garakuta.net> >>


=head1 SEE ALSO

L<Net::SNMP> - Core module of C<Net::SNMP::Util> which brings us good SNMP
implementations.
L<Net::SNMP::Util::OID> - Sub module of C<Net::SNMP::Util> which provides
easy and simple functions to treat OID.
L<Net::SNMP::Util::TC> - Sub module of C<Net::SNMP::Util> which provides
easy and simple functions to treat textual conversion.


=head1 LICENSE AND COPYRIGHT

Copyright(C) 2011- Takahiro Ondoera.

This program is free software; you may redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut