#!/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 =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())