/* This file is part of Mysqlstat
* Copyright (C) 2016 Sergey Poznyakoff
*
* Mysqlstat is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3, or (at your option)
* any later version.
*
* Mysqlstat is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Mysqlstat. If not, see .
*/
#include
#include
#undef NDEBUG
#include
#include "mysqlstat_mib.h"
static char *config_file = CONFDIR "/mysqlstat.cnf";
struct mysqlstat_connection {
MYSQL mysql;
};
static void *
xmalloc(size_t s)
{
void *p = malloc(s);
assert(p != NULL);
return p;
}
static void *
xcalloc(size_t nmemb, size_t size)
{
void *p = calloc(nmemb, size);
assert(p != NULL);
return p;
}
mysqlstat_connection_t
mysqlstat_connect(void)
{
static struct mysqlstat_connection *conn;
my_bool t = 1;
if (!conn) {
conn = xcalloc(1, sizeof(*conn));
mysql_init(&conn->mysql);
if (access(config_file, F_OK) == 0)
mysql_options(&conn->mysql, MYSQL_READ_DEFAULT_FILE,
config_file);
mysql_options(&conn->mysql,
MYSQL_READ_DEFAULT_GROUP, "mysqlstat");
mysql_options(&conn->mysql, MYSQL_OPT_RECONNECT, &t);
if (!mysql_real_connect(&conn->mysql,
NULL, NULL, NULL, NULL, 0,
NULL, 0)) {
snmp_log(LOG_ERR, "can't connect to MySQL: %s\n",
mysql_error(&conn->mysql));
free(conn);
conn = NULL;
}
}
return conn;
}
unsigned mysqlstat_cache_timeout = CACHE_TIMEOUT;
#define __cat2__(a,b) a ## b
#define ASSIGN_STRING(e,m,s) \
do { \
if (s) { \
(e)->__cat2__(m,_len) = strlen(s); \
(e)->m = xmalloc((e)->__cat2__(m,_len)); \
memcpy((e)->m, s, (e)->__cat2__(m,_len)); \
} else { \
(e)->__cat2__(m,_len) = 0; \
(e)->m = NULL; \
} \
} while (0)
#define ASSIGN_ROW(e,m,r) ASSIGN_STRING(e,m,(r)[__cat2__(FI_,m)])
#define STREQ(pat,str) (strcmp(#pat, str) == 0)
struct process_list {
time_t ts;
MYSQL_RES *res;
uint32_t total;
uint32_t active;
uint32_t slaves;
uint32_t itr;
};
enum {
FI_processID,
FI_processUser,
FI_processHost,
FI_processDatabase,
FI_processCommand,
FI_processTime,
FI_processState,
FI_processInfo
};
static int
get_process_list(struct process_list **p)
{
static struct process_list plist;
time_t now;
now = time(NULL);
if (!plist.res || now - plist.ts >= mysqlstat_cache_timeout) {
mysqlstat_connection_t conn;
unsigned i;
if (plist.res) {
mysql_free_result(plist.res);
plist.res = NULL;
plist.total = plist.active = plist.slaves = 0;
}
DEBUGMSGTL(("mysqlstat:sql", "Getting process list\n"));
conn = mysqlstat_connect();
if (!conn)
return SNMP_ERR_GENERR;
if (mysql_query(&conn->mysql, "SHOW PROCESSLIST")) {
snmp_log(LOG_ERR, "can't get slave process list: %s\n",
mysql_error(&conn->mysql));
return SNMP_ERR_NOSUCHNAME;
}
plist.res = mysql_store_result(&conn->mysql);
plist.ts = now;
plist.total = mysql_num_rows(plist.res);
for (i = 0; i < plist.total; i++) {
MYSQL_ROW row;
mysql_data_seek(plist.res, i);
row = mysql_fetch_row(plist.res);
if (!row[FI_processState]
|| STREQ(Sleep, row[FI_processState]))
continue;
if (row[FI_processCommand]
&& strcmp(row[FI_processCommand], "Binlog Dump") == 0)
plist.slaves++;
else
plist.active++;
}
}
*p = &plist;
return 0;
}
MYSQL_ROW
process_next(struct process_list *p)
{
if (p->itr >= p->total)
return NULL;
mysql_data_seek(p->res, p->itr);
++p->itr;
return mysql_fetch_row(p->res);
}
MYSQL_ROW
process_first(struct process_list **state)
{
struct process_list *p;
if (get_process_list(&p))
return NULL;
p->itr = 0;
*state = p;
return process_next(p);
}
static long
val_Slave_IO_Running(char const *val)
{
if (strcmp(val, "Yes") == 0)
return 1;
else if (strcmp(val, "No") == 0)
return 2;
else if (strcmp(val, "Connected") == 0)
return 3;
return 4;
}
static long
val_bool(char const *val)
{
if (strcmp(val, "Yes") == 0)
return 1;
return 2;
}
static void
store_slave_status_row(struct replSlaveStatusTable_entry *ent,
char const *name, char const *value)
{
if (STREQ(Slave_IO_State, name)) {
ASSIGN_STRING(ent, replSlaveIOState, value);
} else if (STREQ(Master_Host, name)) {
ASSIGN_STRING(ent, replMasterHost, value);
} else if (STREQ(Master_User, name)) {
ASSIGN_STRING(ent, replMasterUser, value);
} else if (STREQ(Master_Port, name)) {
ent->replMasterPort = strtoul(value, NULL, 10);
} else if (STREQ(Connect_Retry, name)) {
ent->replConnectRetry = strtoul(value, NULL, 10);
} else if (STREQ(Master_Log_File, name)) {
ASSIGN_STRING(ent, replMasterLogFile, value);
} else if (STREQ(Read_Master_Log_Pos, name)) {
ent->replReadMasterLogPos = strtoul(value, NULL, 10);
} else if (STREQ(Relay_Log_File, name)) {
ASSIGN_STRING(ent, replRelayLogFile, value);
} else if (STREQ(Relay_Log_Pos, name)) {
ent->replRelayLogPos = strtoul(value, NULL, 10);
} else if (STREQ(Relay_Master_Log_File, name)) {
ASSIGN_STRING(ent, replMasterLogFile, value);
} else if (STREQ(Slave_IO_Running, name)) {
ent->replSlaveIORunning = val_Slave_IO_Running(value);
} else if (STREQ(Slave_SQL_Running, name)) {
ent->replSlaveSQLRunning = val_bool(value);
} else if (STREQ(Replicate_Do_DB, name)) {
ASSIGN_STRING(ent, replReplicateDoDB, value);
} else if (STREQ(Replicate_Ignore_DB, name)) {
ASSIGN_STRING(ent, replReplicateIgnoreDB, value);
} else if (STREQ(Replicate_Do_Table, name)) {
ASSIGN_STRING(ent, replReplicateDoTable, value);
} else if (STREQ(Replicate_Ignore_Table, name)) {
ASSIGN_STRING(ent, replReplicateIgnoreTable, value);
} else if (STREQ(Replicate_Wild_Do_Table, name)) {
ASSIGN_STRING(ent, replReplicateWildDoTable, value);
} else if (STREQ(Replicate_Wild_Ignore_Table, name)) {
ASSIGN_STRING(ent, replReplicateWildIgnoreTable, value);
} else if (STREQ(Last_IO_Errno, name)) {
ent->replLastIOErrno = strtoul(value, NULL, 10);
} else if (STREQ(Last_IO_Error, name)) {
ASSIGN_STRING(ent, replLastIOError, value);
} else if (STREQ(Last_SQL_Errno, name)) {
ent->replLastSQLErrno = strtoul(value, NULL, 10);
} else if (STREQ(Last_SQL_Error, name)) {
ASSIGN_STRING(ent, replLastSQLError, value);
} else if (STREQ(Skip_Counter, name)) {
ent->replSkipCounter = strtoul(value, NULL, 10);
} else if (STREQ(Exec_Master_Log_Pos, name)) {
ent->replExecMasterLogPos = strtoul(value, NULL, 10);
} else if (STREQ(Relay_Log_Space, name)) {
ent->replRelayLogSpace = strtoul(value, NULL, 10);
} else if (STREQ(Until_Condition, name)) {
ASSIGN_STRING(ent, replUntilCondition, value);
} else if (STREQ(Until_Log_File, name)) {
ASSIGN_STRING(ent, replUntilLogFile, value);
} else if (STREQ(Until_Log_Pos, name)) {
ASSIGN_STRING(ent, replUntilLogPos, value);
} else if (STREQ(Master_SSL_Allowed, name)) {
ASSIGN_STRING(ent, replMasterSSLAllowed, value);
} else if (STREQ(Master_SSL_CA_File, name)) {
ASSIGN_STRING(ent, replMasterSSLCAFile, value);
} else if (STREQ(Master_SSL_CA_Path, name)) {
ASSIGN_STRING(ent, replMasterSSLCAPath, value);
} else if (STREQ(Master_SSL_Cert, name)) {
ASSIGN_STRING(ent, replMasterSSLCert, value);
} else if (STREQ(Master_SSL_Cipher, name)) {
ASSIGN_STRING(ent, replMasterSSLCipher, value);
} else if (STREQ(Master_SSL_Key, name)) {
ASSIGN_STRING(ent, replMasterSSLKey, value);
} else if (STREQ(Seconds_Behind_Master, name)) {
ent->replSecondsBehindMaster = strtoul(value, NULL, 10) * 100;
} else if (STREQ(Master_SSL_Verify_Server_Cert, name)) {
ent->replMasterSSLVerifyServerCert = val_bool(value);
} else {
snmp_log(LOG_ERR, "unrecognized slave status column: %s\n",
name);
}
}
int
replSlaveStatusTable_load(netsnmp_cache *cache, void *vmagic)
{
mysqlstat_connection_t conn;
MYSQL_RES *res;
MYSQL_ROW row;
MYSQL_FIELD *fields;
unsigned int num_fields, i;
netsnmp_tdata *table = (netsnmp_tdata *) vmagic;
struct replSlaveStatusTable_entry *ent;
netsnmp_tdata_row *data_row;
conn = mysqlstat_connect();
if (!conn)
return SNMP_ERR_NOSUCHNAME;
DEBUGMSGTL(("mysqlstat:sql", "Getting slave status\n"));
if (mysql_query(&conn->mysql, "SHOW SLAVE STATUS")) {
snmp_log(LOG_ERR, "can't get slave status: %s\n",
mysql_error(&conn->mysql));
return SNMP_ERR_NOSUCHNAME;
}
res = mysql_store_result(&conn->mysql);
if (mysql_num_rows(res) < 1) {
/* snmp_log(LOG_INFO, "empty slave status\n"); */
return 0;
}
num_fields = mysql_num_fields(res);
fields = mysql_fetch_fields(res);
row = mysql_fetch_row(res);
ent = SNMP_MALLOC_TYPEDEF(struct replSlaveStatusTable_entry);
if (!ent) {
return SNMP_ERR_GENERR;
}
data_row = netsnmp_tdata_create_row();
if (!data_row) {
SNMP_FREE(ent);
return SNMP_ERR_GENERR;
}
data_row->data = ent;
ent->replSlaveIndex = 0;
netsnmp_tdata_row_add_index(data_row, ASN_INTEGER,
&ent->replSlaveIndex,
sizeof(ent->replSlaveIndex));
netsnmp_tdata_add_row(table, data_row);
for (i = 0; i < num_fields; i++)
store_slave_status_row(ent, fields[i].name, row[i]);
mysql_free_result(res);
return 0;
}
void
replSlaveStatusTable_entry_free(void *data)
{
struct replSlaveStatusTable_entry *ent = data;
free(ent->replSlaveIOState);
free(ent->replMasterLogFile);
free(ent->replRelayLogFile);
free(ent->replRelayMasterLogFile);
free(ent->replLastSQLError);
free(ent->replLastIOError);
free(ent->replMasterHost);
free(ent->replMasterUser);
free(ent->replReplicateDoDB);
free(ent->replReplicateIgnoreDB);
free(ent->replReplicateDoTable);
free(ent->replReplicateIgnoreTable);
free(ent->replReplicateWildDoTable);
free(ent->replReplicateWildIgnoreTable);
free(ent->replUntilCondition);
free(ent->replUntilLogFile);
free(ent->replUntilLogPos);
free(ent->replMasterSSLAllowed);
free(ent->replMasterSSLCAFile);
free(ent->replMasterSSLCAPath);
free(ent->replMasterSSLCert);
free(ent->replMasterSSLCipher);
free(ent->replMasterSSLKey);
}
static void
store_master_status_row(struct replMasterStatusTable_entry *ent,
char const *name, char const *value)
{
if (STREQ(File, name)) {
ASSIGN_STRING(ent, replMasterCurrentLogFile, value);
} else if (STREQ(Position, name)) {
ent->replMasterCurrentLogPos = strtoul(value, NULL, 10);
} else if (STREQ(Binlog_Do_DB, name)) {
ASSIGN_STRING(ent, replMasterBinlogDoDB, value);
} else if (STREQ(Binlog_Ignore_DB, name)) {
ASSIGN_STRING(ent, replMasterBinlogIgnoreDB, value);
} else {
snmp_log(LOG_ERR, "unrecognized master status column: %s\n",
name);
}
}
int
replMasterStatusTable_load(netsnmp_cache *cache, void *vmagic)
{
mysqlstat_connection_t conn;
MYSQL_RES *res;
MYSQL_ROW row;
MYSQL_FIELD *fields;
unsigned int num_fields, i;
netsnmp_tdata *table = (netsnmp_tdata *) vmagic;
struct replMasterStatusTable_entry *ent;
netsnmp_tdata_row *data_row;
conn = mysqlstat_connect();
if (!conn)
return SNMP_ERR_NOSUCHNAME;
DEBUGMSGTL(("mysqlstat:sql", "Getting master status\n"));
if (mysql_query(&conn->mysql, "SHOW MASTER STATUS")) {
snmp_log(LOG_ERR, "can't get master status: %s\n",
mysql_error(&conn->mysql));
return SNMP_ERR_NOSUCHNAME;
}
res = mysql_store_result(&conn->mysql);
if (mysql_num_rows(res) < 1)
return 0;
num_fields = mysql_num_fields(res);
fields = mysql_fetch_fields(res);
row = mysql_fetch_row(res);
ent = SNMP_MALLOC_TYPEDEF(struct replMasterStatusTable_entry);
if (!ent)
return SNMP_ERR_GENERR;
data_row = netsnmp_tdata_create_row();
if (!data_row) {
SNMP_FREE(ent);
return SNMP_ERR_GENERR;
}
data_row->data = ent;
ent->replMasterIndex = 0;
netsnmp_tdata_row_add_index(data_row, ASN_INTEGER,
&ent->replMasterIndex,
sizeof(ent->replMasterIndex));
netsnmp_tdata_add_row(table, data_row);
for (i = 0; i < num_fields; i++)
store_master_status_row(ent, fields[i].name, row[i]);
mysql_free_result(res);
return 0;
}
void
replMasterStatusTable_entry_free(void *data)
{
struct replMasterStatusTable_entry *ent = data;
free(ent->replMasterCurrentLogFile);
free(ent->replMasterBinlogDoDB);
free(ent->replMasterBinlogIgnoreDB);
}
static void
process_list_add(netsnmp_tdata *table_data, long idx, MYSQL_ROW mysql_row)
{
struct processListTable_entry *ent;
netsnmp_tdata_row *data_row;
ent = SNMP_MALLOC_TYPEDEF(struct processListTable_entry);
if (!ent)
return;
data_row = netsnmp_tdata_create_row();
if (!data_row) {
SNMP_FREE(ent);
return;
}
data_row->data = ent;
ent->processIndex = idx;
if (mysql_row[FI_processID])
ent->processID = strtol(mysql_row[FI_processID], NULL, 10);
ASSIGN_ROW(ent, processUser, mysql_row);
ent->processHost_len = strcspn(mysql_row[FI_processHost], ":");
ent->processHost = xmalloc(ent->processHost_len);
memcpy(ent->processHost, mysql_row[FI_processHost], ent->processHost_len);
ASSIGN_ROW(ent, processDatabase, mysql_row);
ASSIGN_ROW(ent, processCommand, mysql_row);
if (mysql_row[FI_processTime])
ent->processTime = strtol(mysql_row[FI_processTime], NULL, 10) * 100;
ASSIGN_ROW(ent, processState, mysql_row);
ASSIGN_ROW(ent, processInfo, mysql_row);
netsnmp_tdata_row_add_index(data_row, ASN_INTEGER,
&ent->processIndex,
sizeof(ent->processIndex));
netsnmp_tdata_add_row(table_data, data_row);
}
int
processListTable_load(netsnmp_cache *cache, void *vmagic)
{
struct process_list *p;
netsnmp_tdata *table = (netsnmp_tdata *) vmagic;
MYSQL_ROW row;
uint32_t i;
for (row = process_first(&p), i = 0; row; row = process_next(p), i++) {
process_list_add(table, i, row);
}
return 0;
}
void
processListTable_entry_free(void *data)
{
struct processListTable_entry *ent = data;
free(ent->processHost);
free(ent->processUser);
free(ent->processCommand);
free(ent->processState);
free(ent->processInfo);
}
uint32_t
process_total_count(void)
{
struct process_list *p;
if (get_process_list(&p))
return 0;
return p->total;
}
uint32_t
process_active_count(void)
{
struct process_list *p;
if (get_process_list(&p))
return 0;
return p->active;
}
uint32_t
process_slave_count(void)
{
struct process_list *p;
if (get_process_list(&p))
return 0;
return p->slaves;
}