root/src/modules/mysql.c

Revision 304ec80b8cf842fc0abe5f9029790908b6455957, 13.6 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 1 month ago)

Convert to libmtev.

  • Property mode set to 100644
Line 
1 /*
2  * Copyright (c) 2007, OmniTI Computer Consulting, Inc.
3  * All rights reserved.
4  * Copyright (c) 2015, Circonus, Inc. All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are
8  * met:
9  *
10  *     * Redistributions of source code must retain the above copyright
11  *       notice, this list of conditions and the following disclaimer.
12  *     * Redistributions in binary form must reproduce the above
13  *       copyright notice, this list of conditions and the following
14  *       disclaimer in the documentation and/or other materials provided
15  *       with the distribution.
16  *     * Neither the name OmniTI Computer Consulting, Inc. nor the names
17  *       of its contributors may be used to endorse or promote products
18  *       derived from this software without specific prior written
19  *       permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32  */
33
34 #include <mtev_defines.h>
35
36 #include <stdio.h>
37 #include <unistd.h>
38 #include <errno.h>
39 #include <assert.h>
40 #include <math.h>
41
42 #include <mtev_hash.h>
43
44 #include "noit_config.h"
45 #include "noit_module.h"
46 #include "noit_check.h"
47 #include "noit_check_tools.h"
48 #include "noit_mtev_bridge.h"
49
50 #ifdef HAVE_MYSQL_H
51 #include <mysql.h>
52 #else
53 #ifdef HAVE_MYSQL_MYSQL_H
54 #include <mysql/mysql.h>
55 #else
56 #error No mysql.h header present.  This is not going to work at all.
57 #endif
58 #endif
59
60 typedef struct {
61   noit_module_t *self;
62   noit_check_t *check;
63   MYSQL *conn;
64   MYSQL_RES *result;
65   double connect_duration_d;
66   double *connect_duration;
67   double query_duration_d;
68   double *query_duration;
69   int rv;
70   mtev_hash_table attrs;
71   int timed_out;
72   char *error;
73 } mysql_check_info_t;
74
75 static mtev_log_stream_t nlerr = NULL;
76 static mtev_log_stream_t nldeb = NULL;
77
78 static void mysql_cleanup(noit_module_t *self, noit_check_t *check) {
79   mysql_check_info_t *ci = check->closure;
80   if(ci) {
81     if(ci->result) mysql_free_result(ci->result);
82     if(ci->conn) mysql_close(ci->conn);
83     noit_check_release_attrs(&ci->attrs);
84     if(ci->error) free(ci->error);
85     memset(ci, 0, sizeof(*ci));
86   }
87 }
88 static void mysql_ingest_stats(mysql_check_info_t *ci) {
89   if(ci->rv > 0) {
90     /* metrics */
91     int nrows, ncols, i, j;
92     nrows = ci->rv;
93     ncols = mysql_num_fields(ci->result);
94     MYSQL_FIELD *cdesc = mysql_fetch_fields(ci->result);
95     for (i=0; i<nrows; i++) {
96       mtevL(nldeb, "mysql: row %d [%d cols]:\n", i, ncols);
97       if(ncols<2) continue;
98       MYSQL_ROW row = mysql_fetch_row(ci->result);
99       if(NULL == row[0]) continue;
100       for (j=1; j<ncols; j++) {
101         enum enum_field_types coltype;
102         int iv, *piv;
103         int64_t lv, *plv;
104         double dv, *pdv;
105         char *sv;
106         char mname[128];
107  
108         snprintf(mname, sizeof(mname), "%s`%s", row[0], cdesc[j].name);
109         coltype = cdesc[j].type;
110         mtevL(nldeb, "mysql:   col %d (%s) type %d:\n", j, mname, coltype);
111         switch(coltype) {
112           case FIELD_TYPE_TINY:
113           case FIELD_TYPE_SHORT:
114           case FIELD_TYPE_LONG:
115             if(!row[j]) piv = NULL;
116             else {
117               iv = strtol(row[j], NULL, 10);
118               piv = &iv;
119             }
120             noit_stats_set_metric(ci->check, &ci->check->stats.inprogress, mname, METRIC_INT32, piv);
121             break;
122           case FIELD_TYPE_INT24:
123           case FIELD_TYPE_LONGLONG:
124             if(!row[j]) plv = NULL;
125             else {
126               lv = strtoll(row[j], NULL, 10);
127               plv = &lv;
128             }
129             noit_stats_set_metric(ci->check, &ci->check->stats.inprogress, mname, METRIC_INT64, plv);
130             break;
131           case FIELD_TYPE_DECIMAL:
132           case FIELD_TYPE_FLOAT:
133           case FIELD_TYPE_DOUBLE:
134             if(!row[j]) pdv = NULL;
135             else {
136               dv = atof(row[j]);
137               pdv = &dv;
138             }
139             noit_stats_set_metric(ci->check, &ci->check->stats.inprogress, mname, METRIC_DOUBLE, pdv);
140             break;
141           default:
142             if(!row[j]) sv = NULL;
143             else sv = row[j];
144             noit_stats_set_metric(ci->check, &ci->check->stats.inprogress, mname, METRIC_GUESS, sv);
145             break;
146         }
147       }
148     }
149   }
150 }
151 static void mysql_log_results(noit_module_t *self, noit_check_t *check) {
152   struct timeval duration;
153   mysql_check_info_t *ci = check->closure;
154
155   gettimeofday(&ci->check->stats.inprogress.whence, NULL);
156   sub_timeval(ci->check->stats.inprogress.whence, check->last_fire_time, &duration);
157   ci->check->stats.inprogress.duration = duration.tv_sec * 1000 + duration.tv_usec / 1000;
158   ci->check->stats.inprogress.available = NP_UNAVAILABLE;
159   ci->check->stats.inprogress.state = NP_BAD;
160   if(ci->error) ci->check->stats.inprogress.status = ci->error;
161   else if(ci->timed_out) ci->check->stats.inprogress.status = "timeout";
162   else if(ci->rv == 0) {
163     ci->check->stats.inprogress.available = NP_AVAILABLE;
164     ci->check->stats.inprogress.state = NP_GOOD;
165     ci->check->stats.inprogress.status = "no rows, ok";
166   }
167   else {
168     ci->check->stats.inprogress.available = NP_AVAILABLE;
169     ci->check->stats.inprogress.state = NP_GOOD;
170     ci->check->stats.inprogress.status = "got rows, ok";
171   }
172
173   if(ci->rv >= 0)
174     noit_stats_set_metric(check, &check->stats.inprogress, "row_count", METRIC_INT32, &ci->rv);
175   if(ci->connect_duration)
176     noit_stats_set_metric(check, &check->stats.inprogress, "connect_duration", METRIC_DOUBLE,
177                           ci->connect_duration);
178   if(ci->query_duration)
179     noit_stats_set_metric(check, &check->stats.inprogress, "query_duration", METRIC_DOUBLE,
180                           ci->query_duration);
181
182   noit_check_set_stats(check, &ci->check->stats.inprogress);
183   noit_check_stats_clear(check, &check->stats.inprogress);
184 }
185
186 #define FETCH_CONFIG_OR(key, str) do { \
187   if(!mtev_hash_retrieve(check->config, #key, strlen(#key), (void **)&key)) \
188     key = str; \
189 } while(0)
190
191 #define AVAIL_BAIL(str) do { \
192   MYSQL *conn_swap = ci->conn; \
193   MYSQL_RES *result_swap = ci->result; \
194   if(ci->result) { \
195     ci->result = NULL; \
196     mysql_free_result(result_swap); \
197   } \
198   if(ci->conn) { \
199     ci->conn = NULL; \
200     mysql_close(conn_swap); \
201   } \
202   ci->timed_out = 0; \
203   ci->error = strdup(str); \
204   mtev_hash_destroy(&dsn_h, free, free); \
205   return 0; \
206 } while(0)
207
208 static char *
209 __noit__strndup(const char *src, int len) {
210   int slen;
211   char *dst;
212   for(slen = 0; slen < len; slen++)
213     if(src[slen] == '\0') break;
214   dst = malloc(slen + 1);
215   memcpy(dst, src, slen);
216   dst[slen] = '\0';
217   return dst;
218 }
219
220 void mysql_parse_dsn(const char *dsn, mtev_hash_table *h) {
221   const char *a=dsn, *b=NULL, *c=NULL;
222   while (a && (NULL != (b = strchr(a, '=')))) {
223     char *key, *val=NULL;
224     key = __noit__strndup(a, b-a);
225     b++;
226     if (b) {
227       if (NULL != (c = strchr(b, ' '))) {
228         val = __noit__strndup(b, c-b);
229       } else {
230         val = strdup(b);
231       }
232     }
233     mtev_hash_replace(h, key, key?strlen(key):0, val, free, free);
234     a = c;
235     if (a) a++;
236   }
237 }
238
239 static int mysql_drive_session(eventer_t e, int mask, void *closure,
240                                   struct timeval *now) {
241   const char *dsn, *sql;
242   char sql_buff[8192];
243   char dsn_buff[512];
244   mysql_check_info_t *ci = closure;
245   noit_check_t *check = ci->check;
246   struct timeval t1, t2, diff;
247   mtev_hash_table dsn_h = MTEV_HASH_EMPTY;
248   const char *host=NULL;
249   const char *user=NULL;
250   const char *password=NULL;
251   const char *dbname=NULL;
252   const char *port_s=NULL;
253   const char *socket=NULL;
254   const char *sslmode=NULL;
255   u_int32_t port;
256   unsigned long client_flag = CLIENT_IGNORE_SIGPIPE;
257   unsigned int timeout;
258
259   if(mask & (EVENTER_READ | EVENTER_WRITE)) {
260     /* this case is impossible from the eventer.  It is called as
261      * such on the synchronous completion of the event.
262      */
263     mysql_log_results(ci->self, ci->check);
264     mysql_cleanup(ci->self, ci->check);
265     check->flags &= ~NP_RUNNING;
266     return 0;
267   }
268   switch(mask) {
269     case EVENTER_ASYNCH_WORK:
270       noit_check_stats_clear(ci->check, &ci->check->stats.inprogress);
271       ci->connect_duration = NULL;
272       ci->query_duration = NULL;
273
274       FETCH_CONFIG_OR(dsn, "");
275       noit_check_interpolate(dsn_buff, sizeof(dsn_buff), dsn,
276                              &ci->attrs, check->config);
277
278       mysql_parse_dsn(dsn_buff, &dsn_h);
279       mtev_hash_retrieve(&dsn_h, "host", strlen("host"), (void**)&host);
280       mtev_hash_retrieve(&dsn_h, "user", strlen("user"), (void**)&user);
281       mtev_hash_retrieve(&dsn_h, "password", strlen("password"), (void**)&password);
282       mtev_hash_retrieve(&dsn_h, "dbname", strlen("dbname"), (void**)&dbname);
283       mtev_hash_retrieve(&dsn_h, "port", strlen("port"), (void**)&port_s);
284       if(mtev_hash_retrieve(&dsn_h, "sslmode", strlen("sslmode"), (void**)&sslmode) &&
285          !strcmp(sslmode, "require"))
286         client_flag |= CLIENT_SSL;
287       port = port_s ? strtol(port_s, NULL, 10) : 3306;
288       mtev_hash_retrieve(&dsn_h, "socket", strlen("socket"), (void**)&socket);
289
290       ci->conn = mysql_init(NULL); /* allocate us a handle */
291       if(!ci->conn) AVAIL_BAIL("mysql_init failed");
292       timeout = check->timeout / 1000;
293       mysql_options(ci->conn, MYSQL_OPT_CONNECT_TIMEOUT, (const char *)&timeout);
294       if(!mysql_real_connect(ci->conn, host, user, password,
295                              dbname, port, socket, client_flag)) {
296         mtevL(noit_stderr, "error during mysql_real_connect: %s\n",
297           mysql_error(ci->conn));
298         AVAIL_BAIL(mysql_error(ci->conn));
299       }
300       if(mysql_ping(ci->conn))
301         AVAIL_BAIL(mysql_error(ci->conn));
302
303 #if MYSQL_VERSION_ID >= 50000
304       if (sslmode && !strcmp(sslmode, "require")) {
305         /* mysql has a bad habit of silently failing to establish ssl and
306          * falling back to unencrypted, so after making the connection, let's
307          * check that we're actually using SSL by checking for a non-NULL
308          * return value from mysql_get_ssl_cipher().
309          */
310         if (mysql_get_ssl_cipher(ci->conn) == NULL) {
311           mtevL(nldeb, "mysql_get_ssl_cipher() returns NULL, but SSL mode required.");
312           AVAIL_BAIL("mysql_get_ssl_cipher() returns NULL, but SSL mode required.");
313         }
314       }
315 #endif
316
317       gettimeofday(&t1, NULL);
318       sub_timeval(t1, check->last_fire_time, &diff);
319       ci->connect_duration_d = diff.tv_sec * 1000.0 + diff.tv_usec / 1000.0;
320       ci->connect_duration = &ci->connect_duration_d;
321
322       FETCH_CONFIG_OR(sql, "");
323       noit_check_interpolate(sql_buff, sizeof(sql_buff), sql,
324                              &ci->attrs, check->config);
325       if (mysql_query(ci->conn, sql_buff))
326         AVAIL_BAIL(mysql_error(ci->conn));
327
328       gettimeofday(&t2, NULL);
329       sub_timeval(t2, t1, &diff);
330       ci->query_duration_d = diff.tv_sec * 1000.0 + diff.tv_usec / 1000.0;
331       ci->query_duration = &ci->query_duration_d;
332
333       ci->result = mysql_store_result(ci->conn);
334       if(!ci->result) AVAIL_BAIL("mysql_store_result failed");
335       ci->rv = mysql_num_rows(ci->result);
336       mysql_ingest_stats(ci);
337       if(ci->result) {
338         MYSQL_RES *result_swap = ci->result;
339         ci->result = NULL;
340         mysql_free_result(result_swap);
341       }
342       if(ci->conn) {
343         MYSQL *conn_swap = ci->conn;
344         ci->conn = NULL;
345         mysql_close(conn_swap);
346       }
347       ci->timed_out = 0;
348       mtev_hash_destroy(&dsn_h, free, free);
349       return 0;
350       break;
351     case EVENTER_ASYNCH_CLEANUP:
352       /* This sets us up for a completion call. */
353       e->mask = EVENTER_READ | EVENTER_WRITE;
354       break;
355     default:
356       abort();
357   }
358   return 0;
359 }
360
361 static int mysql_initiate(noit_module_t *self, noit_check_t *check,
362                           noit_check_t *cause) {
363   mysql_check_info_t *ci = check->closure;
364   struct timeval __now;
365
366   /* We cannot be running */
367   BAIL_ON_RUNNING_CHECK(check);
368   check->flags |= NP_RUNNING;
369
370   ci->self = self;
371   ci->check = check;
372
373   ci->timed_out = 1;
374   ci->rv = -1;
375   noit_check_make_attrs(check, &ci->attrs);
376   gettimeofday(&__now, NULL);
377   memcpy(&check->last_fire_time, &__now, sizeof(__now));
378
379   /* Register a handler for the worker */
380   noit_check_run_full_asynch(check, mysql_drive_session);
381   return 0;
382 }
383
384 static int mysql_initiate_check(noit_module_t *self, noit_check_t *check,
385                                    int once, noit_check_t *cause) {
386   if(!check->closure) check->closure = calloc(1, sizeof(mysql_check_info_t));
387   INITIATE_CHECK(mysql_initiate, self, check, cause);
388   return 0;
389 }
390
391 static int mysql_onload(mtev_image_t *self) {
392   nlerr = mtev_log_stream_find("error/mysql");
393   nldeb = mtev_log_stream_find("debug/mysql");
394   if(!nlerr) nlerr = noit_stderr;
395   if(!nldeb) nldeb = noit_debug;
396
397   eventer_name_callback("mysql/mysql_drive_session", mysql_drive_session);
398   return 0;
399 }
400
401 #include "mysql.xmlh"
402 noit_module_t mysql = {
403   {
404     .magic = NOIT_MODULE_MAGIC,
405     .version = NOIT_MODULE_ABI_VERSION,
406     .name = "mysql",
407     .description = "MySQL Checker",
408     .xml_description = mysql_xml_description,
409     .onload = mysql_onload
410   },
411   NULL,
412   NULL,
413   mysql_initiate_check,
414   mysql_cleanup
415 };
416
Note: See TracBrowser for help on using the browser.