#!/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.


def apache_status_check_params(value, params, op_txt):
    def apply_operator(val, param, op_txt):
        if op_txt == "at":
            return val >= param
        elif op_txt == "below":
            return val < param

    warn, crit = params
    extra_perf = [warn, crit]
    levelstxt = ""
    state = 0

    if apply_operator(value, crit, op_txt):
        state = 2
        symbol = "(!!)"
    elif apply_operator(value, warn, op_txt):
        state = 1
        symbol = "(!)"

    if state:
        levelstxt += " (warn/crit %s %s/%s)%s" % \
                       (op_txt, warn, crit, symbol)

    return state, levelstxt, extra_perf


_apache_status_fields = {
    # key                sort  convert-func param_function
    'Uptime': (0, int),
    'IdleWorkers': (5, int),
    'BusyWorkers': (6, int, apache_status_check_params, "at"),
    'OpenSlots': (7, int, apache_status_check_params, "below"),
    'TotalSlots': (8, int),
    'Total Accesses': (9, int),
    'CPULoad': (10, float),
    'Total kBytes': (11, float),
    'ReqPerSec': (12, float),
    'BytesPerReq': (13, float),
    'BytesPerSec': (14, float),
    'Scoreboard': (15, str),
    'ConnsTotal': (16, int),
    'ConnsAsyncWriting': (17, int),
    'ConnsAsyncKeepAlive': (18, int),
    'ConnsAsyncClosing': (19, int),
    'BusyServers': (20, int),
    'IdleServers': (21, int),
}

_apache_status_stats = collections.OrderedDict((
    # scoreboard stats
    ('Waiting', '_'),
    ('StartingUp', 'S'),
    ('ReadingRequest', 'R'),
    ('SendingReply', 'W'),
    ('Keepalive', 'K'),
    ('DNS', 'D'),
    ('Closing', 'C'),
    ('Logging', 'L'),
    ('Finishing', 'G'),
    ('IdleCleanup', 'O'),
))


def apache_status_parse_legacy(info):
    # This parse function is required for compatibility with agents older than the 1.6 release.
    data = {}
    for line in info:
        if len(line) != 4 and not (len(line) == 5 and line[2] == 'Total'):
            continue  # Skip unexpected lines
        label = (' '.join(line[2:-1])).rstrip(':')
        if label not in _apache_status_fields:
            continue

        address, port = line[:2]
        value = _apache_status_fields[label][1](line[-1])
        if port == "None":
            item = address
        else:
            item = '%s:%s' % (address, port)

        if item not in data:
            data[item] = {}

        # Get statistics from scoreboard
        if label == 'Scoreboard':
            for stat_label, key in _apache_status_stats.items():
                data[item]['State_' + stat_label] = value.count(key)
            data[item]['OpenSlots'] = value.count('.')

        data[item][label] = value

        # Count number of total slots after all needed infos are present
        if 'OpenSlots' in data[item] and 'IdleWorkers' in data[item] \
           and 'BusyWorkers' in data[item]:
            data[item]['TotalSlots'] = data[item]['OpenSlots'] \
                                       + data[item]['IdleWorkers'] \
                                       + data[item]['BusyWorkers']

    return data


def apache_status_parse(info):
    if len(frozenset(len(_) for _ in info)) != 1:
        # The separator was changed in 1.6 so that the elements of `info`
        # have a constant length.
        return apache_status_parse_legacy(info)

    data = collections.defaultdict(dict)
    for address, port, instance, apache_info in info:
        try:
            label, status = apache_info.split(":", 1)
        except ValueError:
            # There is nothing to split.
            continue
        try:
            value = _apache_status_fields[label][1](status)
        except KeyError:
            # Not a label we handle.
            continue
        if instance and port != "None":
            item = '%s:%s' % (instance, port)
        elif instance:
            item = instance
        elif port != "None":
            item = '%s:%s' % (address, port)
        else:
            item = address

        # Get statistics from scoreboard
        if label == 'Scoreboard':
            for stat_label, key in _apache_status_stats.items():
                data[item]['State_' + stat_label] = value.count(key)
            data[item]['OpenSlots'] = value.count('.')

        data[item][label] = value

        # Count number of total slots after all needed infos are present
        if 'OpenSlots' in data[item] and 'IdleWorkers' in data[item] \
           and 'BusyWorkers' in data[item]:
            data[item]['TotalSlots'] = data[item]['OpenSlots'] \
                                       + data[item]['IdleWorkers'] \
                                       + data[item]['BusyWorkers']

    return data


def inventory_apache_status(info):
    return [(item, {}) for item in apache_status_parse(info)]


def check_apache_status(item, params, info):
    if params is None:
        params = {}

    if item.endswith(":None"):
        # fix item name discovered before werk 2763
        item = item[:-5]

    all_data = apache_status_parse(info)
    if item not in all_data:
        return (3, 'Unable to find instance in agent output')
    data_dict = all_data[item]

    this_time = int(time.time())

    if "Total Accesses" in data_dict:
        data_dict["ReqPerSec"] = get_rate("apache_status_%s_accesses" % item, this_time,
                                          data_dict["Total Accesses"])
        del data_dict["Total Accesses"]
    if "Total kBytes" in data_dict:
        data_dict["BytesPerSec"] = get_rate("apache_status_%s_bytes" % item, this_time,
                                            data_dict["Total kBytes"] * 1024)
        del data_dict["Total kBytes"]

    data = data_dict.items()

    worst_state = 0
    output = []
    perfdata = []

    # Sort keys
    data.sort(cmp=lambda x, y: cmp(
        _apache_status_fields.get(x[0], (0, None))[0],
        _apache_status_fields.get(y[0], (0, None))[0]))

    for key, value in data:
        if key not in _apache_status_fields.keys():
            continue

        # Don't process the scoreboard data directly. Print states instead
        if key == 'Scoreboard':
            states = []
            for stat_label, key in _apache_status_stats.items():
                val = data_dict.get('State_' + stat_label, 0)
                if val > 0:
                    states.append('%s: %d' % (stat_label, val))
                perfdata.append(('State_' + stat_label, val))
            output.append('States: (%s)' % ', '.join(states))
            continue

        if key == 'Uptime':
            display_value = get_age_human_readable(value)
        elif isinstance(value, float):
            display_value = '%0.2f' % value
        else:
            display_value = '%d' % value

        extra_info = ""
        extra_perf = []
        apache_status_key = _apache_status_fields[key]
        if params.get(key) and len(apache_status_key) > 3:
            key_state, extra_info, extra_perf = \
                apache_status_key[2](value, params.get(key), apache_status_key[3])
            worst_state = max(key_state, worst_state)

        output.append('%s: %s%s' % (key, display_value, extra_info))
        perfdata.append(tuple([key.replace(' ', '_'), value] + extra_perf))

    return (worst_state, ', '.join(output), perfdata)


check_info['apache_status'] = {
    "check_function": check_apache_status,
    "inventory_function": inventory_apache_status,
    "service_description": "Apache %s Status",
    "has_perfdata": True,
    "group": "apache_status"
}
