mysql plugin: add support for multiple databases
[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         char *host;
38         char *user;
39         char *pass;
40         char *name;
41         char *socket;
42         int   port;
43 };
44 typedef struct mysql_database_s mysql_database_t; /* }}} */
45
46 static mysql_database_t **databases     = NULL;
47 static size_t             databases_num = 0;
48
49 static void mysql_database_free (mysql_database_t *db) /* {{{ */
50 {
51         if (db == NULL)
52                 return;
53
54         sfree (db->host);
55         sfree (db->user);
56         sfree (db->pass);
57         sfree (db->socket);
58         sfree (db);
59 } /* }}} void mysql_database_free */
60
61 /* Configuration handling functions {{{
62  *
63  * <Plugin mysql>
64  *   <Database "plugin_instance1">
65  *     Host "localhost"
66  *     Port 22000
67  *     ...
68  *   </Database>
69  * </Plugin>
70  */
71
72 static int mysql_config_set_string (char **ret_string, /* {{{ */
73                                     oconfig_item_t *ci)
74 {
75         char *string;
76
77         if ((ci->values_num != 1)
78             || (ci->values[0].type != OCONFIG_TYPE_STRING))
79         {
80                 WARNING ("mysql plugin: The `%s' config option "
81                          "needs exactly one string argument.", ci->key);
82                 return (-1);
83         }
84
85         string = strdup (ci->values[0].value.string);
86         if (string == NULL)
87         {
88                 ERROR ("mysql plugin: strdup failed.");
89                 return (-1);
90         }
91
92         if (*ret_string != NULL)
93                 free (*ret_string);
94         *ret_string = string;
95
96         return (0);
97 } /* }}} int mysql_config_set_string */
98
99 static int mysql_config_set_int (int *ret_int, /* {{{ */
100                                  oconfig_item_t *ci)
101 {
102         if ((ci->values_num != 1)
103             || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
104         {
105                 WARNING ("mysql plugin: The `%s' config option "
106                          "needs exactly one string argument.", ci->key);
107                 return (-1);
108         }
109
110         *ret_int = ci->values[0].value.number;
111
112         return (0);
113 } /* }}} int mysql_config_set_int */
114
115 static int mysql_config_add_database (oconfig_item_t *ci) /* {{{ */
116 {
117         mysql_database_t *db;
118         int status;
119         int i;
120
121         if ((ci->values_num != 1)
122             || (ci->values[0].type != OCONFIG_TYPE_STRING))
123         {
124                 WARNING ("mysql plugin: The `Database' block "
125                          "needs exactly one string argument.");
126                 return (-1);
127         }
128
129         db = (mysql_database_t *) malloc (sizeof (*db));
130         if (db == NULL)
131         {
132                 ERROR ("mysql plugin: malloc failed.");
133                 return (-1);
134         }
135         memset (db, 0, sizeof (*db));
136
137         status = mysql_config_set_string (&db->name, ci);
138         if (status != 0)
139         {
140                 sfree (db);
141                 return (status);
142         }
143
144         /* Fill the `mysql_database_t' structure.. */
145         for (i = 0; i < ci->children_num; i++)
146         {
147                 oconfig_item_t *child = ci->children + i;
148
149                 if (strcasecmp ("Host", child->key) == 0)
150                         status = mysql_config_set_string (&db->host, child);
151                 else if (strcasecmp ("User", child->key) == 0)
152                         status = mysql_config_set_string (&db->user, child);
153                 else if (strcasecmp ("Password", child->key) == 0)
154                         status = mysql_config_set_string (&db->pass, child);
155                 else if (strcasecmp ("Port", child->key) == 0)
156                         status = mysql_config_set_int (&db->port, child);
157                 else if (strcasecmp ("Socket", child->key) == 0)
158                         status = mysql_config_set_string (&db->socket, child);
159                 else
160                 {
161                         WARNING ("mysql plugin: Option `%s' not allowed here.", child->key);
162                         status = -1;
163                 }
164
165                 if (status != 0)
166                         break;
167         }
168
169         /* Check that all necessary options have been given. */
170         while (status == 0)
171         {
172                 if ((db->port < 0) || (db->port >= 65535))
173                 {
174                         ERROR ("mysql plugin: Port number out of range: %i",
175                                db->port);
176                         status = -1;
177                 }
178                 break;
179         } /* while (status == 0) */
180
181         /* If all went well, add this database to the global list of databases. */
182         if (status == 0)
183         {
184                 mysql_database_t **temp;
185
186                 temp = (mysql_database_t **) realloc (databases,
187                                                      sizeof (*databases) * (databases_num + 1));
188                 if (temp == NULL)
189                 {
190                         ERROR ("mysql plugin: realloc failed");
191                         status = -1;
192                 }
193                 else
194                 {
195                         databases = temp;
196                         databases[databases_num] = db;
197                         databases_num++;
198                 }
199         }
200
201         if (status != 0)
202         {
203                 mysql_database_free (db);
204                 return (-1);
205         }
206
207         return (0);
208 } /* }}} int mysql_config_add_database */
209
210 static int mysql_config (oconfig_item_t *ci) /* {{{ */
211 {
212         int status = 0;
213         int i;
214         oconfig_item_t *lci = NULL; /* legacy config */
215
216         for (i = 0; i < ci->children_num; i++)
217         {
218                 oconfig_item_t *child = ci->children + i;
219
220                 if (strcasecmp ("Database", child->key) == 0 && child->children_num > 0)
221                         mysql_config_add_database (child);
222                 else
223                 {
224                         /* legacy mode - convert to <Database ...> config */
225                         if (lci == NULL)
226                         {
227                                 lci = malloc (sizeof(*lci));
228                                 if (lci == NULL)
229                                 {
230                                         ERROR ("mysql plugin: malloc failed.");
231                                         return (-1);
232                                 }
233                                 memset (lci, '\0', sizeof (*lci));
234                         }
235                         if (strcasecmp ("Database", child->key) == 0)
236                         {
237                                 lci->key = child->key;
238                                 lci->values = child->values;
239                                 lci->values_num = child->values_num;
240                                 lci->parent = child->parent;
241                         }
242                         else
243                         {
244                                 lci->children_num++;
245                                 lci->children =
246                                         realloc (lci->children,
247                                                  lci->children_num * sizeof (*child));
248                                 if (lci->children == NULL)
249                                 {
250                                         ERROR ("mysql plugin: realloc failed.");
251                                         return (-1);
252                                 }
253                                 memcpy (&lci->children[lci->children_num-1], child, sizeof (*child));
254                         }
255                 }
256         } /* for (ci->children) */
257
258         if (lci)
259         {
260                 if (lci->key == NULL)
261                 {
262                         ERROR ("mysql plugin: no Database configured.");
263                         status = -1;
264                 }
265                 else
266                         mysql_config_add_database (lci);
267                 sfree (lci->children);
268                 sfree (lci);
269         }
270         return (status);
271 } /* }}} int mysql_config */
272
273 static MYSQL *getconnection (mysql_database_t *db)
274 {
275         static MYSQL *con;
276         static int    state;
277
278         static int wait_for = 0;
279         static int wait_increase = 60;
280
281         if (state != 0)
282         {
283                 int err;
284                 if ((err = mysql_ping (con)) != 0)
285                 {
286                         WARNING ("mysql_ping failed: %s", mysql_error (con));
287                         state = 0;
288                 }
289                 else
290                 {
291                         state = 1;
292                         return (con);
293                 }
294         }
295
296         if (wait_for > 0)
297         {
298                 wait_for -= interval_g;
299                 return (NULL);
300         }
301
302         wait_for = wait_increase;
303         wait_increase *= 2;
304         if (wait_increase > 86400)
305                 wait_increase = 86400;
306
307         if ((con = mysql_init (con)) == NULL)
308         {
309                 ERROR ("mysql_init failed: %s", mysql_error (con));
310                 state = 0;
311                 return (NULL);
312         }
313
314         if (mysql_real_connect (con, db->host, db->user, db->pass, db->name, db->port, db->socket, 0) == NULL)
315         {
316                 ERROR ("mysql_real_connect failed: %s", mysql_error (con));
317                 state = 0;
318                 return (NULL);
319         }
320         else
321         {
322                 state = 1;
323                 wait_for = 0;
324                 wait_increase = 60;
325                 return (con);
326         }
327 } /* static MYSQL *getconnection (void) */
328
329 static void set_plugin_instance (mysql_database_t *db, value_list_t *vl)
330 {
331         /* XXX legacy mode - no plugin_instance */
332         if (databases_num > 0)
333                 sstrncpy (vl->plugin_instance, db->name, sizeof (vl->plugin_instance));
334 }
335
336 static void counter_submit (const char *type, const char *type_instance,
337                 counter_t value, mysql_database_t *db)
338 {
339         value_t values[1];
340         value_list_t vl = VALUE_LIST_INIT;
341
342         values[0].counter = value;
343
344         vl.values = values;
345         vl.values_len = 1;
346         sstrncpy (vl.host, hostname_g, sizeof (vl.host));
347         sstrncpy (vl.plugin, "mysql", sizeof (vl.plugin));
348         sstrncpy (vl.type, type, sizeof (vl.type));
349         sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
350         set_plugin_instance (db, &vl);
351
352         plugin_dispatch_values (&vl);
353 } /* void counter_submit */
354
355 static void qcache_submit (counter_t hits, counter_t inserts,
356                 counter_t not_cached, counter_t lowmem_prunes,
357                 gauge_t queries_in_cache, mysql_database_t *db)
358 {
359         value_t values[5];
360         value_list_t vl = VALUE_LIST_INIT;
361
362         values[0].counter = hits;
363         values[1].counter = inserts;
364         values[2].counter = not_cached;
365         values[3].counter = lowmem_prunes;
366         values[4].gauge   = queries_in_cache;
367
368         vl.values = values;
369         vl.values_len = 5;
370         sstrncpy (vl.host, hostname_g, sizeof (vl.host));
371         sstrncpy (vl.plugin, "mysql", sizeof (vl.plugin));
372         sstrncpy (vl.type, "mysql_qcache", sizeof (vl.type));
373         set_plugin_instance (db, &vl);
374
375         plugin_dispatch_values (&vl);
376 } /* void qcache_submit */
377
378 static void threads_submit (gauge_t running, gauge_t connected, gauge_t cached,
379                 counter_t created, mysql_database_t *db)
380 {
381         value_t values[4];
382         value_list_t vl = VALUE_LIST_INIT;
383
384         values[0].gauge   = running;
385         values[1].gauge   = connected;
386         values[2].gauge   = cached;
387         values[3].counter = created;
388
389         vl.values = values;
390         vl.values_len = 4;
391         sstrncpy (vl.host, hostname_g, sizeof (vl.host));
392         sstrncpy (vl.plugin, "mysql", sizeof (vl.plugin));
393         sstrncpy (vl.type, "mysql_threads", sizeof (vl.type));
394         set_plugin_instance (db, &vl);
395
396         plugin_dispatch_values (&vl);
397 } /* void threads_submit */
398
399 static void traffic_submit (counter_t rx, counter_t tx, mysql_database_t *db)
400 {
401         value_t values[2];
402         value_list_t vl = VALUE_LIST_INIT;
403
404         values[0].counter = rx;
405         values[1].counter = tx;
406
407         vl.values = values;
408         vl.values_len = 2;
409         sstrncpy (vl.host, hostname_g, sizeof (vl.host));
410         sstrncpy (vl.plugin, "mysql", sizeof (vl.plugin));
411         sstrncpy (vl.type, "mysql_octets", sizeof (vl.type));
412         set_plugin_instance (db, &vl);
413
414         plugin_dispatch_values (&vl);
415 } /* void traffic_submit */
416
417 static int mysql_read_database (mysql_database_t *db)
418 {
419         MYSQL     *con;
420         MYSQL_RES *res;
421         MYSQL_ROW  row;
422         char      *query;
423         int        query_len;
424         int        field_num;
425
426         unsigned long long qcache_hits          = 0ULL;
427         unsigned long long qcache_inserts       = 0ULL;
428         unsigned long long qcache_not_cached    = 0ULL;
429         unsigned long long qcache_lowmem_prunes = 0ULL;
430         int qcache_queries_in_cache = -1;
431
432         int threads_running   = -1;
433         int threads_connected = -1;
434         int threads_cached    = -1;
435         unsigned long long threads_created = 0ULL;
436
437         unsigned long long traffic_incoming = 0ULL;
438         unsigned long long traffic_outgoing = 0ULL;
439
440         /* An error message will have been printed in this case */
441         if ((con = getconnection (db)) == NULL)
442                 return (-1);
443
444         query = "SHOW STATUS";
445         if (mysql_get_server_version (con) >= 50002)
446                 query = "SHOW GLOBAL STATUS";
447
448         query_len = strlen (query);
449
450         if (mysql_real_query (con, query, query_len))
451         {
452                 ERROR ("mysql_real_query failed: %s\n",
453                                 mysql_error (con));
454                 return (-1);
455         }
456
457         if ((res = mysql_store_result (con)) == NULL)
458         {
459                 ERROR ("mysql_store_result failed: %s\n",
460                                 mysql_error (con));
461                 return (-1);
462         }
463
464         field_num = mysql_num_fields (res);
465         while ((row = mysql_fetch_row (res)))
466         {
467                 char *key;
468                 unsigned long long val;
469
470                 key = row[0];
471                 val = atoll (row[1]);
472
473                 if (strncmp (key, "Com_", 4) == 0)
474                 {
475                         if (val == 0ULL)
476                                 continue;
477
478                         /* Ignore `prepared statements' */
479                         if (strncmp (key, "Com_stmt_", 9) != 0)
480                                 counter_submit ("mysql_commands", key + 4, val, db);
481                 }
482                 else if (strncmp (key, "Handler_", 8) == 0)
483                 {
484                         if (val == 0ULL)
485                                 continue;
486
487                         counter_submit ("mysql_handler", key + 8, val, db);
488                 }
489                 else if (strncmp (key, "Qcache_", 7) == 0)
490                 {
491                         if (strcmp (key, "Qcache_hits") == 0)
492                                 qcache_hits = val;
493                         else if (strcmp (key, "Qcache_inserts") == 0)
494                                 qcache_inserts = val;
495                         else if (strcmp (key, "Qcache_not_cached") == 0)
496                                 qcache_not_cached = val;
497                         else if (strcmp (key, "Qcache_lowmem_prunes") == 0)
498                                 qcache_lowmem_prunes = val;
499                         else if (strcmp (key, "Qcache_queries_in_cache") == 0)
500                                 qcache_queries_in_cache = (int) val;
501                 }
502                 else if (strncmp (key, "Bytes_", 6) == 0)
503                 {
504                         if (strcmp (key, "Bytes_received") == 0)
505                                 traffic_incoming += val;
506                         else if (strcmp (key, "Bytes_sent") == 0)
507                                 traffic_outgoing += val;
508                 }
509                 else if (strncmp (key, "Threads_", 8) == 0)
510                 {
511                         if (strcmp (key, "Threads_running") == 0)
512                                 threads_running = (int) val;
513                         else if (strcmp (key, "Threads_connected") == 0)
514                                 threads_connected = (int) val;
515                         else if (strcmp (key, "Threads_cached") == 0)
516                                 threads_cached = (int) val;
517                         else if (strcmp (key, "Threads_created") == 0)
518                                 threads_created = val;
519                 }
520         }
521         mysql_free_result (res); res = NULL;
522
523         if ((qcache_hits != 0ULL)
524                         || (qcache_inserts != 0ULL)
525                         || (qcache_not_cached != 0ULL)
526                         || (qcache_lowmem_prunes != 0ULL))
527                 qcache_submit (qcache_hits, qcache_inserts, qcache_not_cached,
528                                qcache_lowmem_prunes, qcache_queries_in_cache, db);
529
530         if (threads_created != 0ULL)
531                 threads_submit (threads_running, threads_connected,
532                                 threads_cached, threads_created, db);
533
534         traffic_submit  (traffic_incoming, traffic_outgoing, db);
535
536         /* mysql_close (con); */
537
538         return (0);
539 } /* int mysql_read_database */
540
541 static int mysql_read (void)
542 {
543         size_t i;
544         int success = 0;
545         int status;
546
547         for (i = 0; i < databases_num; i++)
548         {
549                 status = mysql_read_database (databases[i]);
550                 if (status == 0)
551                         success++;
552         }
553
554         if (success == 0)
555         {
556                 ERROR ("mysql plugin: No database could be read. Will return an error so "
557                        "the plugin will be delayed.");
558                 return (-1);
559         }
560
561         return (0);
562 } /* int mysql_read */
563
564 void module_register (void)
565 {
566         plugin_register_complex_config ("mysql", mysql_config);
567         plugin_register_read ("mysql", mysql_read);
568 } /* void module_register */