#!/usr/bin/python3 -tt # -*- coding: utf-8 -*- """Munin plugin to monitor stratis pools and filesystems. Copyright 2020 Kim B. Heino, Foobar Oy License GPLv2+ #%# capabilities=autoconf #%# family=auto """ import os import subprocess import sys def safename(variable): """Return safe variable name.""" ret = [] for letter in variable: if letter.isalnum(): ret.append(letter) else: ret.append('_') return ''.join(ret) def run_binary(arg): """Run binary and return output.""" try: cmd = subprocess.Popen( arg, shell=False, close_fds=True, bufsize=-1, stdout=subprocess.PIPE, stderr=subprocess.PIPE) return cmd.communicate()[0].decode('utf-8', 'ignore') except OSError: return '' def parse_unit(number, unit): """Parse "1.60 TiB" to bytes.""" number = float(number) if unit == 'TiB': return number * 1024 * 1024 * 1024 * 1024 if unit == 'GiB': return number * 1024 * 1024 * 1024 if unit == 'MiB': return number * 1024 * 1024 if unit == 'KiB': return number * 1024 return number def find_pools(): """Return list of found pools and filesystems.""" pool = [] for line in run_binary(['/usr/bin/stratis', 'pool']).splitlines(): if line.startswith('Name '): continue line = line.split() total = parse_unit(line[1], line[2]) used = parse_unit(line[4], line[5]) free = parse_unit(line[7], line[8]) pool.append((line[0], total, used, free)) files = [] dflist = run_binary(['/usr/bin/df']).splitlines() for line in run_binary(['/usr/bin/stratis', 'filesystem']).splitlines(): if line.startswith('Pool Name ') or '-snap-' in line: continue line = line.split() df_used = used = parse_unit(line[2], line[3]) for dfline in dflist: if line[9] not in dfline: # match by uuid continue df_used = int(dfline.split()[2]) * 1024 files.append((line[0], line[1], used, df_used)) return sorted(pool), sorted(files) def config(pools, files): """Print plugin config.""" print('multigraph stratis_pool') print('graph_title Stratis pools usage') print('graph_info Stratis pools 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 pools: name = safename(item[0]) print('{}.label Pool {} usage'.format(name, item[0])) print('{}.warning 92'.format(name)) print('{}.critical 98'.format(name)) print('multigraph stratis_fs') print('graph_title Stratis filesystems usage') print('graph_info Stratis filesystems pool usage.') print('graph_category disk') print('graph_vlabel Pool usage') print('graph_args --base 1024 --lower-limit 0') first = True for item in files: name = safename(item[0] + '_' + item[1]) print('{}.label Filesystem {}/{}'.format(name, item[0], item[1])) if first: print('{}.draw AREA'.format(name)) first = False else: print('{}.draw STACK'.format(name)) print('multigraph stratis_thin') print('graph_title Stratis thin filesystems usage vs df') print('graph_info Stratis thin filesystems usage divided by df, in ' 'percents.') print('graph_category disk') print('graph_vlabel %') print('graph_args --base 1000') for item in files: name = safename(item[0] + '_' + item[1]) print('{}.label {}/{} usage vs df'.format(name, item[0], item[1])) print('{}.warning 150'.format(name)) if os.environ.get('MUNIN_CAP_DIRTYCONFIG') == '1': fetch(pools, files) def fetch(pools, files): """Print values.""" print('multigraph stratis_pool') for item in pools: name = safename(item[0]) print('{}.value {}'.format(name, item[2] * 100 / item[1])) print('multigraph stratis_fs') for item in files: name = safename(item[0] + '_' + item[1]) print('{}.value {}'.format(name, item[2])) print('multigraph stratis_thin') for item in files: name = safename(item[0] + '_' + item[1]) print('{}.value {}'.format(name, item[2] * 100 / item[3])) if __name__ == '__main__': if len(sys.argv) > 1 and sys.argv[1] == 'autoconf': print('yes' if find_pools()[0] else 'no') elif len(sys.argv) > 1 and sys.argv[1] == 'config': config(*find_pools()) else: fetch(*find_pools())