/* This file is part of Mysqlstat * Copyright (C) 2014-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; } static char * xstrdup(char const *s) { char *p = strdup(s); 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", "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) { MYSQL_ROW row; 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)) { uint64_t n = strtoull(value, NULL, 10); ent->replRelayLogSpace.high = n >> 32; ent->replRelayLogSpace.low = n & 0xffffffff; } 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); } } 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", "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 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; } #if 0 unsigned replSlaveTable_timeout = CACHE_TIMEOUT; static void replSlaveTable_entry_free(struct replSlaveTable_entry *ent) { } int replSlaveTable_load(netsnmp_cache *cache, void *vmagic) { } void replSlaveTable_free(netsnmp_cache *cache, void *vmagic) { netsnmp_tdata *table = (netsnmp_tdata *) vmagic; netsnmp_tdata_row *row; DEBUGMSGTL(("mysqlstat", "freeing table\n")); while ((row = netsnmp_tdata_row_first(table))) { struct replSlaveTable_entry *entry = row->data; free(ent->replSlaveHost); free(ent->replSlaveUser); free(ent->replSlaveCommand); free(ent->replSlaveState); free(ent->replSlaveInfo); SNMP_FREE(ent); netsnmp_tdata_remove_and_delete_row(table, row); } } #endif