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