Files
2024-10-14 00:08:40 +02:00

320 lines
7.8 KiB
Perl

#!/usr/bin/perl -w
#
# SNMP (near) Real-time grapher.
#
# Cameron Kerr <cameron@humbledown.org>
#
use strict;
use warnings;
use Tk;
use Tk::Table;
use Net::SNMP;
use Getopt::Long;
use POSIX qw(log10 floor);
############################################################################
# Parse options
#
my $usage = <<EOF;
snmprtg [--title "title"]
[--domain udp|udp6|tcp|tcp6|tcp/ipv6|...]
--peer hostname|address
[--port 161]
[--version v2c (default) | v1]
[--community "public"]
--oid OID
[--interval 15] (seconds)
[--window 10] (minutes)
[--gauge]
[--maxY maxYval]
[--quiet]
[--log 0] (0 no, 1 yes)
EOF
my $title;
my $domain = "udp";
my $peer = "localhost";
my $port = 161;
my $timeout = 2; # seconds
my $version = "v2c";
my $community = "public";
my $oid;
my $gauge = 0;
my $interval = 15; # seconds
my $window = 10; # minutes
my $maxY;
my $quiet = 0;
my $log = 0;
GetOptions(
"title=s" => \$title,
"domain=s" => \$domain,
"peer=s" => \$peer,
"port=i" => \$port,
"timeout=i" => \$timeout,
"version=s" => \$version,
"community=s" => \$community,
"oid=s" => \$oid,
"gauge" => \$gauge,
"maxY=i" => \$maxY,
"interval=i" => \$interval,
"window=i" => \$window,
"quiet" => \$quiet,
"log:i" => \$log,
) or die $usage;
die $usage unless defined $oid and defined $peer;
$title = "$oid\@$peer" unless defined($title);
############################################################################
# Set up SNMP
#
if ($oid !~ m/^[0-9.]+$/) {
$oid = `snmptranslate -IR -On -To "$oid"`;
chomp $oid;
print STDERR "OID = $oid\n";
}
my @polldata;
my $oldval = undef;
my ( $session, $error ) = Net::SNMP->session(
-domain => $domain,
-hostname => $peer,
-port => $port,
-timeout => $timeout,
-version => $version,
-community => $community,
);
die "Session: $error\n" unless ($session);
############################################################################
# Create the interface
#
my ( $width, $height ) = ( 500, 150 );
my $axemargin = 5;
my ( $leftmargin, $rightmargin, $topmargin, $bottmargin ) = ( 40, 20, 20, 40 );
my $top;
my $canvas;
$top = MainWindow->new( title => "SNMPrtg - $title" );
$top->iconname( 'SNMPrtg' );
$canvas = $top->Canvas( width => $width, height => $height,
-background => 'white' )->pack( -fill => 'both', -expand => 1 );
$top->bind( '<Configure>' => sub {
my $xe = $top->XEvent;
$width = $xe->w;
$height = $xe->h;
draw_graph();
} );
draw_graph();
#Timer event to capture the data
$canvas->repeat( $interval * 1000, \&do_polling );
MainLoop;
$session->close;
sub draw_graph {
$canvas->delete( 'all' );
#Verticle Axes
$canvas->createLine(
$leftmargin,
$height - $bottmargin + $axemargin,
$leftmargin,
$topmargin - 10,
-fill => 'black',
-arrow => 'last' );
#Horizontal Axes
$canvas->createLine(
$leftmargin - $axemargin,
$height - $bottmargin,
$width - $rightmargin + 10,
$height - $bottmargin,
-fill => 'black',
-arrow => 'last' );
my $x = $leftmargin;
my $deltax = ( $width - $leftmargin - $rightmargin ) / $window;
my $horiz_axes_modulo = 5;
for my $i ( 0 .. $window ) {
$canvas->createLine(
$x, $height - $bottmargin,
$x, $height - $bottmargin + $axemargin )
unless $i >= $window;
$canvas->createText(
$x, $height - $bottmargin + $axemargin,
-text => $i, -anchor => 'n' ) if ($i % $horiz_axes_modulo) eq 0;
$x += $deltax;
}
$canvas->createText( ( $width - $rightmargin ) / 2, $height - 5,
-text => 'Age (minutes)', -anchor => 's' );
#Draw the poll-interval
$canvas->createText( $width - 5, $height - 5,
-text => 'Poll Interval: '. $interval .' secs',
-anchor => 'se' );
##Title (already in window text)
#$canvas->createText( $width - $rightmargin, $topmargin - 1,
# -text => $title,
# -anchor => 'se' );
#What is the current maximum value?
my $maxval = 0;
my $tmp = 0;
for $tmp ( @polldata ) {
$maxval = $tmp if $tmp > $maxval;
}
#Round up maxval, and draw it on the vertical axes
my $majticks;
my $numdigits = 1;
{
$numdigits = int(log10($maxval)) + 1 unless ($maxval == 0);
#What is the most significant digit?
my $msb = floor( $maxval / (10 ** ($numdigits - 1)) );
#Human beans tend to like factors of 10, 5, or 2
#to compare against on a graph.
$majticks = 1;
$majticks = 2 if ( $msb >= 1 );
$majticks = 5 if ( $msb >= 2 );
$majticks = 10 if ( $msb >= 5 );
$maxval = $majticks * 10 ** ($numdigits - 1);
}
##
## TODO: Make y ticks
##
$maxval = $maxY if $maxY;
$canvas->createText( $leftmargin - 5 , $topmargin + 2,
-text => rawtohr($maxval), -anchor => 'e' );
my $revision = 'SNMPrtg v2 http://humbledown.org/';
$canvas->createText( 5, $height - 5, -font => [-size => 6],
-text => $revision, -anchor => 'sw' );
#Plot the data
$x = $leftmargin;
my ( $y, $ox, $oy ) = ( 0, -1, 0 );
$deltax = ( $width - $leftmargin - $rightmargin ) /
( 60 / $interval * $window );
for my $val (@polldata) {
$y = ( $val / ( $maxval + 0.0000001 ) ) *
( $height - $topmargin - $bottmargin );
$y = $height - $bottmargin - $y;
$canvas->createLine( int($x), int($y), int($ox), int($oy),
-fill => 'red' )
unless $ox < 0;
( $ox, $oy ) = ( $x, $y );
$x += $deltax;
}
}
sub do_polling {
my $response = $session->get_request( $oid );
if ( not defined($response) ) {
print STDERR "Error getting request: ". $session->error ."\n";
add_error_to_poll_data();
return; # empty return guaranteed to be false
}
my $newval = $response->{$oid};
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime;
my $d = sprintf "%d-%02d-%02d, %02d:%02d:%02d", $year+1900, $mon+1, $mday, $hour, $min, $sec;
my $f = sprintf "%d-%02d-%02d-%s.log", $year+1900, $mon+1, $mday, $peer;
my $s="";
if (defined $oldval) {
#print "New: $oldval -> $newval, diff ". ($newval - $oldval) ."\n" unless $quiet;
} else {
#print "Initial: $newval\n";
$s=sprintf "Abs/Rel, Value, IP, Date, Time, OID";
print "$s\n" unless $quiet;
`echo "$s" > $f`;
}
if ($gauge) {
add_to_poll_data($newval);
if ($log > 0) {
$s=sprintf ("A, %d, %s, %s, %s", $newval - $oldval, $peer, $d, $oid) if(defined $oldval);
print "$s\n" unless $quiet;
`echo "$s" >> $f`;
}
} else {
add_to_poll_data($newval - $oldval) if defined $oldval;
if ($log > 0) {
$s=sprintf ("R, %d, %s, %s, %s", $newval - $oldval, $peer, $d, $oid) if(defined $oldval);
print "$s\n" unless $quiet;
`echo "$s" >> $f`;
}
}
$oldval = $newval;
draw_graph();
return 1;
}
sub add_error_to_poll_data {
pop( @polldata ) if ( $#polldata >= 60 / $interval * $window );
unshift( @polldata, -1 ) if defined $oldval;
}
sub add_to_poll_data {
my ($delta) = @_;
pop( @polldata ) if ( $#polldata >= 60 / $interval * $window );
if ($gauge) {
unshift( @polldata, $delta ) if defined $oldval;
} else {
unshift( @polldata, $delta / $interval ) if defined $oldval;
}
}
sub rawtohr {
#Convert a number such as 16000 to 16k. Base-2
my ($val, $suffix);
($val, $_) = @_;
$suffix = '';
if ( $val >= 1000000000 ) {
$val = int( $val / 1000000000 );
$suffix = 'G';
} elsif ( $val >= 1000000 ) {
$val = int( $val / 1000000 );
$suffix = 'M';
} elsif ( $val >= 1000 ) {
$val = int( $val / 1000 );
$suffix = 'k';
}
return sprintf( "%d%s", $val, $suffix );
}
sub log_values {
}