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