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

# <<<job>>>
# ==> asd ASD <==
# start_time 1389355839
# exit_code 0
# real_time 0:00.00
# user_time 0.00
# system_time 0.00
# reads 0
# writes 0
# max_res_kbytes 1968
# avg_mem_kbytes 0
#
#
# ==> test <==
# start_time 1389352839
# exit_code 0
# real_time 0:00.00
# user_time 0.00
# system_time 0.00
# reads 0
# writes 0
# max_res_kbytes 1984
# avg_mem_kbytes 0

factory_settings["job_default_levels"] = {
    "age": (0, 0)  # disabled as default
}


def _job_parse_real_time(s):
    parts = s.split(':')
    min_sec, hour_sec = 0, 0
    if len(parts) == 3:
        hour_sec = int(parts[0]) * 60 * 60
    if len(parts) >= 2:
        min_sec = int(parts[-2]) * 60
    return float(parts[-1]) + min_sec + hour_sec


def _job_parse_key_values(line):
    key, val = line
    if key == 'real_time':
        return key, _job_parse_real_time(val)
    if key in ('user_time', 'system_time'):
        return key, float(val)
    if key in ('max_res_kbytes', 'avg_mem_kbytes'):
        return key.replace('kbytes', 'bytes'), int(val) * 1000
    return key, int(val)


def parse_job(info):
    parsed = {}
    job = {}
    prefix = None
    for line in info:
        node_info = line[0]
        line = line[1:]
        if line[0] == "==>" and line[-1] == "<==":
            jobname = " ".join(line[1:-1])
            if jobname.endswith("running"):
                jobstate = "running"
                jobname = jobname.rsplit(".", 1)[0]
                prefix = "running_"
            else:
                jobstate = None
                prefix = ""
            job = parsed.setdefault(jobname, {}).setdefault(node_info, {
                "state": jobstate,
            })

        elif job and prefix is not None and len(line) == 2:
            key, val = _job_parse_key_values(line)
            if prefix:
                job.setdefault(prefix + key, []).append(val)
            else:
                job[key] = val

    return parsed


def inventory_job(parsed):
    for jobname, nodes in parsed.iteritems():
        for jobattrs in nodes.values():
            if jobattrs["state"] != "running":
                yield jobname, {}


def _process_start_time(value, warn, crit):
    display_value = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(value))
    job_age = time.time() - value
    if crit and job_age >= crit:
        return 2, display_value + "(!!) (more than %s ago)" % get_age_human_readable(crit)
    if warn and job_age >= warn:
        return 1, display_value + "(!) (more than %s ago)" % get_age_human_readable(warn)
    return 0, display_value


def _process_job_stats(job, age_levels):
    txt = 'Exit-Code: %d' % job['exit_code']
    state = 0
    if job['exit_code'] != 0:
        state = 2
        txt += ' (!!)'
    output = [txt]
    perfdata = []

    for key, title in (
        ('start_time', 'Started'),
        ('real_time', 'Real-Time'),
        ('user_time', 'User-Time'),
        ('system_time', 'System-Time'),
        ('reads', 'Filesystem Reads'),
        ('writes', 'Filesystem Writes'),
        ('max_res_bytes', 'Max. Memory'),
        ('avg_mem_bytes', 'Avg. Memory'),
        ('vol_context_switches', 'Vol. Context Switches'),
        ('invol_context_switches', 'Invol. Context Switches'),
    ):
        value = job.get(key)
        if value is None:
            continue

        if key in ['max_res_bytes', 'avg_mem_bytes']:
            display_value = get_bytes_human_readable(value, 1000)
        elif key in ['real_time', 'user_time', 'system_time']:
            display_value = get_age_human_readable(value)
        elif key == 'start_time':
            sub_state, display_value = _process_start_time(value, *age_levels)
            state = max(sub_state, state)
        else:
            display_value = str(value)

        output.append('%s: %s' % (title, display_value))
        perfdata.append((key, value))

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


@get_parsed_item_data
def check_job(_no_item, params, nodes):
    warn, crit = params['age']
    results = []
    for node_info, job in nodes.iteritems():
        prefix = "[%s] " % node_info if node_info else ""

        if job.get("exit_code") is None:
            results.append((3, '%sGot incomplete information for this job' % prefix))
            continue

        state, text, perfdata = _process_job_stats(job, (warn, crit))
        if 'running_start_time' in job:
            running_start_time = min(job['running_start_time'])
            state, display_value = _process_start_time(running_start_time, warn, crit)
            prefix = '%sCurrently running (started: %s), Previous result (considered OK): ' \
                     % (prefix, display_value)
        results.append((state, '%s%s' % (prefix, text), perfdata))

    if params.get("outcome_on_cluster", "worst") == "best":
        state = min(state for state, _text, _metrics in results)
        text = ", ".join(text for _state, text, _metrics in results)
        perfdata = sum((metrics for _state, _text, metrics in results), [])
        yield state, text, perfdata
    else:
        for result in results:
            yield result


check_info["job"] = {
    'parse_function': parse_job,
    'check_function': check_job,
    'inventory_function': inventory_job,
    'service_description': 'Job %s',
    'default_levels_variable': 'job_default_levels',
    'group': 'job',
    'has_perfdata': True,
    'node_info': True,
}
