#!/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:
# <<<jolokia_metrics>>>
# 8080 NonHeapMemoryUsage 101078952
# 8080 NonHeapMemoryMax 184549376
# 8080 HeapMemoryUsage 2362781664
# 8080 HeapMemoryMax 9544663040
# 8080 ThreadCount 78
# 8080 DeamonThreadCount 72
# 8080 PeakThreadCount 191
# 8080 TotalStartedThreadCount 941
# 8080 Uptime 572011375
# 8080,java.lang:name=PS_MarkSweep,type=GarbageCollector CollectionCount 0

# MB warn, crit
# jolokia_metrics_mem_default_levels = (2000, 3000)

# Number of threads warn, crit
jolokia_metrics_threads_default_levels = (80, 100)

# Number of sessions low crit, low warn, high warn, high crit
jolokia_metrics_app_sess_default_levels = (-1, -1, 800, 1000)

# Number of requests low crit, low warn, high warn, high crit
jolokia_metrics_serv_req_default_levels = (-1, -1, 5000, 6000)

jolokia_metrics_queue_default_levels = (20, 50)

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


def check_deprecated(_no_item, _no_params, _no_info):
    return 1, "This check is deprecated. Please rediscover the services of this host."


factory_settings["jolokia_metrics_tp_default_levels"] = {}

check_info["jolokia_metrics.tp"] = {
    "service_description": "JVM %s",
    "check_function": check_deprecated,
    "inventory_function": lambda i: None,
}

check_info["jolokia_metrics.threads"] = {
    "service_description": "JVM %s Threads",
    "check_function": check_deprecated,
    "inventory_function": lambda i: None,
}

#.
#   .--Arcane helpers------------------------------------------------------.
#   |                     _                                                |
#   |                    / \   _ __ ___ __ _ _ __   ___                    |
#   |                   / _ \ | '__/ __/ _` | '_ \ / _ \                   |
#   |                  / ___ \| | | (_| (_| | | | |  __/                   |
#   |                 /_/   \_\_|  \___\__,_|_| |_|\___|                   |
#   |                                                                      |
#   |                  _          _                                        |
#   |                 | |__   ___| |_ __   ___ _ __ ___                    |
#   |                 | '_ \ / _ \ | '_ \ / _ \ '__/ __|                   |
#   |                 | | | |  __/ | |_) |  __/ |  \__ \                   |
#   |                 |_| |_|\___|_| .__/ \___|_|  |___/                   |
#   |                              |_|                                     |
#   +----------------------------------------------------------------------+
#   | TODO: See if these can be removed altogether                         |
#   '----------------------------------------------------------------------'


# This bisects the app server and its values
def jolokia_metrics_app(info, split_item):
    inst, app = split_item
    parsed = jolokia_metrics_parse(info)
    if parsed.get(inst, "") is None:
        raise MKCounterWrapped("No information from Jolokia agent")
    if not inst in parsed \
       or not app in parsed[inst].get('apps', {}):
        return None
    return parsed[inst]['apps'][app]


# This bisects info from BEA and passes on to jolokia_metrics_app
def jolokia_metrics_serv(info, split_item):
    inst, app, serv = split_item
    app = jolokia_metrics_app(info, (inst, app))
    if not app or not serv in app.get('servlets', {}):
        return None
    return app['servlets'][serv]


def jolokia_metrics_gc(info, split_item):
    inst, _typ, gc = split_item
    parsed = jolokia_metrics_parse(info)
    if parsed.get(inst, "") is None:
        raise MKCounterWrapped("No information from Jolokia agent")

    if not inst in parsed \
       or not gc in parsed[inst].get('gc', {}):
        return None
    return parsed[inst]['gc'][gc]


#.
#   .--Number of Requests--------------------------------------------------.
#   |               ____                            _                      |
#   |              |  _ \ ___  __ _ _   _  ___  ___| |_ ___                |
#   |              | |_) / _ \/ _` | | | |/ _ \/ __| __/ __|               |
#   |              |  _ <  __/ (_| | |_| |  __/\__ \ |_\__ \               |
#   |              |_| \_\___|\__, |\__,_|\___||___/\__|___/               |
#   |                            |_|                                       |
#   '----------------------------------------------------------------------'


def inventory_jolokia_metrics_serv(info):
    inv = []
    parsed = jolokia_metrics_parse(info)
    levels = None
    levels = 'jolokia_metrics_serv_req_default_levels'
    needed_key = "Requests"
    for inst, vals in parsed.iteritems():
        if vals is None:
            continue  # no data from agent
        for app, val in vals.get('apps', {}).iteritems():
            for serv, servinfo in val.get('servlets', {}).items():
                if needed_key in servinfo:
                    inv.append(('%s %s %s' % (inst, app, serv), levels))
    return inv


def check_jolokia_metrics_serv_req(item, params, info):
    lo_crit, lo_warn, hi_warn, hi_crit = params
    serv = jolokia_metrics_serv(info, item.split())
    if not serv or not 'Requests' in serv:
        return (3, "data not found in agent output")
    req = saveint(serv['Requests'])

    status = 0
    status_txt = ''
    if lo_crit is not None and req <= lo_crit:
        status = 2
        status_txt = ' (Below or equal %d)' % lo_crit
    elif lo_warn is not None and req <= lo_warn:
        status = 1
        status_txt = ' (Below or equal %d)' % lo_warn
    elif hi_crit is not None and req >= hi_crit:
        status = 2
        status_txt = ' (Above or equal %d)' % hi_crit
    elif hi_warn is not None and req >= hi_warn:
        status = 1
        status_txt = ' (Above or equal %d)' % hi_warn

    output = ['Requests: %d%s' % (req, status_txt)]
    perfdata = [('Requests', req, hi_warn, hi_crit)]
    wrapped = False
    this_time = time.time()
    try:
        rate = get_rate("jolokia_metrics.serv_req.%s" % item, this_time, req)
        output.append('RequestRate: %0.2f' % rate)
        perfdata.append(('RequestRate', rate))
    except MKCounterWrapped:
        wrapped = True

    if wrapped:
        return (status, ', '.join(output))
    return (status, ', '.join(output), perfdata)


check_info["jolokia_metrics.serv_req"] = {
    "includes": ["jolokia.include"],
    "service_description": "JVM %s Requests",
    "check_function": check_jolokia_metrics_serv_req,
    "inventory_function": inventory_jolokia_metrics_serv,
    "group": "jvm_requests",
    "has_perfdata": True,
}

#.
#   .--Garbage collection--------------------------------------------------.
#   |                ____            _                                     |
#   |               / ___| __ _ _ __| |__   __ _  __ _  ___                |
#   |              | |  _ / _` | '__| '_ \ / _` |/ _` |/ _ \               |
#   |              | |_| | (_| | |  | |_) | (_| | (_| |  __/               |
#   |               \____|\__,_|_|  |_.__/ \__,_|\__, |\___|               |
#   |                                            |___/                     |
#   |                        _ _           _   _                           |
#   |               ___ ___ | | | ___  ___| |_(_) ___  _ __                |
#   |              / __/ _ \| | |/ _ \/ __| __| |/ _ \| '_ \               |
#   |             | (_| (_) | | |  __/ (__| |_| | (_) | | | |              |
#   |              \___\___/|_|_|\___|\___|\__|_|\___/|_| |_|              |
#   |                                                                      |
#   '----------------------------------------------------------------------'


def inventory_jolokia_metrics_gc(info):
    inv = []
    parsed = jolokia_metrics_parse(info)
    for inst, vals in parsed.iteritems():
        if vals is None:
            continue  # no data from agent
        for gc in vals.get('gc', {}).iterkeys():
            inv.append(("%s GC %s" % (inst, gc), {}))
    return inv


def check_jolokia_metrics_gc(item, params, info):
    gc = jolokia_metrics_gc(info, item.split())
    if gc is None:
        return

    if params is None:
        params = {}


    crate = get_rate("jvm.gc.count.%s" % (item), \
                     time.time(), int(gc['CollectionCount']))
    crate = crate * 60.0

    ctext = ''
    status = 0
    cwarn, ccrit = params.get('CollectionCount', (None, None))
    if cwarn is not None and ccrit is not None:
        if crate >= int(ccrit):
            status = 2
            ctext = " (Level %s) " % ccrit
        elif crate >= int(cwarn):
            status = 1
            ctext = " (Level %s) " % cwarn

    yield status, "%.2f GC Count/minute%s" % (crate, ctext), \
                    [('CollectionCount', crate, cwarn, ccrit)]

    if 'CollectionTime' in gc:
        twarn, tcrit = params.get('CollectionTime', (None, None))
        trate = get_rate("jvm.gc.time.%s" % (item), \
                         time.time(), int(gc['CollectionTime']))
        trate = trate * 60.0

        ttext = ''
        status = 0
        if twarn is not None and tcrit is not None:
            if trate >= int(tcrit):
                status = 2
                ttext = "(Level %s) " % tcrit
            elif trate >= int(twarn):
                status = 1
                ttext = "(Level %s) " % twarn

        yield status, "%.2f GC ms/minute%s" % (trate, ttext), \
                            [('CollectionTime', trate, twarn, tcrit)]


check_info["jolokia_metrics.gc"] = {
    "includes": ["jolokia.include"],
    "service_description": "JVM %s",
    "check_function": check_jolokia_metrics_gc,
    "inventory_function": inventory_jolokia_metrics_gc,
    "group": "jvm_gc",
    "has_perfdata": True,
}

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


def check_jolokia_metrics_mem(item, params, info):
    parsed = jolokia_metrics_parse(info)

    if parsed.get(item, "") is None:
        raise MKCounterWrapped("No information from Jolokia agent")

    if item not in parsed:
        return

    # convert old parameter version ( warn, crit )
    # represented levels of total heap
    if isinstance(params, tuple):
        params = {"total": params}

    # rename totalheap to total
    # this block can be removed in the future (today 22.02.13)
    if "totalheap" in params:
        params = params.copy()
        params["total"] = params["totalheap"]
        del params["totalheap"]

    d = parsed[item]
    mb = 1024 * 1024.0

    if "HeapMemoryUsage" not in d or "NonHeapMemoryUsage" not in d\
        or "HeapMemoryMax" not in d or "NonHeapMemoryMax" not in d:
        return 3, "data in agent output incomplete"

    heap = saveint(d["HeapMemoryUsage"]) / mb
    heapmax = saveint(d.get("HeapMemoryMax", -1)) / mb
    nonheap = saveint(d["NonHeapMemoryUsage"]) / mb
    nonheapmax = saveint(d.get("NonHeapMemoryMax", -1)) / mb
    total = heap + nonheap
    if heapmax > 0 and nonheapmax > 0:
        totalmax = heapmax + nonheapmax
    else:
        totalmax = 0
        heapmax = max(0, heapmax)
        nonheapmax = max(0, nonheapmax)

    worst_state = 0
    perfdata = []
    info_list = []

    for what, value, value_max in [
        ("heap", heap, heapmax),
        ("nonheap", nonheap, nonheapmax),
        ("total", total, totalmax),
    ]:
        param_state = 0
        level_info = ""
        used_info = ""
        if params.get(what):
            warn_level = 0
            crit_level = 0
            if isinstance(params[what][0], int):
                if value_max:
                    warn_level = value_max - params[what][0]
                    crit_level = value_max - params[what][1]

                if what != "total":
                    perfdata.append((what, value, warn_level, crit_level, "", value_max))

                if not value_max:
                    param_state = 0
                elif value >= crit_level:
                    param_state = 2
                    level_info = "%s(crit at %sMB free)" % (state_markers[2], params[what][1])
                elif value >= warn_level:
                    param_state = 1
                    level_info = "%s(warn at %sMB free)" % (state_markers[1], params[what][0])
            else:
                if value_max:
                    warn_level = value_max * params[what][0] / 100.0
                    crit_level = value_max * params[what][1] / 100.0

                if what != "total":
                    perfdata.append((what, value, warn_level, crit_level, "", value_max))

                if not value_max:
                    param_state = 0
                elif value >= crit_level:
                    param_state = 2
                    level_info = "%s(crit at %s%%)" % (state_markers[2], params[what][1])
                elif value >= warn_level:
                    param_state = 1
                    level_info = "%s(warn at %s%%)" % (state_markers[1], params[what][0])
        else:
            if what != "total":
                perfdata.append((what, value, "", "", "", value_max))

        if value_max:
            used_info = "/%.1f%% used" % (value / value_max * 100)
        info_list.append("%s: %0.fMB%s%s" % (what.title(), value, used_info, level_info))
        worst_state = max(param_state, worst_state)

    return (worst_state, ', '.join(info_list), perfdata)


check_info["jolokia_metrics.mem"] = {
    "includes": ["jolokia.include"],
    "service_description": "JVM %s Memory",
    "check_function": check_jolokia_metrics_mem,
    "inventory_function": lambda i: inventory_jolokia_metrics(i, "mem"),
    "has_perfdata": True,
    "group": "jvm_memory",
    "default_levels_variable": "jolokia_metrics_mem_default_levels"
}

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


def check_jolokia_metrics_uptime(item, params, info):
    parsed = jolokia_metrics_parse(info)
    if parsed.get(item, "") is None:
        raise MKCounterWrapped("No information from Jolokia agent")
    if item in parsed:
        if "Uptime" in parsed[item]:
            uptime = int(parsed[item]['Uptime']) / 1000
            return check_uptime_seconds(params, uptime)


check_info["jolokia_metrics.uptime"] = {
    "includes": ["jolokia.include", 'uptime.include'],
    "service_description": "JVM %s Uptime",
    "check_function": check_jolokia_metrics_uptime,
    "inventory_function": lambda i: inventory_jolokia_metrics(i, "uptime"),
    "group": "jvm_uptime",
    "has_perfdata": True,
}

#.
#   .--App state-----------------------------------------------------------.
#   |                _                      _        _                     |
#   |               / \   _ __  _ __    ___| |_ __ _| |_ ___               |
#   |              / _ \ | '_ \| '_ \  / __| __/ _` | __/ _ \              |
#   |             / ___ \| |_) | |_) | \__ \ || (_| | ||  __/              |
#   |            /_/   \_\ .__/| .__/  |___/\__\__,_|\__\___|              |
#   |                    |_|   |_|                                         |
#   '----------------------------------------------------------------------'


def check_jolokia_metrics_app_state(item, _unused, info):
    app_state = 3
    app = jolokia_metrics_app(info, item.split())

    # FIXME: this could be nicer.
    if app and "Running" in app:
        if app['Running'] == '1':
            app_state = 0
        else:
            app_state = 2
    # wenn in app statename steht
    elif app and "stateName" in app:
        if app['stateName'] == 'STARTED':
            app_state = 0
        else:
            app_state = 2
    if app_state == 3:
        return (3, "data not found in agent output")
    elif app_state == 0:
        return (0, 'application is running')
    elif app_state == 2:
        return (2, 'application is not running (Running: %s)')

    return (3, 'error in agent output')


check_info["jolokia_metrics.app_state"] = {
    "includes": ["jolokia.include"],
    "service_description": "JVM %s State",
    "check_function": check_jolokia_metrics_app_state,
    "inventory_function": lambda i: inventory_jolokia_metrics_apps(i, "app_state"),
    "has_perfdata": False,
}

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


def check_jolokia_metrics_app_sess(item, params, info):
    lo_crit, lo_warn, hi_warn, hi_crit = params
    if len(item.split()) == 3:
        app = jolokia_metrics_serv(info, item.split())
    elif len(item.split()) == 2:
        app = jolokia_metrics_app(info, item.split())
    if not app:
        return (3, "application not found")
    sessions = app.get('Sessions', app.get('activeSessions', app.get('OpenSessionsCurrentCount')))
    if sessions is None:
        return (3, "data not found in agent output")
    sess = saveint(sessions)
    maxActive = saveint(
        app.get('Sessions', app.get('maxActiveSessions', app.get('OpenSessionsCurrentCount'))))

    status = 0
    status_txt = ''
    if lo_crit is not None and sess <= lo_crit:
        status = 2
        status_txt = ' (Below or equal %d)' % lo_crit
    elif lo_warn is not None and sess <= lo_warn:
        status = 1
        status_txt = ' (Below or equal %d)' % lo_warn
    elif hi_crit is not None and sess >= hi_crit:
        status = 2
        status_txt = ' (Above or equal %d)' % hi_crit
    elif hi_warn is not None and sess >= hi_warn:
        status = 1
        status_txt = ' (Above or equal %d)' % hi_warn

    if maxActive and maxActive > 0:
        status_txt += " (max active sessions: %d)" % maxActive

    return (status, '%d Sessions%s' % (sess, status_txt), [('sessions', sess, hi_warn, hi_crit)])


def check_jolokia_metrics_bea_queue(item, params, info):
    app = jolokia_metrics_app(info, item.split())
    if not app:
        return (3, "application not found")
    if "QueueLength" not in app:
        return (3, "data not found in agent output")
    queuelength = int(app['QueueLength'])

    status = 0
    warn, crit = params
    if queuelength >= crit:
        status = 2
    elif queuelength >= warn:
        status = 1
    return (status, 'queue length is %d' % queuelength, [("length", queuelength, warn, crit)])


# FIXME: This check could work with any JVM
# It has no levels
# A candidate for 1.2.1 overhaul
def check_jolokia_metrics_bea_requests(item, _no_params, info):
    app = jolokia_metrics_app(info, item.split())
    if not app:
        return (3, "application not found")

    for nk in ["CompletedRequestCount", "requestCount"]:
        if nk in app:
            requests = int(app[nk])
            rate = get_rate("j4p.bea.requests.%s" % item, time.time(), requests)
            return (0, "%.2f requests/sec" % rate, [("rate", rate)])

    return (3, "data not found in agent output")


def check_jolokia_metrics_bea_threads(item, _no_params, info):
    app = jolokia_metrics_app(info, item.split())
    if not app:
        return (3, "data not found in agent output")

    perfdata = []
    infos = []
    for varname, title in [("ExecuteThreadTotalCount", "total"), ("ExecuteThreadIdleCount", "idle"),
                           ("StandbyThreadCount", "standby"), ("HoggingThreadCount", "hogging")]:
        value = int(app[varname])
        perfdata.append((varname, value))
        infos.append("%s: %d" % (title, value))

    return (0, ", ".join(infos), perfdata)


check_info["jolokia_metrics.app_sess"] = {
    "includes": ["jolokia.include"],
    "service_description": "JVM %s Sessions",
    "check_function": check_jolokia_metrics_app_sess,
    "inventory_function": lambda i: inventory_jolokia_metrics_apps(i, "app_sess"),
    "group": "jvm_sessions",
    "has_perfdata": True,
}

check_info["jolokia_metrics.requests"] = {
    "includes": ["jolokia.include"],
    "service_description": "JVM %s Requests",
    "check_function": check_jolokia_metrics_bea_requests,
    "inventory_function": lambda i: inventory_jolokia_metrics_apps(i, "requests"),
    "group": "jvm_requests",
    "has_perfdata": True,
}

# Stuff found on BEA Weblogic
check_info["jolokia_metrics.bea_queue"] = {
    "includes": ["jolokia.include"],
    "service_description": "JVM %s Queue",
    "check_function": check_jolokia_metrics_bea_queue,
    "inventory_function": lambda i: inventory_jolokia_metrics_apps(i, "queue"),
    "group": "jvm_queue",
    "has_perfdata": True,
}

check_info["jolokia_metrics.bea_requests"] = {
    "includes": ["jolokia.include"],
    "service_description": "JVM %s Requests",
    "check_function": check_jolokia_metrics_bea_requests,
    "inventory_function": lambda i: inventory_jolokia_metrics_apps(i, "bea_requests"),
    "group": "jvm_requests",
    "has_perfdata": True,
}

check_info["jolokia_metrics.bea_threads"] = {
    "includes": ["jolokia.include"],
    "service_description": "JVM %s Threads",
    "check_function": check_jolokia_metrics_bea_threads,
    "inventory_function": lambda i: inventory_jolokia_metrics_apps(i, "threads"),
    "group": "jvm_threads",
    "has_perfdata": True,
}

check_info["jolokia_metrics.bea_sess"] = {
    "includes": ["jolokia.include"],
    "service_description": "JVM %s Sessions",
    "check_function": check_jolokia_metrics_app_sess,
    "inventory_function": lambda i: inventory_jolokia_metrics_apps(i, "bea_app_sess"),
    "group": "jvm_sessions",
    "has_perfdata": True,
}

jolokia_metrics_perm_gen_default_levels = {"perm": (80.0, 100.0)}


def inventory_jolokia_metrics_perm_gen(info):
    parsed = jolokia_metrics_parse(info)
    for instance, values in parsed.items():
        if values and "PermGenUsage" in values:
            yield instance, "jolokia_metrics_perm_gen_default_levels"


def check_jolokia_metrics_perm_gen(item, params, info):
    parsed = jolokia_metrics_parse(info)
    if parsed.get(item, "") is None:
        raise MKCounterWrapped("No information from Jolokia agent")
    values = parsed.get(item)
    if values:
        warn, crit = params['perm']
        usage_bytes = int(values['PermGenUsage'])
        size_bytes = int(values['PermGenMax'])
        usage_perc = 100.0 / size_bytes * usage_bytes
        warn_bytes = size_bytes / 100.0 * warn
        crit_bytes = size_bytes / 100.0 * crit
        perfdata = [("mem_perm_used", usage_bytes, warn_bytes, crit_bytes, 0, size_bytes)]
        state = 0
        if usage_perc >= crit:
            state = 2
        elif usage_perc >= warn:
            state = 1
        if state != 0:
            levels = "(warn/crit at %2.f/%.2f %%)" % (warn, crit)
        else:
            levels = ""
        return state, "Usage: %.2f %% (%s of %s used) %s" % \
          ( usage_perc, get_bytes_human_readable(usage_bytes), get_bytes_human_readable(size_bytes), levels ), \
          perfdata


check_info["jolokia_metrics.perm_gen"] = {
    "includes": ["jolokia.include"],
    "service_description": "JVM %s PermGen Usage",
    "check_function": check_jolokia_metrics_perm_gen,
    "inventory_function": inventory_jolokia_metrics_perm_gen,
    "group": "jvm_memory",
    "has_perfdata": True,
}

jolokia_metrics_cache_hits_default_levels = {}


def inventory_jolokia_metrics_cache(key, metrics, info):
    parsed = jolokia_metrics_parse(info)
    metrics_set = set(metrics)
    for inst, vals in [x for x in parsed.iteritems() if x[1] is not None]:
        for cache, cache_vars in vals.get("CacheStatistics", {}).iteritems():
            if metrics_set.intersection(cache_vars) == metrics_set:
                if key is None:
                    yield "%s %s" % (inst, cache), None
                else:
                    yield "%s %s" % (inst, cache), "jolokia_metrics_%s_default_levels" % key


def check_jolokia_metrics_cache(metrics, totals, item, params, info):
    type_map = {
        "CacheHitPercentage": (float, 100.0, "%.1f%%"),
        "InMemoryHitPercentage": (float, 100.0, "%.1f%%"),
        "OnDiskHitPercentage": (float, 100.0, "%.1f%%"),
        "OffHeapHitPercentage": (float, 100.0, "%.1f%%"),
    }

    parsed = jolokia_metrics_parse(info)
    try:
        inst, cache = item.split(" ")

        # we display the "metrics" first, totals after, but to "fix" metrics based on zero-totals
        # we need to go over the totals once
        for total in totals:
            val = int(parsed[inst]["CacheStatistics"][cache][total])
            if val != 0:
                break

        for metric in metrics:
            type_, scale, format_str = type_map.get(metric, (int, 1, "%d"))

            val = type_(parsed[inst]["CacheStatistics"][cache][metric]) * scale
            if isinstance(val, float) and val == 0.0:
                # what a hack! we assume the float is based on the totals (all of them) and if they
                # were all 0, so this float is 0/0, we want to display it as 1 as to not cause
                # an alert
                val = 1.0 * scale
            yield 0, ("%s: " + format_str) % (metric, val), [(metric, val)]

        for total in totals:
            type_, scale, format_str = type_map.get(total, (int, 1, "%d"))
            val = type_(parsed[inst]["CacheStatistics"][cache][total]) * scale
            yield 0, ("%s: " + format_str) % (total, val), []
    except KeyError:
        # some element of the item was missing
        pass


check_info["jolokia_metrics.cache_hits"] = {
    "includes": ["jolokia.include"],
    "service_description": "JVM %s Cache Usage",
    "check_function": lambda item, params, parsed: check_jolokia_metrics_cache(
        ["CacheHitPercentage", "ObjectCount"], ["CacheHits", "CacheMisses"], item, params, parsed),
    "inventory_function": lambda info: inventory_jolokia_metrics_cache(
        "cache_hits", ["CacheHitPercentage", "ObjectCount", "CacheHits", "CacheMisses"], info),
    "has_perfdata": True,
}

check_info["jolokia_metrics.in_memory"] = {
    "includes": ["jolokia.include"],
    "service_description": "JVM %s In Memory",
    "check_function": lambda item, params, parsed: check_jolokia_metrics_cache([
        "InMemoryHitPercentage", "MemoryStoreObjectCount"
    ], ["InMemoryHits", "InMemoryMisses"], item, params, parsed),
    "inventory_function": lambda info: inventory_jolokia_metrics_cache(
        "cache_hits", [
            "InMemoryHitPercentage", "MemoryStoreObjectCount", "InMemoryHits", "InMemoryMisses"
        ], info),
    "has_perfdata": True,
}

check_info["jolokia_metrics.on_disk"] = {
    "includes": ["jolokia.include"],
    "service_description": "JVM %s On Disk",
    "check_function": lambda item, params, parsed: check_jolokia_metrics_cache([
        "OnDiskHitPercentage", "DiskStoreObjectCount"
    ], ["OnDiskHits", "OnDiskMisses"], item, params, parsed),
    "inventory_function": lambda info: inventory_jolokia_metrics_cache(
        "cache_hits", ["OnDiskHitPercentage", "DiskStoreObjectCount", "OnDiskHits", "OnDiskMisses"],
        info),
    "has_perfdata": True,
}

check_info["jolokia_metrics.off_heap"] = {
    "includes": ["jolokia.include"],
    "service_description": "JVM %s Off Heap",
    "check_function": lambda item, params, parsed: check_jolokia_metrics_cache([
        "OffHeapHitPercentage", "OffHeapStoreObjectCount"
    ], ["OffHeapHits", "OffHeapMisses"], item, params, parsed),
    "inventory_function": lambda info: inventory_jolokia_metrics_cache(
        "cache_hits",
        ["OffHeapHitPercentage", "OffHeapStoreObjectCount", "OffHeapHits", "OffHeapMisses"], info),
    "has_perfdata": True,
}

check_info["jolokia_metrics.writer"] = {
    "includes": ["jolokia.include"],
    "service_description": "JVM %s Cache Writer",
    "check_function": lambda item, params, parsed: check_jolokia_metrics_cache(
        ["WriterQueueLength", "WriterMaxQueueSize"], [], item, params, parsed),
    "inventory_function": lambda info: inventory_jolokia_metrics_cache(
        "cache_hits", ["WriterQueueLength", "WriterMaxQueueSize"], info),
    "has_perfdata": True,
}
