|
|
|
|
@ -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();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|