add new version of mysql_longqueries

This commit is contained in:
Ludovic Cartier
2025-11-14 17:58:27 +01:00
parent 9941f4644f
commit e16a0bdd9a
3 changed files with 348 additions and 214 deletions

View File

@@ -1,237 +1,372 @@
#!/usr/bin/perl #!/bin/bash
# $Id$
#
# check_mysql_longqueries plugin for Nagios
#
# Copyright (C) 2009 Vincent Rivellino <vrivellino@paybycash.com>
#
# This program 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; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
#
# Checks MySQL's processlist to see if there are queries running longer than
# defined thresholds.
#
# Requires the following modules:
# DBI
# Monitoring::Plugin
#
# Copyright Notice: GPLv2
#
# CHANGES
#
# 30 Jan 2009 - Vincent Rivellino <vrivellino@paybycash.com>
# Initial version released.
#
# 02 Mar 2020 - Ludovic Cartier <ludovic.cartier@brainsys.io>
# Replace Nagios::Plugin by Monitoring::Plugin
# need debian package libmonitoring-plugin-perl
#
use warnings; # MySQL Long Running Query Monitoring Script
use strict; # Converted from Perl to Bash
use DBI; # Original: Vincent Rivellino <vrivellino@paybycash.com>
use Monitoring::Plugin; # Copyright (C) 2009
# This plugin comes with ABSOLUTELY NO WARRANTY. This is free software.
set -u
## setup Monitoring::Plugin # Default values
my $np = Monitoring::Plugin->new( VERBOSE=0
usage => "Usage: %s [-v|--verbose] [-H <host>] [-P <port>] [-S <socket>] [-u <user>] [-p <password>] -w <warn time> -c <crit time>", HOST=""
version => "1.0", PORT=""
license => "Copyright (C) 2009 Vincent Rivellino <vrivellino\@paybycash.com>\n" . SOCKET=""
"This plugin comes with ABSOLUTELY NO WARRANTY. This is free software, and you\n" . USER=""
"are welcome to redistribute it under the conditions of version 2 of the GPL." PASSWORD=""
); WARN=""
CRIT=""
DB_FILTER=""
SKIP_DB=""
CLIENTUSER_FILTER=""
SKIP_CLIENTUSER=""
CLIENTHOST_FILTER=""
SKIP_CLIENTHOST=""
TIMEOUT=10
## add command line arguments # Nagios exit codes
$np->add_arg( OK=0
spec => 'host|H=s', WARNING=1
help => "-H, --host\n MySQL server host" CRITICAL=2
); UNKNOWN=3
$np->add_arg(
spec => 'port|P=i',
help => "-P, --port\n MySQL server port"
);
$np->add_arg(
spec => 'socket|S=s',
help => "-S, --socket\n MySQL server socket"
);
$np->add_arg(
spec => 'user|u=s',
help => "-u, --user\n database user (must have privilege to SHOW PROCESSLIST)"
);
$np->add_arg(
spec => 'password|p=s',
help => "-p, --password\n database password"
);
$np->add_arg(
spec => 'warn|w=i',
help => "-w, --warn\n Query time in seconds to generate a WARNING",
required => 1
);
$np->add_arg(
spec => 'crit|c=i',
help => "-c, --crit\n Query time in seconds to generate a CRITICAL",
required => 1
);
$np->add_arg(
spec => 'db=s',
help => "--db\n Only check queries running on this database\n To specify more than one, separate with commas."
);
$np->add_arg(
spec => 'skip_db=s',
help => "--skip_db\n Don't check queries running on this database\n To specify more than one, separate with commas."
);
$np->add_arg(
spec => 'clientuser=s',
help => "--clientuser\n Only check queries running by this MySQL user\n To specify more than one, separate with commas."
);
$np->add_arg(
spec => 'skip_clientuser=s',
help => "--skip_clientuser\n Don't check queries running by this MySQL user\n To specify more than one, separate with commas."
);
$np->add_arg(
spec => 'clienthost=s',
help => "--clienthost\n Only check queries running from this client host\n To specify more than one, separate with commas."
);
$np->add_arg(
spec => 'skip_clienthost=s',
help => "--skip_clienthost\n Don't check queries running from this client host\n To specify more than one, separate with commas."
);
# 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>
## parse the command line arguments Options:
$np->getopts; -H, --host MySQL server host
my $verbose = $np->opts->verbose || 0; -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
if ( $verbose >= 2 ) { EOF
print "Plugin options:\n"; exit $UNKNOWN
printf " %-23s %d\n", "verbose:", $verbose;
printf " %-23s %s\n", "host:", $np->opts->host || '';
printf " %-23s %s\n", "port:", $np->opts->port || '';
printf " %-23s %s\n", "socket:", $np->opts->socket || '';
printf " %-23s %s\n", "user:", $np->opts->user || '';
printf " %-23s %s\n", "password:", $np->opts->password || '';
printf " %-23s %d\n", "warn:", $np->opts->warn;
printf " %-23s %d\n", "crit:", $np->opts->crit;
printf " %-23s %s\n", "db:", $np->opts->db || '';
printf " %-23s %s\n", "skip_db:", $np->opts->skip_db || '';
printf " %-23s %s\n", "clientuser:", $np->opts->clientuser || '';
printf " %-23s %s\n", "skip_clientuser:", $np->opts->skip_clientuser || '';
printf " %-23s %s\n", "clienthost:", $np->opts->clienthost || '';
printf " %-23s %s\n", "skip_clienthost:", $np->opts->skip_clienthost || '';
} }
# extract restrictions from args - will grep() these lists # Function to exit with Nagios status
my @db = split( '/,/', $np->opts->db || '' ); nagios_exit() {
my @skipdb = split( '/,/', $np->opts->skip_db || '' ); local status=$1
my @clientuser = split( '/,/', $np->opts->clientuser || '' ); local message=$2
my @skipclientuser = split( '/,/', $np->opts->skip_clientuser || '' );
my @clienthost = split( '/,/', $np->opts->clienthost || '' );
my @skipclienthost = split( '/,/', $np->opts->skip_clienthost || '' );
alarm $np->opts->timeout; case $status in
$OK) echo "OK - $message"; exit $OK ;;
## setup the dsn - no need to specify a database $WARNING) echo "WARNING - $message"; exit $WARNING ;;
my $dsn = 'DBI:mysql:'; $CRITICAL) echo "CRITICAL - $message"; exit $CRITICAL ;;
$UNKNOWN) echo "UNKNOWN - $message"; exit $UNKNOWN ;;
## if we're connecting to localhost (by name) or the host isn't defined ... esac
if ( ! $np->opts->host || $np->opts->host eq 'localhost' ) {
# connect via a local socket (if it's defined)
$dsn .= ';mysql_socket=' . $np->opts->socket
if $np->opts->socket;
## otherwise, attempt to connect via host and/or port (if they're defined)
} else {
$dsn .= ';host=' . $np->opts->host
if $np->opts->host;
$dsn .= ';port=' . $np->opts->port
if $np->opts->port;
} }
## print dsn if really verbose # Parse command line arguments
print "DSN: '$dsn' USER: '", $np->opts->user || '', "' PASS: '", $np->opts->password || '', "'\n" while [[ $# -gt 0 ]]; do
if $verbose >= 2; 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
## connect to the database server # Validate required arguments
my $dbh = DBI->connect( $dsn, $np->opts->user || '', $np->opts->password || '', if [[ -z "$WARN" || -z "$CRIT" ]]; then
{ RaiseError => 0, PrintError => 0, AutoCommit => 1 } ) echo "ERROR: -w (warn) and -c (crit) are required"
or $np->nagios_exit( UNKNOWN, "Could not connect to database: $DBI::errstr" ); usage
fi
## get the list of running queries # Print verbose options
my $sth = $dbh->prepare( 'SHOW FULL PROCESSLIST' ); if [[ $VERBOSE -ge 2 ]]; then
$sth->execute(); echo "Plugin options:"
$np->nagios_exit( UNKNOWN, $sth->errstr ) if $sth->err; 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
## bind each row result to a hash # Convert comma-separated filters to arrays
my %row; IFS=',' read -ra DB_ARRAY <<< "${DB_FILTER:-}"
$sth->bind_columns( \( @row{ @{$sth->{NAME_lc} } } )); 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
## use these to keep track of the longest-running query # Build MySQL connection string
my $longquery_info = ''; MYSQL_OPTS=()
my $longquery_time = 0;
## process the results if [[ -z "$HOST" || "$HOST" == "localhost" ]]; then
my $count = 0; # Connect via socket if specified
while ( $sth->fetch ) { if [[ -n "$SOCKET" ]]; then
$count++; 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
# skip if time is zero or NULL # Add credentials
next unless $row{'time'}; if [[ -n "$USER" ]]; then
MYSQL_OPTS+=(--user="$USER")
fi
if [[ -n "$PASSWORD" ]]; then
MYSQL_OPTS+=(--password="$PASSWORD")
fi
# skip ignorable results # Print connection info if really verbose
next if $row{'user'} eq 'system user'; if [[ $VERBOSE -ge 2 ]]; then
next if $row{'command'} =~ m/(Sleep|Binlog Dump|Ping|Processlist)/io; echo "MySQL Options: ${MYSQL_OPTS[*]}"
fi
# extract connection info # Function to check if value matches any item in array
my $db = $row{'db'} || ''; array_contains() {
my $user = $row{'user'} || ''; local value=$1
my $host = $row{'host'} || ''; shift
$host =~ s/:\d+$//o; local array=("$@")
# skip if connection info does or doest match criteria for item in "${array[@]}"; do
next if $np->opts->db and grep !/^$db$/, @db; if [[ "$value" == "$item" ]]; then
next if $np->opts->skip_db and grep /^$db$/, @skipdb; return 0
fi
done
return 1
}
next if $np->opts->clientuser and grep !/^$user$/, @clientuser; # Function to check if value does NOT match any item in array
next if $np->opts->skip_clientuser and grep /^$user$/, @skipclientuser; array_not_contains() {
local value=$1
shift
local array=("$@")
next if $np->opts->clienthost and grep !/^$host$/, @clienthost; [[ ${#array[@]} -eq 0 ]] && return 0
next if $np->opts->skip_clienthost and grep /^$host$/, @skipclienthost;
# only save the longest running query for item in "${array[@]}"; do
if ( $row{'time'} > $longquery_time ) { if [[ "$value" == "$item" ]]; then
$longquery_time = $row{'time'}; return 1
$longquery_info = "TIME: $row{'time'}"; fi
foreach my $k ( sort keys %row ) { done
next if $k eq 'time' or $k eq 'info'; return 0
$longquery_info .= " $k=" . ( $row{$k} || 'NULL' ); }
# 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
} }
$longquery_info .= " INFO=" . ( $row{'info'} || 'NULL' ); fi
if [[ -n "$SKIP_DB" ]]; then
array_not_contains "$DB_VAL" "${SKIP_DB_ARRAY[@]}" || {
CURRENT_QUERY=""
continue
} }
} fi
# we're done with the db handle if [[ -n "$CLIENTUSER_FILTER" ]]; then
$dbh->disconnect; array_contains "$USER_VAL" "${CLIENTUSER_ARRAY[@]}" || {
CURRENT_QUERY=""
continue
}
fi
# OK if no long queries were found if [[ -n "$SKIP_CLIENTUSER" ]]; then
$np->nagios_exit( OK, "No long running queries found ($count threads checked)" ) unless $longquery_info; array_not_contains "$USER_VAL" "${SKIP_CLIENTUSER_ARRAY[@]}" || {
CURRENT_QUERY=""
continue
}
fi
# check for crit if [[ -n "$CLIENTHOST_FILTER" ]]; then
$np->nagios_exit( CRITICAL, $longquery_info ) if $longquery_time >= $np->opts->crit; array_contains "$HOST_VAL" "${CLIENTHOST_ARRAY[@]}" || {
$np->nagios_exit( WARNING, $longquery_info ) if $longquery_time >= $np->opts->warn; CURRENT_QUERY=""
continue
}
fi
# OK if if the longest query didn't match crit & warn if [[ -n "$SKIP_CLIENTHOST" ]]; then
$np->nagios_exit( OK, "No long running queries found ($count threads checked)" ); 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

View File

@@ -14,7 +14,6 @@
- monitoring-plugins-basic - monitoring-plugins-basic
- monitoring-plugins-common - monitoring-plugins-common
- monitoring-plugins-standard - monitoring-plugins-standard
- libmonitoring-plugin-perl
- name: nrpe | copy nrpe configuration - name: nrpe | copy nrpe configuration
template: template:

View File

@@ -32,7 +32,7 @@ command[check_rw_root]=/usr/lib/nagios/plugins/check_rofs /
{% if nrpe_mysql is defined %} {% if nrpe_mysql is defined %}
# mysql # mysql
command[check_mysql]=/usr/lib/nagios/plugins/check_mysql -u {{ nrpe_mysql_user }} -p{{ nrpe_mysql_password }} -H {{ nrpe_mysql_host }} -d mysql command[check_mysql]=/usr/lib/nagios/plugins/check_mysql -u {{ nrpe_mysql_user }} -p{{ nrpe_mysql_password }} -H {{ nrpe_mysql_host }} -d mysql
command[check_mysql_longqueries]=/usr/lib/nagios/plugins/check_mysql_longqueries -u {{ nrpe_mysql_user }} -p{{ nrpe_mysql_password }} -H {{ nrpe_mysql_host }} -w 600 -c 1200 command[check_mysql_longqueries]=/usr/lib/nagios/plugins/check_mysql_longqueries -u {{ nrpe_mysql_user }} -p {{ nrpe_mysql_password }} -H {{ nrpe_mysql_host }} -w 600 -c 1200
{% endif %} {% endif %}
{% if nrpe_postgresql is defined %} {% if nrpe_postgresql is defined %}