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

# First generation of agents output only the process command line:
# /usr/sbin/xinetd -pidfile /var/run/xinetd.pid -stayalive -inetd_compat -inetd_ipv6

# Second generation of agents output the user in brackets in the first columns:
# (root) /usr/sbin/xinetd -pidfile /var/run/xinetd.pid -stayalive -inetd_compat -inetd_ipv6

# Third generation (from 1.1.5) output also virtual memory, resident memory and %CPU:
# (class,122376,88128,0.0) /usr/jre1.6.0_13/bin/java -Dn=Cart_16TH13 -Dmcs.node=zbgh1ca -Dmcs.mdt.redundan

# Forth generation (>=1.2.5), additional columns in bracket:
# (user, virtual_size, resident_size, %cpu, processID, pagefile_usage, usermodetime, kernelmodetime, openHandles, threadCount) name
#(\\KLAPPRECHNER\ab,29284,2948,0,3124,904,400576,901296,35,1)    NOTEPAD.EXE

# The plugin "psperf.bat" is deprecated. As of version 1.2.5 all of this information
# is reported by the windows agent itself. However, we still support sections from psperf.bat
# if the agent version is lower than 1.2.5.
# Windows agent now ships a plugin "psperf.bat" that adds a section from wmic
# to the output:
# <<<ps:sep(44)>>>
# [wmic process]
# ^M
# Node,KernelModeTime,Name,PageFileUsage,ThreadCount,UserModeTime,VirtualSize,WorkingSetSize^M
# WINDOWSXP,43478281250,System Idle Process,0,2,0,0,28672^M
# WINDOWSXP,155781250,System,0,59,0,1957888,253952^M
# WINDOWSXP,468750,smss.exe,176128,3,156250,3928064,442368^M
# WINDOWSXP,56406250,csrss.exe,1863680,12,11406250,25780224,3956736^M
# WINDOWSXP,18593750,winlogon.exe,6832128,19,4843750,59314176,2686976^M
# WINDOWSXP,167500000,services.exe,1765376,16,13750000,22601728,4444160^M
# WINDOWSXP,16875000,lsass.exe,3964928,21,3906250,43462656,6647808^M
# WINDOWSXP,8750000,VBoxService.exe,1056768,8,468750,26652672,3342336^M


# New since 1.2.1i2: WATO compatible syntax
#
# Holds a list of rules which are matching hosts by names or tags and
# where each rule holds a dictionary.
#
# Each of those entries defines the following options:
#
# 1. descr:    item name to be used for the service description
# 2. match:    matching-definition
# 3. user:     user definition
# 5. perfdata: monitor with perfdata
# 4. levels:   four numbers (thresholds)
inventory_processes_rules = []

inventory_processes = []
inventory_processes_perf = []
ANY_USER = None
GRAB_USER = False

def ps_parse_info(info):
    ps_result = []
    lines = iter(info)
    wmic_info = {}
    use_wmic_info = True
    cpu_cores = 1

    try:
        is_wmic        = False
        while True:
            line = lines.next()
            if line[-1] == '[wmic process]':
                is_wmic = True
                wmic_headers = ["node"] + lines.next()[1:]
                continue
            elif line[-1] == '[wmic process end]':
                is_wmic = False
                continue

            if not is_wmic:
                # Ignore data from the psperf.bat if the agent has this feature builtin
                if use_wmic_info and len(line[1].split(",")) >= 10:
                    use_wmic_info = False
                if not use_wmic_info:
                    # We need to determine the number of cpu_cores without the wmic_info
                    if len(line) < 3:
                        # On some rare instances the name of the process is missing
                        # This line will be ignored
                        continue
                    if line[2].lower() == "system idle process":
                        cpu_cores = int(line[1][1:-1].split(",")[9])
                ps_result.append(line)
            else:
                row = dict(zip(wmic_headers, line))
                wmic_info[(row["node"], row["Name"], row["ProcessId"])] = row
    except StopIteration:
        pass


    # Add wmic_info to process table, but only if present:
    if use_wmic_info and wmic_info:
        def get_ps_info(node, name, processId = 0):
            for key, value in wmic_info.items():
                if (not processId and key[0:2] == (node, name)) or key == (node, name, processId):
                    # each info is only returned once!
                    del wmic_info[key]
                    return value
        info = []
        seen_pids = set([]) # Remove duplicate entries
        for line in ps_result:
            psinfo = get_ps_info(line[0], line[1])
            # Get number of CPU cores from system idle process
            if psinfo:
                if "ThreadCount" in wmic_headers and psinfo["Name"].lower() == "system idle process":
                    cpu_cores = int(psinfo["ThreadCount"])
                pid = int(psinfo["ProcessId"])
                if pid not in seen_pids:
                    seen_pids.add(pid)
                    virt     = int(psinfo["VirtualSize"])    / 1024 # Bytes -> KB
                    resi     = int(psinfo["WorkingSetSize"]) / 1024 # Bytes -> KB
                    pagefile = int(psinfo["PageFileUsage"])  / 1024 # Bytes -> KB
                    userc    = int(psinfo["UserModeTime"])   # do not resolve counter here!
                    kernelc  = int(psinfo["KernelModeTime"]) # do not resolve counter here!
                    handlec  = int(psinfo.get("HandleCount", 0)) # Only in newer psperf.bat versions
                    threadc  = int(psinfo["ThreadCount"])    # do not resolve counter here!
                    line[1:1] = [ "(unknown,%d,%d,0,%d,%d,%d,%d,%d,%d)" %
                                    (virt, resi, pid, pagefile, userc, kernelc, handlec, threadc) ]
            info.append(line)
        return cpu_cores, info
    else:
        return cpu_cores, info


def inventory_ps(info):
    cpu_cores, info = ps_parse_info(info) # parse windows wmic information
    return inventory_ps_common(inventory_processes, inventory_processes_rules, info)

def check_ps(item, params, info):
    cpu_cores, info = ps_parse_info(info) # parse windows wmic information
    return check_ps_common(item, params, info, cpu_cores = cpu_cores )


check_info['ps'] = {
    "check_function"          : check_ps,
    "inventory_function"      : inventory_ps,
    "service_description"     : "Process %s",
    "includes"                : [ "ps.include" ],
    "has_perfdata"            : True,
    "node_info"               : True, # add first column with actual host name
    "group"                   : "ps",
    "default_levels_variable" : "ps_default_levels",
}

# NOTE: This check is deprecated and will be removed any decade now. ps now
# does always performance data.
check_info['ps.perf'] = {
    "check_function"          : check_ps,
    "service_description"     : "Process %s",
    "includes"                : [ "ps.include" ],
    "has_perfdata"            : True,
    "node_info"               : True, # add first column with actual host name
    "group"                   : "ps",
    "default_levels_variable" : "ps_default_levels",
}
