Munin plugin if
Jump to navigation
Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
Pluginas skirtas tinklo sąsajų taip pat wifi signalo bei triukšmo stiprumo monitorinimui.
Nustatymai /etc/munin/plugin-conf.d/if
[if] env.exclude lo env.include wlan0 eth0 env.wlan0_max_bps 433M env.protect_peaks yes env.eth0_max_bps 1G env.protect_peaks yes
Pats scriptas /etc/munin/plugins/if
#!/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' => '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: 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";
}
}