#!/usr/bin/perl -w
# What:   collect transfer statistics from a Motorola TV-Cable Modem
#         SBV 4200E
# Author: Dr. Jürgen Vollmer <juergen.vollmer@informatik-vollmer.de>
# Id: cable-stats,v 1.1 2004/09/21 21:09:59 vollmer draft $

=pod

=head1 NAME

cable-stats - Collect transfer statistics from a Motorola TV-Cable Modem

=head1 SYNOPSIS

cable-stats [-h] [-c community] [-i interval] [-p count] [host]

=head1 OPTIONS

If no I<host> is given the host with the IP address 192.168.100.1 is used.
The I<host> may be given as IP address or as a host name.

=over

=item B<-c> I<community>

=item B<--community> I<community>

Specify a community, default: public.

=item B<-i> I<seconds>

=item B<--interval> I<seconds>

Poll all I<seconds> the network device, default 1.
As values for I<seconds> a floating number is accepted.

=item B<-p> I<count>

=item B<--polls> I<counts>

Get the information I<count> time from the device and then stop.
Default: infinite, until you press CTRL-C.

=item B<-h>

=item B<--help>

Print a brief help message and exits.

=item B<-m>

=item B<--manual>

Prints the manual page and exits.

=back

=head1 DESCRIPTION

This script uses the SNMP (simple network monitor protocol) to read the number
transfered bytes from a Motorola TV-Cabel modem.
My model is a I<Motorola Surfboard SBV 4200E>.
See also http://broadband.motorola.com/consumers/products/sb4200/default.asp
May be for others this script works as well.

After starting the script, it polls the network device using the I<snmpget>
command and displays the uploaded and downloaded traffic, until you stop it by
pressing CTR-C

=head1 REQUIREMENTS

You need the I<net-snmp> program suite see http://www.net-snmp.org.

=head1 EXAMPLE

cable-stats cable.me.home

reads information from host I<cable.me.home> and displays the statistics
until you press CTRL-C.

=head1 AUTHOR

Dr. Jürgen Vollmer <juergen.vollmer@informatik-vollmer.de>
If you find this software useful, I would be glad to receive a postcard
from you, showing the place where you're living.

=head1 HOMEPAGE

http://www.informatik-vollmer.de/software/cable-stats.html

=head1 COPYRIGHT

Copyright (C) 2004 Dr. Juergen Vollmer, Viktoriastrasse 15,
D-76133 Karlsruhe, Germany.

=head1 LICENSE

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
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

=head1 VERSION
1.0

=cut

use strict;
use Getopt::Long;
use File::Basename;
use Pod::Usage;

my $CMD          = basename ($0);
my $host         = "192.168.100.1";
my $community    = "public";
my $interval     = 1;
my $polls;
my $snmpget      = "/usr/bin/snmpget";
my $snmpbulkwalk = "/usr/bin/snmpbulkwalk";
my $snmp_opts    = "-v 2c";

#############################################################################
## check command line options and arguments
#############################################################################

my $help;
my $manual;
Getopt::Long::Configure ("auto_abbrev",    "no_ignore_case",
			 "getopt_compat",  "require_order");

GetOptions("community=s" => \$community,
	   "interval=f"  => \$interval,
	   "polls=i"     => \$polls,
	   "help"        => \$help,
	   "manual"      => \$manual
	   )
    or pod2usage(-exitval => 2, -verbose => 0,
		 -message => "Try --help or --man.");
pod2usage (-exitval => 1, -verbose => 1) if ($help);
pod2usage (-exitval => 0, -verbose => 2) if ($manual);
pod2usage (-exitval => 2, -verbose => 0,
	   -message => "$CMD: Too many arguments given.\n") if ($#ARGV > 0);

$host = $ARGV[0] if ($#ARGV == 0);

#############################################################################
##do the job
#############################################################################

# to get all info's:
#   system ("$snmpbulkwalk -v2c -c $community $host");

# to get info's about the interfaces:
#   system ("$snmpbulkwalk -v2c -c $community $host ifDescr");
#   system ("$snmpbulkwalk -v2c -c $community $host ifType");

# for us of some interest:
#   IF-MIB::ifDescr.3 = STRING: CATV MAC: Broadcom BCM3345 HW_REV:a1
#   IF-MIB::ifDescr.4 = STRING: CATV MAC: Broadcom BCM3345 HW_REV:a1

#   IF-MIB::ifType.3 = INTEGER: docsCableDownstream(128) --> read from internet
#   IF-MIB::ifType.4 = INTEGER: docsCableUpstream(129)   --> write to  internet

# for my Modem I found:
my $items = "ifInOctets.3 ifOutOctets.4";

sub get_snmp
# call snmpget for the given items and extract the transfer numbers
{
    my %res;
    open (F, "$snmpget $snmp_opts -c $community $host $items 2>&1 |") ||
	die "failed to snmpget\n";

    while (<F>) {
	# format of the output lines:
	# IF-MIB::ifOutOctets.2 = Counter32: 5483029

	chomp;
	# printf "$_\n";
	if (/IF-MIB::ifInOctets\.(\d+)\s+=\s+Counter32:\s+(\d+)/) {
	    # print "xxx $1 : $2\n";
	    $res{in}[$1] = $2;
	    next;
	}
	if (/IF-MIB::ifOutOctets\.(\d+)\s+=\s+Counter32:\s+(\d+)/) {
	    # print "xxx $1 : $2\n";
	    $res{out}[$1] = $2;
	    next;
	}
	die "Failed to snmpget: $_\n";
    }
    close (F);
    return %res;
}

$| = 1; # autoflushing stdout

my %start      = get_snmp();
my %diff_last  = get_snmp();
my $start_time = time();

printf "+-%29s-+-%29s-+\n", "-"x29, "-"x29;
printf "| %29s | %29s |\n", "download         ", "upload           ";
printf "| %9s : %7s : %7s | %9s : %7s : %7s |\n",
       "count  ", "average", "current",
       "count  ", "average", "current";
printf "| %9s : %17s | %9s : %17s |\n",
       "kbytes  ", "kbytes/sec   ",
       "kbytes  ", "kbytes/sec   ";
printf "+-%29s-+-%29s-+\n", "-"x29, "-"x29;

while (!defined ($polls) || $polls-- > 0) {
    my %cur       = get_snmp();
    my $diff_time = (time() - $start_time + 1) * 1024.0;

    my %diff_start;
    $diff_start{in}[3]  = $cur{in}[3]  - $start{in}[3]  ;
    $diff_start{out}[4] = $cur{out}[4] - $start{out}[4] ;

    $diff_last{in}[3]  = $cur{in}[3]  - $diff_last{in}[3]  ;
    $diff_last{out}[4] = $cur{out}[4] - $diff_last{out}[4] ;

    printf "\r";
    printf "| %9.1f : %7.1f : %7.1f | %9.1f : %7.1f : %7.1f |",
           $diff_start{in}[3]  / 1024.0,
           $diff_start{in}[3]  / $diff_time,
           $diff_last{in}[3]   / 1024.0,
           $diff_start{out}[4] / 1024.0,
           $diff_start{out}[4] / $diff_time,
           $diff_last{out}[4]  / 1024.0;
    %diff_last = %cur;
    sleep ($interval);
}

printf "\n";

#############################################################################
# Log: cable-stats,v $
# Revision 1.1  2004/09/21 21:09:59  vollmer
# Initial revision
#
#############################################################################
