diff --git a/include/MySQL_Thread.h b/include/MySQL_Thread.h index 658553124..9589c33e3 100644 --- a/include/MySQL_Thread.h +++ b/include/MySQL_Thread.h @@ -596,6 +596,15 @@ class MySQL_Threads_Handler * - 'address_family' is either 'AF_INET' or 'AF_INET6'. * - The address obtained from it isn't '127.0.0.1'. * + * In case 'client_sockaddr' matches the previous description, the update + * of the client host cache is performed in the following way: + * 1. If the cache is full, the oldest element in the cache is searched. + * In case the oldest element address doesn't match the supplied + * address, the oldest element is removed. + * 2. The cache is searched looking for the supplied address, in case of + * being found, the entry is updated, otherwise the entry is inserted in + * the cache. + * * @param client_sockaddr A 'sockaddr' holding the required client information * to update the 'client_host_cache_map'. * @param error 'true' if there was an error in the connection that should be @@ -628,6 +637,22 @@ class MySQL_Threads_Handler * @brief Delete all the entries in the 'client_host_cache' internal map. */ void flush_client_host_cache(); + /** + * @brief Returns the current entries of 'client_host_cache' in a + * 'SQLite3_result'. In case the param 'reset' is specified, the structure + * is cleaned after being queried. + * + * @param reset If 'true' the entries of the internal structure + * 'client_host_cache' will be cleaned after scrapping. + * + * @return SQLite3_result holding the current entries of the + * 'client_host_cache'. In the following format: + * + * [ 'client_address', 'error_num', 'last_updated' ] + * + * Where 'last_updated' is the last updated time expressed in 'ns'. + */ + SQLite3_result* get_client_host_cache(bool reset); /** * @brief Callback to update the metrics. */ diff --git a/include/proxysql_admin.h b/include/proxysql_admin.h index 421715b9e..54927da24 100644 --- a/include/proxysql_admin.h +++ b/include/proxysql_admin.h @@ -355,6 +355,7 @@ class ProxySQL_Admin { void stats___proxysql_servers_metrics(); void stats___mysql_prepared_statements_info(); void stats___mysql_gtid_executed(); + void stats___mysql_client_host_cache(bool reset); // Update prometheus metrics void p_stats___memory_metrics(); diff --git a/lib/MySQL_Thread.cpp b/lib/MySQL_Thread.cpp index 442c4772d..66ab0f945 100644 --- a/lib/MySQL_Thread.cpp +++ b/lib/MySQL_Thread.cpp @@ -2443,6 +2443,77 @@ MySQL_Client_Host_Cache_Entry MySQL_Threads_Handler::find_client_host_cache(stru return entry; } +/** + * @brief Number of columns for representing a 'MySQL_Client_Host_Cache_Entry' + * in a 'SQLite3_result'. + */ +const int CLIENT_HOST_CACHE_COLUMNS = 3; + +/** + * @brief Helper function that converts a given client address and a + * 'MySQL_Client_Host_Cache_Entry', into a row for a 'SQLite3_result' for + * table 'STATS_SQLITE_TABLE_MYSQL_CLIENT_HOST_CACHE'. + * + * @param address The client address to be added to the resulset row. + * @param entry The 'MySQL_Client_Host_Cache_Entry' to be added to the resulset + * row. + * + * @return A pointer array holding the values for each of the columns of the + * row. It should be freed through helper function 'free_client_host_cache_row'. + */ +char** client_host_cache_entry_row( + const std::string address, const MySQL_Client_Host_Cache_Entry& entry +) { + // INET6_ADDRSTRLEN length should be enough for holding any member: + // { address: MAX INET6_ADDRSTRLEN, updated_at: uint64_t, error_count: uint32_t } + char buff[INET6_ADDRSTRLEN]; + char** row = + static_cast(malloc(sizeof(char*)*CLIENT_HOST_CACHE_COLUMNS)); + + row[0]=strdup(address.c_str()); + sprintf(buff, "%u", entry.error_count); + row[1]=strdup(buff); + sprintf(buff, "%lu", entry.updated_at); + row[2]=strdup(buff); + + return row; +} + +/** + * @brief Helper function to free the row returned by + * 'client_host_cache_entry_row'. + * + * @param row The pointer array holding the row values to be freed. + */ +void free_client_host_cache_row(char** row) { + for (int i = 0; i < CLIENT_HOST_CACHE_COLUMNS; i++) { + free(row[i]); + } + free(row); +} + +SQLite3_result* MySQL_Threads_Handler::get_client_host_cache(bool reset) { + SQLite3_result *result = new SQLite3_result(CLIENT_HOST_CACHE_COLUMNS); + + pthread_mutex_lock(&mutex_client_host_cache); + result->add_column_definition(SQLITE_TEXT,"client_address"); + result->add_column_definition(SQLITE_TEXT,"error_count"); + result->add_column_definition(SQLITE_TEXT,"last_updated"); + + for (const auto& cache_entry : client_host_cache) { + char** row = client_host_cache_entry_row(cache_entry.first, cache_entry.second); + result->add_row(row); + free_client_host_cache_row(row); + } + + if (reset) { + client_host_cache.clear(); + } + + pthread_mutex_unlock(&mutex_client_host_cache); + return result; +} + void MySQL_Threads_Handler::update_client_host_cache(struct sockaddr* client_sockaddr, bool error) { if (client_sockaddr->sa_family != AF_INET && client_sockaddr->sa_family != AF_INET6) { return; diff --git a/lib/ProxySQL_Admin.cpp b/lib/ProxySQL_Admin.cpp index ed74787a6..73cea52da 100644 --- a/lib/ProxySQL_Admin.cpp +++ b/lib/ProxySQL_Admin.cpp @@ -490,6 +490,9 @@ static int http_handler(void *cls, struct MHD_Connection *connection, const char #define STATS_SQLITE_TABLE_MYSQL_ERRORS "CREATE TABLE stats_mysql_errors (hostgroup INT NOT NULL , hostname VARCHAR NOT NULL , port INT NOT NULL , username VARCHAR NOT NULL , client_address VARCHAR NOT NULL , schemaname VARCHAR NOT NULL , errno INT NOT NULL , count_star INTEGER NOT NULL , first_seen INTEGER NOT NULL , last_seen INTEGER NOT NULL , last_error VARCHAR NOT NULL DEFAULT '' , PRIMARY KEY (hostgroup, hostname, port, username, schemaname, errno) )" #define STATS_SQLITE_TABLE_MYSQL_ERRORS_RESET "CREATE TABLE stats_mysql_errors_reset (hostgroup INT NOT NULL , hostname VARCHAR NOT NULL , port INT NOT NULL , username VARCHAR NOT NULL , client_address VARCHAR NOT NULL , schemaname VARCHAR NOT NULL , errno INT NOT NULL , count_star INTEGER NOT NULL , first_seen INTEGER NOT NULL , last_seen INTEGER NOT NULL , last_error VARCHAR NOT NULL DEFAULT '' , PRIMARY KEY (hostgroup, hostname, port, username, schemaname, errno) )" +#define STATS_SQLITE_TABLE_MYSQL_CLIENT_HOST_CACHE "CREATE TABLE stats_mysql_client_host_cache (client_address VARCHAR NOT NULL, error_count INT NOT NULL, last_updated BIGINT NOT NULL)" +#define STATS_SQLITE_TABLE_MYSQL_CLIENT_HOST_CACHE_RESET "CREATE TABLE stats_mysql_client_host_cache_reset (client_address VARCHAR NOT NULL, error_count INT NOT NULL, last_updated BIGINT NOT NULL)" + #ifdef DEBUG #define ADMIN_SQLITE_TABLE_DEBUG_LEVELS "CREATE TABLE debug_levels (module VARCHAR NOT NULL PRIMARY KEY , verbosity INT NOT NULL DEFAULT 0)" #define ADMIN_SQLITE_TABLE_DEBUG_FILTERS "CREATE TABLE debug_filters (filename VARCHAR NOT NULL , line INT NOT NULL , funct VARCHAR NOT NULL , PRIMARY KEY (filename, line, funct) )" @@ -2936,6 +2939,8 @@ bool ProxySQL_Admin::GenericRefreshStatistics(const char *query_no_space, unsign bool stats_mysql_query_rules=false; bool stats_mysql_users=false; bool stats_mysql_gtid_executed=false; + bool stats_mysql_client_host_cache=false; + bool stats_mysql_client_host_cache_reset=false; bool dump_global_variables=false; bool runtime_scheduler=false; @@ -3023,6 +3028,10 @@ bool ProxySQL_Admin::GenericRefreshStatistics(const char *query_no_space, unsign { stats_mysql_users=true; refresh=true; } if (strstr(query_no_space,"stats_mysql_gtid_executed")) { stats_mysql_gtid_executed=true; refresh=true; } + if (strstr(query_no_space,"stats_mysql_client_host_cache")) + { stats_mysql_client_host_cache=true; refresh=true; } + if (strstr(query_no_space,"stats_mysql_client_host_cache_reset")) + { stats_mysql_client_host_cache_reset=true; refresh=true; } if (strstr(query_no_space,"stats_proxysql_servers_checksums")) { stats_proxysql_servers_checksums = true; refresh = true; } @@ -3163,6 +3172,13 @@ bool ProxySQL_Admin::GenericRefreshStatistics(const char *query_no_space, unsign stats___mysql_prepared_statements_info(); } + if (stats_mysql_client_host_cache) { + stats___mysql_client_host_cache(false); + } + if (stats_mysql_client_host_cache_reset) { + stats___mysql_client_host_cache(true); + } + if (admin) { if (dump_global_variables) { pthread_mutex_lock(&GloVars.checksum_mutex); @@ -5537,6 +5553,8 @@ bool ProxySQL_Admin::init() { insert_into_tables_defs(tables_defs_stats,"stats_mysql_users", STATS_SQLITE_TABLE_MYSQL_USERS); insert_into_tables_defs(tables_defs_stats,"global_variables", ADMIN_SQLITE_TABLE_GLOBAL_VARIABLES); // workaround for issue #708 insert_into_tables_defs(tables_defs_stats,"stats_mysql_prepared_statements_info", ADMIN_SQLITE_TABLE_STATS_MYSQL_PREPARED_STATEMENTS_INFO); + insert_into_tables_defs(tables_defs_stats,"stats_mysql_client_host_cache", STATS_SQLITE_TABLE_MYSQL_CLIENT_HOST_CACHE); + insert_into_tables_defs(tables_defs_stats,"stats_mysql_client_host_cache_reset", STATS_SQLITE_TABLE_MYSQL_CLIENT_HOST_CACHE_RESET); // ProxySQL Cluster insert_into_tables_defs(tables_defs_admin,"proxysql_servers", ADMIN_SQLITE_TABLE_PROXYSQL_SERVERS); @@ -8770,6 +8788,50 @@ void ProxySQL_Admin::stats___mysql_query_digests(bool reset, bool copy) { delete resultset; } +void ProxySQL_Admin::stats___mysql_client_host_cache(bool reset) { + if (!GloQPro) return; + + SQLite3_result* resultset = GloMTH->get_client_host_cache(reset); + if (resultset==NULL) return; + + statsdb->execute("BEGIN"); + + int rc = 0; + sqlite3_stmt* statement=NULL; + char* query = NULL; + + if (reset) { + query=(char*)"INSERT INTO stats_mysql_client_host_cache_reset VALUES (?1, ?2, ?3)"; + } else { + query=(char*)"INSERT INTO stats_mysql_client_host_cache VALUES (?1, ?2, ?3)"; + } + + if (reset) { + statsdb->execute("DELETE FROM stats_mysql_client_host_cache_reset"); + } else { + statsdb->execute("DELETE FROM stats_mysql_client_host_cache"); + } + + rc = statsdb->prepare_v2(query, &statement); + ASSERT_SQLITE_OK(rc, statsdb); + + for (std::vector::iterator it = resultset->rows.begin() ; it != resultset->rows.end(); ++it) { + SQLite3_row *row = *it; + + rc=(*proxy_sqlite3_bind_text)(statement, 1, row->fields[0], -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb); + rc=(*proxy_sqlite3_bind_int64)(statement, 2, atoll(row->fields[1])); ASSERT_SQLITE_OK(rc, statsdb); + rc=(*proxy_sqlite3_bind_int64)(statement, 3, atoll(row->fields[2])); ASSERT_SQLITE_OK(rc, statsdb); + + SAFE_SQLITE3_STEP2(statement); + rc=(*proxy_sqlite3_clear_bindings)(statement); + rc=(*proxy_sqlite3_reset)(statement); + } + + (*proxy_sqlite3_finalize)(statement); + statsdb->execute("COMMIT"); + delete resultset; +} + void ProxySQL_Admin::stats___mysql_errors(bool reset) { if (!GloQPro) return; SQLite3_result * resultset=NULL;