You've already forked nrpe
372 lines
11 KiB
Bash
Executable File
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 |