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:
commit
24e77a2624
22
plugins/network/linux_if/README.md
Normal file
22
plugins/network/linux_if/README.md
Normal 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
297
plugins/network/linux_if/linux_if
Executable 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)
|
||||
|
BIN
plugins/network/linux_if/linux_if_bonding.png
Normal file
BIN
plugins/network/linux_if/linux_if_bonding.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 42 KiB |
BIN
plugins/network/linux_if/linux_if_vlans.png
Normal file
BIN
plugins/network/linux_if/linux_if_vlans.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 49 KiB |
Loading…
Reference in New Issue
Block a user