check_dbi.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656
  1. /*****************************************************************************
  2. *
  3. * Nagios check_dbi plugin
  4. *
  5. * License: GPL
  6. * Copyright (c) 2011 Nagios Plugins Development Team
  7. * Author: Sebastian 'tokkee' Harl <sh@teamix.net>
  8. *
  9. * Description:
  10. *
  11. * This file contains the check_dbi plugin
  12. *
  13. * Runs an arbitrary (SQL) command and checks the result.
  14. *
  15. *
  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.
  20. *
  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.
  25. *
  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/>.
  28. *
  29. *
  30. *****************************************************************************/
  31. const char *progname = "check_dbi";
  32. const char *copyright = "2011";
  33. const char *email = "nagiosplug-devel@lists.sourceforge.net";
  34. #include "common.h"
  35. #include "utils.h"
  36. #include "netutils.h"
  37. /* required for NAN */
  38. #ifndef _ISOC99_SOURCE
  39. #define _ISOC99_SOURCE
  40. #endif
  41. #include <assert.h>
  42. #include <math.h>
  43. #include <dbi/dbi.h>
  44. #include <stdarg.h>
  45. typedef enum {
  46. METRIC_CONN_TIME,
  47. METRIC_QUERY_RESULT,
  48. METRIC_QUERY_TIME,
  49. } np_dbi_metric_t;
  50. typedef struct {
  51. char *key;
  52. char *value;
  53. } driver_option_t;
  54. char *host = NULL;
  55. int verbose = 0;
  56. char *warning_range = NULL;
  57. char *critical_range = NULL;
  58. thresholds *dbi_thresholds = NULL;
  59. np_dbi_metric_t metric = METRIC_QUERY_RESULT;
  60. char *np_dbi_driver = NULL;
  61. driver_option_t *np_dbi_options = NULL;
  62. int np_dbi_options_num = 0;
  63. char *np_dbi_database = NULL;
  64. char *np_dbi_query = NULL;
  65. int process_arguments (int, char **);
  66. int validate_arguments (void);
  67. void print_usage (void);
  68. void print_help (void);
  69. double timediff (struct timeval, struct timeval);
  70. void np_dbi_print_error (dbi_conn, char *, ...);
  71. int do_query (dbi_conn, double *, double *);
  72. int
  73. main (int argc, char **argv)
  74. {
  75. int status = STATE_UNKNOWN;
  76. dbi_driver driver;
  77. dbi_conn conn;
  78. struct timeval start_timeval, end_timeval;
  79. double conn_time = 0.0;
  80. double query_time = 0.0;
  81. double query_val = 0.0;
  82. int i;
  83. setlocale (LC_ALL, "");
  84. bindtextdomain (PACKAGE, LOCALEDIR);
  85. textdomain (PACKAGE);
  86. /* Parse extra opts if any */
  87. argv = np_extra_opts (&argc, argv, progname);
  88. if (process_arguments (argc, argv) == ERROR)
  89. usage4 (_("Could not parse arguments"));
  90. /* Set signal handling and alarm */
  91. if (signal (SIGALRM, timeout_alarm_handler) == SIG_ERR) {
  92. usage4 (_("Cannot catch SIGALRM"));
  93. }
  94. alarm (timeout_interval);
  95. if (verbose > 2)
  96. printf ("Initializing DBI\n");
  97. if (dbi_initialize (NULL) < 0) {
  98. printf ("UNKNOWN - failed to initialize DBI.\n");
  99. return STATE_UNKNOWN;
  100. }
  101. if (verbose)
  102. printf ("Opening DBI driver '%s'\n", np_dbi_driver);
  103. driver = dbi_driver_open (np_dbi_driver);
  104. if (! driver) {
  105. printf ("UNKNOWN - failed to open DBI driver '%s'; possibly it's not installed.\n",
  106. np_dbi_driver);
  107. printf ("Known drivers:\n");
  108. for (driver = dbi_driver_list (NULL); driver; driver = dbi_driver_list (driver)) {
  109. printf (" - %s\n", dbi_driver_get_name (driver));
  110. }
  111. return STATE_UNKNOWN;
  112. }
  113. /* make a connection to the database */
  114. gettimeofday (&start_timeval, NULL);
  115. conn = dbi_conn_open (driver);
  116. if (! conn) {
  117. printf ("UNKNOWN - failed top open connection object.\n");
  118. dbi_conn_close (conn);
  119. return STATE_UNKNOWN;
  120. }
  121. for (i = 0; i < np_dbi_options_num; ++i) {
  122. const char *opt;
  123. if (verbose > 1)
  124. printf ("Setting DBI driver option '%s' to '%s'\n",
  125. np_dbi_options[i].key, np_dbi_options[i].value);
  126. if (! dbi_conn_set_option (conn, np_dbi_options[i].key, np_dbi_options[i].value))
  127. continue;
  128. /* else: status != 0 */
  129. np_dbi_print_error (conn, "UNKNOWN - failed to set option '%s' to '%s'",
  130. np_dbi_options[i].key, np_dbi_options[i].value);
  131. printf ("Known driver options:\n");
  132. for (opt = dbi_conn_get_option_list (conn, NULL); opt;
  133. opt = dbi_conn_get_option_list (conn, opt)) {
  134. printf (" - %s\n", opt);
  135. }
  136. dbi_conn_close (conn);
  137. return STATE_UNKNOWN;
  138. }
  139. if (host) {
  140. if (verbose > 1)
  141. printf ("Setting DBI driver option 'host' to '%s'\n", host);
  142. dbi_conn_set_option (conn, "host", host);
  143. }
  144. if (verbose) {
  145. const char *dbname, *host;
  146. dbname = dbi_conn_get_option (conn, "dbname");
  147. host = dbi_conn_get_option (conn, "host");
  148. if (! dbname)
  149. dbname = "<unspecified>";
  150. if (! host)
  151. host = "<unspecified>";
  152. printf ("Connecting to database '%s' at host '%s'\n",
  153. dbname, host);
  154. }
  155. if (dbi_conn_connect (conn) < 0) {
  156. np_dbi_print_error (conn, "UNKOWN - failed to connect to database");
  157. return STATE_UNKNOWN;
  158. }
  159. gettimeofday (&end_timeval, NULL);
  160. conn_time = timediff (start_timeval, end_timeval);
  161. if (verbose)
  162. printf("Time elapsed: %f\n", conn_time);
  163. if (metric == METRIC_CONN_TIME)
  164. status = get_status (conn_time, dbi_thresholds);
  165. /* select a database */
  166. if (np_dbi_database) {
  167. if (verbose > 1)
  168. printf ("Selecting database '%s'\n", np_dbi_database);
  169. if (dbi_conn_select_db (conn, np_dbi_database)) {
  170. np_dbi_print_error (conn, "UNKOWN - failed to select database '%s'",
  171. np_dbi_database);
  172. return STATE_UNKNOWN;
  173. }
  174. }
  175. if (np_dbi_query) {
  176. /* execute query */
  177. status = do_query (conn, &query_val, &query_time);
  178. if (status != STATE_OK)
  179. /* do_query prints an error message in this case */
  180. return status;
  181. if (metric == METRIC_QUERY_RESULT)
  182. status = get_status (query_val, dbi_thresholds);
  183. else if (metric == METRIC_QUERY_TIME)
  184. status = get_status (query_time, dbi_thresholds);
  185. }
  186. if (verbose)
  187. printf("Closing connection\n");
  188. dbi_conn_close (conn);
  189. /* In case of METRIC_QUERY_RESULT, isnan(query_val) indicates an error
  190. * which should have been reported and handled (abort) before */
  191. assert ((metric != METRIC_QUERY_RESULT) || (! isnan (query_val)));
  192. printf ("%s - connection time: %fs", state_text (status), conn_time);
  193. if (np_dbi_query) {
  194. if (isnan (query_val))
  195. printf (", '%s' query execution time: %fs", np_dbi_query, query_time);
  196. else
  197. printf (", '%s' returned %f in %fs", np_dbi_query, query_val, query_time);
  198. }
  199. printf (" | conntime=%fs;%s;%s;0;", conn_time,
  200. ((metric == METRIC_CONN_TIME) && warning_range) ? warning_range : "",
  201. ((metric == METRIC_CONN_TIME) && critical_range) ? critical_range : "");
  202. if (np_dbi_query) {
  203. if (! isnan (query_val))
  204. printf (" query=%f;%s;%s;;", query_val,
  205. ((metric == METRIC_QUERY_RESULT) && warning_range) ? warning_range : "",
  206. ((metric == METRIC_QUERY_RESULT) && critical_range) ? critical_range : "");
  207. printf (" querytime=%fs;%s;%s;0;", query_time,
  208. ((metric == METRIC_QUERY_TIME) && warning_range) ? warning_range : "",
  209. ((metric == METRIC_QUERY_TIME) && critical_range) ? critical_range : "");
  210. }
  211. printf ("\n");
  212. return status;
  213. }
  214. /* process command-line arguments */
  215. int
  216. process_arguments (int argc, char **argv)
  217. {
  218. int c;
  219. int option = 0;
  220. static struct option longopts[] = {
  221. STD_LONG_OPTS,
  222. {"metric", required_argument, 0, 'm'},
  223. {"driver", required_argument, 0, 'd'},
  224. {"option", required_argument, 0, 'o'},
  225. {"query", required_argument, 0, 'q'},
  226. {"database", required_argument, 0, 'D'},
  227. {0, 0, 0, 0}
  228. };
  229. while (1) {
  230. c = getopt_long (argc, argv, "Vvht:c:w:m:H:d:o:q:D:",
  231. longopts, &option);
  232. if (c == EOF)
  233. break;
  234. switch (c) {
  235. case '?': /* usage */
  236. usage5 ();
  237. case 'h': /* help */
  238. print_help ();
  239. exit (STATE_OK);
  240. case 'V': /* version */
  241. print_revision (progname, NP_VERSION);
  242. exit (STATE_OK);
  243. case 'c': /* critical range */
  244. critical_range = optarg;
  245. break;
  246. case 'w': /* warning range */
  247. warning_range = optarg;
  248. break;
  249. case 'm':
  250. if (! strcasecmp (optarg, "CONN_TIME"))
  251. metric = METRIC_CONN_TIME;
  252. else if (! strcasecmp (optarg, "QUERY_RESULT"))
  253. metric = METRIC_QUERY_RESULT;
  254. else if (! strcasecmp (optarg, "QUERY_TIME"))
  255. metric = METRIC_QUERY_TIME;
  256. else
  257. usage2 (_("Invalid metric"), optarg);
  258. break;
  259. case 't': /* timeout */
  260. if (!is_intnonneg (optarg))
  261. usage2 (_("Timeout interval must be a positive integer"), optarg);
  262. else
  263. timeout_interval = atoi (optarg);
  264. case 'H': /* host */
  265. if (!is_host (optarg))
  266. usage2 (_("Invalid hostname/address"), optarg);
  267. else
  268. host = optarg;
  269. break;
  270. case 'v':
  271. verbose++;
  272. break;
  273. case 'd':
  274. np_dbi_driver = optarg;
  275. break;
  276. case 'o':
  277. {
  278. driver_option_t *new;
  279. char *k, *v;
  280. k = optarg;
  281. v = strchr (k, (int)'=');
  282. if (! v)
  283. usage2 (_("Option must be '<key>=<value>'"), optarg);
  284. *v = '\0';
  285. ++v;
  286. new = realloc (np_dbi_options,
  287. (np_dbi_options_num + 1) * sizeof (*new));
  288. if (! new) {
  289. printf ("UNKOWN - failed to reallocate memory\n");
  290. exit (STATE_UNKNOWN);
  291. }
  292. np_dbi_options = new;
  293. new = np_dbi_options + np_dbi_options_num;
  294. ++np_dbi_options_num;
  295. new->key = k;
  296. new->value = v;
  297. }
  298. break;
  299. case 'q':
  300. np_dbi_query = optarg;
  301. break;
  302. case 'D':
  303. np_dbi_database = optarg;
  304. break;
  305. }
  306. }
  307. set_thresholds (&dbi_thresholds, warning_range, critical_range);
  308. return validate_arguments ();
  309. }
  310. int
  311. validate_arguments ()
  312. {
  313. if (! np_dbi_driver)
  314. usage ("Must specify a DBI driver");
  315. if (((metric == METRIC_QUERY_RESULT) || (metric == METRIC_QUERY_TIME))
  316. && (! np_dbi_query))
  317. usage ("Must specify a query to execute (metric == QUERY_RESULT)");
  318. if ((metric != METRIC_CONN_TIME)
  319. && (metric != METRIC_QUERY_RESULT)
  320. && (metric != METRIC_QUERY_TIME))
  321. usage ("Invalid metric specified");
  322. return OK;
  323. }
  324. void
  325. print_help (void)
  326. {
  327. print_revision (progname, NP_VERSION);
  328. printf (COPYRIGHT, copyright, email);
  329. printf (_("This program connects to an (SQL) database using DBI and checks the\n"
  330. "specified metric against threshold levels. The default metric is\n"
  331. "the result of the specified query.\n"));
  332. printf ("\n\n");
  333. print_usage ();
  334. printf (UT_HELP_VRSN);
  335. /* include this conditionally to avoid 'zero-length printf format string'
  336. * compiler warnings */
  337. #ifdef NP_EXTRA_OPTS
  338. printf (UT_EXTRA_OPTS);
  339. #endif
  340. printf ("\n");
  341. printf (" %s\n", "-d, --driver=STRING");
  342. printf (" %s\n", _("DBI driver to use"));
  343. printf (" %s\n", "-o, --option=STRING");
  344. printf (" %s\n", _("DBI driver options"));
  345. printf (" %s\n", "-q, --query=STRING");
  346. printf (" %s\n", _("query to execute"));
  347. printf ("\n");
  348. printf (UT_WARN_CRIT_RANGE);
  349. printf (" %s\n", "-m, --metric=METRIC");
  350. printf (" %s\n", _("Metric to check thresholds against. Available metrics:"));
  351. printf (" CONN_TIME - %s\n", _("time used for setting up the database connection"));
  352. printf (" QUERY_RESULT - %s\n", _("result (first column of first row) of the query"));
  353. printf (" QUERY_TIME - %s\n", _("time used to execute the query"));
  354. printf (" %s\n", _("(ignore the query result)"));
  355. printf ("\n");
  356. printf (UT_TIMEOUT, DEFAULT_SOCKET_TIMEOUT);
  357. printf (UT_VERBOSE);
  358. printf ("\n");
  359. printf (" %s\n", _("A DBI driver (-d option) is required. If the specified metric operates"));
  360. printf (" %s\n\n", _("on a query, one has to be specified (-q option)."));
  361. printf (" %s\n", _("This plugin connects to an (SQL) database using libdbi and, optionally,"));
  362. printf (" %s\n", _("executes the specified query. The first column of the first row of the"));
  363. printf (" %s\n", _("result will be parsed and, in QUERY_RESULT mode, compared with the"));
  364. printf (" %s\n", _("warning and critical ranges. The result from the query has to be numeric"));
  365. printf (" %s\n\n", _("(strings representing numbers are fine)."));
  366. printf (" %s\n", _("The number and type of required DBI driver options depends on the actual"));
  367. printf (" %s\n", _("driver. See its documentation at http://libdbi-drivers.sourceforge.net/"));
  368. printf (" %s\n\n", _("for details."));
  369. printf (" %s\n", _("Examples:"));
  370. printf (" check_dbi -d pgsql -o username=postgres -m QUERY_RESULT \\\n");
  371. printf (" -q 'SELECT COUNT(*) FROM pg_stat_activity' -w 5 -c 10\n");
  372. printf (" Warning if more than five connections; critical if more than ten.\n\n");
  373. printf (" check_dbi -d mysql -H localhost -o username=user -o password=secret \\\n");
  374. printf (" -q 'SELECT COUNT(*) FROM logged_in_users -w 5:20 -c 0:50\n");
  375. printf (" Warning if less than 5 or more than 20 users are logged in; critical\n");
  376. printf (" if more than 50 users.\n\n");
  377. printf (" check_dbi -d firebird -o username=user -o password=secret -o dbname=foo \\\n");
  378. printf (" -m CONN_TIME -w 0.5 -c 2\n");
  379. printf (" Warning if connecting to the database takes more than half of a second;\n");
  380. printf (" critical if it takes more than 2 seconds.\n");
  381. printf (UT_SUPPORT);
  382. }
  383. void
  384. print_usage (void)
  385. {
  386. printf ("%s\n", _("Usage:"));
  387. printf ("%s -d <DBI driver> [-o <DBI driver option> [...]] [-q <query>]\n", progname);
  388. printf (" [-H <host>] [-c <critical range>] [-w <warning range>] [-m <metric>]\n");
  389. }
  390. #define CHECK_IGNORE_ERROR(s) \
  391. do { \
  392. if (metric != METRIC_QUERY_RESULT) \
  393. return (s); \
  394. } while (0)
  395. double
  396. get_field (dbi_conn conn, dbi_result res, unsigned short *field_type)
  397. {
  398. double val = NAN;
  399. if (*field_type == DBI_TYPE_INTEGER) {
  400. val = (double)dbi_result_get_longlong_idx (res, 1);
  401. }
  402. else if (*field_type == DBI_TYPE_DECIMAL) {
  403. val = dbi_result_get_double_idx (res, 1);
  404. }
  405. else if (*field_type == DBI_TYPE_STRING) {
  406. const char *val_str;
  407. char *endptr = NULL;
  408. val_str = dbi_result_get_string_idx (res, 1);
  409. if ((! val_str) || (strcmp (val_str, "ERROR") == 0)) {
  410. CHECK_IGNORE_ERROR (NAN);
  411. np_dbi_print_error (conn, "CRITICAL - failed to fetch string value");
  412. *field_type = DBI_TYPE_ERROR;
  413. return NAN;
  414. }
  415. if (verbose > 2)
  416. printf ("Query returned string '%s'\n", val_str);
  417. val = strtod (val_str, &endptr);
  418. if (endptr == val_str) {
  419. CHECK_IGNORE_ERROR (NAN);
  420. printf ("CRITICAL - result value is not a numeric: %s\n", val_str);
  421. *field_type = DBI_TYPE_ERROR;
  422. return NAN;
  423. }
  424. else if ((endptr != NULL) && (*endptr != '\0')) {
  425. if (verbose)
  426. printf ("Garbage after value: %s\n", endptr);
  427. }
  428. }
  429. else {
  430. CHECK_IGNORE_ERROR (NAN);
  431. printf ("CRITICAL - cannot parse value of type %s (%i)\n",
  432. (*field_type == DBI_TYPE_BINARY)
  433. ? "BINARY"
  434. : (*field_type == DBI_TYPE_DATETIME)
  435. ? "DATETIME"
  436. : "<unknown>",
  437. *field_type);
  438. *field_type = DBI_TYPE_ERROR;
  439. return NAN;
  440. }
  441. return val;
  442. }
  443. double
  444. get_query_result (dbi_conn conn, dbi_result res, double *res_val)
  445. {
  446. unsigned short field_type;
  447. double val = NAN;
  448. if (dbi_result_get_numrows (res) == DBI_ROW_ERROR) {
  449. CHECK_IGNORE_ERROR (STATE_OK);
  450. np_dbi_print_error (conn, "CRITICAL - failed to fetch rows");
  451. return STATE_CRITICAL;
  452. }
  453. if (dbi_result_get_numrows (res) < 1) {
  454. CHECK_IGNORE_ERROR (STATE_OK);
  455. printf ("WARNING - no rows returned\n");
  456. return STATE_WARNING;
  457. }
  458. if (dbi_result_get_numfields (res) == DBI_FIELD_ERROR) {
  459. CHECK_IGNORE_ERROR (STATE_OK);
  460. np_dbi_print_error (conn, "CRITICAL - failed to fetch fields");
  461. return STATE_CRITICAL;
  462. }
  463. if (dbi_result_get_numfields (res) < 1) {
  464. CHECK_IGNORE_ERROR (STATE_OK);
  465. printf ("WARNING - no fields returned\n");
  466. return STATE_WARNING;
  467. }
  468. if (dbi_result_first_row (res) != 1) {
  469. CHECK_IGNORE_ERROR (STATE_OK);
  470. np_dbi_print_error (conn, "CRITICAL - failed to fetch first row");
  471. return STATE_CRITICAL;
  472. }
  473. field_type = dbi_result_get_field_type_idx (res, 1);
  474. if (field_type != DBI_TYPE_ERROR)
  475. val = get_field (conn, res, &field_type);
  476. *res_val = val;
  477. if (field_type == DBI_TYPE_ERROR) {
  478. CHECK_IGNORE_ERROR (STATE_OK);
  479. np_dbi_print_error (conn, "CRITICAL - failed to fetch data");
  480. return STATE_CRITICAL;
  481. }
  482. dbi_result_free (res);
  483. return STATE_OK;
  484. }
  485. #undef CHECK_IGNORE_ERROR
  486. int
  487. do_query (dbi_conn conn, double *res_val, double *res_time)
  488. {
  489. dbi_result res;
  490. struct timeval timeval_start, timeval_end;
  491. int status = STATE_OK;
  492. assert (np_dbi_query);
  493. if (verbose)
  494. printf ("Executing query '%s'\n", np_dbi_query);
  495. gettimeofday (&timeval_start, NULL);
  496. res = dbi_conn_query (conn, np_dbi_query);
  497. if (! res) {
  498. np_dbi_print_error (conn, "CRITICAL - failed to execute query '%s'", np_dbi_query);
  499. return STATE_CRITICAL;
  500. }
  501. status = get_query_result (conn, res, res_val);
  502. gettimeofday (&timeval_end, NULL);
  503. *res_time = timediff (timeval_start, timeval_end);
  504. return status;
  505. }
  506. double
  507. timediff (struct timeval start, struct timeval end)
  508. {
  509. double diff;
  510. while (start.tv_usec > end.tv_usec) {
  511. --end.tv_sec;
  512. end.tv_usec += 1000000;
  513. }
  514. diff = (double)(end.tv_sec - start.tv_sec)
  515. + (double)(end.tv_usec - start.tv_usec) / 1000000.0;
  516. return diff;
  517. }
  518. void
  519. np_dbi_print_error (dbi_conn conn, char *fmt, ...)
  520. {
  521. const char *errmsg = NULL;
  522. va_list ap;
  523. va_start (ap, fmt);
  524. dbi_conn_error (conn, &errmsg);
  525. vprintf (fmt, ap);
  526. printf (": %s\n", errmsg);
  527. va_end (ap);
  528. }