From a3f42f82b5e52bc2099e17dcd9c79f564b5dae0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Sat, 21 Mar 2020 17:42:28 +0100 Subject: [PATCH] Initial implemention of the prometheus exporter POC --- src/main.cpp | 239 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index c1018b9ae..94b615c07 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,6 @@ #include #include +#include #include "btree_map.h" #include "proxysql.h" #if defined(__FreeBSD__) || defined(__APPLE__) @@ -32,6 +33,11 @@ #include +// Minimal headers for exporting metrics using prometheus +#include +#include +#include + #include /* @@ -1217,7 +1223,234 @@ void ProxySQL_Main_init() { } } +using LabelMap = std::map; +using CounterConfig = std::tuple>; +using CountersMap = std::map>; +const std::map> metricsCountersMap { + { + // Family name + "Server_Connections", + { + // CounterConfig + { + "Handled server connections", + { + // Metric list mapped into labels + { "Server_Connections_created", { { "OK", "Created" } } }, + { "Server_Connections_connected", { { "OK", "Delayed" } } }, + { "Server_Connections_delayed", { { "OK", "Connected" } } }, + { "Server_Connections_aborted", { { "NOT_OK", "Aborted" } } } + } + } + } + }, + { + "ProxySQL_Uptime", + { + // CounterConfig + { + "Number of seconds of uptime", + { + // Metric list mapped into labels + { "ProxySQL_Uptime", { { "unit", "seconds" } } }, + } + } + } + }, + { + // Family name + "Client_Connections", + { + { + "Handled client connections", + { + // Metric list mapped into labels + { "Client_Connections_created", { { "OK", "Created" } } }, + { "Client_Connections_connected", { { "OK", "Connected" } } }, + { "Client_Connections_non_idle", { { "OK", "Non Idle" } } }, + { "Client_Connections_aborted", { { "NOT_OK", "Aborted" } } }, + { "Client_Connections_hostgroup_locked", { { "NOT_OK", "Hostgroup Locked" } } }, + } + } + } + }, + { + // Family name + "Queries_bytes_total", + { + // CounterConfig + { + "Total number of bytes served", + { + // Metric list mapped into labels + { "Queries_backends_bytes_recv", { { "target", "backend" }, { "direction", "received" } } }, + { "Queries_backends_bytes_sent", { { "target", "backend" }, { "direction", "sent" } } }, + { "Queries_frontends_bytes_recv", { { "target", "frontend" }, { "direction", "received" } } }, + { "Queries_frontends_bytes_sent", { { "target", "frontend" }, { "direction", "sent" } } }, + } + } + } + } +}; + +using std::shared_ptr; +using std::vector; +using std::string; + +/** + * @brief Create a counter map holding all the metrics families specified in the metrics parameter. + * + * @param families A vector holding the identifiers of the families for which counters needs to be created. + * @param registry The registry in which to register all the created counters. + * @return CountersMap A map associating the metrics identifiers with the corresponding counter associated to the metric. + */ +CountersMap createCounters(const vector& families, shared_ptr registry) { + using namespace prometheus; + CountersMap result {}; + + for (const auto& family : families) { + const auto& fit { metricsCountersMap.find(family) }; + + if (fit != metricsCountersMap.end()) { + const CounterConfig& counterConfig { std::get<0>(fit->second) }; + const std::string& help { std::get<0>(counterConfig) }; + const auto& labels { std::get<1>(counterConfig) }; + + auto& new_counter_family { + BuildCounter() + .Name(family) + .Help(help) + .Register(*registry) + }; + + for (const auto& id_labelmap : labels) { + result.insert( { id_labelmap.first, { new_counter_family.Add(id_labelmap.second) } } ); + } + } + } + + return result; +} + +/** + * @brief POC: Prometheus exporter main thread. + */ +void prometheus_thread() { + using namespace prometheus; + + // TODO: Exposer port should be received from the config file + // =============================================================== + + // Create an http server running on port 8080 + Exposer exposer {"8084,[::]:8084"}; + + // =============================================================== + + // Create a metrics registry + auto registry { std::make_shared() }; + + // Locals for the query execution + std::string query { "SELECT Variable_Name AS Variable_name, Variable_Value AS Value FROM stats_mysql_global ORDER BY variable_name" }; + char* query_error { nullptr }; + int cols { 0 }; + int affected_rows { 0 }; + SQLite3_result* resultset { nullptr }; + + // Lambda to check if the db is ready for being queried + const auto check_if_db_ready = [] () -> bool { + // Locals for the query statement + SQLite3_result* resultset { nullptr }; + char* query_error { nullptr }; + int cols { 0 }; + int affected_rows {0}; + + // If db isn't ready, just wait and continue + const bool db_not_ready = + GloAdmin == nullptr || + GloAdmin->admindb == nullptr || + GloAdmin->admindb->get_db() == nullptr; + + if (db_not_ready) { + return false; + } + + // Check if 'stats_mysql_global' table exists already + GloAdmin->admindb->execute("PRAGMA query_only = ON"); + std::string query_table_check { "SELECT count(type) FROM sqlite_master WHERE type='table' AND name='stats_mysql_global'" }; + GloAdmin->admindb->execute_statement(query_table_check.c_str(), &query_error , &cols , &affected_rows , &resultset); + GloAdmin->admindb->execute("PRAGMA query_only = OFF"); + + if (resultset == nullptr || resultset->rows_count == 0) { + return false; + } else { + delete resultset; + } + + return true; + }; + + // Flag to ensure that stats are loaded + bool stats_ready { false }; + + // TODO: Counter families should be received from the config file + // =============================================================== + + // Create the desired counters + auto counters { + createCounters( + { + "ProxySQL_Uptime", + "Server_Connections", + "Client_Connections", + "Queries_bytes_total" + }, + registry + ) + }; + + // =============================================================== + + // Ask the exposer to scrape the registry on incoming scrapes + exposer.RegisterCollectable(registry); + + // TODO: Delay should be specified in the config file + // =============================================================== + uint32_t s_delay { 1 }; + // =============================================================== + + while (glovars.shutdown == false) { + if (stats_ready == false) { + stats_ready = check_if_db_ready(); + std::this_thread::sleep_for(std::chrono::seconds(s_delay)); + continue; + } + + // Update for new stats + const std::string global_stats { "stats_mysql_global" }; + GloAdmin->GenericRefreshStatistics(global_stats.c_str(), global_stats.size(), false); + + // Query the stats values + GloAdmin->admindb->execute("PRAGMA query_only = ON"); + GloAdmin->admindb->execute_statement(query.c_str(), &query_error , &cols , &affected_rows , &resultset); + GloAdmin->admindb->execute("PRAGMA query_only = OFF"); + + // Search for the requested query values + if (resultset != nullptr && !resultset->rows.empty()) { + for (auto& row : resultset->rows) { + auto row_counter_it { counters.find(row->fields[0]) }; + if (row_counter_it != counters.end()) { + Counter& curCounter { row_counter_it->second.get() }; + const auto& cur_counter_val { curCounter.Value() }; + const auto& cur_stat_val { atoi(row->fields[1]) }; + + curCounter.Increment(cur_stat_val - cur_counter_val); + } + } + } + std::this_thread::sleep_for(std::chrono::seconds(s_delay)); + } +} static void LoadPlugins() { if (GloVars.web_interface_plugin) { @@ -1283,6 +1516,12 @@ static void LoadPlugins() { //} } } + if (!GloVars.prometheus_plugin.empty()) { + // TODO: Export current implementation to a plugin + // =============================================== + std::thread exporter { prometheus_thread }; + exporter.detach(); + } }