munin-contrib/plugins/network/if

1592 lines
45 KiB
Perl
Executable File

#!/usr/bin/perl -w
# -*- perl -*-
=head1 NAME
if - Multigraph plugin to monitor network wired and wireless interfaces
=head1 INTERPRETATION
In the general graphs made key fields for each interface, a subsidiary graphs for each interface there are detailed
This plugin displays the following charts:
Traffic, bit
Traffic, packets
Average packet size
Interface errors
WiFi interface errors
WiFi interface signal and noise
WiFi interface link quality
Interface utilisation
Virtual interface names prefixes with '~'
=head1 CONFIGURATION
This plugin is configurable environment variables.
env.exclude - Removing interfaces from graphs, default empty
env.include - Includes interfaces into graphs, default empty
env.if_max_bps - Maximum interface bps. Available suffixes: k, M, G, default empty
env.protexct_peaks - Protect graph peaks, default 'no'
env.min_packet_size - Minimal network packet size, default 20
Example:
[if]
env.exclude lo
env.include wlan0 tun0
env.wlan0_max_bps 54M
env.eth0_max_bps 1G
env.protect_peaks yes
Protect peak:
1. Protect wifi signal and noise values, all values > 0 print as NaN
2. protect all percent values. All values > 100 print as NaN
3. Protect bps values. env.if_max_bps must be set. All values > max_bps prints as 0
4. protect pps values. env.if_max_bps must be set. All values > max_bps/minimal packet size, prints as 0
=head1 AUTHOR
Gorlow Maxim aka Sheridan <sheridan@sheridan-home.ru> (email and jabber)
=head1 LICENSE
GPLv2
=head1 MAGIC MARKERS
#%# family=auto
#%# capabilities=autoconf
=cut
use strict;
use warnings;
use IO::Dir;
use Munin::Plugin;
use Data::Dumper;
# ------------------------------------------------------------- constants ---------------------
my $exclude = $ENV{exclude} || '';
my $include = $ENV{include} || '-';
my $protect_peacks = $ENV{protect_peaks} || 'no';
my $min_packet_size = $ENV{min_packet_size} || 20;
my $ifpath = '/sys/class/net';
# ----------------------------------- global -----------------
my $interfaces = {};
# ------------------------ available graphs -------------------------
my $graphs =
{
'if_bit' =>
{
'munin' =>
{
'category' => 'network',
'args' => '--base 1000',
'title' => ':if: traffic, bit',
'vlabel' => 'Bit in (-) / out (+), avg. per second',
'info' => 'This graph shows the traffic in bit of the :if:, averaged value per second from last update'
},
'per_if_fields' => [qw(rx_bytes tx_bytes)],
'general_fields' => [qw(rx_bytes tx_bytes)]
},
'if_packets' =>
{
'munin' =>
{
'category' => 'network',
'args' => '--base 1000',
'title' => ':if: traffic, packets',
'vlabel' => 'Packets in (-) / out (+), avg. per second',
'info' => 'This graph shows the traffic in packets of the :if:, averaged value per second from last update'
},
'per_if_fields' => [qw(rx_packets tx_packets rx_compressed tx_compressed rx_dropped tx_dropped multicast)],
'general_fields' => [qw(rx_packets tx_packets)]
},
'if_errors' =>
{
'munin' =>
{
'category' => 'network',
'args' => '--base 1000',
'title' => ':if: errors',
'vlabel' => 'Errors RX (-) / TX (+)',
'info' => 'This graph shows the errors of the :if: from last update',
'scale' => 'no'
},
'per_if_fields' => [qw(rx_errors tx_errors rx_fifo_errors tx_fifo_errors rx_crc_errors rx_frame_errors rx_length_errors rx_missed_errors rx_over_errors collisions tx_aborted_errors tx_carrier_errors tx_heartbeat_errors tx_window_errors)],
'general_fields' => [qw(rx_errors tx_errors)]
},
'if_wifi_sino' =>
{
'munin' =>
{
'category' => 'wireless',
'args' => '--base 1000 -u 0',
'title' => ':if: signal and noise levels',
'vlabel' => 'dB',
'info' => 'This graph shows the WiFi signal and noise levels of the :if:',
'scale' => 'no'
},
'per_if_fields' => [qw(signal noise)],
'general_fields' => [qw(signal)]
},
'if_wifi_link_quality' =>
{
'munin' =>
{
'category' => 'wireless',
'args' => '--base 1000',
'title' => ':if: link quality',
'vlabel' => '%',
'info' => 'This graph shows the WiFi link quality of the :if:',
'scale' => 'no'
},
'per_if_fields' => [qw(link)],
'general_fields' => [qw(link)]
},
'if_wifi_errors' =>
{
'munin' =>
{
'category' => 'wireless',
'args' => '--base 1000',
'title' => ':if: errors',
'vlabel' => 'Errors RX (-) / TX (+)',
'info' => 'This graph shows the WiFi errors of the :if: from last update',
'scale' => 'no'
},
'per_if_fields' => [qw(nwid fragment crypt beacon retries misc)],
'general_fields' => [qw(rx_wifierr tx_wifierr)]
},
'if_utilisation' =>
{
'munin' =>
{
'category' => 'network',
'args' => '--base 1000',
'title' => ':if: utilisation',
'vlabel' => '%',
'info' => 'This graph shows utilisation of the :if:',
'scale' => 'no'
},
'per_if_fields' => [qw(rx_percent tx_percent)],
'general_fields' => [qw(rx_percent tx_percent)]
},
'if_avgpacketsize' =>
{
'munin' =>
{
'category' => 'network',
'args' => '--base 1024',
'title' => ':if: average packet size',
'vlabel' => 'bytes',
'info' => 'This graph shows average packet size of the :if:'
},
'per_if_fields' => [qw(rx_size tx_size)],
'general_fields' => [qw(rx_size tx_size)]
}
};
#-------------------------- available fields -------------------------
# info:
# 'munin' => {} - just copy fields to munin config
# 'source' => - field data source
# {
# 'type' => types:
# 'file' - data just cat from file
# 'location' => file location
# 'calculated' => calculated data
# {
# 'type' - types:
# 'percent',
# 'full' =>
# {
# 'source' => 'interface',
# 'name' => 'bps'
# },
# 'part' =>
# {
# 'source' => 'field',
# 'name' => 'tx_bytes'
# }
# 'division',
# 'dividend' =>
# {
# 'source' => 'field',
# 'name' => 'rx_bytes'
# },
# 'divider' =>
# {
# 'source' => 'field',
# 'name' => 'rx_packets'
# }
# 'sum',
# 'sum' => [qw(nwid fragment crypt)]
#
# }
# }
# 'difference' => difference types:
# count - just count from last update
# per_secund - count from last update / time difference per last update
# 'negative' => - if field under zero line
# {
# 'type' => types:
# 'dummy' - dummy field, not draw
# 'value' => '' - value for dummy field in update
# 'field' - exists field, must be included in graph
# 'name' => '' - field name
# }
# 'peack_protect' => protect peaks. Using munin field.max and field.min and truncate data to NaN
# protect types
# 'max_interface_bps' - maximum: max interface bps (if configured), minimum - zero
# 'packet_size_range' - maximum: mtu, minimum: minimal packet size (may be configured)
# 'max_interface_pps' - maximum: max interface bps/minimum packt size (if configured), minimum - zero
# 'percents' - maximum: 100, minimum: 0
# 'min_number:max_number' - no comments :)
my $fields =
{
'collisions' =>
{
'munin' =>
{
'label' => 'Collisions' ,
'info' => 'Transmit collisions',
'type' => 'GAUGE',
'draw' => 'LINE1'
},
'source' =>
{
'type' => 'file',
'location' => $ifpath.'/:if:/statistics/collisions'
},
'difference' => 'count'
},
# --------------------------------------------------------------------------
'multicast' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'Multicast packets',
'info' => 'Multicast packets received'
},
'source' =>
{
'type' => 'file',
'location' => $ifpath.'/:if:/statistics/multicast'
},
'negative' =>
{
'type' => 'dummy',
'value' => 'NaN'
},
'difference' => 'per_secund'
},
# --------------------------------------------------------------------------
'rx_bytes' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'RX bit',
'info' => 'RX bit'
},
'source' =>
{
'type' => 'file',
'location' => $ifpath.'/:if:/statistics/rx_bytes'
},
'cdef' => '8,*',
'peack_protect' => 'max_interface_bps',
'negative' =>
{
'type' => 'field',
'name' => 'tx_bytes'
},
'difference' => 'per_secund'
},
# --------------------------------------------------------------------------
'rx_compressed' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'RX compressed packets',
'info' => 'Compressed packets',
},
'source' =>
{
'type' => 'file',
'location' => $ifpath.'/:if:/statistics/rx_compressed'
},
'peack_protect' => 'max_interface_pps',
'negative' =>
{
'type' => 'field',
'name' => 'tx_compressed'
},
'difference' => 'per_secund'
},
# --------------------------------------------------------------------------
'rx_crc_errors' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'CRC errors' ,
'info' => 'CRC errors'
},
'source' =>
{
'type' => 'file',
'location' => $ifpath.'/:if:/statistics/rx_crc_errors'
},
'negative' =>
{
'type' => 'dummy',
'value' => 'NaN'
},
'difference' => 'count'
},
# --------------------------------------------------------------------------
'rx_dropped' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'RX dropped packets',
'info' => 'Dropped frames'
},
'source' =>
{
'type' => 'file',
'location' => $ifpath.'/:if:/statistics/rx_dropped'
},
'negative' =>
{
'type' => 'field',
'name' => 'tx_dropped'
},
'difference' => 'per_secund'
},
# --------------------------------------------------------------------------
'rx_errors' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'RX errors',
'info' => 'Bad packets'
},
'source' =>
{
'type' => 'file',
'location' => $ifpath.'/:if:/statistics/rx_errors'
},
'negative' =>
{
'type' => 'field',
'name' => 'tx_errors'
},
'difference' => 'count'
},
# --------------------------------------------------------------------------
'rx_fifo_errors' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'RX FIFO errors',
'info' => 'FIFO overrun errors'
},
'source' =>
{
'type' => 'file',
'location' => $ifpath.'/:if:/statistics/rx_fifo_errors'
},
'negative' =>
{
'type' => 'field',
'name' => 'tx_fifo_errors'
},
'difference' => 'count'
},
# --------------------------------------------------------------------------
'rx_frame_errors' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'Frame format errors',
'info' => 'Frame format errors'
},
'source' =>
{
'type' => 'file',
'location' => $ifpath.'/:if:/statistics/rx_frame_errors'
},
'negative' =>
{
'type' => 'dummy',
'value' => 'NaN'
},
'difference' => 'count'
},
# --------------------------------------------------------------------------
'rx_length_errors' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'Length errors',
'info' => 'Length errors'
},
'source' =>
{
'type' => 'file',
'location' => $ifpath.'/:if:/statistics/rx_length_errors'
},
'negative' =>
{
'type' => 'dummy',
'value' => 'NaN'
},
'difference' => 'count'
},
# --------------------------------------------------------------------------
'rx_missed_errors' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'Missed packetss',
'info' => 'Missed packets'
},
'source' =>
{
'type' => 'file',
'location' => $ifpath.'/:if:/statistics/rx_missed_errors'
},
'negative' =>
{
'type' => 'dummy',
'value' => 'NaN'
},
'difference' => 'count'
},
# --------------------------------------------------------------------------
'rx_over_errors' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'Overrun errors',
'info' => 'Overrun errors'
},
'source' =>
{
'type' => 'file',
'location' => $ifpath.'/:if:/statistics/rx_over_errors'
},
'negative' =>
{
'type' => 'dummy',
'value' => 'NaN'
},
'difference' => 'count'
},
# --------------------------------------------------------------------------
'rx_packets' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'RX packets',
'info' => 'RX packets'
},
'source' =>
{
'type' => 'file',
'location' => $ifpath.'/:if:/statistics/rx_packets'
},
'peack_protect' => 'max_interface_pps',
'negative' =>
{
'type' => 'field',
'name' => 'tx_packets'
},
'difference' => 'per_secund'
},
# --------------------------------------------------------------------------
'tx_aborted_errors' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'Aborted frames',
'info' => 'Aborted frames'
},
'source' =>
{
'type' => 'file',
'location' => $ifpath.'/:if:/statistics/tx_aborted_errors'
},
'difference' => 'count'
},
# --------------------------------------------------------------------------
'tx_bytes' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'TX bit',
'info' => 'TX bit'
},
'source' =>
{
'type' => 'file',
'location' => $ifpath.'/:if:/statistics/tx_bytes'
},
'cdef' => '8,*',
'peack_protect' => 'max_interface_bps',
'difference' => 'per_secund'
},
# --------------------------------------------------------------------------
'tx_carrier_errors' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'Carrier errors',
'info' => 'Carrier errors'
},
'source' =>
{
'type' => 'file',
'location' => $ifpath.'/:if:/statistics/tx_carrier_errors'
},
'difference' => 'count'
},
# --------------------------------------------------------------------------
'tx_compressed' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'TX compressed packets',
'info' => 'Compressed packets'
},
'source' =>
{
'type' => 'file',
'location' => $ifpath.'/:if:/statistics/tx_compressed'
},
'peack_protect' => 'max_interface_pps',
'difference' => 'per_secund'
},
# --------------------------------------------------------------------------
'tx_dropped' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'TX dropped packets',
'info' => 'Dropped frames'
},
'source' =>
{
'type' => 'file',
'location' => $ifpath.'/:if:/statistics/tx_dropped'
},
'difference' => 'per_secund'
},
# --------------------------------------------------------------------------
'tx_errors' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'TX errors',
'info' => 'Transmit problems'
},
'source' =>
{
'type' => 'file',
'location' => $ifpath.'/:if:/statistics/tx_errors'
},
'difference' => 'count'
},
# --------------------------------------------------------------------------
'tx_fifo_errors' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'TX FIFO errors',
'info' => 'FIFO errors'
},
'source' =>
{
'type' => 'file',
'location' => $ifpath.'/:if:/statistics/tx_fifo_errors'
},
'difference' => 'count'
},
# --------------------------------------------------------------------------
'tx_heartbeat_errors' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'Heartbeat errors',
'info' => 'Heartbeat errors'
},
'source' =>
{
'type' => 'file',
'location' => $ifpath.'/:if:/statistics/tx_heartbeat_errors'
},
'difference' => 'count'
},
# --------------------------------------------------------------------------
'tx_packets' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'TX packets',
'info' => 'TX packets'
},
'source' =>
{
'type' => 'file',
'location' => $ifpath.'/:if:/statistics/tx_packets'
},
'peack_protect' => 'max_interface_pps',
'difference' => 'per_secund'
},
# --------------------------------------------------------------------------
'tx_window_errors' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'Window errors',
'info' => 'Window errors'
},
'source' =>
{
'type' => 'file',
'location' => $ifpath.'/:if:/statistics/tx_window_errors'
},
'difference' => 'count'
},
# --------------------------------------------------------------------------
'signal' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'Signal level',
'info' => 'WiFi signal level'
},
'source' =>
{
'type' => 'file',
'location' => $ifpath.'/:if:/wireless/level',
'prepare' => ':data:=:data:-256'
},
# 'cdef' => '-256,+',
'peack_protect' => '-256:0'
},
# --------------------------------------------------------------------------
'noise' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'Noise level',
'info' => 'WiFi noise level'
},
'source' =>
{
'type' => 'file',
'location' => $ifpath.'/:if:/wireless/noise',
'prepare' => ':data:=:data:-256'
},
# 'cdef' => '-256,+',
'peack_protect' => '-256:0'
},
# --------------------------------------------------------------------------
'link' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'Signal quality',
'info' => 'WiFi signal quality'
},
'source' =>
{
'type' => 'file',
'location' => $ifpath.'/:if:/wireless/link'
},
'peack_protect' => 'percent'
},
# --------------------------------------------------------------------------
'rx_percent' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'RX Utilisation',
'info' => 'RX utilisation'
},
'source' =>
{
'type' => 'calculated',
'calculated' =>
{
'type' => 'percent',
'full' =>
{
'source' => 'interface',
'name' => 'bps'
},
'part' =>
{
'source' => 'field',
'name' => 'rx_bytes'
}
}
},
'peack_protect' => 'percent',
'negative' =>
{
'type' => 'field',
'name' => 'tx_percent'
}
},
# --------------------------------------------------------------------------
'tx_percent' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'TX Utilisation',
'info' => 'TX utilisation'
},
'source' =>
{
'type' => 'calculated',
'calculated' =>
{
'type' => 'percent',
'full' =>
{
'source' => 'interface',
'name' => 'bps'
},
'part' =>
{
'source' => 'field',
'name' => 'tx_bytes'
}
}
},
'peack_protect' => 'percent'
},
# --------------------------------------------------------------------------
'rx_size' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'RX packet size',
'info' => 'Average RX packet size'
},
'source' =>
{
'type' => 'calculated',
'calculated' =>
{
'type' => 'division',
'dividend' =>
{
'source' => 'field',
'name' => 'rx_bytes'
},
'divider' =>
{
'source' => 'field',
'name' => 'rx_packets'
}
}
},
'peack_protect' => 'packet_size_range',
'negative' =>
{
'type' => 'field',
'name' => 'tx_size'
}
},
# --------------------------------------------------------------------------
'tx_size' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'TX packet size',
'info' => 'Average TX packet size'
},
'source' =>
{
'type' => 'calculated',
'calculated' =>
{
'type' => 'division',
'dividend' =>
{
'source' => 'field',
'name' => 'tx_bytes'
},
'divider' =>
{
'source' => 'field',
'name' => 'tx_packets'
}
}
},
'peack_protect' => 'packet_size_range'
},
# --------------------------------------------------------------------------
'retries' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'Max. retries reached',
'info' => 'Max MAC retries num reached'
},
'source' =>
{
'type' => 'file',
'location' => $ifpath.'/:if:/wireless/retries'
},
'difference' => 'count'
},
# --------------------------------------------------------------------------
'nwid' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'Wrong nwid/essid',
'info' => 'Wrong nwid/essid'
},
'source' =>
{
'type' => 'file',
'location' => $ifpath.'/:if:/wireless/nwid'
},
'negative' =>
{
'type' => 'dummy',
'value' => 'NaN'
},
'difference' => 'count'
},
# --------------------------------------------------------------------------
'misc' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'Other',
'info' => 'Others cases'
},
'source' =>
{
'type' => 'file',
'location' => $ifpath.'/:if:/wireless/misc'
},
'difference' => 'count'
},
# --------------------------------------------------------------------------
'fragment' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'MAC reassemby',
'info' => 'Can\'t perform MAC reassembly'
},
'source' =>
{
'type' => 'file',
'location' => $ifpath.'/:if:/wireless/fragment'
},
'negative' =>
{
'type' => 'dummy',
'value' => 'NaN'
},
'difference' => 'count'
},
# --------------------------------------------------------------------------
'beacon' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'Missed beacons',
'info' => 'Missed beacons/superframe'
},
'source' =>
{
'type' => 'file',
'location' => $ifpath.'/:if:/wireless/beacon'
},
'difference' => 'count'
},
# --------------------------------------------------------------------------
'crypt' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'Code/decode',
'info' => 'Unable to code/decode (WEP)'
},
'source' =>
{
'type' => 'file',
'location' => $ifpath.'/:if:/wireless/crypt'
},
'negative' =>
{
'type' => 'dummy',
'value' => 'NaN'
},
'difference' => 'count'
},
# --------------------------------------------------------------------------
'rx_wifierr' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'RX errors',
'info' => 'Total RX Wifi Errors'
},
'source' =>
{
'type' => 'calculated',
'calculated' =>
{
'type' => 'sum',
'sum' => [qw(nwid fragment crypt)]
}
},
'negative' =>
{
'type' => 'field',
'name' => 'tx_wifierr'
}
},
# --------------------------------------------------------------------------
'tx_wifierr' =>
{
'munin' =>
{
'type' => 'GAUGE',
'draw' => 'LINE1',
'label' => 'TX errors',
'info' => 'Total TX Wifi errors'
},
'source' =>
{
'type' => 'calculated',
'calculated' =>
{
'type' => 'sum',
'sum' => [qw(misc beacon retries)]
}
}
}
};
# ----------------- main ----------------
need_multigraph();
if (defined($ARGV[0]) and ($ARGV[0] eq 'autoconf'))
{
printf("%s\n", -e $ifpath ? "yes" : "no ($ifpath not exists)");
exit (0);
}
$interfaces = get_interfaces();
if (defined($ARGV[0]) and ($ARGV[0] eq 'config'))
{
print_config();
exit (0);
}
print_values();
exit(0);
# ====================================== both config and values ===========================
# --------------- read sysfs file (one file - one value) --------------
sub get_file_content
{
my $file = $_[0];
return 'NaN' if (-z $file);
open (FH, '<', $file) or die "$! $file \n";
my $content = <FH>;
close (FH);
chomp $content;
#print "$content\n";
return trim($content);
}
# ------------------ build interface list and his options -------------------------
sub get_interfaces
{
my $interfaces;
my $ifdir = IO::Dir->new($ifpath);
if(defined $ifdir)
{
my $if;
while (defined ($if = $ifdir->read))
{
next unless -d "$ifpath/$if";
next if $if =~ m/\./;
unless($if =~ m/$include/)
{
next unless get_file_content(sprintf("%s/%s/operstate", $ifpath, $if)) =~ m/(up|unknown)/;
next if $exclude =~ m/$if/;
}
my $mtufile = sprintf("%s/%s/mtu", $ifpath, $if);
if(-e $mtufile)
{
$interfaces->{$if}{'mtu'} = get_file_content($mtufile);
}
my $bps = $ENV{"${if}_max_bps"} || undef;
if(defined($bps))
{
my ($num, $suff) = $bps =~ /(\d+)(\w)/;
if ($suff eq 'k') { $bps = $num * 1000 / 8; }
elsif($suff eq 'M') { $bps = $num * 1000 * 1000 / 8; }
elsif($suff eq 'G') { $bps = $num * 1000 * 1000 * 1000 / 8; }
$interfaces->{$if}{'bps'} = $bps;
}
if (-e sprintf("/sys/devices/virtual/net/%s", $if)) { $interfaces->{$if}{'virtual'} = 1; }
$interfaces->{$if}{'name'} = exists($interfaces->{$if}{'virtual'}) ? sprintf("~%s", $if) : $if;
}
my ($maxlen, $tl) = (0, 0);
for (keys %{$interfaces}) { $tl = length($interfaces->{$_}{'name'}); $maxlen = $tl if $tl > $maxlen; }
for (keys %{$interfaces}) { $interfaces->{$_}{'name'} = sprintf("[%${maxlen}s]", $interfaces->{$_}{'name'}); }
}
else { die "$ifpath not exists\n"; }
return $interfaces;
}
# ----------------------- trim whitespace at begin and end of string ------------
sub trim
{
my($string)=@_;
for ($string) { s/^\s+//; s/\s+$//; }
return $string;
}
# ------------------------ replacing :if: from strings to need value ----------------------
sub replace_if_template
{
my ($string, $replacement) = @_[0..1];
$string =~ s/:if:/$replacement/g;
return $string;
}
# --------------------------- calculating range values for peack_protect --------------------------
sub get_peak_range
{
my ($field, $if) = @_[0..1];
my $range = {};
return $range unless defined($fields->{$field}{'peack_protect'});
# percent
if ($fields->{$field}{'peack_protect'} eq 'percent')
{
$range->{'max'} = 100;
$range->{'min'} = 0;
}
# numbers
elsif ($fields->{$field}{'peack_protect'} =~ m/[-\d]+:[-\d]+/)
{
my @r = split(/:/, $fields->{$field}{'peack_protect'});
$range->{'max'} = $r[1];
$range->{'min'} = $r[0];
}
# bytes per sec
elsif($fields->{$field}{'peack_protect'} eq 'max_interface_bps' and defined ($interfaces->{$if}{'bps'}))
{
$range->{'max'} = $interfaces->{$if}{'bps'};
$range->{'min'} = 0;
}
# packets per sec
elsif($fields->{$field}{'peack_protect'} eq 'max_interface_pps' and defined ($interfaces->{$if}{'bps'}))
{
$range->{'max'} = $interfaces->{$if}{'bps'}/$min_packet_size;
$range->{'min'} = 0;
}
# packets size range
elsif($fields->{$field}{'peack_protect'} eq 'packet_size_range' and defined ($interfaces->{$if}{'mtu'}))
{
$range->{'max'} = $interfaces->{$if}{'mtu'};
$range->{'min'} = $min_packet_size;
}
return $range;
}
# ----------------------------- checking avialability of fields -------------------------
sub check_field_avialability
{
my ($if, $field) = @_[0..1];
unless(exists($fields->{$field}{'avialable'}{$if}))
{
# -------------------- file ----------------
if($fields->{$field}{'source'}{'type'} eq 'file')
{
my $file = $fields->{$field}{'source'}{'location'};
$file =~ s/:if:/$if/g;
$fields->{$field}{'avialable'}{$if} = 1 if (-e $file);
}
#---------------------------- calculated ----------------
elsif ($fields->{$field}{'source'}{'type'} eq 'calculated')
{
#------------------------------ percent ------------------------
if($fields->{$field}{'source'}{'calculated'}{'type'} eq 'percent')
{
my %avialable;
for my $a ('full', 'part')
{
if($fields->{$field}{'source'}{'calculated'}{$a}{'source'} eq 'interface')
{
$avialable{$a} = exists($interfaces->{$if}{$fields->{$field}{'source'}{'calculated'}{$a}{'name'}});
}
elsif($fields->{$field}{'source'}{'calculated'}{$a}{'source'} eq 'field')
{
$avialable{$a} = check_field_avialability($if, $fields->{$field}{'source'}{'calculated'}{$a}{'name'});
}
}
$fields->{$field}{'avialable'}{$if} = ($avialable{'full'} and $avialable{'part'});
}
#------------------------------ division ------------------------
elsif($fields->{$field}{'source'}{'calculated'}{'type'} eq 'division')
{
my %avialable;
for my $a ('dividend', 'divider')
{
if($fields->{$field}{'source'}{'calculated'}{$a}{'source'} eq 'interface')
{
$avialable{$a} = exists($interfaces->{$if}{$fields->{$field}{'source'}{'calculated'}{$a}{'name'}});
}
elsif($fields->{$field}{'source'}{'calculated'}{$a}{'source'} eq 'field')
{
$avialable{$a} = check_field_avialability($if, $fields->{$field}{'source'}{'calculated'}{$a}{'name'});
}
}
$fields->{$field}{'avialable'}{$if} = ($avialable{'dividend'} and $avialable{'divider'});
}
#------------------------------ sum ------------------------
elsif($fields->{$field}{'source'}{'calculated'}{'type'} eq 'sum')
{
my $count = 0;
for my $a (@{$fields->{$field}{'source'}{'calculated'}{'sum'}})
{
$count++ if (check_field_avialability($if, $a));
}
$fields->{$field}{'avialable'}{$if} = ($count == scalar(@{$fields->{$field}{'source'}{'calculated'}{'sum'}}));
}
}
}
return $fields->{$field}{'avialable'}{$if};
}
# ================================== config-only ==============================
# --------------- concatenate field names ------------------
sub concat_names
{
my ($f1, $f2, $if) = @_[0..2];
my $name = $f1;
if ($f1 ne $f2)
{
my @a = split(' ', $f1);
my @b = split(' ', $f2);
my ($t, $ra, $rb) = ('','','');
for (my $i = scalar(@a) - 1; $i >= 0; $i--)
{
#printf("%s %s\n", $a[$i], $b[$i]);
if ($a[$i] eq $b[$i]) { $t = sprintf("%s %s", $a[$i], $t); }
else { $ra = sprintf("%s %s", $a[$i], $ra); $rb = sprintf("%s %s", $b[$i], $rb); }
}
$name = trim(sprintf("%s/%s %s", trim($ra), trim($rb), trim($t)));
}
if (exists($interfaces->{$if}))
{
$name = sprintf ("%s %s", $interfaces->{$if}{'name'}, $name);
}
return $name;
}
# --------------------------- generating graph field ----------------------
sub generate_field
{
my ($config, $graph_name, $field, $if, $is_general_graph) = @_[0..4];
return '' unless(check_field_avialability($if, $field));
my $field_graph_name = $is_general_graph ? sprintf("%s_%s", $if, $field) : $field;
for my $option (keys %{$fields->{$field}{'munin'}})
{
next if exists($config->{$graph_name}{'fields'}{$field_graph_name}{$option});
$config->{$graph_name}{'fields'}{$field_graph_name}{$option} = replace_if_template($fields->{$field}{'munin'}{$option}, $interfaces->{$if}{'name'});
}
if(exists($fields->{$field}{'cdef'}))
{
$config->{$graph_name}{'fields'}{$field_graph_name}{'cdef'} = sprintf("%s,%s", $field_graph_name, $fields->{$field}{'cdef'});
}
if(exists($fields->{$field}{'negative'}))
{
my ($up_field_name, $down_field_name) = ('', $field_graph_name);
my ($up_field, $down_field) = ('', $field);
if($fields->{$down_field}{'negative'}{'type'} eq 'field')
{
$up_field = $fields->{$down_field}{'negative'}{'name'};
$up_field_name = $is_general_graph ? sprintf("%s_%s", $if, $up_field) : $up_field;
$config->{$graph_name}{'fields'}{$up_field_name}{'label'} =
concat_names($fields->{$down_field}{'munin'}{'label'}, $fields->{$up_field}{'munin'}{'label'}, $is_general_graph ? $if : '');
}
elsif($fields->{$down_field}{'negative'}{'type'} eq 'dummy')
{
$up_field_name = $is_general_graph ? sprintf("%s_%s_dummy", $if, $down_field) : sprintf("%s_dummy", $down_field);
$config->{$graph_name}{'fields'}{$up_field_name}{'label'} =
concat_names($fields->{$down_field}{'munin'}{'label'}, $fields->{$down_field}{'munin'}{'label'}, $is_general_graph ? $if : '');
$config->{$graph_name}{'fields'}{$up_field_name}{'info'} = $fields->{$down_field}{'munin'}{'info'};
}
$config->{$graph_name}{'fields'}{$up_field_name}{'negative'} = $down_field_name;
$config->{$graph_name}{'fields'}{$down_field_name}{'graph'} = 'no';
$config->{$graph_name}{'fields'}{$down_field_name}{'label'} = 'none';
}
# Fix field label on general graphs
if ($is_general_graph and not $config->{$graph_name}{'fields'}{$field_graph_name}{'label'} =~ m/$if/)
{
$config->{$graph_name}{'fields'}{$field_graph_name}{'label'} = sprintf ("%s %s", $interfaces->{$if}{'name'}, $fields->{$field}{'munin'}{'label'});
}
# do peaks protect
if($protect_peacks ne 'no' and exists($fields->{$field}{'peack_protect'}))
{
my $range = get_peak_range($field, $if);
for my $a (qw(min max))
{
if (exists($range->{$a}))
{
$config->{$graph_name}{'fields'}{$field_graph_name}{$a} = $range->{$a};
}
}
}
return $field_graph_name;
}
# ------------------------------- generating graph ----------------------------
sub generate_graph
{
my ($config, $graph, $if, $is_general_graph) = @_[0..4];
my @order = ();
my $graph_name = $is_general_graph ? $graph : sprintf("%s.%s", $graph, $if);
if($is_general_graph)
{
for my $field (@{$graphs->{$graph}{'general_fields'}})
{
for my $general_if (keys %{$interfaces})
{
my $res_field = generate_field($config, $graph_name, $field, $general_if, 1);
push(@order, $res_field) if $res_field ne '';
}
}
}
else
{
for my $field (@{$graphs->{$graph}{'per_if_fields'}})
{
my $res_field = generate_field($config, $graph_name, $field, $if, 0);
push(@order, $res_field) if $res_field ne '';
}
}
if(scalar(@order) > 0)
{
for my $option (keys %{$graphs->{$graph}{'munin'}})
{
$config->{$graph_name}{'graph'}{$option} = replace_if_template($graphs->{$graph}{'munin'}{$option}, $is_general_graph ? 'All interfaces' : $interfaces->{$if}{'name'});
}
$config->{$graph_name}{'graph'}{'order'} = join(' ', @order); # if scalar(@order) > 1;
unless($is_general_graph)
{
$config->{$graph_name}{'graph'}{'category'} = $if;
}
}
}
# ------------------------ generate general and per-interface graphs ------------------------------
sub generate_graphs
{
my ($config, $graph) = @_[0..1];
generate_graph($config, $graph, '', 1);
for my $if (keys %{$interfaces})
{
generate_graph($config, $graph, $if, 0);
}
}
# ---------------------------------------------------------- config ------------------------------------------------------
sub print_config
{
my $config = {};
my $graph;
for $graph (keys %{$graphs}) { generate_graphs($config, $graph); }
#-------------------- print ---------------
for $graph (sort keys %{$config})
{
printf ("multigraph %s\n", $graph);
for my $option (sort keys %{$config->{$graph}{'graph'}})
{
printf ("graph_%s %s\n", $option, $config->{$graph}{'graph'}{$option});
}
for my $field (sort keys %{$config->{$graph}{'fields'}})
{
for my $type (sort keys %{$config->{$graph}{'fields'}{$field}})
{
printf ("%s.%s %s\n", $field, $type, $config->{$graph}{'fields'}{$field}{$type});
}
}
print "\n";
}
}
# =========================================== values ==========================================================
# ------------------------------- calculate percent --------------------------
sub percent
{
my ($full, $current) = @_[0..1];
return $current/($full/100);
}
# ----------------------------------- saving state data using munin --------------------
sub save_state_data
{
my $data = $_[0];
my $d = Data::Dumper->new([$data]);
$d->Indent(0);
save_state($d->Dump);
}
# -------------------------------- loading previous state data using munin -------------------
sub restore_state_data
{
my $VAR1;
my $states = (restore_state())[0];
eval $states if defined $states;
return $VAR1;
}
# -------------------- protect field data from under zero value (for example prev tx_bytes = 10000, interface reset, current tx_bytes = 100, 100-1000=-900)
sub underzero_protect
{
my ($a, $b) = @_[0..1];
return $a > $b ? $b : $b - $a;
}
# ------------------- calculating difference from last stored data ---------------------------------
sub calc_diff
{
my ($raw_data, $raw_prev_data, $if, $field) = @_[0..4];
return $raw_data->{$if}{$field} unless (exists($fields->{$field}{'difference'}) and defined($raw_prev_data));
if ($fields->{$field}{'difference'} eq 'count' ) { return underzero_protect($raw_prev_data->{$if}{$field}, $raw_data->{$if}{$field}); }
elsif ($fields->{$field}{'difference'} eq 'per_secund') { return underzero_protect($raw_prev_data->{$if}{$field}, $raw_data->{$if}{$field}) / ($raw_data->{'timestamp'} - $raw_prev_data->{'timestamp'}); }
}
# ---------------------- protecting values from peaks ------------------------
sub protect_data_peak
{
my ($field, $if, $value) = @_[0..2];
my $range = get_peak_range($field, $if);
return $value if (
$protect_peacks ne 'no' or
(
$value ne 'NaN' and
exists($range->{'max'}) and
$value <= $range->{'max'} and
$value >= $range->{'min'}
)
);
return 'NaN';
}
# --------------------------------- loading or calculating fields values ----------------------------
sub get_field_data
{
my ($data, $raw_data, $raw_prev_data, $if, $field) = @_[0..4];
unless (exists($data->{$if}{$field}))
{
# ---------------------------- file source ------------------------------------------------------------
if($fields->{$field}{'source'}{'type'} eq 'file' and not exists($raw_data->{$if}{$field}))
{
$raw_data->{$if}{$field} = get_file_content(replace_if_template($fields->{$field}{'source'}{'location'}, $if));
$data->{$if}{$field} = calc_diff($raw_data, $raw_prev_data, $if, $field);
}
# ---------------------------- calculated source ------------------------------------------------------------
elsif($fields->{$field}{'source'}{'type'} eq 'calculated')
{
# -------------------------------- percent ---------------------------
if($fields->{$field}{'source'}{'calculated'}{'type'} eq 'percent')
{
my $percents = {};
for my $pf (qw(full part))
{
if ($fields->{$field}{'source'}{'calculated'}{$pf}{'source'} eq 'interface')
{
$percents->{$pf} = $interfaces->{$if}{$fields->{$field}{'source'}{'calculated'}{$pf}{'name'}};
}
elsif ($fields->{$field}{'source'}{'calculated'}{$pf}{'source'} eq 'field')
{
$percents->{$pf} = get_field_data($data, $raw_data, $raw_prev_data, $if, $fields->{$field}{'source'}{'calculated'}{$pf}{'name'});
}
}
$data->{$if}{$field} = percent($percents->{'full'}, $percents->{'part'});
}
# -------------------------------- division ---------------------------
if($fields->{$field}{'source'}{'calculated'}{'type'} eq 'division')
{
my $division = {};
for my $df (qw(dividend divider))
{
if ($fields->{$field}{'source'}{'calculated'}{$df}{'source'} eq 'interface')
{
$division->{$df} = $interfaces->{$if}{$fields->{$field}{'source'}{'calculated'}{$df}{'name'}};
}
elsif ($fields->{$field}{'source'}{'calculated'}{$df}{'source'} eq 'field')
{
$division->{$df} = get_field_data($data, $raw_data, $raw_prev_data, $if, $fields->{$field}{'source'}{'calculated'}{$df}{'name'});
}
}
$data->{$if}{$field} = $division->{'divider'} != 0 ? $division->{'dividend'}/$division->{'divider'} : 'NaN';
}
# -------------------------------- sum ---------------------------
if($fields->{$field}{'source'}{'calculated'}{'type'} eq 'sum')
{
my $sum = 0;
for my $s (@{$fields->{$field}{'source'}{'calculated'}{'sum'}})
{
$sum += get_field_data($data, $raw_data, $raw_prev_data, $if, $s);
}
$data->{$if}{$field} = $sum;
}
}
if(exists($fields->{$field}{'source'}{'prepare'}))
{
my $eval = $fields->{$field}{'source'}{'prepare'};
$eval =~ s/:data:/\$data->{\$if}{\$field}/g;
eval $eval;
}
$data->{$if}{$field} = protect_data_peak($field, $if, $data->{$if}{$field});
}
return $data->{$if}{$field};
}
# ------------------------- preparing value for print ----------------------------
sub prepare_value
{
my ($values, $field, $field_name, $graph_name, $if, $data, $raw_data, $raw_prev_data) = @_[0..7];
if(check_field_avialability($if, $field))
{
$values->{$graph_name}{$field_name} = get_field_data($data, $raw_data, $raw_prev_data, $if, $field);
if(exists($fields->{$field}{'negative'}) and $fields->{$field}{'negative'}{'type'} eq 'dummy')
{
$values->{$graph_name}{$field_name.'_dummy'} = $fields->{$field}{'negative'}{'value'};
}
}
}
# --------------------------------- print field.value value for every graph ----------------------
sub print_values
{
my $data = {};
my $raw_data = {};
my $raw_prev_data = restore_state_data();
my $values = {};
$raw_data->{'timestamp'} = time();
for my $graph (keys %{$graphs}) {
for my $field (@{$graphs->{$graph}{'general_fields'}}) {
for my $if (keys %{$interfaces}) {
prepare_value($values, $field, sprintf("%s_%s", $if, $field), $graph, $if, $data, $raw_data, $raw_prev_data); } } }
for my $if (keys %{$interfaces})
{
for my $graph (keys %{$graphs})
{
my $graph_name = sprintf("%s.%s", $graph, $if);
for my $field (@{$graphs->{$graph}{'per_if_fields'}})
{
prepare_value($values, $field, $field, $graph_name, $if, $data, $raw_data, $raw_prev_data);
}
}
}
save_state_data($raw_data);
exit (0) unless defined ($raw_prev_data); # first update need just for collect and save data
# ------------------------ print ------------------------
for my $graph (sort (keys %{$values}))
{
printf ("multigraph %s\n", $graph);
for my $field (sort keys %{$values->{$graph}})
{
printf("%s.value %s\n", $field, $values->{$graph}{$field});
}
print "\n";
}
}