mirror of
https://github.com/munin-monitoring/contrib.git
synced 2018-11-08 00:59:34 +01:00
1592 lines
45 KiB
Perl
Executable File
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. Avialable 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 = {};
|
|
|
|
# ------------------------ avialable 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' => 'wifi',
|
|
'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' => 'wifi',
|
|
'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' => 'wifi',
|
|
'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: avgerage 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)]
|
|
}
|
|
};
|
|
|
|
#-------------------------- avialable 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";
|
|
}
|
|
}
|