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