#!/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 parse_hr_mem(info):
    map_types = {
        '.1.3.6.1.2.1.25.2.1.1': 'other',
        '.1.3.6.1.2.1.25.2.1.2': 'RAM',
        '.1.3.6.1.2.1.25.2.1.3': 'virtual memory',
        '.1.3.6.1.2.1.25.2.1.4': 'fixed disk',
        '.1.3.6.1.2.1.25.2.1.5': 'removeable disk',
        '.1.3.6.1.2.1.25.2.1.6': 'floppy disk',
        '.1.3.6.1.2.1.25.2.1.7': 'compact disk',
        '.1.3.6.1.2.1.25.2.1.8': 'RAM disk',
        '.1.3.6.1.2.1.25.2.1.9': 'flash memory',
        '.1.3.6.1.2.1.25.2.1.10': 'network disk',
    }

    parsed = {}
    for hrtype, hrdescr, hrunits, hrsize, hrused in info:
        try:
            size = int(hrsize) * int(hrunits) / 1048576.0
            used = int(hrused) * int(hrunits) / 1048576.0
            parsed.setdefault(map_types[hrtype], []).append((hrdescr.lower(), size, used))
        except (ValueError, KeyError):
            pass

    return parsed


memused_default_levels = (150.0, 200.0)


# Memory information is - together with filesystems - in
# hrStorage. We need the entries of the types hrStorageVirtualMemory
# and hrStorageRam
def inventory_hr_mem(parsed):
    # Do we find at least one entry concerning memory?
    for _, size, __ in parsed.get('RAM', []) + parsed.get('virtual memory', []):
        if size > 1:
            # some device have zero (broken) values
            return [(None, "memused_default_levels")]


def check_hr_mem(_no_item, params, parsed):
    # This check does not yet support averaging. We need to
    # convert it to mem.include
    if isinstance(params, dict):
        params = params["levels"]

    usage = {}
    cached_mb = 0
    for type_readable, entries in parsed.iteritems():
        for descr, size, used in entries:
            if type_readable in ['RAM', 'virtual memory'] and descr != "virtual memory":
                # We use only the first entry of each type. We have
                # seen devices (pfSense), that have lots of additional
                # entries that are not useful.
                usage.setdefault(type_readable, (size, used))

            if descr in ["cached memory", "memory buffers"] and used > 0:
                # Account for cached memory (this works at least for systems using
                # the UCD snmpd (such as Linux based applicances)
                # some devices report negative used cache values...
                cached_mb += used

    totalram_mb, ramused_mb = usage.get("RAM", (0, 0))
    ramused_mb -= cached_mb
    totalvirt_mb, virtused_mb = usage.get("virtual memory", (0, 0))
    totalmem_mb, totalused_mb = totalram_mb + totalvirt_mb, ramused_mb + virtused_mb

    if totalmem_mb > 0 and totalram_mb > 0:
        totalused_perc = 100 * totalused_mb / totalram_mb

        perfdata = [('ramused', str(ramused_mb) + 'MB', None, None, 0, totalram_mb),
                    ('swapused', str(virtused_mb) + 'MB', None, None, 0, totalvirt_mb)]

        infotext = "%.2f GB used (%.2f GB RAM + %.2f GB SWAP, this is %.1f%% of %.2f GB RAM)" % \
               (totalused_mb / 1024.0, ramused_mb / 1024, virtused_mb / 1024, totalused_perc, totalram_mb / 1024.0)

        warn, crit = params
        if isinstance(warn, float):
            perfdata.append(('memused', str(totalused_mb) + 'MB', int(warn / 100.0 * totalram_mb),
                             int(crit / 100.0 * totalram_mb), 0, totalvirt_mb))
            if totalused_perc >= crit:
                return (2, '%s, critical at %.1f%%' % (infotext, crit), perfdata)
            elif totalused_perc >= warn:
                return (1, '%s, warning at %.1f%%' % (infotext, warn), perfdata)
            return (0, '%s' % infotext, perfdata)

        perfdata.append(('memused', str(totalused_mb) + 'MB', warn, crit, 0, totalram_mb))
        if totalused_mb >= crit:
            return (2, '%s, critical at %.2f GB' % (infotext, crit / 1024.0), perfdata)
        elif totalused_mb >= warn:
            return (1, '%s, warning at %.2f GB' % (infotext, warn / 1024.0), perfdata)
        return (0, '%s' % infotext, perfdata)

    return 3, "Invalid information. Total memory is empty."


check_info["hr_mem"] = {
    'parse_function': parse_hr_mem,
    'inventory_function': inventory_hr_mem,
    'check_function': check_hr_mem,
    'service_description': 'Memory used',
    'has_perfdata': True,
    'snmp_info': (
        '.1.3.6.1.2.1.25.2.3.1',
        [
            2,  # hrStorageType
            3,  # hrStorageDescr
            4,  # hrStorageAllocationUnits
            5,  # hrStorageSize
            6,  # hrStorageUsed
        ]),
    # Some devices are reporting wrong data on
    # HOST-RESOURCES-MIB. Use UCD-MIB in these
    # cases instead
    'snmp_scan_function': is_hr_mem,
    'group': 'memory',
    'includes': ["ucd_hr.include"]
}
