#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (C) 2019 tribe29 GmbH - License: GNU General Public License v2
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
# conditions defined in the file COPYING, which is part of this source code package.

import cmk.base.plugins.agent_based.esx_vsphere_counters as esx_vsphere_counters

# NOTE: Careful when replacing the *-import below with a more specific import. This can cause
# problems because it might remove variables from the check-context which are necessary for
# resolving legacy discovery results such as [("SUMMARY", "diskstat_default_levels")]. Furthermore,
# it might also remove variables needed for accessing discovery rulesets.
from cmk.base.check_legacy_includes.df import *  # pylint: disable=wildcard-import,unused-wildcard-import

# NOTE: Careful when replacing the *-import below with a more specific import. This can cause
# problems because it might remove variables from the check-context which are necessary for
# resolving legacy discovery results such as [("SUMMARY", "diskstat_default_levels")]. Furthermore,
# it might also remove variables needed for accessing discovery rulesets.
from cmk.base.check_legacy_includes.diskstat import *  # pylint: disable=wildcard-import,unused-wildcard-import

# NOTE: Careful when replacing the *-import below with a more specific import. This can cause
# problems because it might remove variables from the check-context which are necessary for
# resolving legacy discovery results such as [("SUMMARY", "diskstat_default_levels")]. Furthermore,
# it might also remove variables needed for accessing discovery rulesets.
from cmk.base.check_legacy_includes.uptime import *  # pylint: disable=wildcard-import,unused-wildcard-import
from cmk.base.plugins.agent_based.utils.df import df_discovery, FILESYSTEM_DEFAULT_LEVELS

# Example output:
# <<<esx_vsphere_counters:sep(124)>>>
# net.broadcastRx|vmnic0|11|number
# net.broadcastRx||11|number
# net.broadcastTx|vmnic0|0|number
# net.broadcastTx||0|number
# net.bytesRx|vmnic0|3820|kiloBytesPerSecond
# net.bytesRx|vmnic1|0|kiloBytesPerSecond
# net.bytesRx|vmnic2|0|kiloBytesPerSecond
# net.bytesRx|vmnic3|0|kiloBytesPerSecond
# net.bytesRx||3820|kiloBytesPerSecond
# net.bytesTx|vmnic0|97|kiloBytesPerSecond
# net.bytesTx|vmnic1|0|kiloBytesPerSecond
# net.bytesTx|vmnic2|0|kiloBytesPerSecond
# net.bytesTx|vmnic3|0|kiloBytesPerSecond
# net.bytesTx||97|kiloBytesPerSecond
# net.droppedRx|vmnic0|0|number
# net.droppedRx|vmnic1|0|number
# net.droppedRx|vmnic2|0|number
# net.droppedRx|vmnic3|0|number
# net.droppedRx||0|number
# net.droppedTx|vmnic0|0|number
# net.droppedTx|vmnic1|0|number
# ...
# sys.uptime||630664|second

# .
#   .--Datastore IO--------------------------------------------------------.
#   |        ____        _            _                   ___ ___          |
#   |       |  _ \  __ _| |_ __ _ ___| |_ ___  _ __ ___  |_ _/ _ \         |
#   |       | | | |/ _` | __/ _` / __| __/ _ \| '__/ _ \  | | | | |        |
#   |       | |_| | (_| | || (_| \__ \ || (_) | | |  __/  | | |_| |        |
#   |       |____/ \__,_|\__\__,_|___/\__\___/|_|  \___| |___\___/         |
#   |                                                                      |
#   +----------------------------------------------------------------------+

# Example output:
# datastore.read|4c4ece34-3d60f64f-1584-0022194fe902|0#1#2|kiloBytesPerSecond
# datastore.read|4c4ece5b-f1461510-2932-0022194fe902|0#4#5|kiloBytesPerSecond
# datastore.numberReadAveraged|511e4e86-1c009d48-19d2-bc305bf54b07|0#0#0|number
# datastore.numberWriteAveraged|4c4ece34-3d60f64f-1584-0022194fe902|0#0#1|number
# datastore.totalReadLatency|511e4e86-1c009d48-19d2-bc305bf54b07|0#5#5|millisecond
# datastore.totalWriteLatency|4c4ece34-3d60f64f-1584-0022194fe902|0#2#7|millisecond

esx_vsphere_get_average = esx_vsphere_counters.average_parsed_data


# esx datastores are either shown by human readable name (if available) or by the uid
def esx_vsphere_counters_get_item_mapping(parsed):
    map_instance_to_item = {}
    for counter in [
        "read",
        "write",
        "datastoreReadIops",
        "datastoreWriteIops",
        "totalReadLatency",
        "totalWriteLatency",
        "sizeNormalizedDatastoreLatency",
    ]:
        for instance in parsed.get("datastore." + counter, {}):
            map_instance_to_item[instance] = instance

    for instance, values in parsed.get("datastore.name", {}).items():
        if instance in map_instance_to_item and values[0][0] != "":
            map_instance_to_item[instance] = values[0][0][-1].replace(" ", "_")
    return map_instance_to_item


def inventory_esx_vsphere_counters_datastoreio(parsed):
    return inventory_diskstat_generic(
        [[None, x] for x in esx_vsphere_counters_get_item_mapping(parsed).values()]
    )


def check_esx_vsphere_counters_datastoreio(item, params, parsed):
    if "datastore.read" not in parsed:
        raise MKCounterWrapped("Counter data is missing")

    datastores = {}
    item_mapping = esx_vsphere_counters_get_item_mapping(parsed)

    for new_name, eval_function, name, scaling in [
        ("read_throughput", lambda x: int(esx_vsphere_get_average(x)), "datastore.read", 1024),
        ("write_throughput", lambda x: int(esx_vsphere_get_average(x)), "datastore.write", 1024),
        ("read_ios", lambda x: int(esx_vsphere_get_average(x)), "datastore.datastoreReadIops", 1),
        ("write_ios", lambda x: int(esx_vsphere_get_average(x)), "datastore.datastoreWriteIops", 1),
        ("read_latency", lambda x: max(map(int, x)), "datastore.totalReadLatency", 1e-3),
        ("write_latency", lambda x: max(map(int, x)), "datastore.totalWriteLatency", 1e-3),
        ("latency", lambda x: max(map(int, x)), "datastore.sizeNormalizedDatastoreLatency", 1e-6),
    ]:
        field_data = parsed.get(name, {})

        for instance, values in field_data.items():
            item_name = item_mapping[instance]
            datastores.setdefault(item_name, {})
            value = eval_function(values[0][0])
            datastores[item_name][new_name] = value * scaling

    return check_diskstat_dict(item, params, datastores)


check_info["esx_vsphere_counters"] = {
    "inventory_function": inventory_esx_vsphere_counters_datastoreio,
    "check_function": check_esx_vsphere_counters_datastoreio,
    "service_description": "Datastore IO %s",
    "has_perfdata": True,
    "group": "diskstat",
}

# .
#   .--Uptime--------------------------------------------------------------.
#   |                  _   _       _   _                                   |
#   |                 | | | |_ __ | |_(_)_ __ ___   ___                    |
#   |                 | | | | '_ \| __| | '_ ` _ \ / _ \                   |
#   |                 | |_| | |_) | |_| | | | | | |  __/                   |
#   |                  \___/| .__/ \__|_|_| |_| |_|\___|                   |
#   |                       |_|                                            |
#   '----------------------------------------------------------------------'


def inventory_esx_vsphere_counters_uptime(parsed):
    if "sys.uptime" in parsed:
        return [(None, {})]


def check_esx_vsphere_counters_uptime(_no_item, params, parsed):
    if "sys.uptime" not in parsed:
        raise MKCounterWrapped("Counter data is missing")
    uptime = int(parsed["sys.uptime"][""][0][0][-1])
    if uptime < 0:
        raise MKCounterWrapped("Counter data is corrupt")
    return check_uptime_seconds(params, uptime)


check_info["esx_vsphere_counters.uptime"] = {
    "inventory_function": inventory_esx_vsphere_counters_uptime,
    "check_function": check_esx_vsphere_counters_uptime,
    "service_description": "Uptime",
    "has_perfdata": True,
    "group": "uptime",
}

# .
#   .--Ramdisk-------------------------------------------------------------.
#   |                ____                     _ _     _                    |
#   |               |  _ \ __ _ _ __ ___   __| (_)___| | __                |
#   |               | |_) / _` | '_ ` _ \ / _` | / __| |/ /                |
#   |               |  _ < (_| | | | | | | (_| | \__ \   <                 |
#   |               |_| \_\__,_|_| |_| |_|\__,_|_|___/_|\_\                |
#   |                                                                      |
#   +----------------------------------------------------------------------+

# We assume that all ramdisks have the same size (in mb) on all hosts
# -> To get size infos about unknown ramdisks, connect to the ESX host via
#    SSH and check the size of the disk via "du" command
esx_vsphere_counters_ramdisk_sizes = {
    "root": 32,
    "etc": 28,
    "tmp": 192,
    "hostdstats": 319,
    "snmptraps": 1,
    "upgradescratch": 300,
    "ibmscratch": 300,
    "sfcbtickets": 1,
}


def inventory_esx_vsphere_counters_ramdisk(parsed):
    ramdisks = []

    for instance in parsed.get("sys.resourceMemConsumed", {}):
        if instance.startswith("host/system/kernel/kmanaged/visorfs/"):
            ramdisks.append(instance.split("/")[-1])

    return df_discovery(host_extra_conf(host_name(), filesystem_groups), ramdisks)


def check_esx_vsphere_counters_ramdisk(item, params, parsed):
    if "sys.resourceMemConsumed" not in parsed:
        raise MKCounterWrapped("Counter data is missing")

    ramdisks = []
    for instance in parsed.get("sys.resourceMemConsumed"):
        if instance.startswith("host/system/kernel/kmanaged/visorfs/"):
            name = instance.split("/")[-1]
            try:
                size_mb = esx_vsphere_counters_ramdisk_sizes[name]
            except KeyError:
                if item == name:
                    return 3, "Unhandled ramdisk found (%s)" % name
                else:
                    continue
            used_mb = float(parsed["sys.resourceMemConsumed"][instance][0][0][-1]) / 1000.0
            avail_mb = size_mb - used_mb
            ramdisks.append((name, size_mb, avail_mb, 0))

    return df_check_filesystem_list(item, params, ramdisks)


factory_settings["filesystem_default_levels"] = FILESYSTEM_DEFAULT_LEVELS


check_info["esx_vsphere_counters.ramdisk"] = {
    "inventory_function": inventory_esx_vsphere_counters_ramdisk,
    "check_function": check_esx_vsphere_counters_ramdisk,
    "service_description": "Ramdisk %s",
    "has_perfdata": True,
    "group": "filesystem",
    "default_levels_variable": "filesystem_default_levels",
}

#   .--swap----------------------------------------------------------------.
#   |                                                                      |
#   |                       _____      ____ _ _ __                         |
#   |                      / __\ \ /\ / / _` | '_ \                        |
#   |                      \__ \\ V  V / (_| | |_) |                       |
#   |                      |___/ \_/\_/ \__,_| .__/                        |
#   |                                        |_|                           |
#   +----------------------------------------------------------------------+


def _parse_esx_vsphere_counters_swap(parsed):

    swap_values = {}

    for agent_key, key in (("mem.swapin", "in"), ("mem.swapout", "out"), ("mem.swapused", "used")):
        try:
            swap_values[key] = parsed[agent_key][""][0][0][0]
        except (KeyError, IndexError, TypeError, ValueError):
            continue

    return swap_values


def inventory_esx_vsphere_counters_swap(parsed):
    SWAP = _parse_esx_vsphere_counters_swap(parsed)

    if any(elem for elem in SWAP.values()):
        return [(None, {})]


def check_esx_vsphere_counters_swap(item, params, parsed):
    SWAP = _parse_esx_vsphere_counters_swap(parsed)

    for key in ("in", "out", "used"):
        if SWAP.get(key):
            value = get_bytes_human_readable(float(SWAP[key]), unit="KB")
        else:
            value = "not available"

        yield 0, "Swap %s: %s" % (key, value)


check_info["esx_vsphere_counters.swap"] = {
    "inventory_function": inventory_esx_vsphere_counters_swap,
    "check_function": check_esx_vsphere_counters_swap,
    "service_description": "VMKernel Swap",
    "has_perfdata": False,
}
