diff --git a/informix/2.4/informix_transactions-1.1.0.mkp b/informix/2.4/informix_transactions-1.1.0.mkp new file mode 100755 index 0000000..895c1d4 Binary files /dev/null and b/informix/2.4/informix_transactions-1.1.0.mkp differ diff --git a/informix/2.4/local/lib/python3/cmk_addons/plugins/informix_transactions/agent_based/informix_transactions.py b/informix/2.4/local/lib/python3/cmk_addons/plugins/informix_transactions/agent_based/informix_transactions.py new file mode 100644 index 0000000..48fc05e --- /dev/null +++ b/informix/2.4/local/lib/python3/cmk_addons/plugins/informix_transactions/agent_based/informix_transactions.py @@ -0,0 +1,220 @@ +# Copyright 2026 Spearhead Systems SRL +# Copyright (C) 2019 tribe29 GmbH +# +# Expected example input (and expect arbitrary blank lines): +# +# <<>> +# [[[1/2]]] +# a40dbb0 6 U---C-L 6 1:3263 500 372 74.40 +# +# a6d8028 A---- a695028 0 - - COMMIT - 0 +# a6d8348 A---- a695878 0 - - COMMIT - 0 +# a6d8668 A---- a6960c8 0 - - COMMIT - 0 +# a6d8988 A---- a696918 0 - - COMMIT - 0 +# a6d8fc8 A---- a698208 0 - - COMMIT - 0 +# a6d92e8 A---- a6979b8 0 - - COMMIT - 0 +# a6d9608 A---- a698a58 0 - - COMMIT - 0 +# a6d9928 A---- a6992a8 1 - - DIRTY - 0 +# a6d9c48 A---- a6992a8 0 - - NOTRANS - 0 +# a6d9f68 A---- a69a348 0 - - COMMIT - 0 +# a6da288 A---- a69ab98 0 - - COMMIT - 0 +# a6da5a8 A---- a69b3e8 0 - - COMMIT - 0 +# a6da8c8 A---- a69bc38 0 - - COMMIT - 0 +# a6dabe8 A---- a69c488 0 - - COMMIT - 0 +# a6daf08 A---- a699af8 0 - - COMMIT - 0 +# a6db228 A---- a6992a8 0 - - COMMIT - 0 +# a6db548 A---- a69ccd8 1 - - DIRTY - 0 +# a6db868 A---- a69d528 1 - - DIRTY - 0 +# a6dbb88 A---- a69ccd8 0 - - COMMIT - 0 +# a6dbea8 A---- a69dd78 0 - - COMMIT - 0 +# a6dc1c8 A---- a69e5c8 0 - - COMMIT - 0 +# a6dc4e8 A-B-- a69ee18 502 33:0x25018 34:0x486fc COMMIT 0 + + +import json +from cmk.agent_based.v2 import ( + Result, + Service, + Metric, + State, + CheckPlugin, + AgentSection, +) + + +# This is what the columns from the onstat -x output represent: +# +# line[0]= first line represents current log ID +# line[1]= +# line[2]= sessionID +# line[3]= number of locks kept by sessionID +# line[4]= begin log position +# line[5]= current log position +def parse_informix_transactions(string_table): + instance = string_table[0][0][3:-3] + curr_log = "" + sessions = [] + + for line in string_table[1:]: + if len(line) <= 5: + continue + + if "C" in line[2]: + curr_log = int(line[3]) + continue + + if ":" in line[4]: + line[4] = int(line[4].split(":")[0]) + else: + line[4] = None + + if ":" in line[5]: + line[5] = int(line[5].split(":")[0]) + else: + line[5] = None + + sessions.append({ + "id": line[2], + "locks": int(line[3]), + "log_start": line[4], + "log_end": line[5], + }) + + return { + instance: { + "curr_log": curr_log, + "sessions": sessions, + } + } + + +def discover_informix_transactions(section): + for instance in section: + yield Service(item=instance) + + +def check_informix_locks(item, params, section): + data = section.get(item) + if not data: + return + + levels = params.get("levels") + infotext = "" + state = State.OK + max_locks = 0 + + if levels and levels[0] == "fixed": + warn, crit = levels[1] + else: + warn, crit = None, None + + for session in data["sessions"]: + id = session["id"] + locks = session["locks"] + log_start = session["log_start"] + + max_locks = max(max_locks, locks) + + if log_start == "-": + continue + + if crit and locks >= crit: + state = State.CRIT + infotext += f"Session with ID {id} has {locks} locks; " + elif warn and locks >= warn: + state = State.WARN + infotext += f"Session with ID {id} has {locks} locks; " + + if state != State.OK: + infotext += f"(warn/crit at {warn}/{crit})" + else: + infotext = "There are no sessions with a high number of locks" + + yield Metric(name="MaximumLocksInSession", value=max_locks) + yield Result(state=state, summary=infotext) + + +def check_informix_activity(item, params, section): + data = section.get(item) + if not data: + return + + state = State.OK + infotext = "" + curr_log = data["curr_log"] + + for session in data["sessions"]: + id = session["id"] + log_start = session["log_start"] + + if log_start is None: + continue + + if log_start < curr_log: + state = State.CRIT + infotext += f"Session {id} doesn't have activity in current log; " + + if state == State.OK: + infotext = "There are no inactive sessions in current log" + + yield Result(state=state, summary=infotext) + + +def check_informix_long_transactions(item, params, section): + data = section.get(item) + if not data: + return + + state = State.OK + infotext = "" + + for session in data["sessions"]: + id = session["id"] + log_start = session["log_start"] + log_end = session["log_end"] + + if log_start is None or log_end is None: + continue + + if log_end - log_start > 2: + state = State.CRIT + infotext += f"Session {id} is using more than 3 logical logs; " + + if state == State.OK: + infotext = "There are no long-running transactions" + + yield Result(state=state, summary=infotext) + + +agent_section_informix_transactions = AgentSection( + name = "informix_transactions", + parse_function = parse_informix_transactions, +) + +check_plugin_informix_session_locks = CheckPlugin( + name = "informix_session_locks", + check_ruleset_name = "informix_session_locks", + service_name = "Informix session locks %s", + sections = ["informix_transactions"], + discovery_function = discover_informix_transactions, + check_function = check_informix_locks, + check_default_parameters = {}, +) + +check_plugin_informix_session_activity = CheckPlugin( + name = "informix_session_activity", + service_name = "Informix session activity %s", + sections = ["informix_transactions"], + discovery_function = discover_informix_transactions, + check_function = check_informix_activity, + check_default_parameters = {}, +) + +check_plugin_informix_session_long_transactions = CheckPlugin( + name = "informix_session_long_transactions", + service_name = "Informix session long transactions %s", + sections = ["informix_transactions"], + discovery_function = discover_informix_transactions, + check_function = check_informix_long_transactions, + check_default_parameters = {}, +) diff --git a/informix/2.4/local/lib/python3/cmk_addons/plugins/informix_transactions/rulesets/informix_transactions.py b/informix/2.4/local/lib/python3/cmk_addons/plugins/informix_transactions/rulesets/informix_transactions.py new file mode 100644 index 0000000..eb94e76 --- /dev/null +++ b/informix/2.4/local/lib/python3/cmk_addons/plugins/informix_transactions/rulesets/informix_transactions.py @@ -0,0 +1,46 @@ +# Copyright 2026 Spearhead Systems SRL + +from cmk.rulesets.v1.form_specs import ( + Dictionary, + DictElement, + Integer, + DefaultValue, + LevelDirection, + SimpleLevels, +) +from cmk.rulesets.v1.rule_specs import ( + CheckParameters, + HostAndItemCondition, + Topic, + Title, + Help, +) + + +def _valuespec_informix_session_locks(): + return Dictionary( + elements = { + "levels": DictElement( + parameter_form = SimpleLevels( + title = Title("Session locks"), + help_text = Help( + "You can set a limit to the number of locks for a " + "session in Informix Database application" + ), + level_direction = LevelDirection.UPPER, + form_spec_template = Integer(title = Title("locks")), + prefill_fixed_levels = DefaultValue(value=(40, 70)) + ) + ), + }, + ) + +rule_spec_informix_session_locks = CheckParameters( + title = Title("Informix Session Locks"), + name = "informix_session_locks", + topic = Topic.APPLICATIONS, + parameter_form = _valuespec_informix_session_locks, + condition = HostAndItemCondition( + item_title = Title("DBMS"), + ), +) diff --git a/informix/2.4/local/share/check_mk/agents/custom/informix_transactions/lib/plugins/informix_transactions b/informix/2.4/local/share/check_mk/agents/custom/informix_transactions/lib/plugins/informix_transactions new file mode 100755 index 0000000..8cd421c --- /dev/null +++ b/informix/2.4/local/share/check_mk/agents/custom/informix_transactions/lib/plugins/informix_transactions @@ -0,0 +1,184 @@ +#!/bin/bash +# Copyright (C) 2019 Checkmk 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. + +# Reason for this no-op: shellcheck disable=... before the first command disables the error for the +# entire script. +: + +# Disable unused variable error (needed to keep track of version) +# shellcheck disable=SC2034 +CMK_VERSION="2.4.0" + +# Informix +# Make ENV-VARs avail for subshells +set -a + +# .--helper--------------------------------------------------------------. +# | _ _ | +# | | |__ ___| |_ __ ___ _ __ | +# | | '_ \ / _ \ | '_ \ / _ \ '__| | +# | | | | | __/ | |_) | __/ | | +# | |_| |_|\___|_| .__/ \___|_| | +# | |_| | +# '----------------------------------------------------------------------' + +# BEGIN COMMON PLUGIN CODE + +# check that no users other than root can change the file +only_root_can_modify() { + permissions=$1 + owner=$2 + group=$3 + + group_write_perm=$(echo "$permissions" | cut -c 6) + other_write_perm=$(echo "$permissions" | cut -c 9) + + if [ "$owner" != "root" ] || [ "$other_write_perm" != "-" ]; then + return 1 + fi + + [ "$group" = "root" ] || [ "$group_write_perm" = "-" ] +} + +get_binary_owner() { + BINARY_PATH=$1 + stat -c '%U' "${BINARY_PATH}" +} + +get_binary_execution_mode() { + BINARY_PATH=$1 + BINARY_USER=$2 + + # if the executable belongs to someone besides root, do not execute it as root + if needs_user_switch_before_executing "$BINARY_PATH"; then + echo "su ${BINARY_USER} -c" + return + fi + echo "bash -c" +} + +needs_user_switch_before_executing() { + BINARY_PATH=$1 + + [ "$(whoami)" = "root" ] && ! only_root_can_modify "$(stat -c '%A' "$BINARY_PATH")" "$(stat -c '%U' "$BINARY_PATH")" "$(stat -c '%G' "$BINARY_PATH")" +} + +# END COMMON PLUGIN CODE + +set_env() { + # set environment variables given in the form VARNAME1=value1;VARNAME2=value2;... + while IFS=';' read -ra parts; do + for part in "${parts[@]}"; do + var_name="${part%%=*}" + var_value="${part#*=}" + export "$var_name"="$var_value" + done + done <<<"$1" +} + + +#. +# .--sqls----------------------------------------------------------------. +# | _ | +# | ___ __ _| |___ | +# | / __|/ _` | / __| | +# | \__ \ (_| | \__ \ | +# | |___/\__, |_|___/ | +# | |_| | +# '----------------------------------------------------------------------' + +informix_transactions() { + echo "<<>>" + echo "[[[$INFORMIXSERVER/$SERVERNUM]]]" + $EXECUTION_MODE "\"$INFORMIXDIR\"/bin/onstat -l" | grep C | tail -n 1 + $EXECUTION_MODE "\"$INFORMIXDIR\"/bin/onstat -x" | egrep -v 'IBM|maximum|Transactions|est.|userthread|logpos' | sed 's/:[^:]*$//' +} + + +#. +# .--config--------------------------------------------------------------. +# | __ _ | +# | ___ ___ _ __ / _(_) __ _ | +# | / __/ _ \| '_ \| |_| |/ _` | | +# | | (_| (_) | | | | _| | (_| | | +# | \___\___/|_| |_|_| |_|\__, | | +# | |___/ | +# '----------------------------------------------------------------------' + +# Config opts: +# - oninit-path; Default is empty, which means autodetection: +# ONINIT_PATH= +# - Excluding sections ("status sessions locks tabextents dbspaces logusage"): +# EXCLUDES="SECTION SECTION ..." +# EXCLUDES=ALL + +# shellcheck source=agents/cfg_examples/informix.cfg +. "$MK_CONFDIR/informix.cfg" 2>/dev/null + +if [ -z "$ONINIT_PATH" ] || [ ! -x "$ONINIT_PATH" ]; then + ONINIT=$(UNIX95=true ps ax | grep oninit | grep -v grep | head -1 | awk '{print $1 " " $5}') + if [ -z "$ONINIT" ]; then + exit 0 + fi + + ONINIT_PATH=${ONINIT#* } + ONINIT_PID=${ONINIT% *} + case "$ONINIT_PATH" in + /*) ;; + + *) # BUG not platform independent! + ONINIT_PATH=$(readlink "/proc/$ONINIT_PID/exe") + ;; + esac + + # If not set in config or not found we end up here + if [ -z "$ONINIT_PATH" ] || [ ! -f "$ONINIT_PATH" ]; then + exit 1 + fi +fi + +#. +# .--main----------------------------------------------------------------. +# | _ | +# | _ __ ___ __ _(_)_ __ | +# | | '_ ` _ \ / _` | | '_ \ | +# | | | | | | | (_| | | | | | | +# | |_| |_| |_|\__,_|_|_| |_| | +# | | +# '----------------------------------------------------------------------' + +INFORMIXDIR=${ONINIT_PATH%/bin*} + +if [ ! -f "$INFORMIXDIR/bin/onstat" ]; then + exit 1 +fi + +EXECUTION_MODE="$(get_binary_execution_mode "$INFORMIXDIR/bin/onstat" "$(get_binary_owner "$INFORMIXDIR/bin/onstat")")" + +for IDSENV in $( + $EXECUTION_MODE "$INFORMIXDIR/bin/onstat -g dis" | + grep -E '^Server[ ]*:|^Server Number[ ]*:|^INFORMIX|^SQLHOSTS|^ONCONFIG' | + sed -e 's/Server Number/SERVERNUM/' \ + -e 's/Server/INFORMIXSERVER/' \ + -e 's/SQLHOSTS/INFORMIXSQLHOSTS/' \ + -e 's/[ ]*:[ ]*/=/' | + tr '\n' ';' | + sed -e 's/;$/\n/' -e 's/;\(INFORMIXSERVER=[^;]*;\)/\n\1/g' + +); do + ( + set_env "$IDSENV" + + # try to set them via 'onstat -g env' otherwise + # DB HAS TO BE RUNNING + if [ -z "$INFORMIXSQLHOSTS" ] || [ -z "$ONCONFIG" ]; then + $EXECUTION_MODE "$INFORMIXDIR/bin/onstat -g env" | grep -E -e '^INFORMIXSQLHOSTS' \ + -e '^ONCONFIG' | + sed -e 's/[ ][ ]*/=/' + fi + + informix_transactions + ) +done