munin-contrib/plugins/disk/zram

181 lines
4.9 KiB
Python
Executable File

#!/usr/bin/env python3
"""Munin plugin to monitor zram devices.
=head1 NAME
zram - monitor zram devices
=head1 APPLICABLE SYSTEMS
Linux systems with zram devices.
=head1 CONFIGURATION
No configuration is required for this plugin.
Optionally you may specify specific warning and critical levels:
[zram]
env.df_dev_zram0_warning 98
env.df_dev_zram0_critical 99
=head1 AUTHOR
Kim B. Heino <b@bbbs.net>
=head1 LICENSE
GPLv2
=head1 MAGIC MARKERS
#%# family=auto
#%# capabilities=autoconf
=cut
"""
import os
import subprocess
import sys
import unicodedata
def safename(name):
"""Return safe variable name."""
# Convert ä->a as isalpha('ä') is true
value = unicodedata.normalize('NFKD', name)
value = value.encode('ASCII', 'ignore').decode('utf-8')
# Remove non-alphanumeric chars
return ''.join(char.lower() if char.isalnum() else '_' for char in value)
def run_binary(arg):
"""Run binary and return output."""
try:
return subprocess.run(arg, stdout=subprocess.PIPE, check=False,
encoding='utf-8', errors='ignore').stdout
except FileNotFoundError:
return ''
def parse_unit(value):
"""Parse "1.60G" to bytes."""
unit = value[-1]
number = float(value[:-1])
if unit == 'T':
return number * 1024 * 1024 * 1024 * 1024
if unit == 'G':
return number * 1024 * 1024 * 1024
if unit == 'M':
return number * 1024 * 1024
if unit == 'K':
return number * 1024
return number
def warning_critical(graph, name, warning=None, critical=None):
"""Print warning/critical config entries."""
warning = os.environ.get(f'{graph}{name}_warning', warning)
critical = os.environ.get(f'{graph}{name}_critical', critical)
if warning and warning != ':':
print(f'{name}.warning {warning}')
if critical and critical != ':':
print(f'{name}.critical {critical}')
def find_zram():
"""Return list of found zram devices."""
zram = []
for line in sorted(run_binary(['zramctl']).splitlines()):
if not line.startswith('/dev/'):
continue
items = line.split()
if len(items) == 7: # Not mounted, use device name
mount = items[0]
else:
mount = items[7]
if mount == '[SWAP]':
mount = 'Swap'
zram.append((safename(items[0]), # 0, device
parse_unit(items[2]), # 1, disksize
parse_unit(items[3]), # 2, data
parse_unit(items[5]), # 3, total
mount)) # 4, mountpoint
return zram
def config(zram):
"""Print plugin config."""
# Similar to df plugin
print('multigraph zram_df')
print('graph_title zram usage in percent')
print('graph_info zram device usage in percent.')
print('graph_category disk')
print('graph_vlabel %')
print('graph_args --lower-limit 0 --upper-limit 100')
print('graph_scale no')
for item in zram:
print(f'{item[0]}.label {item[4]}')
warning_critical('df', item[0], 92, 98)
# Compression ratio:
# 100 = compresses to few bytes only
# 0 = stays same (or increases, this happens if metadata grows)
print('multigraph zram_compression')
print('graph_title zram compression ratio')
print('graph_info zram data compression ratio.')
print('graph_category disk')
print('graph_vlabel %')
print('graph_args --lower-limit 0 --upper-limit 100')
for item in zram:
print(f'{item[0]}.label {item[4]}')
warning_critical('compression', item[0])
# Real memory usage
print('multigraph zram_memory')
print('graph_title zram memory usage')
print('graph_info zram device compressed memory usage.')
print('graph_category disk')
print('graph_vlabel bytes')
print('graph_args --base 1024 --lower-limit 0')
first = True
for item in zram:
print(f'{item[0]}.label {item[4]}')
warning_critical('memory', item[0])
print(f'{item[0]}.draw {"STACK" if first else "AREA"}')
first = False
if os.environ.get('MUNIN_CAP_DIRTYCONFIG') == '1':
fetch(zram)
def fetch(zram):
"""Print values."""
print('multigraph zram_df')
for item in zram:
print(f'{item[0]}.value {100 * item[2] / item[1]:.3f}')
print('multigraph zram_compression')
for item in zram:
if item[3] >= item[2]: # Empty or size increases
print(f'{item[0]}.value 0')
else:
print(f'{item[0]}.value {100 - 100 * (item[3] / item[2])}')
print('multigraph zram_memory')
for item in zram:
print(f'{item[0]}.value {item[3]}')
if __name__ == '__main__':
if len(sys.argv) > 1 and sys.argv[1] == 'autoconf':
print('yes' if find_zram() else 'no (no zram devices found)')
elif len(sys.argv) > 1 and sys.argv[1] == 'config':
config(find_zram())
else:
fetch(find_zram())