Repository
Munin (contrib)
Last change
2021-01-10
Graph Categories
Family
auto
Capabilities
Keywords
Language
Python (3.x)
License
GPL-2.0-only
Authors

stratis

Name

stratis - monitor stratis pools and filesystems

Applicable Systems

Linux systems with stratis filesystems.

Configuration

No configuration is required for this plugin.

Author

Kim B. Heino b@bbbs.net

License

GPLv2

Magic Markers

#%# family=auto
#%# capabilities=autoconf
#!/usr/bin/env python3

"""Munin plugin to monitor stratis pools and filesystems.

=head1 NAME

stratis - monitor stratis pools and filesystems

=head1 APPLICABLE SYSTEMS

Linux systems with stratis filesystems.

=head1 CONFIGURATION

No configuration is required for this plugin.

=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(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(['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(['df']).splitlines()
    for line in run_binary(['stratis', 'filesystem']).splitlines():
        if line.startswith('Pool Name ') or '-snap-' in line:
            continue
        tokens = line.split()
        df_used = used = parse_unit(tokens[2], tokens[3])
        for dfline in dflist:
            if tokens[9] not in dfline:  # match by uuid
                continue
            df_used = int(dfline.split()[2]) * 1024
        files.append((tokens[0], tokens[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 (no stratis pools found)')
    elif len(sys.argv) > 1 and sys.argv[1] == 'config':
        config(*find_pools())
    else:
        fetch(*find_pools())