munin-contrib/plugins/network/linux_if/linux_if

332 lines
9.1 KiB
Python
Executable File

#!/usr/bin/env python
"""
=head1 NAME
linux_if - munin plugin monitoring Linux network interfaces
=head1 DESCRIPTION
This is not a wildcard plugin. Monitored interfaces are controlled
by 'include', 'exclude' in config. By default, only statically
configured interfaces (and their sub-interfaces) are monitored.
Features:
=over
=item bonding - group bonding slave interfaces with master
=item vlans - group vlan sub-interfaces with main (dot1q trunk) interface
=back
=head1 CONFIGURATION
[linux_if]
# run plugin as root (required if you have VLAN sub-interfaces)
user = root
# comma separated list of interface patterns to exclude from monitoring
# default: lo
# example:
env.exclude = lo,vnet*
# comma separated list of interface patterns to include in monitoring
# default: (empty)
# example:
env.include = br_*
# should statically configured interfaces be included (they have ifcfg-* file)
# default: true
env.include_configured_if = true
Include/exclude logic in detail. Interface name is matched according to the following rules:
=over 4
=item 1. if matched by any exclude pattern, then exclude. Otherwise next step.
=item 2. if matched by any include pattern, then include, Otherwise next step.
=item 3. if 'include_configured_if' is true and 'ifcfg-*' file exists then include
=item 4. default is not to include interface in monitoring
=item 5. automatically include sub-interface, if the parent interface is monitored
=back
Tested on: RHEL 6.x and clones (with Python 2.6)
=head1 TODO
=over 4
=item implement 'data loaning' between graphs, removes duplicit measures
=item add support for bridging
=item configurable graph max based on interface speed
=back
=head1 MAGIC MARKERS
#%# family=manual
=cut
"""
__author__ = 'Brano Zarnovican'
__email__ = 'zarnovican@gmail.com'
__license__ = 'BSD'
__version__ = '0.9'
import fnmatch, os, sys
#from pprint import pprint
#
# handle 'autoconf' option
#
if len(sys.argv) > 1 and sys.argv[1] == 'autoconf':
if os.path.exists('/proc/net/dev'):
print('yes')
sys.exit(0)
else:
print('no')
sys.exit(1)
#
# plugin configuration
#
exclude_patterns = os.environ.get('exclude', 'lo').split(',')
include_patterns = os.environ.get('include', '').split(',')
include_configured_if = os.environ.get('include_configured_if', 'true').lower()
def interface_is_enabled(ifname):
"""logic to include or exclude this interface in plugin based on configuration"""
if any(fnmatch.fnmatch(ifname, pattern) for pattern in exclude_patterns):
return False
if any(fnmatch.fnmatch(ifname, pattern) for pattern in include_patterns):
return True
if include_configured_if == 'true' and \
os.path.exists('/etc/sysconfig/network-scripts/ifcfg-'+ifname):
return True
return False
#
# read counts for all interfaces (for both 'update' or 'config')
#
interface = {} # interface[name][measure] = value
try:
fieldnames = ('rxbytes', 'rxpackets', 'rxerrs', 'rxdrop', 'rxfifo', 'rxframe', 'rxcompressed', 'rxmulticast') +\
('txbytes', 'txpackets', 'txerrs', 'txdrop', 'txfifo', 'txcolls', 'txcarrier', 'txcompressed')
with open('/proc/net/dev') as f:
f.readline() # skip 2-line header
f.readline()
for line in f:
l = line.replace('|', ' ').replace(':', ' ').split()
ifname = l[0].strip(':')
assert len(l) == 17, 'Unexpected number of fields (%d)' % len(l)
interface[ifname] = dict(zip(fieldnames, l[1:]))
interface[ifname]['name'] = ifname
interface[ifname]['sname'] = ifname.replace('.', '_') # sanitized interface name
except IOError as e:
print(e)
sys.exit(-1)
#
# associate slave interfaces to their bond masters
#
bond = {} # bond[bondname][slavename][measure] = value
try:
with open('/sys/class/net/bonding_masters') as f:
bond_list = f.read().split()
for bondname in bond_list:
if bondname not in interface: continue
if not interface_is_enabled(bondname): continue
bond[bondname] = { 'subifs': [], }
bond[bondname]['parent'] = interface[bondname]
with open('/sys/class/net/'+bondname+'/bonding/slaves') as f:
slave_list = f.read().split()
for slave in slave_list:
if slave not in interface: continue
bond[bondname]['subifs'].append(slave)
bond[bondname][slave] = interface[slave]
del interface[slave]
except IOError:
pass # bonding not configured
#
# associate VLAN sub-interfaces to their trunks
#
trunk = {} # trunk[trunkname][subifname][measure] = value
try:
with open('/proc/net/vlan/config') as f:
f.readline()
f.readline()
for line in f:
(subif, vlanid, trunkif) = line.replace('|', ' ').split()
if trunkif not in interface: continue
if subif not in interface: continue
if not interface_is_enabled(trunkif): continue
if trunkif not in trunk:
trunk[trunkif] = { 'subifs': [], }
trunk[trunkif]['parent'] = interface[trunkif]
trunk[trunkif]['subifs'].append(subif)
trunk[trunkif][subif] = interface[subif]
del interface[subif]
except IOError:
pass # vlans not configured (or not running as root)
#
# all remaining interfaces are considered 'plain'
#
plain = {} # plain[ifname][measure] = value
for (ifname, counts) in interface.items():
if ifname in bond or ifname in trunk: continue
if not interface_is_enabled(ifname): continue
plain[ifname] = counts
#
# now, do the actual stdout output..
#
in_config = (len(sys.argv) > 1 and sys.argv[1] == 'config')
def graph_interface_traffic(data):
if in_config:
print("""graph_title {name} traffic
graph_order down up
graph_args --base 1000 --lower-limit 0
graph_vlabel bits in (-) / out (+) per ${{graph_period}}
graph_category network
down.label received
down.type DERIVE
down.graph no
down.cdef down,8,*
down.min 0
up.label bps
up.type DERIVE
up.negative down
up.cdef up,8,*
up.min 0""".format(**data))
else:
print("""down.value {rxbytes}
up.value {txbytes}""".format(**data))
print('')
def graph_interface_errors(data):
if in_config:
print("""graph_title {name} errors
graph_args --base 1000 --lower-limit 0
graph_vlabel counts RX (-) / TX (+) per ${{graph_period}}
graph_category network
rxerrs.label errors
rxerrs.type COUNTER
rxerrs.graph no
txerrs.label errors
txerrs.type COUNTER
txerrs.negative rxerrs
rxdrop.label drops
rxdrop.type COUNTER
rxdrop.graph no
txdrop.label drops
txdrop.type COUNTER
txdrop.negative rxdrop
txcolls.label collisions
txcolls.type COUNTER""".format(**data))
else:
print("""rxerrs.value {rxerrs}
txerrs.value {txerrs}
rxdrop.value {rxdrop}
txdrop.value {txdrop}
txcolls.value {txcolls}""".format(**data))
print('')
def graph_traffic_with_subifs(ddata, title):
if in_config:
print('graph_title ' + title)
print("""graph_args --base 1000 --lower-limit 0
graph_vlabel bits in (-) / out (+) per ${graph_period}
graph_category network""")
for ifname in ddata['subifs'] + ['parent',]:
data = d[ifname]
if ifname == 'parent':
label = 'total'
drawtype = 'LINE1'
else:
label = data['name']
drawtype = 'AREASTACK'
if in_config:
print("""{sname}_down.label {label}
{sname}_down.type DERIVE
{sname}_down.graph no
{sname}_down.cdef {sname}_down,8,*
{sname}_down.min 0
{sname}_up.label {label}
{sname}_up.type DERIVE
{sname}_up.negative {sname}_down
{sname}_up.cdef {sname}_up,8,*
{sname}_up.draw {drawtype}
{sname}_up.min 0""".format(label=label, drawtype=drawtype, **data))
if ifname == 'parent':
print('{sname}_up.colour 000000'.format(**data))
else:
print("""{sname}_down.value {rxbytes}
{sname}_up.value {txbytes}""".format(**data))
print('')
for d in plain.values():
print('multigraph interface_{sname}_traffic'.format(**d))
graph_interface_traffic(d)
print('multigraph interface_{sname}_errors'.format(**d))
graph_interface_errors(d)
for d in bond.values():
parent = d['parent']
print('multigraph bond_{sname}_traffic'.format(**parent))
graph_traffic_with_subifs(d, title='{0} traffic (stacked)'.format(parent['name']))
print('multigraph bond_{sname}_errors'.format(**parent))
graph_interface_errors(parent)
for ifname in d['subifs']:
if_data = d[ifname]
print('multigraph bond_{0}_traffic.{1}'.format(parent['sname'], if_data['sname']))
graph_interface_traffic(if_data)
print('multigraph bond_{0}_errors.{1}'.format(parent['sname'], if_data['sname']))
graph_interface_errors(if_data)
for d in trunk.values():
parent = d['parent']
print('multigraph trunk_{sname}_traffic'.format(**parent))
graph_traffic_with_subifs(d, title='{0} trunk (stacked)'.format(parent['name']))
for ifname in d['subifs']:
if_data = d[ifname]
print('multigraph trunk_{0}_traffic.{1}'.format(parent['sname'], if_data['sname']))
graph_interface_traffic(if_data)