1 /*****************************************************************************
3 * Monitoring check_dbi plugin
6 * Copyright (c) 2011 Monitoring Plugins Development Team
7 * Author: Sebastian 'tokkee' Harl <sh@teamix.net>
11 * This file contains the check_dbi plugin
13 * Runs an arbitrary (SQL) command and checks the result.
16 * This program is free software: you can redistribute it and/or modify
17 * it under the terms of the GNU General Public License as published by
18 * the Free Software Foundation, either version 3 of the License, or
19 * (at your option) any later version.
21 * This program is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
26 * You should have received a copy of the GNU General Public License
27 * along with this program. If not, see <http://www.gnu.org/licenses/>.
30 *****************************************************************************/
32 const char *progname
= "check_dbi";
33 const char *copyright
= "2011";
34 const char *email
= "devel@monitoring-plugins.org";
43 /* required for NAN */
44 #ifndef _ISOC99_SOURCE
45 #define _ISOC99_SOURCE
57 METRIC_SERVER_VERSION
,
75 char *warning_range
= NULL
;
76 char *critical_range
= NULL
;
77 thresholds
*dbi_thresholds
= NULL
;
82 char *expect_re_str
= NULL
;
83 int expect_re_cflags
= 0;
85 np_dbi_metric_t metric
= METRIC_QUERY_RESULT
;
86 np_dbi_type_t type
= TYPE_NUMERIC
;
88 char *np_dbi_driver
= NULL
;
89 driver_option_t
*np_dbi_options
= NULL
;
90 int np_dbi_options_num
= 0;
91 char *np_dbi_database
= NULL
;
92 char *np_dbi_query
= NULL
;
94 int process_arguments (int, char **);
95 int validate_arguments (void);
96 void print_usage (void);
97 void print_help (void);
99 double timediff (struct timeval
, struct timeval
);
101 void np_dbi_print_error (dbi_conn
, char *, ...);
103 int do_query (dbi_conn
, const char **, double *, double *);
106 main (int argc
, char **argv
)
108 int status
= STATE_UNKNOWN
;
113 unsigned int server_version
;
115 struct timeval start_timeval
, end_timeval
;
116 double conn_time
= 0.0;
117 double query_time
= 0.0;
119 const char *query_val_str
= NULL
;
120 double query_val
= 0.0;
124 setlocale (LC_ALL
, "");
125 bindtextdomain (PACKAGE
, LOCALEDIR
);
126 textdomain (PACKAGE
);
128 /* Parse extra opts if any */
129 argv
= np_extra_opts (&argc
, argv
, progname
);
131 if (process_arguments (argc
, argv
) == ERROR
)
132 usage4 (_("Could not parse arguments"));
134 /* Set signal handling and alarm */
135 if (signal (SIGALRM
, timeout_alarm_handler
) == SIG_ERR
) {
136 usage4 (_("Cannot catch SIGALRM"));
138 alarm (timeout_interval
);
141 printf ("Initializing DBI\n");
143 if (dbi_initialize (NULL
) < 0) {
144 printf ("UNKNOWN - failed to initialize DBI; possibly you don't have any drivers installed.\n");
145 return STATE_UNKNOWN
;
149 printf ("Opening DBI driver '%s'\n", np_dbi_driver
);
151 driver
= dbi_driver_open (np_dbi_driver
);
153 printf ("UNKNOWN - failed to open DBI driver '%s'; possibly it's not installed.\n",
156 printf ("Known drivers:\n");
157 for (driver
= dbi_driver_list (NULL
); driver
; driver
= dbi_driver_list (driver
)) {
158 printf (" - %s\n", dbi_driver_get_name (driver
));
160 return STATE_UNKNOWN
;
163 /* make a connection to the database */
164 gettimeofday (&start_timeval
, NULL
);
166 conn
= dbi_conn_open (driver
);
168 printf ("UNKNOWN - failed top open connection object.\n");
169 dbi_conn_close (conn
);
170 return STATE_UNKNOWN
;
173 for (i
= 0; i
< np_dbi_options_num
; ++i
) {
177 printf ("Setting DBI driver option '%s' to '%s'\n",
178 np_dbi_options
[i
].key
, np_dbi_options
[i
].value
);
180 if (! dbi_conn_set_option (conn
, np_dbi_options
[i
].key
, np_dbi_options
[i
].value
))
182 /* else: status != 0 */
184 np_dbi_print_error (conn
, "UNKNOWN - failed to set option '%s' to '%s'",
185 np_dbi_options
[i
].key
, np_dbi_options
[i
].value
);
186 printf ("Known driver options:\n");
188 for (opt
= dbi_conn_get_option_list (conn
, NULL
); opt
;
189 opt
= dbi_conn_get_option_list (conn
, opt
)) {
190 printf (" - %s\n", opt
);
192 dbi_conn_close (conn
);
193 return STATE_UNKNOWN
;
198 printf ("Setting DBI driver option 'host' to '%s'\n", host
);
199 dbi_conn_set_option (conn
, "host", host
);
203 const char *dbname
, *host
;
205 dbname
= dbi_conn_get_option (conn
, "dbname");
206 host
= dbi_conn_get_option (conn
, "host");
209 dbname
= "<unspecified>";
211 host
= "<unspecified>";
213 printf ("Connecting to database '%s' at host '%s'\n",
217 if (dbi_conn_connect (conn
) < 0) {
218 np_dbi_print_error (conn
, "UNKNOWN - failed to connect to database");
219 return STATE_UNKNOWN
;
222 gettimeofday (&end_timeval
, NULL
);
223 conn_time
= timediff (start_timeval
, end_timeval
);
225 server_version
= dbi_conn_get_engine_version (conn
);
227 printf ("Connected to server version %u\n", server_version
);
229 if (metric
== METRIC_SERVER_VERSION
)
230 status
= get_status (server_version
, dbi_thresholds
);
233 printf ("Time elapsed: %f\n", conn_time
);
235 if (metric
== METRIC_CONN_TIME
)
236 status
= get_status (conn_time
, dbi_thresholds
);
238 /* select a database */
239 if (np_dbi_database
) {
241 printf ("Selecting database '%s'\n", np_dbi_database
);
243 if (dbi_conn_select_db (conn
, np_dbi_database
)) {
244 np_dbi_print_error (conn
, "UNKNOWN - failed to select database '%s'",
246 return STATE_UNKNOWN
;
252 status
= do_query (conn
, &query_val_str
, &query_val
, &query_time
);
253 if (status
!= STATE_OK
)
254 /* do_query prints an error message in this case */
257 if (metric
== METRIC_QUERY_RESULT
) {
259 if ((! query_val_str
) || strcmp (query_val_str
, expect
))
260 status
= STATE_CRITICAL
;
264 else if (expect_re_str
) {
267 err
= regexec (&expect_re
, query_val_str
, 0, NULL
, /* flags = */ 0);
270 else if (err
== REG_NOMATCH
)
271 status
= STATE_CRITICAL
;
274 regerror (err
, &expect_re
, errmsg
, sizeof (errmsg
));
275 printf ("ERROR - failed to execute regular expression: %s\n",
277 status
= STATE_CRITICAL
;
281 status
= get_status (query_val
, dbi_thresholds
);
283 else if (metric
== METRIC_QUERY_TIME
)
284 status
= get_status (query_time
, dbi_thresholds
);
288 printf("Closing connection\n");
289 dbi_conn_close (conn
);
291 /* In case of METRIC_QUERY_RESULT, isnan(query_val) indicates an error
292 * which should have been reported and handled (abort) before
293 * ... unless we expected a string to be returned */
294 assert ((metric
!= METRIC_QUERY_RESULT
) || (! isnan (query_val
))
295 || (type
== TYPE_STRING
));
297 assert ((type
!= TYPE_STRING
) || (expect
|| expect_re_str
));
299 printf ("%s - connection time: %fs", state_text (status
), conn_time
);
301 if (type
== TYPE_STRING
) {
302 assert (expect
|| expect_re_str
);
303 printf (", '%s' returned '%s' in %fs", np_dbi_query
,
304 query_val_str
? query_val_str
: "<nothing>", query_time
);
305 if (status
!= STATE_OK
) {
307 printf (" (expected '%s')", expect
);
308 else if (expect_re_str
)
309 printf (" (expected regex /%s/%s)", expect_re_str
,
310 ((expect_re_cflags
& REG_ICASE
) ? "i" : ""));
313 else if (isnan (query_val
))
314 printf (", '%s' query execution time: %fs", np_dbi_query
, query_time
);
316 printf (", '%s' returned %f in %fs", np_dbi_query
, query_val
, query_time
);
319 printf (" | conntime=%fs;%s;%s;0; server_version=%u;%s;%s;0;", conn_time
,
320 ((metric
== METRIC_CONN_TIME
) && warning_range
) ? warning_range
: "",
321 ((metric
== METRIC_CONN_TIME
) && critical_range
) ? critical_range
: "",
323 ((metric
== METRIC_SERVER_VERSION
) && warning_range
) ? warning_range
: "",
324 ((metric
== METRIC_SERVER_VERSION
) && critical_range
) ? critical_range
: "");
326 if (! isnan (query_val
)) /* this is also true when -e is used */
327 printf (" query=%f;%s;%s;;", query_val
,
328 ((metric
== METRIC_QUERY_RESULT
) && warning_range
) ? warning_range
: "",
329 ((metric
== METRIC_QUERY_RESULT
) && critical_range
) ? critical_range
: "");
330 printf (" querytime=%fs;%s;%s;0;", query_time
,
331 ((metric
== METRIC_QUERY_TIME
) && warning_range
) ? warning_range
: "",
332 ((metric
== METRIC_QUERY_TIME
) && critical_range
) ? critical_range
: "");
338 /* process command-line arguments */
340 process_arguments (int argc
, char **argv
)
345 static struct option longopts
[] = {
348 {"expect", required_argument
, 0, 'e'},
349 {"regex", required_argument
, 0, 'r'},
350 {"regexi", required_argument
, 0, 'R'},
351 {"metric", required_argument
, 0, 'm'},
352 {"driver", required_argument
, 0, 'd'},
353 {"option", required_argument
, 0, 'o'},
354 {"query", required_argument
, 0, 'q'},
355 {"database", required_argument
, 0, 'D'},
360 c
= getopt_long (argc
, argv
, "Vvht:c:w:e:r:R:m:H:d:o:q:D:",
367 case '?': /* usage */
371 exit (STATE_UNKNOWN
);
372 case 'V': /* version */
373 print_revision (progname
, NP_VERSION
);
374 exit (STATE_UNKNOWN
);
376 case 'c': /* critical range */
377 critical_range
= optarg
;
380 case 'w': /* warning range */
381 warning_range
= optarg
;
389 expect_re_cflags
= REG_ICASE
;
395 expect_re_cflags
|= REG_EXTENDED
| REG_NOSUB
| REG_NEWLINE
;
396 expect_re_str
= optarg
;
399 err
= regcomp (&expect_re
, expect_re_str
, expect_re_cflags
);
402 regerror (err
, &expect_re
, errmsg
, sizeof (errmsg
));
403 printf ("ERROR - failed to compile regular expression: %s\n",
411 if (! strcasecmp (optarg
, "CONN_TIME"))
412 metric
= METRIC_CONN_TIME
;
413 else if (! strcasecmp (optarg
, "SERVER_VERSION"))
414 metric
= METRIC_SERVER_VERSION
;
415 else if (! strcasecmp (optarg
, "QUERY_RESULT"))
416 metric
= METRIC_QUERY_RESULT
;
417 else if (! strcasecmp (optarg
, "QUERY_TIME"))
418 metric
= METRIC_QUERY_TIME
;
420 usage2 (_("Invalid metric"), optarg
);
422 case 't': /* timeout */
423 if (!is_intnonneg (optarg
))
424 usage2 (_("Timeout interval must be a positive integer"), optarg
);
426 timeout_interval
= atoi (optarg
);
429 if (!is_host (optarg
))
430 usage2 (_("Invalid hostname/address"), optarg
);
439 np_dbi_driver
= optarg
;
443 driver_option_t
*new;
448 v
= strchr (k
, (int)'=');
451 usage2 (_("Option must be '<key>=<value>'"), optarg
);
456 new = realloc (np_dbi_options
,
457 (np_dbi_options_num
+ 1) * sizeof (*new));
459 printf ("UNKNOWN - failed to reallocate memory\n");
460 exit (STATE_UNKNOWN
);
463 np_dbi_options
= new;
464 new = np_dbi_options
+ np_dbi_options_num
;
465 ++np_dbi_options_num
;
472 np_dbi_query
= optarg
;
475 np_dbi_database
= optarg
;
480 set_thresholds (&dbi_thresholds
, warning_range
, critical_range
);
482 return validate_arguments ();
486 validate_arguments ()
489 usage ("Must specify a DBI driver");
491 if (((metric
== METRIC_QUERY_RESULT
) || (metric
== METRIC_QUERY_TIME
))
493 usage ("Must specify a query to execute (metric == QUERY_RESULT)");
495 if ((metric
!= METRIC_CONN_TIME
)
496 && (metric
!= METRIC_SERVER_VERSION
)
497 && (metric
!= METRIC_QUERY_RESULT
)
498 && (metric
!= METRIC_QUERY_TIME
))
499 usage ("Invalid metric specified");
501 if (expect
&& (warning_range
|| critical_range
|| expect_re_str
))
502 usage ("Do not mix -e and -w/-c/-r/-R");
504 if (expect_re_str
&& (warning_range
|| critical_range
|| expect
))
505 usage ("Do not mix -r/-R and -w/-c/-e");
507 if (expect
&& (metric
!= METRIC_QUERY_RESULT
))
508 usage ("Option -e requires metric QUERY_RESULT");
510 if (expect_re_str
&& (metric
!= METRIC_QUERY_RESULT
))
511 usage ("Options -r/-R require metric QUERY_RESULT");
519 print_revision (progname
, NP_VERSION
);
521 printf (COPYRIGHT
, copyright
, email
);
523 printf (_("This program connects to an (SQL) database using DBI and checks the\n"
524 "specified metric against threshold levels. The default metric is\n"
525 "the result of the specified query.\n"));
531 printf (UT_HELP_VRSN
);
532 /* include this conditionally to avoid 'zero-length printf format string'
533 * compiler warnings */
535 printf (UT_EXTRA_OPTS
);
539 printf (" %s\n", "-d, --driver=STRING");
540 printf (" %s\n", _("DBI driver to use"));
541 printf (" %s\n", "-o, --option=STRING");
542 printf (" %s\n", _("DBI driver options"));
543 printf (" %s\n", "-q, --query=STRING");
544 printf (" %s\n", _("query to execute"));
547 printf (UT_WARN_CRIT_RANGE
);
548 printf (" %s\n", "-e, --expect=STRING");
549 printf (" %s\n", _("String to expect as query result"));
550 printf (" %s\n", _("Do not mix with -w, -c, -r, or -R!"));
551 printf (" %s\n", "-r, --regex=REGEX");
552 printf (" %s\n", _("Extended POSIX regular expression to check query result against"));
553 printf (" %s\n", _("Do not mix with -w, -c, -e, or -R!"));
554 printf (" %s\n", "-R, --regexi=REGEX");
555 printf (" %s\n", _("Case-insensitive extended POSIX regex to check query result against"));
556 printf (" %s\n", _("Do not mix with -w, -c, -e, or -r!"));
557 printf (" %s\n", "-m, --metric=METRIC");
558 printf (" %s\n", _("Metric to check thresholds against. Available metrics:"));
559 printf (" CONN_TIME - %s\n", _("time used for setting up the database connection"));
560 printf (" QUERY_RESULT - %s\n", _("result (first column of first row) of the query"));
561 printf (" QUERY_TIME - %s\n", _("time used to execute the query"));
562 printf (" %s\n", _("(ignore the query result)"));
565 printf (UT_CONN_TIMEOUT
, DEFAULT_SOCKET_TIMEOUT
);
570 printf (" %s\n", _("A DBI driver (-d option) is required. If the specified metric operates"));
571 printf (" %s\n\n", _("on a query, one has to be specified (-q option)."));
573 printf (" %s\n", _("This plugin connects to an (SQL) database using libdbi and, optionally,"));
574 printf (" %s\n", _("executes the specified query. The first column of the first row of the"));
575 printf (" %s\n", _("result will be parsed and, in QUERY_RESULT mode, compared with the"));
576 printf (" %s\n", _("warning and critical ranges. The result from the query has to be numeric"));
577 printf (" %s\n\n", _("(strings representing numbers are fine)."));
579 printf (" %s\n", _("The number and type of required DBI driver options depends on the actual"));
580 printf (" %s\n", _("driver. See its documentation at http://libdbi-drivers.sourceforge.net/"));
581 printf (" %s\n\n", _("for details."));
583 printf (" %s\n", _("Examples:"));
584 printf (" check_dbi -d pgsql -o username=postgres -m QUERY_RESULT \\\n");
585 printf (" -q 'SELECT COUNT(*) FROM pg_stat_activity' -w 5 -c 10\n");
586 printf (" Warning if more than five connections; critical if more than ten.\n\n");
588 printf (" check_dbi -d mysql -H localhost -o username=user -o password=secret \\\n");
589 printf (" -q 'SELECT COUNT(*) FROM logged_in_users -w 5:20 -c 0:50\n");
590 printf (" Warning if less than 5 or more than 20 users are logged in; critical\n");
591 printf (" if more than 50 users.\n\n");
593 printf (" check_dbi -d firebird -o username=user -o password=secret -o dbname=foo \\\n");
594 printf (" -m CONN_TIME -w 0.5 -c 2\n");
595 printf (" Warning if connecting to the database takes more than half of a second;\n");
596 printf (" critical if it takes more than 2 seconds.\n\n");
598 printf (" check_dbi -d mysql -H localhost -o username=user \\\n");
599 printf (" -q 'SELECT concat(@@version, \" \", @@version_comment)' \\\n");
600 printf (" -r '^5\\.[01].*MySQL Enterprise Server'\n");
601 printf (" Critical if the database server is not a MySQL enterprise server in either\n");
602 printf (" version 5.0.x or 5.1.x.\n\n");
604 printf (" check_dbi -d pgsql -u username=user -m SERVER_VERSION \\\n");
605 printf (" -w 090000:090099 -c 090000:090199\n");
606 printf (" Warn if the PostgreSQL server version is not 9.0.x; critical if the version\n");
607 printf (" is less than 9.x or higher than 9.1.x.\n");
615 printf ("%s\n", _("Usage:"));
616 printf ("%s -d <DBI driver> [-o <DBI driver option> [...]] [-q <query>]\n", progname
);
617 printf (" [-H <host>] [-c <critical range>] [-w <warning range>] [-m <metric>]\n");
618 printf (" [-e <string>] [-r|-R <regex>]\n");
621 #define CHECK_IGNORE_ERROR(s) \
623 if (metric != METRIC_QUERY_RESULT) \
628 get_field_str (dbi_conn conn
, dbi_result res
, unsigned short field_type
)
632 if (field_type
!= DBI_TYPE_STRING
) {
633 printf ("CRITICAL - result value is not a string\n");
637 str
= dbi_result_get_string_idx (res
, 1);
638 if ((! str
) || (strcmp (str
, "ERROR") == 0)) {
639 CHECK_IGNORE_ERROR (NULL
);
640 np_dbi_print_error (conn
, "CRITICAL - failed to fetch string value");
644 if ((verbose
&& (type
== TYPE_STRING
)) || (verbose
> 2))
645 printf ("Query returned string '%s'\n", str
);
650 get_field (dbi_conn conn
, dbi_result res
, unsigned short *field_type
)
654 if (*field_type
== DBI_TYPE_INTEGER
) {
655 val
= (double)dbi_result_get_longlong_idx (res
, 1);
657 else if (*field_type
== DBI_TYPE_DECIMAL
) {
658 val
= dbi_result_get_double_idx (res
, 1);
660 else if (*field_type
== DBI_TYPE_STRING
) {
664 val_str
= get_field_str (conn
, res
, *field_type
);
666 CHECK_IGNORE_ERROR (NAN
);
667 *field_type
= DBI_TYPE_ERROR
;
671 val
= strtod (val_str
, &endptr
);
672 if (endptr
== val_str
) {
673 CHECK_IGNORE_ERROR (NAN
);
674 printf ("CRITICAL - result value is not a numeric: %s\n", val_str
);
675 *field_type
= DBI_TYPE_ERROR
;
678 else if ((endptr
!= NULL
) && (*endptr
!= '\0')) {
680 printf ("Garbage after value: %s\n", endptr
);
684 CHECK_IGNORE_ERROR (NAN
);
685 printf ("CRITICAL - cannot parse value of type %s (%i)\n",
686 (*field_type
== DBI_TYPE_BINARY
)
688 : (*field_type
== DBI_TYPE_DATETIME
)
692 *field_type
= DBI_TYPE_ERROR
;
699 get_query_result (dbi_conn conn
, dbi_result res
, const char **res_val_str
, double *res_val
)
701 unsigned short field_type
;
704 if (dbi_result_get_numrows (res
) == DBI_ROW_ERROR
) {
705 CHECK_IGNORE_ERROR (STATE_OK
);
706 np_dbi_print_error (conn
, "CRITICAL - failed to fetch rows");
707 return STATE_CRITICAL
;
710 if (dbi_result_get_numrows (res
) < 1) {
711 CHECK_IGNORE_ERROR (STATE_OK
);
712 printf ("WARNING - no rows returned\n");
713 return STATE_WARNING
;
716 if (dbi_result_get_numfields (res
) == DBI_FIELD_ERROR
) {
717 CHECK_IGNORE_ERROR (STATE_OK
);
718 np_dbi_print_error (conn
, "CRITICAL - failed to fetch fields");
719 return STATE_CRITICAL
;
722 if (dbi_result_get_numfields (res
) < 1) {
723 CHECK_IGNORE_ERROR (STATE_OK
);
724 printf ("WARNING - no fields returned\n");
725 return STATE_WARNING
;
728 if (dbi_result_first_row (res
) != 1) {
729 CHECK_IGNORE_ERROR (STATE_OK
);
730 np_dbi_print_error (conn
, "CRITICAL - failed to fetch first row");
731 return STATE_CRITICAL
;
734 field_type
= dbi_result_get_field_type_idx (res
, 1);
735 if (field_type
!= DBI_TYPE_ERROR
) {
736 if (type
== TYPE_STRING
)
737 /* the value will be freed in dbi_result_free */
738 *res_val_str
= strdup (get_field_str (conn
, res
, field_type
));
740 val
= get_field (conn
, res
, &field_type
);
745 if (field_type
== DBI_TYPE_ERROR
) {
746 CHECK_IGNORE_ERROR (STATE_OK
);
747 np_dbi_print_error (conn
, "CRITICAL - failed to fetch data");
748 return STATE_CRITICAL
;
751 dbi_result_free (res
);
755 #undef CHECK_IGNORE_ERROR
758 do_query (dbi_conn conn
, const char **res_val_str
, double *res_val
, double *res_time
)
762 struct timeval timeval_start
, timeval_end
;
763 int status
= STATE_OK
;
765 assert (np_dbi_query
);
768 printf ("Executing query '%s'\n", np_dbi_query
);
770 gettimeofday (&timeval_start
, NULL
);
772 res
= dbi_conn_query (conn
, np_dbi_query
);
774 np_dbi_print_error (conn
, "CRITICAL - failed to execute query '%s'", np_dbi_query
);
775 return STATE_CRITICAL
;
778 status
= get_query_result (conn
, res
, res_val_str
, res_val
);
780 gettimeofday (&timeval_end
, NULL
);
781 *res_time
= timediff (timeval_start
, timeval_end
);
784 printf ("Time elapsed: %f\n", *res_time
);
790 timediff (struct timeval start
, struct timeval end
)
794 while (start
.tv_usec
> end
.tv_usec
) {
796 end
.tv_usec
+= 1000000;
798 diff
= (double)(end
.tv_sec
- start
.tv_sec
)
799 + (double)(end
.tv_usec
- start
.tv_usec
) / 1000000.0;
804 np_dbi_print_error (dbi_conn conn
, char *fmt
, ...)
806 const char *errmsg
= NULL
;
811 dbi_conn_error (conn
, &errmsg
);
813 printf (": %s\n", errmsg
);