#!/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:
# <<<uptime>>>
# 15876.96 187476.72

from datetime import timedelta


def parse_human_read_uptime(string):
    """Human readable string as given by solaris uptime into seconds"""
    to_secs = {'day': 3600 * 24, 'min': 60, 'hour': 3600}
    uptime = {}
    mmin = re.search(r"(\d+) min\(s\)", string)
    if mmin:
        uptime['min'] = int(mmin.group(1))

    mday = re.search(r"(\d+) day\(s\)", string)
    if mday:
        uptime['day'] = int(mday.group(1))

    mhrs = re.search(r"(\d+) hr\(s\)", string)
    if mhrs:
        uptime['hour'] = int(mhrs.group(1))

    mhm = re.search(r"(\d+):(\d+)", string)
    if mhm:
        uptime['hour'] = int(mhm.group(1))
        uptime['min'] = int(mhm.group(2))

    return sum(to_secs[unit] * value for unit, value in uptime.items())


def parse_solaris_uptime(info, from_boot_time):
    """Solaris agent Version>= 1.5.0p15 delivers a lot of context information

    This was necesary because Solaris returns very inconsistent output for
    the standard uptime query. Thus some cross validation of the output is
    required.

    Output looks like this

<<<uptime>>>
1122                                                               # seconds since boot
[uptime_solaris_start]
SunOS unknown 5.10 Generic_147148-26 i86pc i386 i86pc              # uname
global                                                             # zonename
  4:23pm  up 19 min(s),  2 users,  load average: 0.03, 0.09, 0.09  # uptime command
unix:0:system_misc:snaptime     1131.467157594                     # snaptime
[uptime_solaris_end]


    In an ideal situation uptime, from_boot_time, and snaptime represent
    the same value, and none of this redundancy would be required. They
    might be off by 30s at most.

    We generously allow 600s seconds difference between pairs, and require
    only that a pair between uptime, from_boot_time, snaptime overlaps to
    validate the uptime. Otherwise a message is printed and the check
    returns unknown."""

    uptime_struct = {}
    uptime_struct['from_boot_time'] = from_boot_time
    uptime_struct['uname'] = " ".join(info[0])
    uptime_struct['zonename'] = info[1][0]
    uptime_str = re.match(r'.*up (.*), +\d+ user.*', " ".join(info[2])).group(1)
    uptime_struct['uptime_parsed'] = parse_human_read_uptime(uptime_str)
    uptime_struct['snaptime'] = float(info[3][1])

    if abs(uptime_struct['uptime_parsed'] - uptime_struct['from_boot_time']) < 600:
        uptime_struct['uptime_sec'] = uptime_struct['from_boot_time']
    elif abs(uptime_struct['uptime_parsed'] - uptime_struct['snaptime']) < 600:
        uptime_struct['uptime_sec'] = uptime_struct['snaptime']
    elif abs(uptime_struct['from_boot_time'] - uptime_struct['snaptime']) < 600:
        uptime_struct['uptime_sec'] = uptime_struct['from_boot_time']
    else:

        uptimes_summary = "Uptime command: %s; Kernel time since boot: %s; Snaptime: %s" % tuple(
            timedelta(seconds=x) for x in (
                uptime_struct['uptime_parsed'],
                uptime_struct['from_boot_time'],
                uptime_struct['snaptime'],
            ))

        uptime_struct['message'] = ("Your Solaris system gives inconsistent uptime information. "
                                    "Please get it fixed. ") + uptimes_summary

    return uptime_struct


def uptime_parse(info):
    def extract_solaris_subsection(info):
        is_solaris = False
        solaris_info = []
        for line in info:
            if line[-1] == u'[uptime_solaris_start]':
                is_solaris = True
                continue
            elif line[-1] == u'[uptime_solaris_end]':
                is_solaris = False
                continue

            if is_solaris:
                solaris_info.append(line)
        return solaris_info

    if info:
        from_boot_time = float(info[0][0])
        solaris_info = extract_solaris_subsection(info)

        if solaris_info:
            return parse_solaris_uptime(solaris_info, from_boot_time)

        return {'uptime_sec': from_boot_time}


def check_uptime(_no_item, params, parsed):

    uptime_sec = parsed.get('uptime_sec')
    if uptime_sec is not None:
        yield check_uptime_seconds(params, uptime_sec)
    if 'message' in parsed:
        yield 3, parsed['message']


check_info["uptime"] = {
    'parse_function': uptime_parse,
    'check_function': check_uptime,
    'inventory_function': discover_single,
    'service_description': 'Uptime',
    'has_perfdata': True,
    'includes': ['uptime.include'],
    'group': 'uptime',
}
