#!/usr/bin/env python3
# -*- coding: utf-8; py-indent-offset: 4 -*-
#
# Author:  Linuxfabrik GmbH, Zurich, Switzerland
# Contact: info (at) linuxfabrik (dot) ch
#          https://www.linuxfabrik.ch/
# License: The Unlicense, see LICENSE file.

# https://github.com/Linuxfabrik/monitoring-plugins/blob/main/CONTRIBUTING.md

"""See the check's README for more details.
"""

import argparse  # pylint: disable=C0413
import sys  # pylint: disable=C0413

import lib.args  # pylint: disable=C0413
import lib.base  # pylint: disable=C0413
import lib.cache  # pylint: disable=C0413
import lib.human  # pylint: disable=C0413
import lib.net  # pylint: disable=C0413
import lib.lftest  # pylint: disable=C0413
import lib.time  # pylint: disable=C0413
import lib.txt  # pylint: disable=C0413
from lib.globals import (STATE_CRIT, STATE_OK,  # pylint: disable=C0413
                          STATE_UNKNOWN, STATE_WARN)

__author__ = 'Linuxfabrik GmbH, Zurich/Switzerland'
__version__ = '2025021501'

DESCRIPTION = """Monitors the heap and non-heap memory usage of the Java VM of the
                Starface PBX.
                It uses the data output of the Starface Monitoring Module, which was
                originally written for Check_MK and listens on port 6556. Supports both IPv4 and
                IPv6. Fetched data is cached up to one minute, so that other Starface plugins
                running in parallel do not query the data again and overload the PBX."""

DEFAULT_CACHE_EXPIRE = 1        # minutes
DEFAULT_CRIT = 90               # %
DEFAULT_HOSTNAME = 'localhost'
DEFAULT_IPV6 = False
DEFAULT_PORT = 6556
DEFAULT_TIMEOUT = 8             # seconds
DEFAULT_WARN = 80               # %


def parse_args():
    """Parse command line arguments using argparse.
    """
    parser = argparse.ArgumentParser(description=DESCRIPTION)

    parser.add_argument(
        '-V', '--version',
        action='version',
        version='%(prog)s: v{} by {}'.format(__version__, __author__)
    )

    parser.add_argument(
        '--always-ok',
        help='Always returns OK.',
        dest='ALWAYS_OK',
        action='store_true',
        default=False,
    )

    parser.add_argument(
        '--cache-expire',
        help='The amount of time after which the cached data expires, in minutes. Default: %(default)s',
        dest='CACHE_EXPIRE',
        type=int,
        default=DEFAULT_CACHE_EXPIRE,
    )

    parser.add_argument(
        '--critical',
        help='Set the critical threshold (percentage). Default: %(default)s',
        dest='CRIT',
        default=DEFAULT_CRIT,
    )

    parser.add_argument(
        '-H', '--hostname',
        help='Starface PBX address, can be IP address or hostname. Default: %(default)s',
        dest='HOSTNAME',
        default=DEFAULT_HOSTNAME,
    )

    parser.add_argument(
        '--port',
        help='Starface PBX monitoring port. Default: %(default)s',
        dest='PORT',
        type=int,
        default=DEFAULT_PORT,
    )

    parser.add_argument(
        '--test',
        help='For unit tests. Needs "path-to-stdout-file,path-to-stderr-file,expected-retc".',
        dest='TEST',
        type=lib.args.csv,
    )

    parser.add_argument(
        '--timeout',
        help='Network timeout in seconds. Default: %(default)s (seconds)',
        dest='TIMEOUT',
        type=int,
        default=DEFAULT_TIMEOUT,
    )

    parser.add_argument(
        '--warning',
        help='Set the warning threshold (percentage). Default: %(default)s',
        dest='WARN',
        default=DEFAULT_WARN,
    )

    parser.add_argument(
        '--ipv6',
        help='Use IPv6.',
        dest='USE_IPV6',
        action='store_true',
        default=DEFAULT_IPV6,
    )

    return parser.parse_args()


def main():
    """The main function. Hier spielt die Musik.
    """

    # parse the command line, exit with UNKNOWN if it fails
    try:
        args = parse_args()
    except SystemExit:
        sys.exit(STATE_UNKNOWN)

    # fetch data
    if args.TEST is None:
        # fetch data from PBX, but first from cache
        data = lib.cache.get(args.HOSTNAME, filename='linuxfabrik-monitoring-plugins-starface.db')
        if not data:
            data = lib.base.coe(lib.net.fetch(args.HOSTNAME, args.PORT, timeout=args.TIMEOUT))
            lib.cache.set(args.HOSTNAME, data, lib.time.now() + args.CACHE_EXPIRE*60, filename='linuxfabrik-monitoring-plugins-starface.db')
    else:
        # do not call the command, put in test data
        stdout, stderr, retc = lib.lftest.test(args.TEST)
        data = stdout

    if not data:
        lib.base.cu('Got no valuable response from {}:{}.'.format(args.HOSTNAME, args.PORT))

    # extract data
    result = lib.txt.extract_str(data, '<<<starface_java>>>', '<<<').splitlines()

    # init some vars
    msg = ''
    state = STATE_OK
    perfdata = ''

    # build the message
    for row in result:
        if not row:
            continue

        if row.startswith('java_heap_memory'):
            mem_used, mem_max = row.replace('java_heap_memory ', '').split('/')
            mem_used = float(mem_used)
            mem_max = float(mem_max)
            if mem_max == -1:
                # unlimited memory usage allowed
                msg += 'Heap used: {} (unlimited memory usage allowed), '.format(lib.human.bytes2human(mem_used))
                perfdata += lib.base.get_perfdata('heap-used', mem_used, 'B', None, None, 0, None)
            else:
                used_percent = round(mem_used / mem_max * 100, 1)
                used_state = lib.base.get_state(used_percent, args.WARN, args.CRIT, 'ge')
                msg += 'Heap used: {}% ({} of {}){}, '.format(
                    used_percent, lib.human.bytes2human(mem_used), lib.human.bytes2human(mem_max), lib.base.state2str(used_state, prefix=' '),
                )
                state += lib.base.get_worst(used_state, state)
                perfdata += lib.base.get_perfdata('heap-used-percent', used_percent, '%', args.WARN, args.CRIT, 0, 100)
                perfdata += lib.base.get_perfdata('heap-used', mem_used, 'B', None, None, 0, mem_max)
                perfdata += lib.base.get_perfdata('heap-max', mem_max, 'B', None, None, 0, mem_max)

        if row.startswith('java_non_heap_memory'):
            mem_used, mem_max = row.replace('java_non_heap_memory ', '').split('/')
            mem_used = float(mem_used)
            mem_max = float(mem_max)
            if mem_max == -1:
                # unlimited memory usage allowed
                msg += 'Non-Heap used: {} (unlimited memory usage allowed), '.format(lib.human.bytes2human(mem_used))
                perfdata += lib.base.get_perfdata('non-heap-used', mem_used, 'B', None, None, 0, None)
            else:
                used_percent = round(mem_used / mem_max * 100, 1)
                used_state = lib.base.get_state(used_percent, args.WARN, args.CRIT, 'ge')
                msg += 'Non-Heap used: {}% ({} of {}){}, '.format(
                    used_percent, lib.human.bytes2human(mem_used), lib.human.bytes2human(mem_max), lib.base.state2str(used_state, prefix=' '),
                )
                state += lib.base.get_worst(used_state, state)
                perfdata += lib.base.get_perfdata('non-heap-used-percent', used_percent, '%', args.WARN, args.CRIT, 0, 100)
                perfdata += lib.base.get_perfdata('non-heap-used', mem_used, 'B', None, None, 0, mem_max)
                perfdata += lib.base.get_perfdata('non-heap-max', mem_max, 'B', None, None, 0, mem_max)

    # over and out
    lib.base.oao(msg[:-2], state, perfdata, always_ok=args.ALWAYS_OK)


if __name__ == '__main__':
    try:
        main()
    except Exception:   # pylint: disable=W0703
        lib.base.cu()
