#!/usr/bin/perl # -*- cperl -*- =head1 NAME zenus_ - Munin plugin to monitor the usage of a Zen Internet Broadband account =head1 APPLICABLE SYSTEMS Any (Though most likely those connected to a Zen Internet Broadband connection) =head1 CONFIGURATION This plugin requires the C module to be installed. This can be fetched from L. Tip: To see if it's already setup correctly, just run this plugin with the parameter 'autoconf'. If you get a "yes", everything should work like a charm already. This configuration section shows the defaults of the plugin: [zenus_*] env.user env.pass env.account env.tick 60 You must supply C and C. These will be your PORTAL username and password, NOT your ADSL account details. You may either specify the account to report through C (e.g. zen12345@zen) or by naming the symlink in your plugins directory (e.g. zenus_zen12345_zen). If no account is specified, the default account will be chosen. C specifies how often (in minutes) the data will be refreshed from upstream. This is to avoid hitting the Zen servers every five minutes. Data is cached between runs. =head1 INTERPRETATION The plugin shows the amount of data downloaded and uploaded since the beginning of the chargable period (i.e. the calendar month). A thick line shows the current download allowance (planned plus banked). A thinner line shows the estimated amount remaining at the end of the month. If you are estimated to exceed your allowance before the end of the month, a second line appears showing the rate at which you're downloading beyond the sustainable rate. Warnings are sent if your estimated allowance drops below 25% of your cap and if the overspeed rate rises above zero. Critical warnings are sent if your estimated allowance drops below 10% of your cap. =head1 MAGIC MARKERS #%# family=auto contrib #%# capabilities=autoconf suggest =head1 AUTHOR Paul Saunders L =cut use strict; use warnings; use lib $ENV{'MUNIN_LIBDIR'}; use Munin::Plugin; use Data::Dump qw(pp); use Time::Local; my $ret = undef; # Load modules like so if ( !eval "require zenus;" ) { $ret = "Could not load zenus. Get it from http://www.rachaelandtom.info/zenus\n"; $ret .= "(BTW, \@INC is: " . join( ', ', @INC ) . ")\n"; } my $CAN_LOG = 1; if ( !eval "require Log::Log4perl;" ) { $CAN_LOG = 0; } elsif ( !eval "require Log::Dispatch::FileRotate;" ) { $CAN_LOG = 0; } my $USER = $ENV{user}; my $PASS = $ENV{pass}; my $ACCT = $ENV{account} || ""; my $TICK = $ENV{tick} || 60; # minutes my @name_fields = split /_/, $0; if ( scalar @name_fields == 3 ) { if ( $name_fields[1] eq '' or $name_fields[2] eq '' ) { print "Misconfigured symlink. See Documentation\n"; exit 1; } else { $ACCT = $name_fields[1] . '@' . $name_fields[2]; } } # If there are more or less than 3 components to the filename, # we just carry on with the default account if ($CAN_LOG) { my $loggerconf = q( log4perl.rootLogger = DEBUG, LOG1 log4perl.appender.LOG1 = Log::Dispatch::FileRotate log4perl.appender.LOG1.filename = /var/log/munin/zenus.log log4perl.appender.LOG1.mode = append log4perl.appender.LOG1.DatePattern = yyyy-ww log4perl.appender.LOG1.layout = Log::Log4perl::Layout::PatternLayout log4perl.appender.LOG1.layout.ConversionPattern = %d %p %c: %m%n ); Log::Log4perl::init( \$loggerconf ); our $logger = Log::Log4perl->get_logger($ACCT); } else { our $logger = ""; } my $lastread; sub save_data { my $hashref = shift; my $update_lastread = shift || 1; # Do we need to save this info if ( !defined $lastread or time > $lastread + ( $TICK * 60 ) ) { $lastread = time if $update_lastread; print "# Updating LastRead to " . localtime($lastread) . "\n"; my @save_vector; push @save_vector, $lastread; # Push the hash values on to the array foreach ( keys %$hashref ) { push @save_vector, $_ . '¬' . $hashref->{$_}; } $::logger->info( "Saving Data (LastRead is " . localtime($lastread) . "\n" ) if $CAN_LOG; #Go! save_state(@save_vector); } } sub load_data { # Bring the data back in my @save_vector = restore_state(); # Read the timestamp. Do we need to refresh the data? $lastread = shift @save_vector; my $hashref; foreach (@save_vector) { my ( $key, $value ) = split /¬/; $hashref->{$key} = $value; } my $force_save = 0; if ( !defined $lastread or time >= ( $lastread + ( $TICK * 60 ) ) ) { # Data is stale print "# Data is " . ( time - $lastread ) . " seconds old\n"; $::logger->info( "Data is " . ( time - $lastread ) . " seconds old\n" ) if $CAN_LOG; if ( exists $hashref->{backoff} ) { if ( time <= $hashref->{backoff} ) { # We're in a back-off period print "# Back-off in effect\n"; $::logger->info("Back-off in effect\n") if $CAN_LOG; exit 0; } else { # We've just come out of a back-off period print "# Back-off ends\n"; $::logger->info("Back-off ends\n") if $CAN_LOG; delete $hashref->{backoff}; } } elsif ( !defined $lastread ) { # Initial run. Carry on print "# Initial Run\n"; my $force_save = 1; } elsif ( time >= ( $lastread + ( 120 * 60 ) ) ) { # Data is VERY Stale. Assume a login issue and # back-off for 24 hours. print "# Starting Back-Off\n"; $hashref->{backoff} = time + ( 24 * 60 * 60 ); $::logger->info( "Backing off until " . localtime( $hashref->{backoff} ) . "\n" ) if $CAN_LOG; save_data( $hashref, 0 ); exit 1; } print "# REFRESHING DATA\n"; $::logger->info("Refreshing Data\n") if $CAN_LOG; my $temphash; eval { zenus::login( $USER, $PASS ); ( $temphash->{name}, $temphash->{used}, $temphash->{avail}, $temphash->{per}, $temphash->{uploadAmount} ) = zenus::getUsage($ACCT); ( $temphash->{dayofmonth}, $temphash->{permonth}, $temphash->{avedaily}, $temphash->{maxdaily}, $temphash->{daysremain}, $temphash->{estusage}, $temphash->{daysinmonth} ) = zenus::estimateRemaining( $temphash->{used}, $temphash->{avail} ); $hashref = $temphash; 1; # If zenus threw an error we won't copy the data over, # so we still use the cached data. } or do { print "# Zenus Error $@\n"; }; } else { print "# Using existing data\n"; $::logger->info("Using existing data\n") if $CAN_LOG; } save_data( $hashref, 1 ) if $force_save; return $hashref; } if ( defined $ARGV[0] and $ARGV[0] eq "autoconf" ) { if ($ret) { print "no ($ret)\n"; } else { print "yes\n"; } exit 0; } if ( defined $ARGV[0] and $ARGV[0] eq "suggest" ) { if ( defined $USER and defined $PASS ) { zenus::login( $USER, $PASS ); for my $account ( zenus::getAccounts() ) { if ( defined $account ) { $account =~ s/\@/_/g; print "$account\n"; } } } exit 0; } if ( defined $ARGV[0] and $ARGV[0] eq "config" ) { if ($ret) { print $ret; exit 1; } my $data = load_data(); my $datestr = scalar( localtime($lastread) ); print <{name} upload.label Uploaded upload.type GAUGE upload.info Amount of data uploaded (No upper limit) upload.draw AREA upload.graph no upload.colour 00CC00EE download.label Transfer download.type GAUGE download.draw AREA download.extinfo Last Read was $datestr download.negative upload download.colour 00CC00EE EOF if ( defined $data->{avail} and $data->{avail} != 0 ) { my $cap = sprintf( "%.3f", $data->{avail} ); my $warn = sprintf( "%.3f", $cap * 0.25 ); my $crit = sprintf( "%.3f", $cap * 0.1 ); print <{uploadAmount} . "\n"; print "download.value " . $data->{used} . "\n"; if ( defined $data->{avail} and $data->{avail} != 0 ) { print "allowance.value " . $data->{avail} . "\n"; my $remain = $data->{avail} - $data->{estusage}; $remain = 0 if $remain < 0; print "remaining.value " . $remain . "\n"; my $overrate = $data->{avedaily} - $data->{maxdaily}; $overrate = 0 if $overrate < 0; print "overrate.value " . $overrate . "\n"; } save_data($data); exit 0;