Skip to content

Instantly share code, notes, and snippets.

@thorsummoner
Last active April 7, 2022 20:25
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thorsummoner/e9707c9a7805f5fe3342c06eafa4e1aa to your computer and use it in GitHub Desktop.
Save thorsummoner/e9707c9a7805f5fe3342c06eafa4e1aa to your computer and use it in GitHub Desktop.
munin diskmon plugin ported to python
#!/usr/bin/env python3
# ported from https://github.com/terrorobe/munin-plugins/blob/master/alumni/linux_diskstats
# GNP GPLv2
import glob
import re as regex
class diskmon(object):
"""docstring for diskmon"""
poll_interval = 300
def __init__(
self,
graph_width=400
):
super(diskmon, self).__init__()
self.graph_width = graph_width
def parse_diskstats(self):
stats = False \
or self.read_sysfs() \
or self.read_procfs()
diskstats = None
for entry in stats:
devstat = dict(zip([
'major',
'minor',
'devname',
'rd_ios',
'rd_merges',
'rd_sectors',
'rd_ticks',
'wr_ios',
'wr_merges',
'wr_sectors',
'wr_ticks',
'ios_in_prog',
'tot_ticks',
'rq_ticks',
], entry))
device = devstat['devname']
pretty_device = None
if re.match('/^dm-\d+$/', device):
pretty_device = self.translate_devicemapper_name(device)
devstat['pretty_device_name'] = pretty_device or device
# Short device name only containing the stuff after the last '/'
# for graph labels et al.
devstat['short_pretty_device_name'] = devstat['pretty_device_name'].split('/')[-1]
def translate_devicemapper_name(self, device):
want_minor = re.search('/^dm-(\d+)$/')
if None is want_minor:
raise LookupError('Failed to extract devicemapper id')
dm_major = self.find_devicemapper_major()
if None is dm_major:
raise LookupError('Failed to get device-mapper major number')
for entry in next(os.walk('/dev/mapper/'))[2]:
entry = os.path.join('/dev/mapper/', entry)
rdev = int(os.stat(entry).st_dev)
major, minor = (rdev // 256, rdev % 256)
if magor == dm_major and minor == want_minor:
return self.translate_lvm_name(entry)
return entry
# Return original string if the device can't be found.
return $device;
def read_sysfs(self):
lines = []
devices = next(os.walk('/sys/block'))[1]
for cur_device in devices:
stats_file = "/sys/block/%s/stat" % tuple(cur_device)
with open(stats_file) as statfh:
# Trimming whitespace
line = statfh.readline().strip()
elems = re.split('\s+', line)
if len(elems) is not 11:
raise LookupError("'%s' doesn't contain exactly 11 values. Aborting" % tuple(stats_file))
lines.append(elems)
return lines
def read_procfs(self):
lines = []
with open('/proc/diskstats') as statfh:
for line in statfh.readlines():
line = line.strip()
# Strip trailing newline and leading whitespace
elems = re.split('\s+', line)
# We explicitly don't support old-style diskstats
# There are situations where only _some_ lines (e.g.
# partitions on older 2.6 kernels) have fewer stats
# numbers, therefore we'll skip them silently
if len(elems) != 14:
continue
lines.append(elems)
return lines
def find_devicemapper_major(self):
with open('/proc/devices') as devicefh:
dm_major = None
for line in devicefh.readlines():
line = line.strip()
major, name = re.split('/\s+/', line, 2)
if not name:
continue
if name is 'device-mapper':
dm_major = major
break
return dm_major
def translate_lvm_name(self, entry):
device_name = os.path.basename(entry)
# Check for single-dash-occurence to see if this could be a lvm devicemapper device.
if re.match('/(?<!-)-(?!-)/', device_name):
# split device name into vg and lv parts
vg, lv = re.split('/(?<!-)-(?!-)/', device_name, 2)
if not vg and not lv:
return None
# remove extraneous dashes from vg and lv names
vg = re.sub('/--/', '-')
lv = re.sub('/--/', '-')
device_name = '/'.join(vg, lv)
# Sanity check - does the constructed device name exist?
# Breaks unless we are root.
if os.path.exists(os.path.join('/dev', device_name)):
return device_name
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment