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


#   .--Disk IO-------------------------------------------------------------.
#   |                     ____  _     _      ___ ___                       |
#   |                    |  _ \(_)___| | __ |_ _/ _ \                      |
#   |                    | | | | / __| |/ /  | | | | |                     |
#   |                    | |_| | \__ \   <   | | |_| |                     |
#   |                    |____/|_|___/_|\_\ |___\___/                      |
#   |                                                                      |
#   '----------------------------------------------------------------------'
# Example output:
# disk.deviceLatency|naa.600605b002db9f7018d0a40c2a1444b0|0|millisecond
# disk.numberRead|naa.600605b002db9f7018d0a40c2a1444b0|8|number
# disk.numberWrite|naa.600605b002db9f7018d0a40c2a1444b0|47|number
# disk.read|naa.600605b002db9f7018d0a40c2a1444b0|12|kiloBytesPerSecond
# disk.read||12|kiloBytesPerSecond
# disk.write|naa.600605b002db9f7018d0a40c2a1444b0|51|kiloBytesPerSecond
# disk.write||51|kiloBytesPerSecond


def inventory_esx_vsphere_counters_diskio(info):
    for counter, item, value, unit in info:
        if counter == 'disk.read' and item == '':
            return [(None, None)]

def check_esx_vsphere_counters_diskio(_no_item, _no_params, info):
    if not info:
        raise MKCounterWrapped("Counter data is missing")

    read_bytes  = 0
    write_bytes = 0
    reads       = 0
    writes      = 0
    latency     = 0
    for counter, item, value, unit in info:
        if item == '':
            if counter == 'disk.read':
                read_bytes = int(value) * 1024
            elif counter == 'disk.write':
                write_bytes = int(value) * 1024
        elif counter == 'disk.numberRead':
            reads += int(value)
        elif counter == 'disk.numberWrite':
            writes += int(value)
        elif counter == 'disk.deviceLatency':
            latency = max(latency, int(value))

    return 0, "%s/sec read, %s/sec write, IOs: %.2f/sec, latency: %d ms" % (
        get_bytes_human_readable(read_bytes), get_bytes_human_readable(write_bytes), reads + writes, latency), \
        [ ("read", read_bytes), ("write", write_bytes), ("ios", reads + writes), ("latency" , latency) ]


check_info['esx_vsphere_counters.diskio'] = {
   'inventory_function'      : inventory_esx_vsphere_counters_diskio,
   'check_function'          : check_esx_vsphere_counters_diskio,
   'service_description':     'Disk IO SUMMARY',
   'has_perfdata':            True,
}



#.
#   .--Interfaces----------------------------------------------------------.
#   |           ___       _             __                                 |
#   |          |_ _|_ __ | |_ ___ _ __ / _| __ _  ___ ___  ___             |
#   |           | || '_ \| __/ _ \ '__| |_ / _` |/ __/ _ \/ __|            |
#   |           | || | | | ||  __/ |  |  _| (_| | (_|  __/\__ \            |
#   |          |___|_| |_|\__\___|_|  |_|  \__,_|\___\___||___/            |
#   |                                                                      |
#   '----------------------------------------------------------------------'

# The bad thing here: ESX does not send *counters* but *rates*. This might
# seem user friendly on the first look, but is really bad at the second. The
# sampling rate defaults to 20s and is not aligned with our check rate. Also
# a reschedule of the check does not create new data. And: our if.include really
# requires counters. In order to use if.include we therefore simulate counters.

def convert_esx_counters_if(info):
    this_time = time.time()
    by_item = {}
    for counter, item, value, unit in info:
        if item and counter.startswith("net."):
            name = counter[4:]
            by_item.setdefault(item, {})
            by_item[item][name] = int(value)

    # Example of by_item:
    # {
    #   'vmnic0': {
    #         'broadcastRx': 31,
    #         'broadcastTx': 0,
    #         'bytesRx': 3905,  # is in Kilobytes!
    #         'bytesTx': 134,
    #         'droppedRx': 0,
    #         'droppedTx': 0,
    #         'errorsRx': 0,
    #         'errorsTx': 0,
    #         'multicastRx': 5,
    #         'multicastTx': 1,
    #         'packetsRx': 53040,
    #         'packetsTx': 30822,
    #         'received': 3905,
    #         'transmitted': 134,
    #         'unknownProtos': 0,
    #         'usage': 4040,
    #     },
    # }
    nics = by_item.keys()
    nics.sort()

    converted = [
        [], #  0 ifIndex                   0
        [], #  1 ifDescr                   1
        [], #  2 ifType                    2
        [], #  3 ifHighSpeed               .. 1000 means 1Gbit
        [], #  4 ifOperStatus              4
        [], #  5 ifHCInOctets              5
        [], #  6 ifHCInUcastPkts           6
        [], #  7 ifHCInMulticastPkts       7
        [], #  8 ifHCInBroadcastPkts       8
        [], #  9 ifInDiscards              9
        [], # 10 ifInErrors               10
        [], # 11 ifHCOutOctets            11
        [], # 12 ifHCOutUcastPkts         12
        [], # 13 ifHCOutMulticastPkts     13
        [], # 14 ifHCOutBroadcastPkts     14
        [], # 15 ifOutDiscards            15
        [], # 16 ifOutErrors              16
        [], # 17 ifOutQLen                17
        [], # 18 ifAlias                  18
        [], # 19 ifPhysAddress            19
    ]

    tableindex = {
        'bytesRx':      5,  # is in Kilobytes!
        'packetsRx':    6,
        'multicastRx':  7,
        'broadcastRx':  8,
        'droppedRx':    9,
        'errorsRx':    10,
        'bytesTx':     11,
        'packetsTx':   12,
        'multicastTx': 13,
        'broadcastTx': 14,
        'droppedTx':   15,
        'errorsTx':    16,
        # 'received': 3905,
        # 'transmitted': 134,
        # 'unknownProtos': 0,
        # 'usage': 4040,
    }

    converted = []
    for index, name in enumerate(nics):
        entry = ['0'] * 20
        converted.append(entry)
        if name: # Skip summary entry without interface name
            entry[0] = (str(index))
            entry[1] = (name)
            entry[2] = ('6')   # Ethernet
            entry[3] = ('')  # Speed not known
            entry[4] = ('1') # Assume up
            entry[18] = (name) # ifAlias
            entry[19] = ('')   # MAC address not known here
            for ctr_name, ti in tableindex.items():
                ctr_value = by_item[name].get(ctr_name, 0)
                if ctr_name.startswith("bytes"):
                    ctr_value *= 1024
                countername = "vmnic." + name + "." + ctr_name
                if countername in g_counters:
                    last_time, last_value = g_counters[countername]
                    new_value = last_value + ((this_time - last_time) * ctr_value)
                else:
                    last_time = this_time - 60
                    last_value = 0
                    new_value = ctr_value * 60
                g_counters[countername] = (this_time, new_value)
                entry[ti] = str(int(new_value))

    return converted


def inventory_esx_vsphere_counters_if(info):
    converted = convert_esx_counters_if(info)
    return inventory_if_common(converted)

def check_esx_vsphere_counters_if(item, params, info):
    if not info:
        raise MKCounterWrapped("Counter data is missing")

    converted = convert_esx_counters_if(info)
    return check_if_common(item, params, converted)

check_info['esx_vsphere_counters.if'] = {
   'inventory_function'      : inventory_esx_vsphere_counters_if,
   'check_function'          : check_esx_vsphere_counters_if,
   'service_description'     : 'Interface %s',
   'has_perfdata'            : True,
   'group'                   : 'if',
   'default_levels_variable' : 'if_default_levels',
   'includes'                : [ 'if.include' ],
}

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

def inventory_esx_vsphere_counters_uptime(info):
    for name, instance, counter, unit in info:
        if name == "sys.uptime":
            return [ (None, {}) ]

def check_esx_vsphere_counters_uptime(_no_item, params, info):
    if not info:
        raise MKCounterWrapped("Counter data is missing")

    for name, instance, counter, unit in info:
        if name == "sys.uptime":
            return check_uptime_seconds(params, int(counter))
    return 3, "No uptime information found in agent output"


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,
   'includes':               ['uptime.include'],
   '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(info):
    ramdisks = []
    for name, instance, counter, unit in info:
        if name == 'sys.resourceMemConsumed' \
           and instance.startswith('host/system/kernel/kmanaged/visorfs/'):
            ramdisks.append(instance.split('/')[-1])

    return df_inventory(ramdisks)

def check_esx_vsphere_counters_ramdisk(item, params, info):
    if not info:
        raise MKCounterWrapped("Counter data is missing")

    ramdisks = []
    for name, instance, counter, unit in info:
        if name == 'sys.resourceMemConsumed' \
           and 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(counter) / 1000
            avail_mb = size_mb - used_mb
            ramdisks.append((name, size_mb, avail_mb))

    return df_check_filesystem_list(item, params, ramdisks)

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,
    'includes':                [ 'df.include' ],
    'group':                   'filesystem',
    'default_levels_variable': 'filesystem_default_levels',
}
