mysql plugin: Some changes to the configuration handling.
[collectd.git] / src / mysql.c
1 /**
2  * collectd - src/mysql.c
3  * Copyright (C) 2006,2007  Florian octo Forster
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation; only version 2 of the License is applicable.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
17  *
18  * Authors:
19  *   Florian octo Forster <octo at verplant.org>
20  **/
21
22 #include "collectd.h"
23 #include "common.h"
24 #include "plugin.h"
25 #include "configfile.h"
26
27 #ifdef HAVE_MYSQL_H
28 #include <mysql.h>
29 #elif defined(HAVE_MYSQL_MYSQL_H)
30 #include <mysql/mysql.h>
31 #endif
32
33 /* TODO: Understand `Select_*' and possibly do that stuff as well.. */
34
35 struct mysql_database_s /* {{{ */
36 {
37         /* instance == NULL  =>  legacy mode */
38         char *instance;
39         char *host;
40         char *user;
41         char *pass;
42         char *database;
43         char *socket;
44         int   port;
45 };
46 typedef struct mysql_database_s mysql_database_t; /* }}} */
47
48 static mysql_database_t **databases     = NULL;
49 static size_t             databases_num = 0;
50
51 static void mysql_database_free (mysql_database_t *db) /* {{{ */
52 {
53         if (db == NULL)
54                 return;
55
56         sfree (db->host);
57         sfree (db->user);
58         sfree (db->pass);
59         sfree (db->socket);
60         sfree (db);
61 } /* }}} void mysql_database_free */
62
63 /* Configuration handling functions {{{
64  *
65  * <Plugin mysql>
66  *   <Database "plugin_instance1">
67  *     Host "localhost"
68  *     Port 22000
69  *     ...
70  *   </Database>
71  * </Plugin>
72  */
73
74 static int mysql_config_set_string (char **ret_string, /* {{{ */
75                                     oconfig_item_t *ci)
76 {
77         char *string;
78
79         if ((ci->values_num != 1)
80             || (ci->values[0].type != OCONFIG_TYPE_STRING))
81         {
82                 WARNING ("mysql plugin: The `%s' config option "
83                          "needs exactly one string argument.", ci->key);
84                 return (-1);
85         }
86
87         string = strdup (ci->values[0].value.string);
88         if (string == NULL)
89         {
90                 ERROR ("mysql plugin: strdup failed.");
91                 return (-1);
92         }
93
94         if (*ret_string != NULL)
95                 free (*ret_string);
96         *ret_string = string;
97
98         return (0);
99 } /* }}} int mysql_config_set_string */
100
101 static int mysql_config_set_int (int *ret_int, /* {{{ */
102                                  oconfig_item_t *ci)
103 {
104         if ((ci->values_num != 1)
105             || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
106         {
107                 WARNING ("mysql plugin: The `%s' config option "
108                          "needs exactly one string argument.", ci->key);
109                 return (-1);
110         }
111
112         *ret_int = ci->values[0].value.number;
113
114         return (0);
115 } /* }}} int mysql_config_set_int */
116
117 static int mysql_config (oconfig_item_t *ci) /* {{{ */
118 {
119         mysql_database_t *db;
120         int plugin_block;
121         int status;
122         int i;
123
124         if ((ci->values_num != 1)
125             || (ci->values[0].type != OCONFIG_TYPE_STRING))
126         {
127                 WARNING ("mysql plugin: The `Database' block "
128                          "needs exactly one string argument.");
129                 return (-1);
130         }
131
132         db = (mysql_database_t *) malloc (sizeof (*db));
133         if (db == NULL)
134         {
135                 ERROR ("mysql plugin: malloc failed.");
136                 return (-1);
137         }
138         memset (db, 0, sizeof (*db));
139
140         /* initialize all the pointers */
141         db->host     = NULL;
142         db->user     = NULL;
143         db->pass     = NULL;
144         db->database = NULL;
145         db->socket   = NULL;
146
147         plugin_block = 1;
148         if (strcasecmp ("Plugin", ci->key) == 0)
149         {
150                 db->instance = NULL;
151         }
152         else if (strcasecmp ("Database", ci->key) == 0)
153         {
154                 plugin_block = 0;
155                 status = mysql_config_set_string (&db->instance, ci);
156                 if (status != 0)
157                 {
158                         sfree (db);
159                         return (status);
160                 }
161                 assert (db->instance != NULL);
162         }
163         else
164         {
165                 ERROR ("mysql plugin: mysql_config: "
166                                 "Invalid key: %s", ci->key);
167                 return (-1);
168         }
169
170         /* Fill the `mysql_database_t' structure.. */
171         for (i = 0; i < ci->children_num; i++)
172         {
173                 oconfig_item_t *child = ci->children + i;
174
175                 if (strcasecmp ("Host", child->key) == 0)
176                         status = mysql_config_set_string (&db->host, child);
177                 else if (strcasecmp ("User", child->key) == 0)
178                         status = mysql_config_set_string (&db->user, child);
179                 else if (strcasecmp ("Password", child->key) == 0)
180                         status = mysql_config_set_string (&db->pass, child);
181                 else if (strcasecmp ("Port", child->key) == 0)
182                         status = mysql_config_set_int (&db->port, child);
183                 else if (strcasecmp ("Socket", child->key) == 0)
184                         status = mysql_config_set_string (&db->socket, child);
185                 /* Check if we're currently handling the `Plugin' block. If so,
186                  * handle `Database' _blocks_, too. */
187                 else if ((plugin_block != 0)
188                                 && (strcasecmp ("Database", child->key) == 0)
189                                 && (child->children != NULL))
190                 {
191                         /* If `plugin_block > 1', there has been at least one
192                          * `Database' block */
193                         plugin_block++;
194                         status = mysql_config (child);
195                 }
196                 /* Now handle ordinary `Database' options (without children) */
197                 else if ((strcasecmp ("Database", child->key) == 0)
198                                 && (child->children == NULL))
199                         status = mysql_config_set_string (&db->database, child);
200                 else
201                 {
202                         WARNING ("mysql plugin: Option `%s' not allowed here.", child->key);
203                         status = -1;
204                 }
205
206                 if (status != 0)
207                         break;
208         }
209
210         /* Check if there were any `Database' blocks. */
211         if (plugin_block > 1)
212         {
213                 /* There were connection blocks. Don't use any legacy stuff. */
214                 if ((db->host != NULL)
215                         || (db->user != NULL)
216                         || (db->pass != NULL)
217                         || (db->database != NULL)
218                         || (db->socket != NULL)
219                         || (db->port != 0))
220                 {
221                         WARNING ("mysql plugin: At least one <Database> "
222                                         "block has been found. The legacy "
223                                         "configuration will be ignored.");
224                 }
225                 mysql_database_free (db);
226                 return (0);
227         }
228         else if (plugin_block != 0)
229         {
230                 WARNING ("mysql plugin: You're using the legacy "
231                                 "configuration options. Please consider "
232                                 "updating your configuration!");
233         }
234
235         /* Check that all necessary options have been given. */
236         while (status == 0)
237         {
238                 /* Zero is allowed and automatically handled by
239                  * `mysql_real_connect'. */
240                 if ((db->port < 0) || (db->port > 65535))
241                 {
242                         ERROR ("mysql plugin: Database %s: Port number out "
243                                         "of range: %i",
244                                         (db->instance != NULL)
245                                         ? db->instance
246                                         : "<legacy>",
247                                         db->port);
248                         status = -1;
249                 }
250                 break;
251         } /* while (status == 0) */
252
253         /* If all went well, add this database to the global list of databases. */
254         if (status == 0)
255         {
256                 mysql_database_t **temp;
257
258                 temp = (mysql_database_t **) realloc (databases,
259                                                      sizeof (*databases) * (databases_num + 1));
260                 if (temp == NULL)
261                 {
262                         ERROR ("mysql plugin: realloc failed");
263                         status = -1;
264                 }
265                 else
266                 {
267                         databases = temp;
268                         databases[databases_num] = db;
269                         databases_num++;
270                 }
271         }
272
273         if (status != 0)
274         {
275                 mysql_database_free (db);
276                 return (-1);
277         }
278
279         return (0);
280 } /* }}} int mysql_config */
281
282 /* }}} End of configuration handling functions */
283
284 static MYSQL *getconnection (mysql_database_t *db)
285 {
286         static MYSQL *con;
287         static int    state;
288
289         static int wait_for = 0;
290         static int wait_increase = 60;
291
292         if (state != 0)
293         {
294                 int err;
295                 if ((err = mysql_ping (con)) != 0)
296                 {
297                         WARNING ("mysql_ping failed: %s", mysql_error (con));
298                         state = 0;
299                 }
300                 else
301                 {
302                         state = 1;
303                         return (con);
304                 }
305         }
306
307         if (wait_for > 0)
308         {
309                 wait_for -= interval_g;
310                 return (NULL);
311         }
312
313         wait_for = wait_increase;
314         wait_increase *= 2;
315         if (wait_increase > 86400)
316                 wait_increase = 86400;
317
318         if ((con = mysql_init (con)) == NULL)
319         {
320                 ERROR ("mysql_init failed: %s", mysql_error (con));
321                 state = 0;
322                 return (NULL);
323         }
324
325         if (mysql_real_connect (con, db->host, db->user, db->pass,
326                                 db->database, db->port, db->socket, 0) == NULL)
327         {
328                 ERROR ("mysql_real_connect failed: %s", mysql_error (con));
329                 state = 0;
330                 return (NULL);
331         }
332         else
333         {
334                 state = 1;
335                 wait_for = 0;
336                 wait_increase = 60;
337                 return (con);
338         }
339 } /* static MYSQL *getconnection (void) */
340
341 static void set_host (mysql_database_t *db, value_list_t *vl)
342 {
343         /* XXX legacy mode - use hostname_g */
344         if (db->instance == NULL)
345                 sstrncpy (vl->host, hostname_g, sizeof (vl->host));
346         else
347         {
348                 if ((db->host == NULL)
349                                 || (strcmp ("", db->host) == 0)
350                                 || (strcmp ("localhost", db->host) == 0))
351                         sstrncpy (vl->host, hostname_g, sizeof (vl->host));
352                 else
353                         sstrncpy (vl->host, db->host, sizeof (vl->host));
354         }
355 }
356
357 static void set_plugin_instance (mysql_database_t *db, value_list_t *vl)
358 {
359         /* XXX legacy mode - no plugin_instance */
360         if (db->instance == NULL)
361                 sstrncpy (vl->plugin_instance, "",
362                                 sizeof (vl->plugin_instance));
363         else
364                 sstrncpy (vl->plugin_instance, db->instance,
365                                 sizeof (vl->plugin_instance));
366 }
367
368 static void counter_submit (const char *type, const char *type_instance,
369                 counter_t value, mysql_database_t *db)
370 {
371         value_t values[1];
372         value_list_t vl = VALUE_LIST_INIT;
373
374         values[0].counter = value;
375
376         vl.values = values;
377         vl.values_len = 1;
378         set_host (db, &vl);
379         sstrncpy (vl.plugin, "mysql", sizeof (vl.plugin));
380         sstrncpy (vl.type, type, sizeof (vl.type));
381         sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
382         set_plugin_instance (db, &vl);
383
384         plugin_dispatch_values (&vl);
385 } /* void counter_submit */
386
387 static void qcache_submit (counter_t hits, counter_t inserts,
388                 counter_t not_cached, counter_t lowmem_prunes,
389                 gauge_t queries_in_cache, mysql_database_t *db)
390 {
391         value_t values[5];
392         value_list_t vl = VALUE_LIST_INIT;
393
394         values[0].counter = hits;
395         values[1].counter = inserts;
396         values[2].counter = not_cached;
397         values[3].counter = lowmem_prunes;
398         values[4].gauge   = queries_in_cache;
399
400         vl.values = values;
401         vl.values_len = 5;
402         set_host (db, &vl);
403         sstrncpy (vl.plugin, "mysql", sizeof (vl.plugin));
404         sstrncpy (vl.type, "mysql_qcache", sizeof (vl.type));
405         set_plugin_instance (db, &vl);
406
407         plugin_dispatch_values (&vl);
408 } /* void qcache_submit */
409
410 static void threads_submit (gauge_t running, gauge_t connected, gauge_t cached,
411                 counter_t created, mysql_database_t *db)
412 {
413         value_t values[4];
414         value_list_t vl = VALUE_LIST_INIT;
415
416         values[0].gauge   = running;
417         values[1].gauge   = connected;
418         values[2].gauge   = cached;
419         values[3].counter = created;
420
421         vl.values = values;
422         vl.values_len = 4;
423         set_host (db, &vl);
424         sstrncpy (vl.plugin, "mysql", sizeof (vl.plugin));
425         sstrncpy (vl.type, "mysql_threads", sizeof (vl.type));
426         set_plugin_instance (db, &vl);
427
428         plugin_dispatch_values (&vl);
429 } /* void threads_submit */
430
431 static void traffic_submit (counter_t rx, counter_t tx, mysql_database_t *db)
432 {
433         value_t values[2];
434         value_list_t vl = VALUE_LIST_INIT;
435
436         values[0].counter = rx;
437         values[1].counter = tx;
438
439         vl.values = values;
440         vl.values_len = 2;
441         set_host (db, &vl);
442         sstrncpy (vl.plugin, "mysql", sizeof (vl.plugin));
443         sstrncpy (vl.type, "mysql_octets", sizeof (vl.type));
444         set_plugin_instance (db, &vl);
445
446         plugin_dispatch_values (&vl);
447 } /* void traffic_submit */
448
449 static int mysql_read_database (mysql_database_t *db)
450 {
451         MYSQL     *con;
452         MYSQL_RES *res;
453         MYSQL_ROW  row;
454         char      *query;
455         int        query_len;
456         int        field_num;
457
458         unsigned long long qcache_hits          = 0ULL;
459         unsigned long long qcache_inserts       = 0ULL;
460         unsigned long long qcache_not_cached    = 0ULL;
461         unsigned long long qcache_lowmem_prunes = 0ULL;
462         int qcache_queries_in_cache = -1;
463
464         int threads_running   = -1;
465         int threads_connected = -1;
466         int threads_cached    = -1;
467         unsigned long long threads_created = 0ULL;
468
469         unsigned long long traffic_incoming = 0ULL;
470         unsigned long long traffic_outgoing = 0ULL;
471
472         /* An error message will have been printed in this case */
473         if ((con = getconnection (db)) == NULL)
474                 return (-1);
475
476         query = "SHOW STATUS";
477         if (mysql_get_server_version (con) >= 50002)
478                 query = "SHOW GLOBAL STATUS";
479
480         query_len = strlen (query);
481
482         if (mysql_real_query (con, query, query_len))
483         {
484                 ERROR ("mysql_real_query failed: %s\n",
485                                 mysql_error (con));
486                 return (-1);
487         }
488
489         if ((res = mysql_store_result (con)) == NULL)
490         {
491                 ERROR ("mysql_store_result failed: %s\n",
492                                 mysql_error (con));
493                 return (-1);
494         }
495
496         field_num = mysql_num_fields (res);
497         while ((row = mysql_fetch_row (res)))
498         {
499                 char *key;
500                 unsigned long long val;
501
502                 key = row[0];
503                 val = atoll (row[1]);
504
505                 if (strncmp (key, "Com_", 4) == 0)
506                 {
507                         if (val == 0ULL)
508                                 continue;
509
510                         /* Ignore `prepared statements' */
511                         if (strncmp (key, "Com_stmt_", 9) != 0)
512                                 counter_submit ("mysql_commands", key + 4, val, db);
513                 }
514                 else if (strncmp (key, "Handler_", 8) == 0)
515                 {
516                         if (val == 0ULL)
517                                 continue;
518
519                         counter_submit ("mysql_handler", key + 8, val, db);
520                 }
521                 else if (strncmp (key, "Qcache_", 7) == 0)
522                 {
523                         if (strcmp (key, "Qcache_hits") == 0)
524                                 qcache_hits = val;
525                         else if (strcmp (key, "Qcache_inserts") == 0)
526                                 qcache_inserts = val;
527                         else if (strcmp (key, "Qcache_not_cached") == 0)
528                                 qcache_not_cached = val;
529                         else if (strcmp (key, "Qcache_lowmem_prunes") == 0)
530                                 qcache_lowmem_prunes = val;
531                         else if (strcmp (key, "Qcache_queries_in_cache") == 0)
532                                 qcache_queries_in_cache = (int) val;
533                 }
534                 else if (strncmp (key, "Bytes_", 6) == 0)
535                 {
536                         if (strcmp (key, "Bytes_received") == 0)
537                                 traffic_incoming += val;
538                         else if (strcmp (key, "Bytes_sent") == 0)
539                                 traffic_outgoing += val;
540                 }
541                 else if (strncmp (key, "Threads_", 8) == 0)
542                 {
543                         if (strcmp (key, "Threads_running") == 0)
544                                 threads_running = (int) val;
545                         else if (strcmp (key, "Threads_connected") == 0)
546                                 threads_connected = (int) val;
547                         else if (strcmp (key, "Threads_cached") == 0)
548                                 threads_cached = (int) val;
549                         else if (strcmp (key, "Threads_created") == 0)
550                                 threads_created = val;
551                 }
552         }
553         mysql_free_result (res); res = NULL;
554
555         if ((qcache_hits != 0ULL)
556                         || (qcache_inserts != 0ULL)
557                         || (qcache_not_cached != 0ULL)
558                         || (qcache_lowmem_prunes != 0ULL))
559                 qcache_submit (qcache_hits, qcache_inserts, qcache_not_cached,
560                                qcache_lowmem_prunes, qcache_queries_in_cache, db);
561
562         if (threads_created != 0ULL)
563                 threads_submit (threads_running, threads_connected,
564                                 threads_cached, threads_created, db);
565
566         traffic_submit  (traffic_incoming, traffic_outgoing, db);
567
568         /* mysql_close (con); */
569
570         return (0);
571 } /* int mysql_read_database */
572
573 static int mysql_read (void)
574 {
575         size_t i;
576         int success = 0;
577         int status;
578
579         for (i = 0; i < databases_num; i++)
580         {
581                 status = mysql_read_database (databases[i]);
582                 if (status == 0)
583                         success++;
584         }
585
586         if (success == 0)
587         {
588                 ERROR ("mysql plugin: No database could be read. Will return an error so "
589                        "the plugin will be delayed.");
590                 return (-1);
591         }
592
593         return (0);
594 } /* int mysql_read */
595
596 void module_register (void)
597 {
598         plugin_register_complex_config ("mysql", mysql_config);
599         plugin_register_read ("mysql", mysql_read);
600 } /* void module_register */