2
0
mirror of https://github.com/munin-monitoring/contrib.git synced 2018-11-08 00:59:34 +01:00

Merge pull request #522 from zarnovican/linux_if

Initial commit of linux_if plugin
This commit is contained in:
Steve Schnepp 2014-09-09 15:06:28 +02:00
commit 24e77a2624
4 changed files with 319 additions and 0 deletions

View File

@ -0,0 +1,22 @@
linux_if
========
Linux network monitoring plugin with support for *bonding* and *vlans*.
Plugin will group all bonding slaves into one aggregated graph. It removes clutter
of having many single-metric graphs for individual interfaces. You still can click
trough to individual graphs.
In this example, `p1p1`, `p2p1` are physical interfaces of one bond0. Only p1p1 is active,
as we are using Active-Backup bonding mode. `Total` (black) is the throughput of parent
interface `bond0`.
![linux_if_bonding text](linux_if_bonding.png "linux_if bonding feature")
Similar aggregation is done also on higher level for vlans. All vlan sub-interfaces
are aggregated into one graph, based on their parent interface. Interfaces do not even
have to follow the same naming convention (_interface_._vlanid_). Interfaces `bond0.279`,
`vlan101`, `vlan102` are all vlan sub-interfaces of bond0.
![linux_if_vlans_text](linux_if_vlans.png "linux_if vlan feature")

297
plugins/network/linux_if/linux_if Executable file
View File

@ -0,0 +1,297 @@
#!/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 staticly 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:
* implment '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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB