contrib-munin/plugins/network/linux_if/linux_if

298 lines
8.9 KiB
Python
Executable File

#!/usr/bin/env python
"""
linux_if - munin plugin monitoring Linux network interfaces
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:
* bonding - group bonding slave interfaces with master
* vlans - group vlan sub-interfaces with main (dot1q trunk) interface
plugin configuration:
[linux_if]
# run plugin as root (required if you have VLAN sub-interfaces)
user = root
# comma separated list of intreface patterns to exclude from monitoring
# default: lo
# example:
env.exclude = lo,vnet*
# comma separated list of intreface 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..
1) if matched by any exclude pattern, then exclude. Otherwise next step.
2) if matched by any include pattern, then include, Otherwise next step.
3) if 'include_configured_if' is true and 'ifcfg-*' file exists then include
4) default is not to include interface in monitoring
5) automatically include sub-interface, if the parent interface is monitored
Tested on: RHEL 6.x and clones (with Python 2.6)
TODO:
* implement 'data loaning' between graphs, removes duplicit measures
* add support for bridging
* configurable graph max based on intreface speed
MUNIN MAGIC MARKER
#%# family=manual
"""
__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)