#!/usr/bin/python
# -*- encoding: utf-8; py-indent-offset: 4 -*-
# +------------------------------------------------------------------+
# |             ____ _               _        __  __ _  __           |
# |            / ___| |__   ___  ___| | __   |  \/  | |/ /           |
# |           | |   | '_ \ / _ \/ __| |/ /   | |\/| | ' /            |
# |           | |___| | | |  __/ (__|   <    | |  | | . \            |
# |            \____|_| |_|\___|\___|_|\_\___|_|  |_|_|\_\           |
# |                                                                  |
# | Copyright Mathias Kettner 2014             mk@mathias-kettner.de |
# +------------------------------------------------------------------+
#
# This file is part of Check_MK.
# The official homepage is at http://mathias-kettner.de/check_mk.
#
# check_mk is free software;  you can redistribute it and/or modify it
# under the  terms of the  GNU General Public License  as published by
# the Free Software Foundation in version 2.  check_mk is  distributed
# in the hope that it will be useful, but WITHOUT ANY WARRANTY;  with-
# out even the implied warranty of  MERCHANTABILITY  or  FITNESS FOR A
# PARTICULAR PURPOSE. See the  GNU General Public License for more de-
# tails. You should have  received  a copy of the  GNU  General Public
# License along with GNU Make; see the file  COPYING.  If  not,  write
# to the Free Software Foundation, Inc., 51 Franklin St,  Fifth Floor,
# Boston, MA 02110-1301 USA.

# Example output from agent:
# 0 Service_FOO V=1 This Check is OK
# 1 Bar_Service - This is WARNING and has no performance data
# 2 NotGood V=120;50;100;0;1000 A critical check
# P Some_other_Service value1=10;30;50|value2=20;10:20;0:50;0;100 Result is computed from two values
# P This_is_OK foo=18;20;50
# P Some_yet_other_Service temp=40;30;50|humidity=28;50:100;0:50;0;100
# P Has-no-var - This has no variable
# P No-Text hirn=-8;-20


# Compute state according to warn/crit levels contained in the
# performance data.
def local_compute_state(perfdata):
    texts = []

    # 16MB -> 16.0
    def float_ignore_uom(value):
        while value:
            if value[-1] not in "0123456789.-":
                value = value[:-1]
            else:
                return float(value)
        return 0.0


    def outof_levels(value, levels):
        if levels == None:
            return

        if ':' in levels:
            lower, upper = map(float, levels.split(':'))
        else:
            lower = None
            upper = float(levels)
        if value > upper:
            return " %s > %s" % (value, upper)
        elif lower != None and value < lower:
            return " %s < %s" % (value, lower)

    worst = 0
    for entry in perfdata:
        if len(entry) < 3:
            continue # No levels attached
        varname = entry[0]
        value = float_ignore_uom(entry[1])
        warn = entry[2]
        if len(entry) >= 4:
            crit = entry[3]
        else:
            crit = None

        text = outof_levels(value, crit)
        if text:
            worst = 2
            text = "%s%s(!!)" % (varname, text)
            texts.append(text)

        else:
            text = outof_levels(value, warn)
            if text:
                worst = max(worst, 1)
                text = "%s%s(!)" % (varname, text)
                texts.append(text)

            else:
                texts.append("%s is %s(.)" % (varname, value))

    return worst, texts


def inventory_local(info):
    inventory = []
    # Lines with P do not need to supply a text
    for line in info:
        nodename      = line[0]
        stripped_line = line[1:]
        if len(stripped_line) >= 4 or len(stripped_line) == 3 and stripped_line[0] == 'P':
            inventory.append( (stripped_line[1], {}) )
        else:
            raise MKGeneralException("Invalid line in agent section <<<local>>>: %s" % " ".join(stripped_line))
    return inventory


# Some helper functions
def _parse_local_line(line):
    if not (len(line) >= 4 or (len(line) == 3 and line[0] == 'P')):
        return 3, "Incomplete line in local check output: %s" % " ".join(line), []

    statechar = line[0]
    perftxt = line[2]

    # convert eventually escaped newinfo_line chars to real newinfo_lines
    # (will be converted back later individually for the different cores)
    output = " ".join(line[3:]).replace("\\n", "\n")

    perfdata = []
    if perftxt != "-":
        for entry in perftxt.split('|'):
            try:
                varname, valuetxt = entry.split('=')
                values = valuetxt.split(';')
                perfdata.append( tuple([varname] + values) )

            except ValueError:
                return 3, "Invalid performance data %s in local check output %s" % \
                       (perftxt, " ".join(line)), []

    if statechar == 'P':
        state, texts = local_compute_state(perfdata)
        if output:
            texts = [output] + texts
        output = ", ".join(texts)

    else:
        try:
            state = int(statechar)
        except:
            return 3, "Invalid state %s in local check output %s: must be P, 0, 1, 2 or 3" % \
                   (statechar, " ".join(line)), []

        if state not in range(0, 4):
            output += ", local check has sent invalid state %d" % state
            state = 3

    return state, output, perfdata


def _calculate_local_best_state(collected_stats):
    states    = []
    infotexts = []
    perfdatas = []
    for nodename, attrs in collected_stats.items():
        for itemname, (state, output, perfdata) in attrs.items():
            if nodename is not None:
                output = "On node %s: %s" % (nodename, output)
            states.append(state)
            infotexts.append(output)
            perfdatas += perfdata
    return min(states), ", ".join(infotexts), perfdatas


def _calculate_local_worst_state(collected_stats):
    for nodename, attrs in collected_stats.items():
        for itemname, (state, output, perfdata) in attrs.items():
            if nodename is not None:
                output = "On node %s: %s" % (nodename, output)
            yield state, output, perfdata


def check_local(item, params, info):
    collected_stats = {}
    for line in info:
        nodename      = line[0]
        stripped_line = line[1:]
        # Ignore invalid lines, tolerate bugs in local checks
        # of unexperienced users
        if len(stripped_line) >= 2 and stripped_line[1] == item:
            collected_stats.setdefault(nodename, {})
            collected_stats[nodename].setdefault(item, _parse_local_line(stripped_line))

    if collected_stats == {}:
        yield 3, "No data found in agent output"
        return

    if params is not None and params.get("outcome_on_cluster", "worst") == "best":
        yield _calculate_local_best_state(collected_stats)
        return
    else:
        for res in _calculate_local_worst_state(collected_stats):
            yield res


check_info["local"] = {
    'check_function'      : check_local,
    'inventory_function'  : inventory_local,
    'service_description' : '%s',
    'has_perfdata'        : True,
    'node_info'           : True,
    'group'               : 'local',
}
