Fix compile time issues
[collectd.git] / src / mysql.c
1 /**
2  * collectd - src/mysql.c
3  * Copyright (C) 2006-2010  Florian octo Forster
4  * Copyright (C) 2008       Mirko Buffoni
5  * Copyright (C) 2009       Doug MacEachern
6  * Copyright (C) 2009       Sebastian tokkee Harl
7  * Copyright (C) 2009       Rodolphe QuiĆ©deville
8  *
9  * This program is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU General Public License as published by the
11  * Free Software Foundation; only version 2 of the License is applicable.
12  *
13  * This program is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License along
19  * with this program; if not, write to the Free Software Foundation, Inc.,
20  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
21  *
22  * Authors:
23  *   Florian octo Forster <octo at collectd.org>
24  *   Mirko Buffoni <briareos at eswat.org>
25  *   Doug MacEachern <dougm at hyperic.com>
26  *   Sebastian tokkee Harl <sh at tokkee.org>
27  *   Rodolphe QuiĆ©deville <rquiedeville at bearstech.com>
28  **/
29
30 #include "collectd.h"
31
32 #include "plugin.h"
33 #include "utils/common/common.h"
34
35 #ifdef HAVE_MYSQL_H
36 #include <mysql.h>
37 #elif defined(HAVE_MYSQL_MYSQL_H)
38 #include <mysql/mysql.h>
39 #endif
40
41 struct mysql_database_s /* {{{ */
42 {
43   char *instance;
44   char *alias;
45   char *host;
46   char *user;
47   char *pass;
48   char *database;
49
50   /* mysql_ssl_set params */
51   char *key;
52   char *cert;
53   char *ca;
54   char *capath;
55   char *cipher;
56
57   char *socket;
58   int port;
59   int timeout;
60
61   bool master_stats;
62   bool slave_stats;
63   bool innodb_stats;
64   bool wsrep_stats;
65
66   bool slave_notif;
67   bool slave_io_running;
68   bool slave_sql_running;
69
70   MYSQL *con;
71   bool is_connected;
72 };
73 typedef struct mysql_database_s mysql_database_t; /* }}} */
74
75 static int mysql_read(user_data_t *ud);
76
77 static void mysql_database_free(void *arg) /* {{{ */
78 {
79   mysql_database_t *db;
80
81   DEBUG("mysql plugin: mysql_database_free (arg = %p);", arg);
82
83   db = arg;
84
85   if (db == NULL)
86     return;
87
88   if (db->con != NULL)
89     mysql_close(db->con);
90
91   sfree(db->alias);
92   sfree(db->host);
93   sfree(db->user);
94   sfree(db->pass);
95   sfree(db->socket);
96   sfree(db->instance);
97   sfree(db->database);
98   sfree(db->key);
99   sfree(db->cert);
100   sfree(db->ca);
101   sfree(db->capath);
102   sfree(db->cipher);
103   sfree(db);
104 } /* }}} void mysql_database_free */
105
106 /* Configuration handling functions {{{
107  *
108  * <Plugin mysql>
109  *   <Database "plugin_instance1">
110  *     Host "localhost"
111  *     Port 22000
112  *     ...
113  *   </Database>
114  * </Plugin>
115  */
116 static int mysql_config_database(oconfig_item_t *ci) /* {{{ */
117 {
118   mysql_database_t *db;
119   int status = 0;
120
121   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
122     WARNING("mysql plugin: The `Database' block "
123             "needs exactly one string argument.");
124     return -1;
125   }
126
127   db = calloc(1, sizeof(*db));
128   if (db == NULL) {
129     ERROR("mysql plugin: calloc failed.");
130     return -1;
131   }
132
133   /* initialize all the pointers */
134   db->alias = NULL;
135   db->host = NULL;
136   db->user = NULL;
137   db->pass = NULL;
138   db->database = NULL;
139   db->key = NULL;
140   db->cert = NULL;
141   db->ca = NULL;
142   db->capath = NULL;
143   db->cipher = NULL;
144
145   db->socket = NULL;
146   db->con = NULL;
147   db->timeout = 0;
148
149   /* trigger a notification, if it's not running */
150   db->slave_io_running = true;
151   db->slave_sql_running = true;
152
153   status = cf_util_get_string(ci, &db->instance);
154   if (status != 0) {
155     sfree(db);
156     return status;
157   }
158   assert(db->instance != NULL);
159
160   /* Fill the `mysql_database_t' structure.. */
161   for (int i = 0; i < ci->children_num; i++) {
162     oconfig_item_t *child = ci->children + i;
163
164     if (strcasecmp("Alias", child->key) == 0)
165       status = cf_util_get_string(child, &db->alias);
166     else if (strcasecmp("Host", child->key) == 0)
167       status = cf_util_get_string(child, &db->host);
168     else if (strcasecmp("User", child->key) == 0)
169       status = cf_util_get_string(child, &db->user);
170     else if (strcasecmp("Password", child->key) == 0)
171       status = cf_util_get_string(child, &db->pass);
172     else if (strcasecmp("Port", child->key) == 0) {
173       status = cf_util_get_port_number(child);
174       if (status > 0) {
175         db->port = status;
176         status = 0;
177       }
178     } else if (strcasecmp("Socket", child->key) == 0)
179       status = cf_util_get_string(child, &db->socket);
180     else if (strcasecmp("Database", child->key) == 0)
181       status = cf_util_get_string(child, &db->database);
182     else if (strcasecmp("SSLKey", child->key) == 0)
183       status = cf_util_get_string(child, &db->key);
184     else if (strcasecmp("SSLCert", child->key) == 0)
185       status = cf_util_get_string(child, &db->cert);
186     else if (strcasecmp("SSLCA", child->key) == 0)
187       status = cf_util_get_string(child, &db->ca);
188     else if (strcasecmp("SSLCAPath", child->key) == 0)
189       status = cf_util_get_string(child, &db->capath);
190     else if (strcasecmp("SSLCipher", child->key) == 0)
191       status = cf_util_get_string(child, &db->cipher);
192     else if (strcasecmp("ConnectTimeout", child->key) == 0)
193       status = cf_util_get_int(child, &db->timeout);
194     else if (strcasecmp("MasterStats", child->key) == 0)
195       status = cf_util_get_boolean(child, &db->master_stats);
196     else if (strcasecmp("SlaveStats", child->key) == 0)
197       status = cf_util_get_boolean(child, &db->slave_stats);
198     else if (strcasecmp("SlaveNotifications", child->key) == 0)
199       status = cf_util_get_boolean(child, &db->slave_notif);
200     else if (strcasecmp("InnodbStats", child->key) == 0)
201       status = cf_util_get_boolean(child, &db->innodb_stats);
202     else if (strcasecmp("WsrepStats", child->key) == 0)
203       status = cf_util_get_boolean(child, &db->wsrep_stats);
204     else {
205       WARNING("mysql plugin: Option `%s' not allowed here.", child->key);
206       status = -1;
207     }
208
209     if (status != 0)
210       break;
211   }
212
213   /* If all went well, register this database for reading */
214   if (status == 0) {
215     char cb_name[DATA_MAX_NAME_LEN];
216
217     DEBUG("mysql plugin: Registering new read callback: %s",
218           (db->database != NULL) ? db->database : "<default>");
219
220     if (db->instance != NULL)
221       ssnprintf(cb_name, sizeof(cb_name), "mysql-%s", db->instance);
222     else
223       sstrncpy(cb_name, "mysql", sizeof(cb_name));
224
225     plugin_register_complex_read(
226         /* group = */ NULL, cb_name, mysql_read, /* interval = */ 0,
227         &(user_data_t){
228             .data = db,
229             .free_func = mysql_database_free,
230         });
231   } else {
232     mysql_database_free(db);
233     return -1;
234   }
235
236   return 0;
237 } /* }}} int mysql_config_database */
238
239 static int mysql_config(oconfig_item_t *ci) /* {{{ */
240 {
241   if (ci == NULL)
242     return EINVAL;
243
244   /* Fill the `mysql_database_t' structure.. */
245   for (int i = 0; i < ci->children_num; i++) {
246     oconfig_item_t *child = ci->children + i;
247
248     if (strcasecmp("Database", child->key) == 0)
249       mysql_config_database(child);
250     else
251       WARNING("mysql plugin: Option \"%s\" not allowed here.", child->key);
252   }
253
254   return 0;
255 } /* }}} int mysql_config */
256
257 /* }}} End of configuration handling functions */
258
259 static MYSQL *getconnection(mysql_database_t *db) {
260   const char *cipher;
261
262   if (db->is_connected) {
263     int status;
264
265     status = mysql_ping(db->con);
266     if (status == 0)
267       return db->con;
268
269     WARNING("mysql plugin: Lost connection to instance \"%s\": %s",
270             db->instance, mysql_error(db->con));
271   }
272   db->is_connected = false;
273
274   /* Close the old connection before initializing a new one. */
275   if (db->con != NULL) {
276     mysql_close(db->con);
277     db->con = NULL;
278   }
279
280   db->con = mysql_init(NULL);
281   if (db->con == NULL) {
282     ERROR("mysql plugin: mysql_init failed: %s", mysql_error(db->con));
283     return NULL;
284   }
285
286   /* Configure TCP connect timeout (default: 0) */
287   db->con->options.connect_timeout = db->timeout;
288
289   mysql_ssl_set(db->con, db->key, db->cert, db->ca, db->capath, db->cipher);
290
291   if (mysql_real_connect(db->con, db->host, db->user, db->pass, db->database,
292                          db->port, db->socket, 0) == NULL) {
293     ERROR("mysql plugin: Failed to connect to database %s "
294           "at server %s: %s",
295           (db->database != NULL) ? db->database : "<none>",
296           (db->host != NULL) ? db->host : "localhost", mysql_error(db->con));
297     return NULL;
298   }
299
300   cipher = mysql_get_ssl_cipher(db->con);
301
302   INFO("mysql plugin: Successfully connected to database %s "
303        "at server %s with cipher %s "
304        "(server version: %s, protocol version: %d) ",
305        (db->database != NULL) ? db->database : "<none>",
306        mysql_get_host_info(db->con), (cipher != NULL) ? cipher : "<none>",
307        mysql_get_server_info(db->con), mysql_get_proto_info(db->con));
308
309   db->is_connected = true;
310   return db->con;
311 } /* static MYSQL *getconnection (mysql_database_t *db) */
312
313 static void set_host(mysql_database_t *db, char *buf, size_t buflen) {
314   if (db->alias)
315     sstrncpy(buf, db->alias, buflen);
316   else if ((db->host == NULL) || (strcmp("", db->host) == 0) ||
317            (strcmp("127.0.0.1", db->host) == 0) ||
318            (strcmp("localhost", db->host) == 0))
319     sstrncpy(buf, hostname_g, buflen);
320   else
321     sstrncpy(buf, db->host, buflen);
322 } /* void set_host */
323
324 static void submit(const char *type, const char *type_instance, value_t *values,
325                    size_t values_len, mysql_database_t *db) {
326   value_list_t vl = VALUE_LIST_INIT;
327
328   vl.values = values;
329   vl.values_len = values_len;
330
331   set_host(db, vl.host, sizeof(vl.host));
332
333   sstrncpy(vl.plugin, "mysql", sizeof(vl.plugin));
334
335   /* Assured by "mysql_config_database" */
336   assert(db->instance != NULL);
337   sstrncpy(vl.plugin_instance, db->instance, sizeof(vl.plugin_instance));
338
339   sstrncpy(vl.type, type, sizeof(vl.type));
340   if (type_instance != NULL)
341     sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
342
343   plugin_dispatch_values(&vl);
344 } /* submit */
345
346 static void gauge_submit(const char *type, const char *type_instance,
347                          gauge_t value, mysql_database_t *db) {
348   submit(type, type_instance, &(value_t){.gauge = value}, 1, db);
349 } /* void gauge_submit */
350
351 static void derive_submit(const char *type, const char *type_instance,
352                           derive_t value, mysql_database_t *db) {
353   submit(type, type_instance, &(value_t){.derive = value}, 1, db);
354 } /* void derive_submit */
355
356 static void traffic_submit(derive_t rx, derive_t tx, mysql_database_t *db) {
357   value_t values[] = {
358       {.derive = rx},
359       {.derive = tx},
360   };
361
362   submit("mysql_octets", NULL, values, STATIC_ARRAY_SIZE(values), db);
363 } /* void traffic_submit */
364
365 static MYSQL_RES *exec_query(MYSQL *con, const char *query) {
366   MYSQL_RES *res;
367
368   size_t query_len = strlen(query);
369
370   if (mysql_real_query(con, query, query_len)) {
371     ERROR("mysql plugin: Failed to execute query: %s", mysql_error(con));
372     INFO("mysql plugin: SQL query was: %s", query);
373     return NULL;
374   }
375
376   res = mysql_store_result(con);
377   if (res == NULL) {
378     ERROR("mysql plugin: Failed to store query result: %s", mysql_error(con));
379     INFO("mysql plugin: SQL query was: %s", query);
380     return NULL;
381   }
382
383   return res;
384 } /* exec_query */
385
386 static int mysql_read_master_stats(mysql_database_t *db, MYSQL *con) {
387   MYSQL_RES *res;
388   MYSQL_ROW row;
389
390   const char *query;
391   int field_num;
392   unsigned long long position;
393
394   query = "SHOW MASTER STATUS";
395
396   res = exec_query(con, query);
397   if (res == NULL)
398     return -1;
399
400   row = mysql_fetch_row(res);
401   if (row == NULL) {
402     ERROR("mysql plugin: Failed to get master statistics: "
403           "`%s' did not return any rows.",
404           query);
405     mysql_free_result(res);
406     return -1;
407   }
408
409   field_num = mysql_num_fields(res);
410   if (field_num < 2) {
411     ERROR("mysql plugin: Failed to get master statistics: "
412           "`%s' returned less than two columns.",
413           query);
414     mysql_free_result(res);
415     return -1;
416   }
417
418   position = atoll(row[1]);
419   derive_submit("mysql_log_position", "master-bin", position, db);
420
421   row = mysql_fetch_row(res);
422   if (row != NULL)
423     WARNING("mysql plugin: `%s' returned more than one row - "
424             "ignoring further results.",
425             query);
426
427   mysql_free_result(res);
428
429   return 0;
430 } /* mysql_read_master_stats */
431
432 static int mysql_read_slave_stats(mysql_database_t *db, MYSQL *con) {
433   MYSQL_RES *res;
434   MYSQL_ROW row;
435
436   const char *query;
437   int field_num;
438
439   /* WTF? libmysqlclient does not seem to provide any means to
440    * translate a column name to a column index ... :-/ */
441   const int READ_MASTER_LOG_POS_IDX = 6;
442   const int SLAVE_IO_RUNNING_IDX = 10;
443   const int SLAVE_SQL_RUNNING_IDX = 11;
444   const int EXEC_MASTER_LOG_POS_IDX = 21;
445   const int SECONDS_BEHIND_MASTER_IDX = 32;
446
447   query = "SHOW SLAVE STATUS";
448
449   res = exec_query(con, query);
450   if (res == NULL)
451     return -1;
452
453   row = mysql_fetch_row(res);
454   if (row == NULL) {
455     ERROR("mysql plugin: Failed to get slave statistics: "
456           "`%s' did not return any rows.",
457           query);
458     mysql_free_result(res);
459     return -1;
460   }
461
462   field_num = mysql_num_fields(res);
463   if (field_num < 33) {
464     ERROR("mysql plugin: Failed to get slave statistics: "
465           "`%s' returned less than 33 columns.",
466           query);
467     mysql_free_result(res);
468     return -1;
469   }
470
471   if (db->slave_stats) {
472     unsigned long long counter;
473     double gauge;
474
475     counter = atoll(row[READ_MASTER_LOG_POS_IDX]);
476     derive_submit("mysql_log_position", "slave-read", counter, db);
477
478     counter = atoll(row[EXEC_MASTER_LOG_POS_IDX]);
479     derive_submit("mysql_log_position", "slave-exec", counter, db);
480
481     if (row[SECONDS_BEHIND_MASTER_IDX] != NULL) {
482       gauge = atof(row[SECONDS_BEHIND_MASTER_IDX]);
483       gauge_submit("time_offset", NULL, gauge, db);
484     }
485   }
486
487   if (db->slave_notif) {
488     notification_t n = {0,  cdtime(),      "", "",  "mysql",
489                         "", "time_offset", "", NULL};
490
491     char *io, *sql;
492
493     io = row[SLAVE_IO_RUNNING_IDX];
494     sql = row[SLAVE_SQL_RUNNING_IDX];
495
496     set_host(db, n.host, sizeof(n.host));
497
498     /* Assured by "mysql_config_database" */
499     assert(db->instance != NULL);
500     sstrncpy(n.plugin_instance, db->instance, sizeof(n.plugin_instance));
501
502     if (((io == NULL) || (strcasecmp(io, "yes") != 0)) &&
503         (db->slave_io_running)) {
504       n.severity = NOTIF_WARNING;
505       ssnprintf(n.message, sizeof(n.message),
506                 "slave I/O thread not started or not connected to master");
507       plugin_dispatch_notification(&n);
508       db->slave_io_running = false;
509     } else if (((io != NULL) && (strcasecmp(io, "yes") == 0)) &&
510                (!db->slave_io_running)) {
511       n.severity = NOTIF_OKAY;
512       ssnprintf(n.message, sizeof(n.message),
513                 "slave I/O thread started and connected to master");
514       plugin_dispatch_notification(&n);
515       db->slave_io_running = true;
516     }
517
518     if (((sql == NULL) || (strcasecmp(sql, "yes") != 0)) &&
519         (db->slave_sql_running)) {
520       n.severity = NOTIF_WARNING;
521       ssnprintf(n.message, sizeof(n.message), "slave SQL thread not started");
522       plugin_dispatch_notification(&n);
523       db->slave_sql_running = false;
524     } else if (((sql != NULL) && (strcasecmp(sql, "yes") == 0)) &&
525                (!db->slave_sql_running)) {
526       n.severity = NOTIF_OKAY;
527       ssnprintf(n.message, sizeof(n.message), "slave SQL thread started");
528       plugin_dispatch_notification(&n);
529       db->slave_sql_running = true;
530     }
531   }
532
533   row = mysql_fetch_row(res);
534   if (row != NULL)
535     WARNING("mysql plugin: `%s' returned more than one row - "
536             "ignoring further results.",
537             query);
538
539   mysql_free_result(res);
540
541   return 0;
542 } /* mysql_read_slave_stats */
543
544 static int mysql_read_innodb_stats(mysql_database_t *db, MYSQL *con) {
545   MYSQL_RES *res;
546   MYSQL_ROW row;
547
548   const char *query;
549   struct {
550     const char *key;
551     const char *type;
552     int ds_type;
553   } metrics[] = {
554       {"metadata_mem_pool_size", "bytes", DS_TYPE_GAUGE},
555       {"lock_deadlocks", "mysql_locks", DS_TYPE_DERIVE},
556       {"lock_timeouts", "mysql_locks", DS_TYPE_DERIVE},
557       {"lock_row_lock_current_waits", "mysql_locks", DS_TYPE_DERIVE},
558       {"buffer_pool_size", "bytes", DS_TYPE_GAUGE},
559
560       {"os_log_bytes_written", "operations", DS_TYPE_DERIVE},
561       {"os_log_pending_fsyncs", "operations", DS_TYPE_DERIVE},
562       {"os_log_pending_writes", "operations", DS_TYPE_DERIVE},
563
564       {"trx_rseg_history_len", "gauge", DS_TYPE_GAUGE},
565
566       {"adaptive_hash_searches", "operations", DS_TYPE_DERIVE},
567
568       {"file_num_open_files", "gauge", DS_TYPE_GAUGE},
569
570       {"ibuf_merges_insert", "operations", DS_TYPE_DERIVE},
571       {"ibuf_merges_delete_mark", "operations", DS_TYPE_DERIVE},
572       {"ibuf_merges_delete", "operations", DS_TYPE_DERIVE},
573       {"ibuf_merges_discard_insert", "operations", DS_TYPE_DERIVE},
574       {"ibuf_merges_discard_delete_mark", "operations", DS_TYPE_DERIVE},
575       {"ibuf_merges_discard_delete", "operations", DS_TYPE_DERIVE},
576       {"ibuf_merges_discard_merges", "operations", DS_TYPE_DERIVE},
577       {"ibuf_size", "bytes", DS_TYPE_GAUGE},
578
579       {"innodb_activity_count", "gauge", DS_TYPE_GAUGE},
580
581       {"innodb_rwlock_s_spin_waits", "operations", DS_TYPE_DERIVE},
582       {"innodb_rwlock_x_spin_waits", "operations", DS_TYPE_DERIVE},
583       {"innodb_rwlock_s_spin_rounds", "operations", DS_TYPE_DERIVE},
584       {"innodb_rwlock_x_spin_rounds", "operations", DS_TYPE_DERIVE},
585       {"innodb_rwlock_s_os_waits", "operations", DS_TYPE_DERIVE},
586       {"innodb_rwlock_x_os_waits", "operations", DS_TYPE_DERIVE},
587
588       {"dml_reads", "operations", DS_TYPE_DERIVE},
589       {"dml_inserts", "operations", DS_TYPE_DERIVE},
590       {"dml_deletes", "operations", DS_TYPE_DERIVE},
591       {"dml_updates", "operations", DS_TYPE_DERIVE},
592
593       {NULL, NULL, 0}};
594
595   query = "SELECT name, count, type FROM information_schema.innodb_metrics "
596           "WHERE status = 'enabled'";
597
598   res = exec_query(con, query);
599   if (res == NULL)
600     return -1;
601
602   while ((row = mysql_fetch_row(res))) {
603     int i;
604     char *key;
605     unsigned long long val;
606
607     key = row[0];
608     val = atoll(row[1]);
609
610     for (i = 0; metrics[i].key != NULL && strcmp(metrics[i].key, key) != 0; i++)
611       ;
612
613     if (metrics[i].key == NULL)
614       continue;
615
616     switch (metrics[i].ds_type) {
617     case DS_TYPE_COUNTER:
618       derive_submit(metrics[i].type, key, (counter_t)val, db);
619       break;
620     case DS_TYPE_GAUGE:
621       gauge_submit(metrics[i].type, key, (gauge_t)val, db);
622       break;
623     case DS_TYPE_DERIVE:
624       derive_submit(metrics[i].type, key, (derive_t)val, db);
625       break;
626     }
627   }
628
629   mysql_free_result(res);
630   return 0;
631 }
632
633 static int mysql_read_wsrep_stats(mysql_database_t *db, MYSQL *con) {
634   MYSQL_RES *res;
635   MYSQL_ROW row;
636
637   const char *query;
638   struct {
639     const char *key;
640     const char *type;
641     int ds_type;
642   } metrics[] = {
643
644       {"wsrep_apply_oooe", "operations", DS_TYPE_DERIVE},
645       {"wsrep_apply_oool", "operations", DS_TYPE_DERIVE},
646       {"wsrep_causal_reads", "operations", DS_TYPE_DERIVE},
647       {"wsrep_commit_oooe", "operations", DS_TYPE_DERIVE},
648       {"wsrep_commit_oool", "operations", DS_TYPE_DERIVE},
649       {"wsrep_flow_control_recv", "operations", DS_TYPE_DERIVE},
650       {"wsrep_flow_control_sent", "operations", DS_TYPE_DERIVE},
651       {"wsrep_flow_control_paused", "operations", DS_TYPE_DERIVE},
652       {"wsrep_local_bf_aborts", "operations", DS_TYPE_DERIVE},
653       {"wsrep_local_cert_failures", "operations", DS_TYPE_DERIVE},
654       {"wsrep_local_commits", "operations", DS_TYPE_DERIVE},
655       {"wsrep_local_replays", "operations", DS_TYPE_DERIVE},
656       {"wsrep_received", "operations", DS_TYPE_DERIVE},
657       {"wsrep_replicated", "operations", DS_TYPE_DERIVE},
658
659       {"wsrep_received_bytes", "total_bytes", DS_TYPE_DERIVE},
660       {"wsrep_replicated_bytes", "total_bytes", DS_TYPE_DERIVE},
661
662       {"wsrep_apply_window", "gauge", DS_TYPE_GAUGE},
663       {"wsrep_commit_window", "gauge", DS_TYPE_GAUGE},
664
665       {"wsrep_cluster_size", "gauge", DS_TYPE_GAUGE},
666       {"wsrep_cert_deps_distance", "gauge", DS_TYPE_GAUGE},
667
668       {"wsrep_local_recv_queue", "queue_length", DS_TYPE_GAUGE},
669       {"wsrep_local_send_queue", "queue_length", DS_TYPE_GAUGE},
670
671       {NULL, NULL, 0}
672
673   };
674
675   query = "SHOW GLOBAL STATUS LIKE 'wsrep_%'";
676
677   res = exec_query(con, query);
678   if (res == NULL)
679     return -1;
680
681   row = mysql_fetch_row(res);
682   if (row == NULL) {
683     ERROR("mysql plugin: Failed to get wsrep statistics: "
684           "`%s' did not return any rows.",
685           query);
686     mysql_free_result(res);
687     return -1;
688   }
689
690   while ((row = mysql_fetch_row(res))) {
691     int i;
692     char *key;
693     unsigned long long val;
694
695     key = row[0];
696     val = atoll(row[1]);
697
698     for (i = 0; metrics[i].key != NULL && strcmp(metrics[i].key, key) != 0; i++)
699       ;
700
701     if (metrics[i].key == NULL)
702       continue;
703
704     switch (metrics[i].ds_type) {
705     case DS_TYPE_GAUGE:
706       gauge_submit(metrics[i].type, key, (gauge_t)val, db);
707       break;
708     case DS_TYPE_DERIVE:
709       derive_submit(metrics[i].type, key, (derive_t)val, db);
710       break;
711     }
712   }
713
714   mysql_free_result(res);
715   return 0;
716 } /* mysql_read_wsrep_stats */
717
718 static int mysql_read(user_data_t *ud) {
719   mysql_database_t *db;
720   MYSQL *con;
721   MYSQL_RES *res;
722   MYSQL_ROW row;
723   const char *query;
724
725   derive_t qcache_hits = 0;
726   derive_t qcache_inserts = 0;
727   derive_t qcache_not_cached = 0;
728   derive_t qcache_lowmem_prunes = 0;
729   gauge_t qcache_queries_in_cache = NAN;
730
731   gauge_t threads_running = NAN;
732   gauge_t threads_connected = NAN;
733   gauge_t threads_cached = NAN;
734   derive_t threads_created = 0;
735
736   unsigned long long traffic_incoming = 0ULL;
737   unsigned long long traffic_outgoing = 0ULL;
738   unsigned long mysql_version = 0ULL;
739
740   if ((ud == NULL) || (ud->data == NULL)) {
741     ERROR("mysql plugin: mysql_database_read: Invalid user data.");
742     return -1;
743   }
744
745   db = (mysql_database_t *)ud->data;
746
747   /* An error message will have been printed in this case */
748   if ((con = getconnection(db)) == NULL)
749     return -1;
750
751   mysql_version = mysql_get_server_version(con);
752
753   query = "SHOW STATUS";
754   if (mysql_version >= 50002)
755     query = "SHOW GLOBAL STATUS";
756
757   res = exec_query(con, query);
758   if (res == NULL)
759     return -1;
760
761   while ((row = mysql_fetch_row(res))) {
762     char *key;
763     unsigned long long val;
764
765     key = row[0];
766     val = atoll(row[1]);
767
768     if (strncmp(key, "Com_", strlen("Com_")) == 0) {
769       if (val == 0ULL)
770         continue;
771
772       /* Ignore `prepared statements' */
773       if (strncmp(key, "Com_stmt_", strlen("Com_stmt_")) != 0)
774         derive_submit("mysql_commands", key + strlen("Com_"), val, db);
775     } else if (strncmp(key, "Handler_", strlen("Handler_")) == 0) {
776       if (val == 0ULL)
777         continue;
778
779       derive_submit("mysql_handler", key + strlen("Handler_"), val, db);
780     } else if (strncmp(key, "Qcache_", strlen("Qcache_")) == 0) {
781       if (strcmp(key, "Qcache_hits") == 0)
782         qcache_hits = (derive_t)val;
783       else if (strcmp(key, "Qcache_inserts") == 0)
784         qcache_inserts = (derive_t)val;
785       else if (strcmp(key, "Qcache_not_cached") == 0)
786         qcache_not_cached = (derive_t)val;
787       else if (strcmp(key, "Qcache_lowmem_prunes") == 0)
788         qcache_lowmem_prunes = (derive_t)val;
789       else if (strcmp(key, "Qcache_queries_in_cache") == 0)
790         qcache_queries_in_cache = (gauge_t)val;
791     } else if (strncmp(key, "Bytes_", strlen("Bytes_")) == 0) {
792       if (strcmp(key, "Bytes_received") == 0)
793         traffic_incoming += val;
794       else if (strcmp(key, "Bytes_sent") == 0)
795         traffic_outgoing += val;
796     } else if (strncmp(key, "Threads_", strlen("Threads_")) == 0) {
797       if (strcmp(key, "Threads_running") == 0)
798         threads_running = (gauge_t)val;
799       else if (strcmp(key, "Threads_connected") == 0)
800         threads_connected = (gauge_t)val;
801       else if (strcmp(key, "Threads_cached") == 0)
802         threads_cached = (gauge_t)val;
803       else if (strcmp(key, "Threads_created") == 0)
804         threads_created = (derive_t)val;
805     } else if (strncmp(key, "Table_locks_", strlen("Table_locks_")) == 0) {
806       derive_submit("mysql_locks", key + strlen("Table_locks_"), val, db);
807     } else if (db->innodb_stats &&
808                strncmp(key, "Innodb_", strlen("Innodb_")) == 0) {
809       /* buffer pool */
810       if (strcmp(key, "Innodb_buffer_pool_pages_data") == 0)
811         gauge_submit("mysql_bpool_pages", "data", val, db);
812       else if (strcmp(key, "Innodb_buffer_pool_pages_dirty") == 0)
813         gauge_submit("mysql_bpool_pages", "dirty", val, db);
814       else if (strcmp(key, "Innodb_buffer_pool_pages_flushed") == 0)
815         derive_submit("mysql_bpool_counters", "pages_flushed", val, db);
816       else if (strcmp(key, "Innodb_buffer_pool_pages_free") == 0)
817         gauge_submit("mysql_bpool_pages", "free", val, db);
818       else if (strcmp(key, "Innodb_buffer_pool_pages_misc") == 0)
819         gauge_submit("mysql_bpool_pages", "misc", val, db);
820       else if (strcmp(key, "Innodb_buffer_pool_pages_total") == 0)
821         gauge_submit("mysql_bpool_pages", "total", val, db);
822       else if (strcmp(key, "Innodb_buffer_pool_read_ahead_rnd") == 0)
823         derive_submit("mysql_bpool_counters", "read_ahead_rnd", val, db);
824       else if (strcmp(key, "Innodb_buffer_pool_read_ahead") == 0)
825         derive_submit("mysql_bpool_counters", "read_ahead", val, db);
826       else if (strcmp(key, "Innodb_buffer_pool_read_ahead_evicted") == 0)
827         derive_submit("mysql_bpool_counters", "read_ahead_evicted", val, db);
828       else if (strcmp(key, "Innodb_buffer_pool_read_requests") == 0)
829         derive_submit("mysql_bpool_counters", "read_requests", val, db);
830       else if (strcmp(key, "Innodb_buffer_pool_reads") == 0)
831         derive_submit("mysql_bpool_counters", "reads", val, db);
832       else if (strcmp(key, "Innodb_buffer_pool_wait_free") == 0)
833         derive_submit("mysql_bpool_counters", "wait_free", val, db);
834       else if (strcmp(key, "Innodb_buffer_pool_write_requests") == 0)
835         derive_submit("mysql_bpool_counters", "write_requests", val, db);
836       else if (strcmp(key, "Innodb_buffer_pool_bytes_data") == 0)
837         gauge_submit("mysql_bpool_bytes", "data", val, db);
838       else if (strcmp(key, "Innodb_buffer_pool_bytes_dirty") == 0)
839         gauge_submit("mysql_bpool_bytes", "dirty", val, db);
840
841       /* data */
842       if (strcmp(key, "Innodb_data_fsyncs") == 0)
843         derive_submit("mysql_innodb_data", "fsyncs", val, db);
844       else if (strcmp(key, "Innodb_data_read") == 0)
845         derive_submit("mysql_innodb_data", "read", val, db);
846       else if (strcmp(key, "Innodb_data_reads") == 0)
847         derive_submit("mysql_innodb_data", "reads", val, db);
848       else if (strcmp(key, "Innodb_data_writes") == 0)
849         derive_submit("mysql_innodb_data", "writes", val, db);
850       else if (strcmp(key, "Innodb_data_written") == 0)
851         derive_submit("mysql_innodb_data", "written", val, db);
852
853       /* double write */
854       else if (strcmp(key, "Innodb_dblwr_writes") == 0)
855         derive_submit("mysql_innodb_dblwr", "writes", val, db);
856       else if (strcmp(key, "Innodb_dblwr_pages_written") == 0)
857         derive_submit("mysql_innodb_dblwr", "written", val, db);
858       else if (strcmp(key, "Innodb_dblwr_page_size") == 0)
859         gauge_submit("mysql_innodb_dblwr", "page_size", val, db);
860
861       /* log */
862       else if (strcmp(key, "Innodb_log_waits") == 0)
863         derive_submit("mysql_innodb_log", "waits", val, db);
864       else if (strcmp(key, "Innodb_log_write_requests") == 0)
865         derive_submit("mysql_innodb_log", "write_requests", val, db);
866       else if (strcmp(key, "Innodb_log_writes") == 0)
867         derive_submit("mysql_innodb_log", "writes", val, db);
868       else if (strcmp(key, "Innodb_os_log_fsyncs") == 0)
869         derive_submit("mysql_innodb_log", "fsyncs", val, db);
870       else if (strcmp(key, "Innodb_os_log_written") == 0)
871         derive_submit("mysql_innodb_log", "written", val, db);
872
873       /* pages */
874       else if (strcmp(key, "Innodb_pages_created") == 0)
875         derive_submit("mysql_innodb_pages", "created", val, db);
876       else if (strcmp(key, "Innodb_pages_read") == 0)
877         derive_submit("mysql_innodb_pages", "read", val, db);
878       else if (strcmp(key, "Innodb_pages_written") == 0)
879         derive_submit("mysql_innodb_pages", "written", val, db);
880
881       /* row lock */
882       else if (strcmp(key, "Innodb_row_lock_time") == 0)
883         derive_submit("mysql_innodb_row_lock", "time", val, db);
884       else if (strcmp(key, "Innodb_row_lock_waits") == 0)
885         derive_submit("mysql_innodb_row_lock", "waits", val, db);
886
887       /* rows */
888       else if (strcmp(key, "Innodb_rows_deleted") == 0)
889         derive_submit("mysql_innodb_rows", "deleted", val, db);
890       else if (strcmp(key, "Innodb_rows_inserted") == 0)
891         derive_submit("mysql_innodb_rows", "inserted", val, db);
892       else if (strcmp(key, "Innodb_rows_read") == 0)
893         derive_submit("mysql_innodb_rows", "read", val, db);
894       else if (strcmp(key, "Innodb_rows_updated") == 0)
895         derive_submit("mysql_innodb_rows", "updated", val, db);
896     } else if (strncmp(key, "Select_", strlen("Select_")) == 0) {
897       derive_submit("mysql_select", key + strlen("Select_"), val, db);
898     } else if (strncmp(key, "Sort_", strlen("Sort_")) == 0) {
899       if (strcmp(key, "Sort_merge_passes") == 0)
900         derive_submit("mysql_sort_merge_passes", NULL, val, db);
901       else if (strcmp(key, "Sort_rows") == 0)
902         derive_submit("mysql_sort_rows", NULL, val, db);
903       else if (strcmp(key, "Sort_range") == 0)
904         derive_submit("mysql_sort", "range", val, db);
905       else if (strcmp(key, "Sort_scan") == 0)
906         derive_submit("mysql_sort", "scan", val, db);
907
908     } else if (strncmp(key, "Slow_queries", strlen("Slow_queries")) == 0) {
909       derive_submit("mysql_slow_queries", NULL, val, db);
910     } else if (strcmp(key, "Uptime") == 0) {
911       gauge_submit("uptime", NULL, val, db);
912     }
913   }
914   mysql_free_result(res);
915   res = NULL;
916
917   if ((qcache_hits != 0) || (qcache_inserts != 0) || (qcache_not_cached != 0) ||
918       (qcache_lowmem_prunes != 0)) {
919     derive_submit("cache_result", "qcache-hits", qcache_hits, db);
920     derive_submit("cache_result", "qcache-inserts", qcache_inserts, db);
921     derive_submit("cache_result", "qcache-not_cached", qcache_not_cached, db);
922     derive_submit("cache_result", "qcache-prunes", qcache_lowmem_prunes, db);
923
924     gauge_submit("cache_size", "qcache", qcache_queries_in_cache, db);
925   }
926
927   if (threads_created != 0) {
928     gauge_submit("threads", "running", threads_running, db);
929     gauge_submit("threads", "connected", threads_connected, db);
930     gauge_submit("threads", "cached", threads_cached, db);
931
932     derive_submit("total_threads", "created", threads_created, db);
933   }
934
935   traffic_submit(traffic_incoming, traffic_outgoing, db);
936
937   if (mysql_version >= 50600 && db->innodb_stats)
938     mysql_read_innodb_stats(db, con);
939
940   if (db->master_stats)
941     mysql_read_master_stats(db, con);
942
943   if ((db->slave_stats) || (db->slave_notif))
944     mysql_read_slave_stats(db, con);
945
946   if (db->wsrep_stats)
947     mysql_read_wsrep_stats(db, con);
948
949   return 0;
950 } /* int mysql_read */
951
952 void module_register(void) {
953   plugin_register_complex_config("mysql", mysql_config);
954 } /* void module_register */