root/src/modules/mysql.c

Revision 4ed37cf09ac9817ced9312616da97a3a1e90c6b3, 13.5 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 3 months ago)

cleanup of modules, verbose structure setting

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