Initial implemention of the prometheus exporter POC

pull/2676/head
Javier Jaramago Fernández 6 years ago
parent 57532a762e
commit a3f42f82b5

@ -1,5 +1,6 @@
#include <iostream>
#include <thread>
#include <functional>
#include "btree_map.h"
#include "proxysql.h"
#if defined(__FreeBSD__) || defined(__APPLE__)
@ -32,6 +33,11 @@
#include <openssl/x509v3.h>
// Minimal headers for exporting metrics using prometheus
#include <prometheus/counter.h>
#include <prometheus/exposer.h>
#include <prometheus/registry.h>
#include <sys/mman.h>
/*
@ -1217,7 +1223,234 @@ void ProxySQL_Main_init() {
}
}
using LabelMap = std::map<std::string, std::string>;
using CounterConfig = std::tuple<std::string, std::map<std::string, LabelMap>>;
using CountersMap = std::map<std::string, std::reference_wrapper<prometheus::Counter>>;
const std::map<std::string, std::tuple<CounterConfig>> 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<string>& families, shared_ptr<prometheus::Registry> 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<Registry>() };
// 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();
}
}

Loading…
Cancel
Save