#!/usr/bin/perl -w
use strict;

# Quick'n'dirty Rackshack bandwidth stats aggregator using rrdtool
# Copyright (c) 2003 Eike Frost (rrdtool@kefro.st)

use LWP::Simple;
use RRDs;
use Storable;

my $maxsources = 30;
my $graphdir = 'graphs/';
my $rrdfile = 'rs_interfaces.rrd';

my @colors_in = qw(00e000 00d800 00d000 00c800 00c000 00b800 00b000 00a800
                   00a000 009800 009000 008800 008000 007800 007000 006800
                   006000);
my @colors_out = qw(0000ff 0000f8 0000f0 0000e8 0000e0 0000d8 0000d0 0000c8
                    0000c0 0000b8 0000b0 0000a8 0000a0 000098 000090 000088
                    000080);

my %stored = (-e 'rs_interfaces.storable' ? 
              %{retrieve('rs_interfaces.storable')} : ());
my @ifs = (defined $stored{list} ? @{$stored{list}} : ());
my %ifs = (defined $stored{hash} ? %{$stored{hash}} : ());

my $networks = get("http://www.rackshack.net/english/aboutus/networks.asp");
my @ifsadr = ($networks =~ m/a href="(http:\/\/www.ev1.net\/stats\/.*?)"/gis);

if (!-e 'rs_interfaces.rrd') {
    my @datasources = ();
    for (my $i = 0; $i < $maxsources; $i++) {
        push @datasources, 'DS:ifin_' .$i.':GAUGE:600:0:125000000',
	                   'DS:ifout_'.$i.':GAUGE:600:0:125000000';	    
    }
    RRDs::create ('rs_interfaces.rrd', '--start', time(), @datasources, 
                  '--step', 300, 
                  'RRA:AVERAGE:0.5:1:2000', 'RRA:AVERAGE:0.5:6:2000',
                  'RRA:AVERAGE:0.5:24:2000', 'RRA:AVERAGE:0.5:288:2000',
                  'RRA:MAX:0.5:1:2000', 'RRA:MAX:0.5:6:2000',
                  'RRA:MAX:0.5:24:2000', 'RRA:MAX:0.5:288:2000');
}

foreach my $if (@ifsadr) {
    my $ifadr = get($if);
    my $ifname = ($ifadr =~ m/<TITLE>.*to (.*)<\/TITLE>/gis)[0];

    unless (exists $ifs{$ifname}) {
	push @ifs, $ifname;
    }

    $ifs{$ifname}{cuin} = ($ifadr =~ m/<!-- cuin d ([0-9]+) -->/gis)[0];
    $ifs{$ifname}{cuout} = ($ifadr =~ m/<!-- cuout d ([0-9]+) -->/gis)[0];        
}

my $output = 'N:';

foreach my $if (@ifs) {
    $output .= int($ifs{$if}{cuin}).':'.int($ifs{$if}{cuout}).':';
}
if (scalar @ifs < $maxsources) {
    for (my $i = 0; $i < $maxsources - scalar @ifs; $i++) {
        $output .= 'U:U:'
    }
}
$output = substr $output, 0, -1;

RRDs::update ($rrdfile, $output);
my $ERR = RRDs::error;
die "ERROR while updating rs_interfaces.rrd: $ERR\n" if $ERR;

my @defs = ();

my $lastbytes = 'CDEF:totalbytes=0,';
my $lastinbytes = 'CDEF:totalinbytes=0,';
my $lastoutbytes = 'CDEF:totaloutbytes=0,';
my $lastin = 'CDEF:totalinbits=0,';
my $lastout = 'CDEF:totaloutbits=0,';

my $timespan = 259200;

for (my $i = 0; $i < scalar @ifs; $i++) {
    push @defs, 'DEF:ifinbytes_'.$i.'='.$rrdfile.':ifin_'.$i.':AVERAGE',
                'DEF:ifoutbytes_'.$i.'='.$rrdfile.':ifout_'.$i.':AVERAGE',
                'CDEF:ifinbits_'.$i.'=ifinbytes_'.$i.',8,*',
                'CDEF:ifoutbits_'.$i.'=ifoutbytes_'.$i.',8,*',
                'CDEF:ifoutbitsneg_'.$i.'=ifoutbits_'.$i.',-1,*',
		'CDEF:iftotalinbytes_'.$i.'=ifinbytes_'.$i.
                  ',0,125000000,LIMIT,UN,0,ifinbytes_'.$i.',IF,'
                  .$timespan.',*',
		'CDEF:iftotaloutbytes_'.$i.'=ifoutbytes_'.$i.
                  ',0,125000000,LIMIT,UN,0,ifoutbytes_'.$i.',IF,'
                  .$timespan.',*',
		'CDEF:iftotalbytes_'.$i.'=iftotalinbytes_'.$i.
                  ',iftotaloutbytes_'.$i.',+';
    $lastbytes .= 'iftotalbytes_'.$i.',+,';
    $lastinbytes .= 'iftotalinbytes_'.$i.',+,';
    $lastoutbytes .= 'iftotaloutbytes_'.$i.',+,';
    $lastin .= 'ifinbits_'.$i.',+,';
    $lastout .= 'ifoutbits_'.$i.',+,';
}

push @defs, map { substr $_, 0, -1 } ($lastbytes, $lastin, $lastout,
                                      $lastinbytes, $lastoutbytes);    
push @defs, 'CDEF:totalbits=totalinbits,totaloutbits,+';

sub trim {
    my $s = shift;
    $s = substr $s, 0, 11;
    if (length ($s) < 11) {
        $s .= ' 'x(11-length($s));
    } 
    return $s;
}

my @areas = ();

my $count = 0;
push @areas, 'AREA:ifinbits_0#'.$colors_in[0].':'.trim($ifs[0]);
push @areas, 'GPRINT:ifinbits_0:AVERAGE:%3.0lf %sb/s';
push @areas, 'GPRINT:iftotalinbytes_0:AVERAGE:%5.1lf %sB';

for (my $i = 1; $i < scalar @ifs; $i++) {
    push @areas, 'STACK:ifinbits_'.$i.'#'.$colors_in[$i].':'.trim($ifs[$i]);
    push @areas, 'GPRINT:ifinbits_'.$i.':AVERAGE:%3.0lf %sb/s';
    push @areas, 'GPRINT:iftotalinbytes_'.$i.':AVERAGE:%5.1lf %sB';

    if ($i%4 == 3) {
       $count++;
       if ($count == 1) {
           push @areas, 'GPRINT:totalinbits:AVERAGE:%6.2lf %sb/s';
       } elsif ($count == 2) {
           push @areas, 'GPRINT:totalinbits:LAST:%6.2lf %sb/s';
       } elsif ($count == 3) {
           push @areas, 'GPRINT:totalinbytes:AVERAGE:%6.2lf %sB';
       }       
       
       push @areas, 'COMMENT:\n';
    }
} 

push @areas, 'COMMENT:\n';

$count = 0;
push @areas, 'AREA:ifoutbitsneg_0#'.$colors_out[0].':'.trim($ifs[0]);
push @areas, 'GPRINT:ifoutbits_0:AVERAGE:%3.0lf %sb/s';
push @areas, 'GPRINT:iftotaloutbytes_0:AVERAGE:%5.1lf %sB';

for (my $i = 1; $i < scalar @ifs; $i++) {
    push @areas, 'STACK:ifoutbitsneg_'.$i.'#'.$colors_out[$i].':'.
                  trim($ifs[$i]);
    push @areas, 'GPRINT:ifoutbits_'.$i.':AVERAGE:%3.0lf %sb/s';
    push @areas, 'GPRINT:iftotaloutbytes_'.$i.':AVERAGE:%5.1lf %sB';

    if ($i%4 ==3) {
       $count++;
       if ($count == 1) {
           push @areas, 'GPRINT:totaloutbits:AVERAGE:%6.2lf %sb/s';
       } elsif ($count == 2) {
           push @areas, 'GPRINT:totaloutbits:LAST:%6.2lf %sb/s';
       } elsif ($count == 3) {
           push @areas, 'GPRINT:totaloutbytes:AVERAGE:%6.2lf %sB';
       }       
    
       push @areas, 'COMMENT:\n';
    }

} 
push @areas, 'COMMENT:\n';

RRDs::graph ($graphdir.'rs_day.png', '--start', $timespan*-1, '-e', '-300', 
             @defs,
	     'COMMENT:                    avg       xfer                      avg      xfer                      avg       xfer                     avg       xfer  Sums(av/mx/to)\n',
	     @areas,
	     'COMMENT:Totals |        Average        Maximum        Current         xfer  '.
                            '| Generated on '.localtime().'\n',
	     'COMMENT:in+out |',
	     'GPRINT:totalbits:AVERAGE: %7.2lf %sb/s',
	     'GPRINT:totalbits:MAX: %7.2lf %sb/s',
	     'GPRINT:totalbits:LAST: %7.2lf %sb/s',
	     'GPRINT:totalbytes:AVERAGE: %7.2lf %sB',
	     'COMMENT:| If you have any questions about this graph, contact rrdtool@kefro.st',
             '-v', 'bits/sec', '-t', 'Rackshack Internet Backbone Bandwidth Utilization (3 days)',
	     '-h', '350', '-w', '864', '-x', 'HOUR:1:HOUR:6:HOUR:2:0:%H');

my $ERR2 = RRDs::error;
die "ERROR while graphing: $ERR2\n" if $ERR2;

$stored{list} = \@ifs;
$stored{hash} = \%ifs;
store (\%stored, 'rs_interfaces.storable');
