diff --git a/check_synology.py b/check_synology.py index 9c4e2e7..e068ff9 100755 --- a/check_synology.py +++ b/check_synology.py @@ -3,10 +3,12 @@ import sys import traceback from enum import Enum +# from pycparser.c_ast import Union as PycUnion from pysnmp.hlapi import * from checker import nagios from checker.result import quit_check +from checker.units import human_readable_size """ https://github.com/SnejPro/check_synology/blob/main/check_synology.py @@ -40,6 +42,23 @@ class SnmpDisk(Enum): disk = '1.3.6.1.4.1.6574.2.1.1' +class SnmpStorage(Enum): + desc = '1.3.6.1.2.1.25.2.3.1.3' + allocated_pre = '1.3.6.1.2.1.25.2.3.1.4.' + size_pre = '1.3.6.1.2.1.25.2.3.1.5.' + used_pre = '1.3.6.1.2.1.25.2.3.1.6.' + + +def format_bytes(size: int, unit): + power = 10 ** 3 + n = 0 + power_labels = {0: '', 1: 'K', 2: 'M', 3: 'G', 4: 'T'} + while size > power: + size /= power + n += 1 + return size, power_labels[n] + unit, str(round(size, 2)) + ' ' + power_labels[n] + unit + + def check_failed(value: str): if value == '1': exit_code = nagios.STATE_OK @@ -115,6 +134,18 @@ def walk_snmp(host, community, oid): return result +def parse_walk(data: dict): + disk_indices = set(key.split('.')[-1] for key in data.keys()) + parsed = [] + for index in disk_indices: + disk_info = [] + for key, value in data.items(): + if key.endswith(index): + disk_info.append(value) + parsed.append(disk_info) + return parsed + + def main(args): if args.choice == 'mem': mem_unused = query_snmp(args.host, args.community, SnmpMem.unused.value) @@ -213,22 +244,14 @@ def main(args): }) elif args.choice == 'disks': data = walk_snmp(args.host, args.community, SnmpDisk.disk.value) - disk_indices = set(key.split('.')[-1] for key in data.keys()) - disk_data = [] - for index in disk_indices: - disk_info = [] - for key, value in data.items(): - if key.endswith(index): - disk_info.append(value) - disk_data.append(disk_info) - + disk_data = parse_walk(data) result_str = '' perfdata_dict = {} exit_code = nagios.STATE_OK for disk, data in enumerate(disk_data): output, value, d_exit_code = check_disk_status(data[4]) exit_code = d_exit_code - result_str = result_str + f'Disk {disk + 1}: {output}' + result_str = result_str + f'Disk {disk + 1}: {output}. ' perfdata_dict[f'disk_{disk + 1}_status'] = { 'value': value, 'unit': '', @@ -238,15 +261,59 @@ def main(args): 'value': data[5], 'unit': 'C' } - quit_check(result_str, exit_code, perfdata_dict) + quit_check(result_str.strip(' '), exit_code, perfdata_dict) elif args.choice == 'storage': - raise NotImplementedError + store_list = walk_snmp(args.host, args.community, SnmpStorage.desc.value) + result_str = '' + perfdata_dict = {} + exit_codes = [] + for k, name in store_list.items(): + if not name.startswith('/volume'): + continue + store_id = k.split('.')[-1] + allocated = int(query_snmp(args.host, args.community, SnmpStorage.allocated_pre.value + store_id)) + size = int(query_snmp(args.host, args.community, SnmpStorage.size_pre.value + store_id)) * allocated + used = int(query_snmp(args.host, args.community, SnmpStorage.used_pre.value + store_id)) * allocated + used_percent = round((used / size) * 100, 1) + result_str = result_str + f'{name}: {human_readable_size(used, decimal_places=0)}/{human_readable_size(size, decimal_places=0)} ({used_percent}%). ' + perf_name = name.replace('/', '') + perfdata_dict[f'{perf_name}_size'] = { + 'value': size, + 'unit': 'B', + 'min': 0 + } + perfdata_dict[f'{perf_name}_used'] = { + 'value': used, + 'unit': 'B', + 'min': 0 + } + perfdata_dict[f'{perf_name}_used_percent'] = { + 'value': used_percent, + 'unit': '%', + 'min': 0 + } + if used_percent >= float(args.crit_store): + exit_codes.append(nagios.STATE_CRIT) + elif used_percent >= float(args.warn_store): + exit_codes.append(nagios.STATE_WARN) + else: + exit_codes.append(nagios.STATE_OK) + quit_check(result_str.strip(' '), max(exit_codes), perfdata_dict) elif args.choice == 'network': raise NotImplementedError else: raise Exception +def validate_int_float_arg(arg, name): + try: + arg = float(arg) + except: + raise Exception(f'{name} must be an int or float') + if not isinstance(arg, (int, float)): + raise Exception(f'{name} must be an int or float') + + if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('-H', '--host', required=True, help='The host to connect to.') @@ -255,9 +322,14 @@ if __name__ == '__main__': parser.add_argument('--warn-mem', type=int, default=75, help='Memory usage percent to warn at. Default: 75%') parser.add_argument('--crit-mem', type=int, default=90, help='Memory usage percent to crit at. Default: 90%') + parser.add_argument('--warn-store', default=75, help='Storage usage percent to warn at. Default: 65%') + parser.add_argument('--crit-store', default=90, help='Storage usage percent to crit at. Default: 70%') args = parser.parse_args() + validate_int_float_arg(args.warn_store, '--warn-store') + validate_int_float_arg(args.crit_store, '--crit-store') + try: main(args) except Exception as e: