Merge pull request #2618 from ajssmith/amqp1_dev1_branch
[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 "common.h"
33 #include "plugin.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       snprintf(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, .free_func = mysql_database_free,
229         });
230   } else {
231     mysql_database_free(db);
232     return -1;
233   }
234
235   return 0;
236 } /* }}} int mysql_config_database */
237
238 static int mysql_config(oconfig_item_t *ci) /* {{{ */
239 {
240   if (ci == NULL)
241     return EINVAL;
242
243   /* Fill the `mysql_database_t' structure.. */
244   for (int i = 0; i < ci->children_num; i++) {
245     oconfig_item_t *child = ci->children + i;
246
247     if (strcasecmp("Database", child->key) == 0)
248       mysql_config_database(child);
249     else
250       WARNING("mysql plugin: Option \"%s\" not allowed here.", child->key);
251   }
252
253   return 0;
254 } /* }}} int mysql_config */
255
256 /* }}} End of configuration handling functions */
257
258 static MYSQL *getconnection(mysql_database_t *db) {
259   const char *cipher;
260
261   if (db->is_connected) {
262     int status;
263
264     status = mysql_ping(db->con);
265     if (status == 0)
266       return db->con;
267
268     WARNING("mysql plugin: Lost connection to instance \"%s\": %s",
269             db->instance, mysql_error(db->con));
270   }
271   db->is_connected = false;
272
273   /* Close the old connection before initializing a new one. */
274   if (db->con != NULL) {
275     mysql_close(db->con);
276     db->con = NULL;
277   }
278
279   db->con = mysql_init(NULL);
280   if (db->con == NULL) {
281     ERROR("mysql plugin: mysql_init failed: %s", mysql_error(db->con));
282     return NULL;
283   }
284
285   /* Configure TCP connect timeout (default: 0) */
286   db->con->options.connect_timeout = db->timeout;
287
288   mysql_ssl_set(db->con, db->key, db->cert, db->ca, db->capath, db->cipher);
289
290   if (mysql_real_connect(db->con, db->host, db->user, db->pass, db->database,
291                          db->port, db->socket, 0) == NULL) {
292     ERROR("mysql plugin: Failed to connect to database %s "
293           "at server %s: %s",
294           (db->database != NULL) ? db->database : "<none>",
295           (db->host != NULL) ? db->host : "localhost", mysql_error(db->con));
296     return NULL;
297   }
298
299   cipher = mysql_get_ssl_cipher(db->con);
300
301   INFO("mysql plugin: Successfully connected to database %s "
302        "at server %s with cipher %s "
303        "(server version: %s, protocol version: %d) ",
304        (db->database != NULL) ? db->database : "<none>",
305        mysql_get_host_info(db->con), (cipher != NULL) ? cipher : "<none>",
306        mysql_get_server_info(db->con), mysql_get_proto_info(db->con));
307
308   db->is_connected = true;
309   return db->con;
310 } /* static MYSQL *getconnection (mysql_database_t *db) */
311
312 static void set_host(mysql_database_t *db, char *buf, size_t buflen) {
313   if (db->alias)
314     sstrncpy(buf, db->alias, buflen);
315   else if ((db->host == NULL) || (strcmp("", db->host) == 0) ||
316            (strcmp("127.0.0.1", db->host) == 0) ||
317            (strcmp("localhost", db->host) == 0))
318     sstrncpy(buf, hostname_g, buflen);
319   else
320     sstrncpy(buf, db->host, buflen);
321 } /* void set_host */
322
323 static void submit(const char *type, const char *type_instance, value_t *values,
324                    size_t values_len, mysql_database_t *db) {
325   value_list_t vl = VALUE_LIST_INIT;
326
327   vl.values = values;
328   vl.values_len = values_len;
329
330   set_host(db, vl.host, sizeof(vl.host));
331
332   sstrncpy(vl.plugin, "mysql", sizeof(vl.plugin));
333
334   /* Assured by "mysql_config_database" */
335   assert(db->instance != NULL);
336   sstrncpy(vl.plugin_instance, db->instance, sizeof(vl.plugin_instance));
337
338   sstrncpy(vl.type, type, sizeof(vl.type));
339   if (type_instance != NULL)
340     sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
341
342   plugin_dispatch_values(&vl);
343 } /* submit */
344
345 static void gauge_submit(const char *type, const char *type_instance,
346                          gauge_t value, mysql_database_t *db) {
347   submit(type, type_instance, &(value_t){.gauge = value}, 1, db);
348 } /* void gauge_submit */
349
350 static void derive_submit(const char *type, const char *type_instance,
351                           derive_t value, mysql_database_t *db) {
352   submit(type, type_instance, &(value_t){.derive = value}, 1, db);
353 } /* void derive_submit */
354
355 static void traffic_submit(derive_t rx, derive_t tx, mysql_database_t *db) {
356   value_t values[] = {
357       {.derive = rx}, {.derive = tx},
358   };
359
360   submit("mysql_octets", NULL, values, STATIC_ARRAY_SIZE(values), db);
361 } /* void traffic_submit */
362
363 static MYSQL_RES *exec_query(MYSQL *con, const char *query) {
364   MYSQL_RES *res;
365
366   int query_len = strlen(query);
367
368   if (mysql_real_query(con, query, query_len)) {
369     ERROR("mysql plugin: Failed to execute query: %s", mysql_error(con));
370     INFO("mysql plugin: SQL query was: %s", query);
371     return NULL;
372   }
373
374   res = mysql_store_result(con);
375   if (res == NULL) {
376     ERROR("mysql plugin: Failed to store query result: %s", mysql_error(con));
377     INFO("mysql plugin: SQL query was: %s", query);
378     return NULL;
379   }
380
381   return res;
382 } /* exec_query */
383
384 static int mysql_read_master_stats(mysql_database_t *db, MYSQL *con) {
385   MYSQL_RES *res;
386   MYSQL_ROW row;
387
388   const char *query;
389   int field_num;
390   unsigned long long position;
391
392   query = "SHOW MASTER STATUS";
393
394   res = exec_query(con, query);
395   if (res == NULL)
396     return -1;
397
398   row = mysql_fetch_row(res);
399   if (row == NULL) {
400     ERROR("mysql plugin: Failed to get master statistics: "
401           "`%s' did not return any rows.",
402           query);
403     mysql_free_result(res);
404     return -1;
405   }
406
407   field_num = mysql_num_fields(res);
408   if (field_num < 2) {
409     ERROR("mysql plugin: Failed to get master statistics: "
410           "`%s' returned less than two columns.",
411           query);
412     mysql_free_result(res);
413     return -1;
414   }
415
416   position = atoll(row[1]);
417   derive_submit("mysql_log_position", "master-bin", position, db);
418
419   row = mysql_fetch_row(res);
420   if (row != NULL)
421     WARNING("mysql plugin: `%s' returned more than one row - "
422             "ignoring further results.",
423             query);
424
425   mysql_free_result(res);
426
427   return 0;
428 } /* mysql_read_master_stats */
429
430 static int mysql_read_slave_stats(mysql_database_t *db, MYSQL *con) {
431   MYSQL_RES *res;
432   MYSQL_ROW row;
433
434   const char *query;
435   int field_num;
436
437   /* WTF? libmysqlclient does not seem to provide any means to
438    * translate a column name to a column index ... :-/ */
439   const int READ_MASTER_LOG_POS_IDX = 6;
440   const int SLAVE_IO_RUNNING_IDX = 10;
441   const int SLAVE_SQL_RUNNING_IDX = 11;
442   const int EXEC_MASTER_LOG_POS_IDX = 21;
443   const int SECONDS_BEHIND_MASTER_IDX = 32;
444
445   query = "SHOW SLAVE STATUS";
446
447   res = exec_query(con, query);
448   if (res == NULL)
449     return -1;
450
451   row = mysql_fetch_row(res);
452   if (row == NULL) {
453     ERROR("mysql plugin: Failed to get slave statistics: "
454           "`%s' did not return any rows.",
455           query);
456     mysql_free_result(res);
457     return -1;
458   }
459
460   field_num = mysql_num_fields(res);
461   if (field_num < 33) {
462     ERROR("mysql plugin: Failed to get slave statistics: "
463           "`%s' returned less than 33 columns.",
464           query);
465     mysql_free_result(res);
466     return -1;
467   }
468
469   if (db->slave_stats) {
470     unsigned long long counter;
471     double gauge;
472
473     counter = atoll(row[READ_MASTER_LOG_POS_IDX]);
474     derive_submit("mysql_log_position", "slave-read", counter, db);
475
476     counter = atoll(row[EXEC_MASTER_LOG_POS_IDX]);
477     derive_submit("mysql_log_position", "slave-exec", counter, db);
478
479     if (row[SECONDS_BEHIND_MASTER_IDX] != NULL) {
480       gauge = atof(row[SECONDS_BEHIND_MASTER_IDX]);
481       gauge_submit("time_offset", NULL, gauge, db);
482     }
483   }
484
485   if (db->slave_notif) {
486     notification_t n = {0,  cdtime(),      "", "",  "mysql",
487                         "", "time_offset", "", NULL};
488
489     char *io, *sql;
490
491     io = row[SLAVE_IO_RUNNING_IDX];
492     sql = row[SLAVE_SQL_RUNNING_IDX];
493
494     set_host(db, n.host, sizeof(n.host));
495
496     /* Assured by "mysql_config_database" */
497     assert(db->instance != NULL);
498     sstrncpy(n.plugin_instance, db->instance, sizeof(n.plugin_instance));
499
500     if (((io == NULL) || (strcasecmp(io, "yes") != 0)) &&
501         (db->slave_io_running)) {
502       n.severity = NOTIF_WARNING;
503       snprintf(n.message, sizeof(n.message),
504                "slave I/O thread not started or not connected to master");
505       plugin_dispatch_notification(&n);
506       db->slave_io_running = false;
507     } else if (((io != NULL) && (strcasecmp(io, "yes") == 0)) &&
508                (!db->slave_io_running)) {
509       n.severity = NOTIF_OKAY;
510       snprintf(n.message, sizeof(n.message),
511                "slave I/O thread started and connected to master");
512       plugin_dispatch_notification(&n);
513       db->slave_io_running = true;
514     }
515
516     if (((sql == NULL) || (strcasecmp(sql, "yes") != 0)) &&
517         (db->slave_sql_running)) {
518       n.severity = NOTIF_WARNING;
519       snprintf(n.message, sizeof(n.message), "slave SQL thread not started");
520       plugin_dispatch_notification(&n);
521       db->slave_sql_running = false;
522     } else if (((sql != NULL) && (strcasecmp(sql, "yes") == 0)) &&
523                (!db->slave_sql_running)) {
524       n.severity = NOTIF_OKAY;
525       snprintf(n.message, sizeof(n.message), "slave SQL thread started");
526       plugin_dispatch_notification(&n);
527       db->slave_sql_running = true;
528     }
529   }
530
531   row = mysql_fetch_row(res);
532   if (row != NULL)
533     WARNING("mysql plugin: `%s' returned more than one row - "
534             "ignoring further results.",
535             query);
536
537   mysql_free_result(res);
538
539   return 0;
540 } /* mysql_read_slave_stats */
541
542 static int mysql_read_innodb_stats(mysql_database_t *db, MYSQL *con) {
543   MYSQL_RES *res;
544   MYSQL_ROW row;
545
546   const char *query;
547   struct {
548     const char *key;
549     const char *type;
550     int ds_type;
551   } metrics[] = {
552       {"metadata_mem_pool_size", "bytes", DS_TYPE_GAUGE},
553       {"lock_deadlocks", "mysql_locks", DS_TYPE_DERIVE},
554       {"lock_timeouts", "mysql_locks", DS_TYPE_DERIVE},
555       {"lock_row_lock_current_waits", "mysql_locks", DS_TYPE_DERIVE},
556       {"buffer_pool_size", "bytes", DS_TYPE_GAUGE},
557
558       {"os_log_bytes_written", "operations", DS_TYPE_DERIVE},
559       {"os_log_pending_fsyncs", "operations", DS_TYPE_DERIVE},
560       {"os_log_pending_writes", "operations", DS_TYPE_DERIVE},
561
562       {"trx_rseg_history_len", "gauge", DS_TYPE_GAUGE},
563
564       {"adaptive_hash_searches", "operations", DS_TYPE_DERIVE},
565
566       {"file_num_open_files", "gauge", DS_TYPE_GAUGE},
567
568       {"ibuf_merges_insert", "operations", DS_TYPE_DERIVE},
569       {"ibuf_merges_delete_mark", "operations", DS_TYPE_DERIVE},
570       {"ibuf_merges_delete", "operations", DS_TYPE_DERIVE},
571       {"ibuf_merges_discard_insert", "operations", DS_TYPE_DERIVE},
572       {"ibuf_merges_discard_delete_mark", "operations", DS_TYPE_DERIVE},
573       {"ibuf_merges_discard_delete", "operations", DS_TYPE_DERIVE},
574       {"ibuf_merges_discard_merges", "operations", DS_TYPE_DERIVE},
575       {"ibuf_size", "bytes", DS_TYPE_GAUGE},
576
577       {"innodb_activity_count", "gauge", DS_TYPE_GAUGE},
578
579       {"innodb_rwlock_s_spin_waits", "operations", DS_TYPE_DERIVE},
580       {"innodb_rwlock_x_spin_waits", "operations", DS_TYPE_DERIVE},
581       {"innodb_rwlock_s_spin_rounds", "operations", DS_TYPE_DERIVE},
582       {"innodb_rwlock_x_spin_rounds", "operations", DS_TYPE_DERIVE},
583       {"innodb_rwlock_s_os_waits", "operations", DS_TYPE_DERIVE},
584       {"innodb_rwlock_x_os_waits", "operations", DS_TYPE_DERIVE},
585
586       {"dml_reads", "operations", DS_TYPE_DERIVE},
587       {"dml_inserts", "operations", DS_TYPE_DERIVE},
588       {"dml_deletes", "operations", DS_TYPE_DERIVE},
589       {"dml_updates", "operations", DS_TYPE_DERIVE},
590
591       {NULL, NULL, 0}};
592
593   query = "SELECT name, count, type FROM information_schema.innodb_metrics "
594           "WHERE status = 'enabled'";
595
596   res = exec_query(con, query);
597   if (res == NULL)
598     return -1;
599
600   while ((row = mysql_fetch_row(res))) {
601     int i;
602     char *key;
603     unsigned long long val;
604
605     key = row[0];
606     val = atoll(row[1]);
607
608     for (i = 0; metrics[i].key != NULL && strcmp(metrics[i].key, key) != 0; i++)
609       ;
610
611     if (metrics[i].key == NULL)
612       continue;
613
614     switch (metrics[i].ds_type) {
615     case DS_TYPE_COUNTER:
616       derive_submit(metrics[i].type, key, (counter_t)val, db);
617       break;
618     case DS_TYPE_GAUGE:
619       gauge_submit(metrics[i].type, key, (gauge_t)val, db);
620       break;
621     case DS_TYPE_DERIVE:
622       derive_submit(metrics[i].type, key, (derive_t)val, db);
623       break;
624     }
625   }
626
627   mysql_free_result(res);
628   return 0;
629 }
630
631 static int mysql_read_wsrep_stats(mysql_database_t *db, MYSQL *con) {
632   MYSQL_RES *res;
633   MYSQL_ROW row;
634
635   const char *query;
636   struct {
637     const char *key;
638     const char *type;
639     int ds_type;
640   } metrics[] = {
641
642       {"wsrep_apply_oooe", "operations", DS_TYPE_DERIVE},
643       {"wsrep_apply_oool", "operations", DS_TYPE_DERIVE},
644       {"wsrep_causal_reads", "operations", DS_TYPE_DERIVE},
645       {"wsrep_commit_oooe", "operations", DS_TYPE_DERIVE},
646       {"wsrep_commit_oool", "operations", DS_TYPE_DERIVE},
647       {"wsrep_flow_control_recv", "operations", DS_TYPE_DERIVE},
648       {"wsrep_flow_control_sent", "operations", DS_TYPE_DERIVE},
649       {"wsrep_flow_control_paused", "operations", DS_TYPE_DERIVE},
650       {"wsrep_local_bf_aborts", "operations", DS_TYPE_DERIVE},
651       {"wsrep_local_cert_failures", "operations", DS_TYPE_DERIVE},
652       {"wsrep_local_commits", "operations", DS_TYPE_DERIVE},
653       {"wsrep_local_replays", "operations", DS_TYPE_DERIVE},
654       {"wsrep_received", "operations", DS_TYPE_DERIVE},
655       {"wsrep_replicated", "operations", DS_TYPE_DERIVE},
656
657       {"wsrep_received_bytes", "total_bytes", DS_TYPE_DERIVE},
658       {"wsrep_replicated_bytes", "total_bytes", DS_TYPE_DERIVE},
659
660       {"wsrep_apply_window", "gauge", DS_TYPE_GAUGE},
661       {"wsrep_commit_window", "gauge", DS_TYPE_GAUGE},
662
663       {"wsrep_cluster_size", "gauge", DS_TYPE_GAUGE},
664       {"wsrep_cert_deps_distance", "gauge", DS_TYPE_GAUGE},
665
666       {"wsrep_local_recv_queue", "queue_length", DS_TYPE_GAUGE},
667       {"wsrep_local_send_queue", "queue_length", DS_TYPE_GAUGE},
668
669       {NULL, NULL, 0}
670
671   };
672
673   query = "SHOW GLOBAL STATUS LIKE 'wsrep_%'";
674
675   res = exec_query(con, query);
676   if (res == NULL)
677     return -1;
678
679   row = mysql_fetch_row(res);
680   if (row == NULL) {
681     ERROR("mysql plugin: Failed to get wsrep statistics: "
682           "`%s' did not return any rows.",
683           query);
684     mysql_free_result(res);
685     return -1;
686   }
687
688   while ((row = mysql_fetch_row(res))) {
689     int i;
690     char *key;
691     unsigned long long val;
692
693     key = row[0];
694     val = atoll(row[1]);
695
696     for (i = 0; metrics[i].key != NULL && strcmp(metrics[i].key, key) != 0; i++)
697       ;
698
699     if (metrics[i].key == NULL)
700       continue;
701
702     switch (metrics[i].ds_type) {
703     case DS_TYPE_GAUGE:
704       gauge_submit(metrics[i].type, key, (gauge_t)val, db);
705       break;
706     case DS_TYPE_DERIVE:
707       derive_submit(metrics[i].type, key, (derive_t)val, db);
708       break;
709     }
710   }
711
712   mysql_free_result(res);
713   return 0;
714 } /* mysql_read_wsrep_stats */
715
716 static int mysql_read(user_data_t *ud) {
717   mysql_database_t *db;
718   MYSQL *con;
719   MYSQL_RES *res;
720   MYSQL_ROW row;
721   const char *query;
722
723   derive_t qcache_hits = 0;
724   derive_t qcache_inserts = 0;
725   derive_t qcache_not_cached = 0;
726   derive_t qcache_lowmem_prunes = 0;
727   gauge_t qcache_queries_in_cache = NAN;
728
729   gauge_t threads_running = NAN;
730   gauge_t threads_connected = NAN;
731   gauge_t threads_cached = NAN;
732   derive_t threads_created = 0;
733
734   unsigned long long traffic_incoming = 0ULL;
735   unsigned long long traffic_outgoing = 0ULL;
736   unsigned long mysql_version = 0ULL;
737
738   if ((ud == NULL) || (ud->data == NULL)) {
739     ERROR("mysql plugin: mysql_database_read: Invalid user data.");
740     return -1;
741   }
742
743   db = (mysql_database_t *)ud->data;
744
745   /* An error message will have been printed in this case */
746   if ((con = getconnection(db)) == NULL)
747     return -1;
748
749   mysql_version = mysql_get_server_version(con);
750
751   query = "SHOW STATUS";
752   if (mysql_version >= 50002)
753     query = "SHOW GLOBAL STATUS";
754
755   res = exec_query(con, query);
756   if (res == NULL)
757     return -1;
758
759   while ((row = mysql_fetch_row(res))) {
760     char *key;
761     unsigned long long val;
762
763     key = row[0];
764     val = atoll(row[1]);
765
766     if (strncmp(key, "Com_", strlen("Com_")) == 0) {
767       if (val == 0ULL)
768         continue;
769
770       /* Ignore `prepared statements' */
771       if (strncmp(key, "Com_stmt_", strlen("Com_stmt_")) != 0)
772         derive_submit("mysql_commands", key + strlen("Com_"), val, db);
773     } else if (strncmp(key, "Handler_", strlen("Handler_")) == 0) {
774       if (val == 0ULL)
775         continue;
776
777       derive_submit("mysql_handler", key + strlen("Handler_"), val, db);
778     } else if (strncmp(key, "Qcache_", strlen("Qcache_")) == 0) {
779       if (strcmp(key, "Qcache_hits") == 0)
780         qcache_hits = (derive_t)val;
781       else if (strcmp(key, "Qcache_inserts") == 0)
782         qcache_inserts = (derive_t)val;
783       else if (strcmp(key, "Qcache_not_cached") == 0)
784         qcache_not_cached = (derive_t)val;
785       else if (strcmp(key, "Qcache_lowmem_prunes") == 0)
786         qcache_lowmem_prunes = (derive_t)val;
787       else if (strcmp(key, "Qcache_queries_in_cache") == 0)
788         qcache_queries_in_cache = (gauge_t)val;
789     } else if (strncmp(key, "Bytes_", strlen("Bytes_")) == 0) {
790       if (strcmp(key, "Bytes_received") == 0)
791         traffic_incoming += val;
792       else if (strcmp(key, "Bytes_sent") == 0)
793         traffic_outgoing += val;
794     } else if (strncmp(key, "Threads_", strlen("Threads_")) == 0) {
795       if (strcmp(key, "Threads_running") == 0)
796         threads_running = (gauge_t)val;
797       else if (strcmp(key, "Threads_connected") == 0)
798         threads_connected = (gauge_t)val;
799       else if (strcmp(key, "Threads_cached") == 0)
800         threads_cached = (gauge_t)val;
801       else if (strcmp(key, "Threads_created") == 0)
802         threads_created = (derive_t)val;
803     } else if (strncmp(key, "Table_locks_", strlen("Table_locks_")) == 0) {
804       derive_submit("mysql_locks", key + strlen("Table_locks_"), val, db);
805     } else if (db->innodb_stats &&
806                strncmp(key, "Innodb_", strlen("Innodb_")) == 0) {
807       /* buffer pool */
808       if (strcmp(key, "Innodb_buffer_pool_pages_data") == 0)
809         gauge_submit("mysql_bpool_pages", "data", val, db);
810       else if (strcmp(key, "Innodb_buffer_pool_pages_dirty") == 0)
811         gauge_submit("mysql_bpool_pages", "dirty", val, db);
812       else if (strcmp(key, "Innodb_buffer_pool_pages_flushed") == 0)
813         derive_submit("mysql_bpool_counters", "pages_flushed", val, db);
814       else if (strcmp(key, "Innodb_buffer_pool_pages_free") == 0)
815         gauge_submit("mysql_bpool_pages", "free", val, db);
816       else if (strcmp(key, "Innodb_buffer_pool_pages_misc") == 0)
817         gauge_submit("mysql_bpool_pages", "misc", val, db);
818       else if (strcmp(key, "Innodb_buffer_pool_pages_total") == 0)
819         gauge_submit("mysql_bpool_pages", "total", val, db);
820       else if (strcmp(key, "Innodb_buffer_pool_read_ahead_rnd") == 0)
821         derive_submit("mysql_bpool_counters", "read_ahead_rnd", val, db);
822       else if (strcmp(key, "Innodb_buffer_pool_read_ahead") == 0)
823         derive_submit("mysql_bpool_counters", "read_ahead", val, db);
824       else if (strcmp(key, "Innodb_buffer_pool_read_ahead_evicted") == 0)
825         derive_submit("mysql_bpool_counters", "read_ahead_evicted", val, db);
826       else if (strcmp(key, "Innodb_buffer_pool_read_requests") == 0)
827         derive_submit("mysql_bpool_counters", "read_requests", val, db);
828       else if (strcmp(key, "Innodb_buffer_pool_reads") == 0)
829         derive_submit("mysql_bpool_counters", "reads", val, db);
830       else if (strcmp(key, "Innodb_buffer_pool_wait_free") == 0)
831         derive_submit("mysql_bpool_counters", "wait_free", val, db);
832       else if (strcmp(key, "Innodb_buffer_pool_write_requests") == 0)
833         derive_submit("mysql_bpool_counters", "write_requests", val, db);
834       else if (strcmp(key, "Innodb_buffer_pool_bytes_data") == 0)
835         gauge_submit("mysql_bpool_bytes", "data", val, db);
836       else if (strcmp(key, "Innodb_buffer_pool_bytes_dirty") == 0)
837         gauge_submit("mysql_bpool_bytes", "dirty", val, db);
838
839       /* data */
840       if (strcmp(key, "Innodb_data_fsyncs") == 0)
841         derive_submit("mysql_innodb_data", "fsyncs", val, db);
842       else if (strcmp(key, "Innodb_data_read") == 0)
843         derive_submit("mysql_innodb_data", "read", val, db);
844       else if (strcmp(key, "Innodb_data_reads") == 0)
845         derive_submit("mysql_innodb_data", "reads", val, db);
846       else if (strcmp(key, "Innodb_data_writes") == 0)
847         derive_submit("mysql_innodb_data", "writes", val, db);
848       else if (strcmp(key, "Innodb_data_written") == 0)
849         derive_submit("mysql_innodb_data", "written", val, db);
850
851       /* double write */
852       else if (strcmp(key, "Innodb_dblwr_writes") == 0)
853         derive_submit("mysql_innodb_dblwr", "writes", val, db);
854       else if (strcmp(key, "Innodb_dblwr_pages_written") == 0)
855         derive_submit("mysql_innodb_dblwr", "written", val, db);
856       else if (strcmp(key, "Innodb_dblwr_page_size") == 0)
857         gauge_submit("mysql_innodb_dblwr", "page_size", val, db);
858
859       /* log */
860       else if (strcmp(key, "Innodb_log_waits") == 0)
861         derive_submit("mysql_innodb_log", "waits", val, db);
862       else if (strcmp(key, "Innodb_log_write_requests") == 0)
863         derive_submit("mysql_innodb_log", "write_requests", val, db);
864       else if (strcmp(key, "Innodb_log_writes") == 0)
865         derive_submit("mysql_innodb_log", "writes", val, db);
866       else if (strcmp(key, "Innodb_os_log_fsyncs") == 0)
867         derive_submit("mysql_innodb_log", "fsyncs", val, db);
868       else if (strcmp(key, "Innodb_os_log_written") == 0)
869         derive_submit("mysql_innodb_log", "written", val, db);
870
871       /* pages */
872       else if (strcmp(key, "Innodb_pages_created") == 0)
873         derive_submit("mysql_innodb_pages", "created", val, db);
874       else if (strcmp(key, "Innodb_pages_read") == 0)
875         derive_submit("mysql_innodb_pages", "read", val, db);
876       else if (strcmp(key, "Innodb_pages_written") == 0)
877         derive_submit("mysql_innodb_pages", "written", val, db);
878
879       /* row lock */
880       else if (strcmp(key, "Innodb_row_lock_time") == 0)
881         derive_submit("mysql_innodb_row_lock", "time", val, db);
882       else if (strcmp(key, "Innodb_row_lock_waits") == 0)
883         derive_submit("mysql_innodb_row_lock", "waits", val, db);
884
885       /* rows */
886       else if (strcmp(key, "Innodb_rows_deleted") == 0)
887         derive_submit("mysql_innodb_rows", "deleted", val, db);
888       else if (strcmp(key, "Innodb_rows_inserted") == 0)
889         derive_submit("mysql_innodb_rows", "inserted", val, db);
890       else if (strcmp(key, "Innodb_rows_read") == 0)
891         derive_submit("mysql_innodb_rows", "read", val, db);
892       else if (strcmp(key, "Innodb_rows_updated") == 0)
893         derive_submit("mysql_innodb_rows", "updated", val, db);
894     } else if (strncmp(key, "Select_", strlen("Select_")) == 0) {
895       derive_submit("mysql_select", key + strlen("Select_"), val, db);
896     } else if (strncmp(key, "Sort_", strlen("Sort_")) == 0) {
897       if (strcmp(key, "Sort_merge_passes") == 0)
898         derive_submit("mysql_sort_merge_passes", NULL, val, db);
899       else if (strcmp(key, "Sort_rows") == 0)
900         derive_submit("mysql_sort_rows", NULL, val, db);
901       else if (strcmp(key, "Sort_range") == 0)
902         derive_submit("mysql_sort", "range", val, db);
903       else if (strcmp(key, "Sort_scan") == 0)
904         derive_submit("mysql_sort", "scan", val, db);
905
906     } else if (strncmp(key, "Slow_queries", strlen("Slow_queries")) == 0) {
907       derive_submit("mysql_slow_queries", NULL, val, db);
908     }
909   }
910   mysql_free_result(res);
911   res = NULL;
912
913   if ((qcache_hits != 0) || (qcache_inserts != 0) || (qcache_not_cached != 0) ||
914       (qcache_lowmem_prunes != 0)) {
915     derive_submit("cache_result", "qcache-hits", qcache_hits, db);
916     derive_submit("cache_result", "qcache-inserts", qcache_inserts, db);
917     derive_submit("cache_result", "qcache-not_cached", qcache_not_cached, db);
918     derive_submit("cache_result", "qcache-prunes", qcache_lowmem_prunes, db);
919
920     gauge_submit("cache_size", "qcache", qcache_queries_in_cache, db);
921   }
922
923   if (threads_created != 0) {
924     gauge_submit("threads", "running", threads_running, db);
925     gauge_submit("threads", "connected", threads_connected, db);
926     gauge_submit("threads", "cached", threads_cached, db);
927
928     derive_submit("total_threads", "created", threads_created, db);
929   }
930
931   traffic_submit(traffic_incoming, traffic_outgoing, db);
932
933   if (mysql_version >= 50600 && db->innodb_stats)
934     mysql_read_innodb_stats(db, con);
935
936   if (db->master_stats)
937     mysql_read_master_stats(db, con);
938
939   if ((db->slave_stats) || (db->slave_notif))
940     mysql_read_slave_stats(db, con);
941
942   if (db->wsrep_stats)
943     mysql_read_wsrep_stats(db, con);
944
945   return 0;
946 } /* int mysql_read */
947
948 void module_register(void) {
949   plugin_register_complex_config("mysql", mysql_config);
950 } /* void module_register */