1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
|
#!/bin/sh
# ########################################################################
# This program is part of $PROJECT_NAME$
# License: GPL License (see COPYING)
# Authors:
# Baron Schwartz, Roman Vynar
# ########################################################################
# ########################################################################
# Redirect STDERR to STDOUT; Nagios doesn't handle STDERR.
# ########################################################################
exec 2>&1
# ########################################################################
# Set up constants, etc.
# ########################################################################
STATE_OK=0
STATE_WARNING=1
STATE_CRITICAL=2
STATE_UNKNOWN=3
STATE_DEPENDENT=4
# ########################################################################
# Run the program.
# ########################################################################
main() {
# Get options
for o; do
case "${o}" in
-c) shift; OPT_CRIT="${1}"; shift; ;;
--defaults-file) shift; OPT_DEFT="${1}"; shift; ;;
-C) shift; OPT_COMP="${1}"; shift; ;;
-H) shift; OPT_HOST="${1}"; shift; ;;
-I) shift; OPT_INCR="${1}"; shift; ;;
-l) shift; OPT_USER="${1}"; shift; ;;
-L) shift; OPT_LOPA="${1}"; shift; ;;
-o) shift; OPT_OPER="${1}"; shift; ;;
-p) shift; OPT_PASS="${1}"; shift; ;;
-P) shift; OPT_PORT="${1}"; shift; ;;
-S) shift; OPT_SOCK="${1}"; shift; ;;
-T) shift; OPT_TRAN="${1}"; shift; ;;
-w) shift; OPT_WARN="${1}"; shift; ;;
-x) shift; OPT_VAR1="${1}"; shift; ;;
-y) shift; OPT_VAR2="${1}"; shift; ;;
--version) grep -A2 '^=head1 VERSION' "$0" | tail -n1; exit 0 ;;
--help) perl -00 -ne 'm/^ Usage:/ && print' "$0"; exit 0 ;;
-*) echo "Unknown option ${o}. Try --help."; exit 1; ;;
esac
done
# Set default option values
OPT_COMP="${OPT_COMP:->=}"
if [ -e '/etc/nagios/mysql.cnf' ]; then
OPT_DEFT="${OPT_DEFT:-/etc/nagios/mysql.cnf}"
fi
if is_not_sourced; then
if [ -n "$1" ]; then
echo "WARN spurious command-line options: $ _æ_ "
exit 1
fi
fi
# Validate the options.
OPT_ERR=""
if [ -z "${OPT_CRIT}${OPT_WARN}" ]; then
OPT_ERR="you must specify either -c or -w"
elif [ -z "${OPT_VAR1}" ]; then
OPT_ERR="you must specify -x"
elif [ "${OPT_OPER}" -a -z "${OPT_VAR2}" ]; then
OPT_ERR="you specified -o but not -y"
elif [ "${OPT_VAR2}" -a -z "${OPT_OPER}" ]; then
OPT_ERR="you specified -y but not -o"
elif [ "${OPT_TRAN}" = 'pct' -a -z "${OPT_VAR2}" ]; then
OPT_ERR="you specified -T pct but not -y"
elif [ "${OPT_TRAN}" ]; then
case "${OPT_TRAN}" in
pct|str)
;;
*)
OPT_ERR="-T must be one of: pct str"
;;
esac
fi
case "${OPT_COMP}" in
'=='|'!='|'>='|'>'|'<'|'<=')
;;
*)
OPT_ERR="-C must be one of: == != >= > < <="
;;
esac
if [ "${OPT_OPER}" ]; then
case "${OPT_OPER}" in
/|'*'|+|-)
:
;;
*)
OPT_ERR="-o must be one of: / * + -"
;;
esac
fi
if [ "${OPT_ERR}" ]; then
echo "Error: $OPT_ERR. Try --help."
exit 1
fi
NOTE="UNK could not evaluate the expression."
# Set up a temporary file
local TEMP1=$(mktemp -t "${0##*/}.XXXXXX") || exit $?
local TEMP2=$(mktemp -t "${0##*/}.XXXXXX") || exit $?
trap "rm -f '${TEMP1}' '${TEMP2}' >/dev/null 2>&1" EXIT
if get_status_variables "${TEMP1}" "${TEMP2}" "${OPT_INCR}"; then
LEVEL=$(compute_result "${TEMP1}" "${OPT_VAR1}" "${OPT_OPER}" "${OPT_VAR2}" "${OPT_TRAN}")
if [ $? = 0 -a -n "${LEVEL}" ]; then
NOTE="${OPT_VAR1}${OPT_OPER:+ ${OPT_OPER}}${OPT_VAR2:+ ${OPT_VAR2}}${OPT_TRAN:+ (${OPT_TRAN})}"
NOTE="${NOTE} = ${LEVEL}"
# XXX Make sure this line and the "case" don't get separated.
compare_result "${LEVEL}" "${OPT_CRIT}" "${OPT_WARN}" "${OPT_COMP}" "${OPT_TRAN}"
case $? in
$STATE_OK)
NOTE="OK $NOTE"
;;
$STATE_CRITICAL)
NOTE="CRIT $NOTE"
;;
$STATE_WARNING)
NOTE="WARN $NOTE"
;;
esac
# Build the common perf data output for graph trending
if [ "${OPT_TRAN}" = 'pct' ]; then
PERFDATA_MAX=100
fi
PERFDATA="${OPT_VAR1}${OPT_OPER}${OPT_VAR2}=${LEVEL};${OPT_WARN};${OPT_CRIT};0;${PERFDATA_MAX}"
NOTE="$NOTE | $PERFDATA"
fi
else
NOTE="UNK could not get MySQL status/variables."
fi
echo $NOTE
}
# ########################################################################
# Execute a MySQL command.
# ########################################################################
mysql_exec() {
mysql ${OPT_DEFT:+--defaults-file="${OPT_DEFT}"} \
${OPT_LOPA:+--login-path="${OPT_LOPA}"} \
${OPT_HOST:+-h"${OPT_HOST}"} ${OPT_PORT:+-P"${OPT_PORT}"} \
${OPT_USER:+-u"${OPT_USER}"} ${OPT_PASS:+-p"${OPT_PASS}"} \
${OPT_SOCK:+-S"${OPT_SOCK}"} -ss -e "$1"
}
# ########################################################################
# Compares the variable to the thresholds. Arguments: VAR CRIT WARN CMP TRAN
# Returns nothing; exits with OK/WARN/CRIT.
# ########################################################################
compare_result() {
local VAR="${1}"
local CRIT="${2}"
local WARN="${3}"
local CMP="${4}"
local TRAN="${5}"
echo 1 | awk "END {
if ( \"${CRIT}\" != \"\" ) {
if ( \"${TRAN}\" == \"str\" ) {
if ( \"${VAR}\" ${CMP} \"${CRIT:-0}\" ) {
exit $STATE_CRITICAL
}
} else {
if ( ${VAR} ${CMP} ${CRIT:-0} ) {
exit $STATE_CRITICAL
}
}
}
if ( \"${WARN}\" != \"\" ) {
if ( \"${TRAN}\" == \"str\" ) {
if ( \"${VAR}\" ${CMP} \"${WARN:-0}\" ) {
exit $STATE_WARNING
}
} else {
if ( ${VAR} ${CMP} ${WARN:-0} ) {
exit $STATE_WARNING
}
}
}
exit $STATE_OK
}"
}
# ########################################################################
# Computes an expression against the file of variables. Returns a float.
# Arguments: TEMP VAR1 OPER VAR2 TRAN
# ########################################################################
compute_result() {
local TEMP="$1"
local VAR1="$2"
local OPER="$3"
local VAR2="$4"
local TRAN="$5"
if [ "${VAR2}" ]; then
# Extract two variables, apply an operator, and possibly apply a
# transform.
awk -F'\t' "
BEGIN {
got1 = \"Could not find variable ${VAR1}\";
got2 = \"Could not find variable ${VAR2}\";
}
\$1 == \"${VAR1}\" {
var1 = \$2;
got1 = \"\";
}
\$1 == \"${VAR2}\" {
var2 = \$2;
got2 = \"\";
}
END {
if ( got1 == \"\" && got2 == \"\" ) {
if ( var2 == 0 && \"${OPER}\" == \"/\" ) {
# Divide-by-zero; make the result simply 0
val = 0;
}
else {
val = var1 ${OPER} var2;
}
if ( \"${TRAN}\" == \"pct\" ) {
val = val * 100;
}
if ( val ~ /\.[0-9]/ ) {
printf \"%.6f\\n\", val;
}
else {
print val;
}
}
else {
print got1, got2 | \"cat 1>&2\";
exit 1;
}
}
" "${TEMP}"
else
# This is the simplest case. We're just extracting a single variable and
# returning it.
awk -F'\t' -v var1="${VAR1}" '
BEGIN {
got = 0;
}
$1 == var1 {
val = $2;
got = 1;
}
END {
if ( got == 1 ) {
print val;
}
else {
print "Unknown variable", var1 | "cat 1>&2";
exit 1;
}
}
' "${TEMP}"
fi
}
# ########################################################################
# Gets status variables. The first argument is the file to store the results.
# Optional second argument is another temp file.
# Optional third argument makes SHOW STATUS incremental/relative, and has the
# added effect of filtering out non-numeric variables.
# ########################################################################
get_status_variables() {
if [ "$3" ]; then
mysql_exec "SHOW /*!50000 GLOBAL*/ STATUS" > "${2}" || exit 1
sleep "$3"
# Technically we ought to use another temp file, and check the return
# status of this mysql_exec, but if the first one worked it's likely that
# this one will too.
mysql_exec "SHOW /*!50000 GLOBAL*/ STATUS" | cat "${2}" - \
| awk -F'\t' '
/Aborted_clients/ { seen++; }
{
if ( seen > 1 && $2 !~ /[^0-9.]/ ) {
print $1 "\t" $2 - var[$1];
}
else {
var[$1] = $2;
}
}
' > "${1}"
else
mysql_exec "SHOW /*!50000 GLOBAL*/ STATUS" > "${1}" || exit 1
fi
mysql_exec "SHOW /*!40101 GLOBAL*/ VARIABLES" >> "${1}"
}
# ########################################################################
# Determine whether this program is being executed directly, or sourced/included
# from another file.
# ########################################################################
is_not_sourced() {
[ "${0##*/}" = "pmp-check-mysql-status" ] || [ "${0##*/}" = "bash" -a "$_" = "$0" ]
}
# ########################################################################
# Execute the program if it was not included from another file.
# This makes it possible to include without executing, and thus test.
# ########################################################################
if is_not_sourced; then
OUTPUT=$(main "$ _æ_ ")
EXITSTATUS=$STATE_UNKNOWN
case "${OUTPUT}" in
UNK*) EXITSTATUS=$STATE_UNKNOWN; ;;
OK*) EXITSTATUS=$STATE_OK; ;;
WARN*) EXITSTATUS=$STATE_WARNING; ;;
CRIT*) EXITSTATUS=$STATE_CRITICAL; ;;
esac
echo "${OUTPUT}"
exit $EXITSTATUS
fi
# ############################################################################
# Documentation
# ############################################################################
: <<'DOCUMENTATION'
=pod
=head1 NAME
pmp-check-mysql-status - Check MySQL SHOW GLOBAL STATUS output.
=head1 SYNOPSIS
Usage: pmp-check-mysql-status [OPTIONS]
Options:
-c CRIT Critical threshold.
--defaults-file FILE Only read mysql options from the given file.
Defaults to /etc/nagios/mysql.cnf if it exists.
-C COMPARE Comparison operator to apply to -c and -w.
Possible values: == != >= > < <=. Default >=.
-H HOST MySQL hostname.
-I INCR Make SHOW STATUS incremental over this delay.
-l USER MySQL username.
-L LOGIN-PATH Use login-path to access MySQL (with MySQL client 5.6).
-o OPERATOR The operator to apply to -x and -y.
-p PASS MySQL password.
-P PORT MySQL port.
-S SOCKET MySQL socket file.
-T TRANS Transformation to apply before comparing to -c and -w.
Possible values: pct str.
-w WARN Warning threshold.
-x VAR1 Required first status or configuration variable.
-y VAR2 Optional second status or configuration variable.
--help Print help and exit.
--version Print version and exit.
Options must be given as --option value, not --option=value or -Ovalue.
Use perldoc to read embedded documentation with more details.
=head1 DESCRIPTION
This Nagios plugin captures SHOW GLOBAL STATUS and SHOW GLOBAL VARIABLES from
MySQL and evaluates expressions against them. The general syntax is as follows:
VAR1 [ OPERATOR VAR2 [ TRANSFORM ] ]
The result of evaluating this is compared against the -w and -c options as usual
to determine whether to raise a warning or critical alert.
Note that all of the examples provided below are simply for illustrative
purposes and are not supposed to be recommendations for what to monitor. You
should get advice from a professional if you are not sure what you should be
monitoring.
For our first example, we will raise a warning if Threads_running is 20 or over,
and a critical alert if it is 40 or over:
-x Threads_running -w 20 -c 40
The threshold is implemented as greater-or-equals by default, not strictly
greater-than, so a value of 20 is a warning and a value of 40 is critical. You
can switch this to less-or-equals or other operators with the -C option, which
accepts the arithmetic comparison operators ==, !=, >, >=, <, and <=.
You can use any variable that is present in SHOW VARIABLES or SHOW STATUS. If
the variable is not found, there is an error. To warn if Threads_connected
exceeds 80% of max_connections:
-x Threads_connected -o / -y max_connections -T pct -w 80
The -T C option only works when you specify both -x and -y and implements
percentage transformation. The plugin uses awk to do its
computations and comparisons, so you can use floating-point math; you are not
restricted to integers for comparisons. Floating-point numbers are printed with
six digits of precision. The -o option accepts the arithmetic operators /, *,
+, and -. A division by zero results in zero, not an error.
If you specify the -I option with an integer argument, the SHOW STATUS values
become incremental instead of absolute. The argument is used as a delay in
seconds, and instead of capturing a single sample of SHOW STATUS and using it
for computations, the plugin captures two samples at the specified interval and
subtracts the second from the first. This lets you evaluate expressions over a
range of time. For example, to warn when there are 10 disk-based temporary
tables per second, over a 5-second sampling period:
-x Created_tmp_disk_tables -o / -y Uptime -I 5 -w 10
That is somewhat contrived, because it could also be written as follows:
-x Created_tmp_disk_tables -I 5 -w 50
The -I option has the side effect of removing any non-numeric SHOW STATUS
variables. Be careful not to set the -I option too large, or Nagios will simply
time the plugin out, usually after about 10 seconds.
This plugin does not support arbitrarily complex expressions, such as computing
the query cache hit ratio and alerting if it is less than some percentage. If
you are trying to do that, you might be doing it wrong. A dubious example for
the query cache might be to alert if the hit-to-insert ratio falls below 2:1, as
follows:
-x Qcache_hits -o / -y Qcache_inserts -C '<' -w 2
Some people might suggest that the following is a more useful alert for the
query cache:
-x query_cache_size -c 1
To check Percona XtraDB Cluster node status you may want to use the following
alert similar to what its clustercheck does:
-x wsrep_local_state -C '!=' -w 4
To compare string variables use -T C transformation. This is required as
numeric and string comparisons are handled differently. The following example
warns when the slave_exec_mode is IDEMPOTENT:
-x slave_exec_mode -C '==' -T str -w IDEMPOTENT
=head1 PRIVILEGES
This plugin executes the following commands against MySQL:
=over
=item *
C.
=item *
C.
=back
This plugin executes no UNIX commands that may need special privileges.
=head1 COPYRIGHT, LICENSE, AND WARRANTY
This program is copyright 2012-$CURRENT_YEAR$ Baron Schwartz, 2012-$CURRENT_YEAR$ Percona Inc.
Feedback and improvements are welcome.
THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
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, version 2. 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
=head1 VERSION
$PROJECT_NAME$ pmp-check-mysql-status $VERSION$
=cut
DOCUMENTATION
|