Files
nrpe/files/nrpe/check_mysql_longqueries
2025-11-14 17:58:27 +01:00

372 lines
11 KiB
Bash
Executable File

#!/bin/bash
# MySQL Long Running Query Monitoring Script
# Converted from Perl to Bash
# Original: Vincent Rivellino <vrivellino@paybycash.com>
# Copyright (C) 2009
# This plugin comes with ABSOLUTELY NO WARRANTY. This is free software.
set -u
# Default values
VERBOSE=0
HOST=""
PORT=""
SOCKET=""
USER=""
PASSWORD=""
WARN=""
CRIT=""
DB_FILTER=""
SKIP_DB=""
CLIENTUSER_FILTER=""
SKIP_CLIENTUSER=""
CLIENTHOST_FILTER=""
SKIP_CLIENTHOST=""
TIMEOUT=10
# Nagios exit codes
OK=0
WARNING=1
CRITICAL=2
UNKNOWN=3
# Function to print usage
usage() {
cat <<EOF
Usage: $0 [-v|--verbose] [-H <host>] [-P <port>] [-S <socket>] [-u <user>] [-p <password>] -w <warn time> -c <crit time>
Options:
-H, --host MySQL server host
-P, --port MySQL server port
-S, --socket MySQL server socket
-u, --user Database user (must have privilege to SHOW PROCESSLIST)
-p, --password Database password
-w, --warn Query time in seconds to generate a WARNING (required)
-c, --crit Query time in seconds to generate a CRITICAL (required)
--db Only check queries running on this database (comma-separated)
--skip_db Don't check queries on this database (comma-separated)
--clientuser Only check queries by this MySQL user (comma-separated)
--skip_clientuser Don't check queries by this MySQL user (comma-separated)
--clienthost Only check queries from this client host (comma-separated)
--skip_clienthost Don't check queries from this client host (comma-separated)
-v, --verbose Increase verbosity
--version Show version
-h, --help Show this help message
EOF
exit $UNKNOWN
}
# Function to exit with Nagios status
nagios_exit() {
local status=$1
local message=$2
case $status in
$OK) echo "OK - $message"; exit $OK ;;
$WARNING) echo "WARNING - $message"; exit $WARNING ;;
$CRITICAL) echo "CRITICAL - $message"; exit $CRITICAL ;;
$UNKNOWN) echo "UNKNOWN - $message"; exit $UNKNOWN ;;
esac
}
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-H|--host)
HOST="$2"
shift 2
;;
-P|--port)
PORT="$2"
shift 2
;;
-S|--socket)
SOCKET="$2"
shift 2
;;
-u|--user)
USER="$2"
shift 2
;;
-p|--password)
PASSWORD="$2"
shift 2
;;
-w|--warn)
WARN="$2"
shift 2
;;
-c|--crit)
CRIT="$2"
shift 2
;;
--db)
DB_FILTER="$2"
shift 2
;;
--skip_db)
SKIP_DB="$2"
shift 2
;;
--clientuser)
CLIENTUSER_FILTER="$2"
shift 2
;;
--skip_clientuser)
SKIP_CLIENTUSER="$2"
shift 2
;;
--clienthost)
CLIENTHOST_FILTER="$2"
shift 2
;;
--skip_clienthost)
SKIP_CLIENTHOST="$2"
shift 2
;;
-v|--verbose)
((VERBOSE++))
shift
;;
--version)
echo "Version 1.0"
exit $OK
;;
-h|--help)
usage
;;
*)
echo "Unknown option: $1"
usage
;;
esac
done
# Validate required arguments
if [[ -z "$WARN" || -z "$CRIT" ]]; then
echo "ERROR: -w (warn) and -c (crit) are required"
usage
fi
# Print verbose options
if [[ $VERBOSE -ge 2 ]]; then
echo "Plugin options:"
printf " %-23s %d\n" "verbose:" "$VERBOSE"
printf " %-23s %s\n" "host:" "${HOST:-}"
printf " %-23s %s\n" "port:" "${PORT:-}"
printf " %-23s %s\n" "socket:" "${SOCKET:-}"
printf " %-23s %s\n" "user:" "${USER:-}"
printf " %-23s %s\n" "password:" "${PASSWORD:-}"
printf " %-23s %d\n" "warn:" "$WARN"
printf " %-23s %d\n" "crit:" "$CRIT"
printf " %-23s %s\n" "db:" "${DB_FILTER:-}"
printf " %-23s %s\n" "skip_db:" "${SKIP_DB:-}"
printf " %-23s %s\n" "clientuser:" "${CLIENTUSER_FILTER:-}"
printf " %-23s %s\n" "skip_clientuser:" "${SKIP_CLIENTUSER:-}"
printf " %-23s %s\n" "clienthost:" "${CLIENTHOST_FILTER:-}"
printf " %-23s %s\n" "skip_clienthost:" "${SKIP_CLIENTHOST:-}"
fi
# Convert comma-separated filters to arrays
IFS=',' read -ra DB_ARRAY <<< "${DB_FILTER:-}"
IFS=',' read -ra SKIP_DB_ARRAY <<< "${SKIP_DB:-}"
IFS=',' read -ra CLIENTUSER_ARRAY <<< "${CLIENTUSER_FILTER:-}"
IFS=',' read -ra SKIP_CLIENTUSER_ARRAY <<< "${SKIP_CLIENTUSER:-}"
IFS=',' read -ra CLIENTHOST_ARRAY <<< "${CLIENTHOST_FILTER:-}"
IFS=',' read -ra SKIP_CLIENTHOST_ARRAY <<< "${SKIP_CLIENTHOST:-}"
# Set alarm timeout
trap 'nagios_exit $UNKNOWN "Plugin timed out"' ALRM
alarm $TIMEOUT 2>/dev/null || true
# Build MySQL connection string
MYSQL_OPTS=()
if [[ -z "$HOST" || "$HOST" == "localhost" ]]; then
# Connect via socket if specified
if [[ -n "$SOCKET" ]]; then
MYSQL_OPTS+=(--socket="$SOCKET")
fi
else
# Connect via host and port
if [[ -n "$HOST" ]]; then
MYSQL_OPTS+=(--host="$HOST")
fi
if [[ -n "$PORT" ]]; then
MYSQL_OPTS+=(--port="$PORT")
fi
fi
# Add credentials
if [[ -n "$USER" ]]; then
MYSQL_OPTS+=(--user="$USER")
fi
if [[ -n "$PASSWORD" ]]; then
MYSQL_OPTS+=(--password="$PASSWORD")
fi
# Print connection info if really verbose
if [[ $VERBOSE -ge 2 ]]; then
echo "MySQL Options: ${MYSQL_OPTS[*]}"
fi
# Function to check if value matches any item in array
array_contains() {
local value=$1
shift
local array=("$@")
for item in "${array[@]}"; do
if [[ "$value" == "$item" ]]; then
return 0
fi
done
return 1
}
# Function to check if value does NOT match any item in array
array_not_contains() {
local value=$1
shift
local array=("$@")
[[ ${#array[@]} -eq 0 ]] && return 0
for item in "${array[@]}"; do
if [[ "$value" == "$item" ]]; then
return 1
fi
done
return 0
}
# Query the MySQL processlist
QUERY_OUTPUT=$(mysql "${MYSQL_OPTS[@]}" -e "SHOW FULL PROCESSLIST\G" 2>&1)
if [[ $? -ne 0 ]]; then
nagios_exit $UNKNOWN "Could not connect to database: $QUERY_OUTPUT"
fi
# Parse the processlist output
LONGQUERY_INFO=""
LONGQUERY_TIME=0
COUNT=0
CURRENT_QUERY=""
while IFS= read -r line; do
# Skip empty lines
[[ -z "$line" ]] && continue
# Check for start of new record
if [[ $line =~ ^[[:space:]]*\*\*\*\*\*\*\*\*\*\* ]]; then
# Process previous query if any
if [[ -n "$CURRENT_QUERY" ]]; then
((COUNT++))
eval "$CURRENT_QUERY"
# Skip if time is 0 or NULL
[[ -z "$TIME" || "$TIME" -eq 0 ]] && CURRENT_QUERY="" && continue
# Skip system queries
[[ "$USER_VAL" == "system user" ]] && CURRENT_QUERY="" && continue
[[ "$COMMAND" =~ (Sleep|Binlog\ Dump|Ping|Processlist) ]] && CURRENT_QUERY="" && continue
# Extract host without port
HOST_VAL="${HOST_VAL%:*}"
# Apply filters
if [[ -n "$DB_FILTER" ]]; then
array_contains "$DB_VAL" "${DB_ARRAY[@]}" || {
CURRENT_QUERY=""
continue
}
fi
if [[ -n "$SKIP_DB" ]]; then
array_not_contains "$DB_VAL" "${SKIP_DB_ARRAY[@]}" || {
CURRENT_QUERY=""
continue
}
fi
if [[ -n "$CLIENTUSER_FILTER" ]]; then
array_contains "$USER_VAL" "${CLIENTUSER_ARRAY[@]}" || {
CURRENT_QUERY=""
continue
}
fi
if [[ -n "$SKIP_CLIENTUSER" ]]; then
array_not_contains "$USER_VAL" "${SKIP_CLIENTUSER_ARRAY[@]}" || {
CURRENT_QUERY=""
continue
}
fi
if [[ -n "$CLIENTHOST_FILTER" ]]; then
array_contains "$HOST_VAL" "${CLIENTHOST_ARRAY[@]}" || {
CURRENT_QUERY=""
continue
}
fi
if [[ -n "$SKIP_CLIENTHOST" ]]; then
array_not_contains "$HOST_VAL" "${SKIP_CLIENTHOST_ARRAY[@]}" || {
CURRENT_QUERY=""
continue
}
fi
# Check if this is the longest running query
if [[ "$TIME" -gt "$LONGQUERY_TIME" ]]; then
LONGQUERY_TIME=$TIME
LONGQUERY_INFO="TIME: $TIME ID=$ID USER=$USER_VAL HOST=$HOST_VAL DB=$DB_VAL COMMAND=$COMMAND"
[[ -n "$INFO" ]] && LONGQUERY_INFO="$LONGQUERY_INFO INFO=$INFO"
fi
fi
CURRENT_QUERY=""
else
# Parse key-value pairs
if [[ $line =~ ^[[:space:]]*([^:]+):[[:space:]]*(.*)$ ]]; then
key="${BASH_REMATCH[1]}"
value="${BASH_REMATCH[2]}"
# Map field names
case "$key" in
"Id") CURRENT_QUERY="${CURRENT_QUERY}ID='$value'; " ;;
"User") CURRENT_QUERY="${CURRENT_QUERY}USER_VAL='$value'; " ;;
"Host") CURRENT_QUERY="${CURRENT_QUERY}HOST_VAL='$value'; " ;;
"db") CURRENT_QUERY="${CURRENT_QUERY}DB_VAL='$value'; " ;;
"Command") CURRENT_QUERY="${CURRENT_QUERY}COMMAND='$value'; " ;;
"Time") CURRENT_QUERY="${CURRENT_QUERY}TIME='${value%% *}'; " ;;
"Info") CURRENT_QUERY="${CURRENT_QUERY}INFO='$value'; " ;;
esac
fi
fi
done <<< "$QUERY_OUTPUT"
# Process last query
if [[ -n "$CURRENT_QUERY" ]]; then
((COUNT++))
eval "$CURRENT_QUERY"
if [[ -n "$TIME" && "$TIME" -gt 0 ]]; then
[[ "$TIME" -gt "$LONGQUERY_TIME" ]] && LONGQUERY_TIME=$TIME && LONGQUERY_INFO="TIME: $TIME"
fi
fi
# Check results
if [[ -z "$LONGQUERY_INFO" ]]; then
nagios_exit $OK "No long running queries found ($COUNT threads checked)"
fi
# Check thresholds
if [[ "$LONGQUERY_TIME" -ge "$CRIT" ]]; then
nagios_exit $CRITICAL "$LONGQUERY_INFO"
elif [[ "$LONGQUERY_TIME" -ge "$WARN" ]]; then
nagios_exit $WARNING "$LONGQUERY_INFO"
else
nagios_exit $OK "No long running queries found ($COUNT threads checked)"
fi