diff --git a/pdns/dnsdistdist/dnsdist-carbon.cc b/pdns/dnsdistdist/dnsdist-carbon.cc index 040783b68a95..66ec734250e4 100644 --- a/pdns/dnsdistdist/dnsdist-carbon.cc +++ b/pdns/dnsdistdist/dnsdist-carbon.cc @@ -57,6 +57,11 @@ static bool doOneCarbonExport(const Carbon::Endpoint& endpoint) { auto entries = dnsdist::metrics::g_stats.entries.read_lock(); for (const auto& entry : *entries) { + // Skip non-empty labels, since labels are not supported in Carbon + if (!entry.d_labels.empty()) { + continue; + } + str << namespace_name << "." << hostname << "." << instance_name << "." << entry.d_name << ' '; if (const auto& val = std::get_if(&entry.d_value)) { str << (*val)->load(); diff --git a/pdns/dnsdistdist/dnsdist-lua-ffi.cc b/pdns/dnsdistdist/dnsdist-lua-ffi.cc index 192e8f1cfd10..03ffb98a2602 100644 --- a/pdns/dnsdistdist/dnsdist-lua-ffi.cc +++ b/pdns/dnsdistdist/dnsdist-lua-ffi.cc @@ -260,7 +260,7 @@ void dnsdist_ffi_dnsquestion_get_sni(const dnsdist_ffi_dnsquestion_t* dq, const const char* dnsdist_ffi_dnsquestion_get_tag(const dnsdist_ffi_dnsquestion_t* dq, const char* label) { - const char * result = nullptr; + const char* result = nullptr; if (dq != nullptr && dq->dq != nullptr && dq->dq->ids.qTag != nullptr) { const auto it = dq->dq->ids.qTag->find(label); @@ -456,7 +456,6 @@ size_t dnsdist_ffi_dnsquestion_get_tag_array(dnsdist_ffi_dnsquestion_t* dq, cons ++pos; } - if (!dq->tagsVect->empty()) { *out = dq->tagsVect->data(); } @@ -1007,7 +1006,7 @@ static constexpr char s_lua_ffi_code[] = R"FFICodeContent( ffi.cdef[[ )FFICodeContent" #include "dnsdist-lua-ffi-interface.inc" -R"FFICodeContent( + R"FFICodeContent( ]] )FFICodeContent"; @@ -1057,7 +1056,8 @@ size_t dnsdist_ffi_generate_proxy_protocol_payload(const size_t addrSize, const if (valuesCount > 0) { valuesVect.reserve(valuesCount); for (size_t idx = 0; idx < valuesCount; idx++) { - valuesVect.push_back({ std::string(values[idx].value, values[idx].size), values[idx].type }); + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + valuesVect.push_back({std::string(values[idx].value, values[idx].size), values[idx].type}); } } @@ -1086,7 +1086,8 @@ size_t dnsdist_ffi_dnsquestion_generate_proxy_protocol_payload(const dnsdist_ffi if (valuesCount > 0) { valuesVect.reserve(valuesCount); for (size_t idx = 0; idx < valuesCount; idx++) { - valuesVect.push_back({ std::string(values[idx].value, values[idx].size), values[idx].type }); + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + valuesVect.push_back({std::string(values[idx].value, values[idx].size), values[idx].type}); } } @@ -1113,7 +1114,7 @@ bool dnsdist_ffi_dnsquestion_add_proxy_protocol_values(dnsdist_ffi_dnsquestion_t dnsQuestion->dq->proxyProtocolValues->reserve(dnsQuestion->dq->proxyProtocolValues->size() + valuesCount); for (size_t idx = 0; idx < valuesCount; idx++) { // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): the Lua FFI API is a C API.. - dnsQuestion->dq->proxyProtocolValues->push_back({ std::string(values[idx].value, values[idx].size), values[idx].type }); + dnsQuestion->dq->proxyProtocolValues->push_back({std::string(values[idx].value, values[idx].size), values[idx].type}); } return true; @@ -1146,7 +1147,8 @@ struct dnsdist_ffi_domain_list_t { std::vector d_domains; }; -struct dnsdist_ffi_address_list_t { +struct dnsdist_ffi_address_list_t +{ std::vector d_addresses; }; @@ -1476,7 +1478,8 @@ void dnsdist_ffi_ring_entry_list_free(dnsdist_ffi_ring_entry_list_t* list) delete list; } -template static void addRingEntryToList(std::unique_ptr& list, const struct timespec& now, const T& entry) +template +static void addRingEntryToList(std::unique_ptr& list, const struct timespec& now, const T& entry) { auto age = DiffTime(entry.when, now); @@ -1805,13 +1808,13 @@ bool dnsdist_ffi_metric_declare(const char* name, size_t nameLen, const char* ty if (name == nullptr || nameLen == 0 || type == nullptr || description == nullptr) { return false; } - auto result = dnsdist::metrics::declareCustomMetric(name, type, description, customName != nullptr ? std::optional(customName) : std::nullopt); + auto result = dnsdist::metrics::declareCustomMetric(name, type, description, customName != nullptr ? std::optional(customName) : std::nullopt, false); return !result; } void dnsdist_ffi_metric_inc(const char* metricName, size_t metricNameLen) { - auto result = dnsdist::metrics::incrementCustomCounter(std::string_view(metricName, metricNameLen), 1U); + auto result = dnsdist::metrics::incrementCustomCounter(std::string_view(metricName, metricNameLen), 1U, {}); if (std::get_if(&result) != nullptr) { return; } @@ -1819,7 +1822,7 @@ void dnsdist_ffi_metric_inc(const char* metricName, size_t metricNameLen) void dnsdist_ffi_metric_inc_by(const char* metricName, size_t metricNameLen, uint64_t value) { - auto result = dnsdist::metrics::incrementCustomCounter(std::string_view(metricName, metricNameLen), value); + auto result = dnsdist::metrics::incrementCustomCounter(std::string_view(metricName, metricNameLen), value, {}); if (std::get_if(&result) != nullptr) { return; } @@ -1827,7 +1830,7 @@ void dnsdist_ffi_metric_inc_by(const char* metricName, size_t metricNameLen, uin void dnsdist_ffi_metric_dec(const char* metricName, size_t metricNameLen) { - auto result = dnsdist::metrics::decrementCustomCounter(std::string_view(metricName, metricNameLen), 1U); + auto result = dnsdist::metrics::decrementCustomCounter(std::string_view(metricName, metricNameLen), 1U, {}); if (std::get_if(&result) != nullptr) { return; } @@ -1835,7 +1838,7 @@ void dnsdist_ffi_metric_dec(const char* metricName, size_t metricNameLen) void dnsdist_ffi_metric_set(const char* metricName, size_t metricNameLen, double value) { - auto result = dnsdist::metrics::setCustomGauge(std::string_view(metricName, metricNameLen), value); + auto result = dnsdist::metrics::setCustomGauge(std::string_view(metricName, metricNameLen), value, {}); if (std::get_if(&result) != nullptr) { return; } @@ -1843,7 +1846,7 @@ void dnsdist_ffi_metric_set(const char* metricName, size_t metricNameLen, double double dnsdist_ffi_metric_get(const char* metricName, size_t metricNameLen, bool isCounter) { - auto result = dnsdist::metrics::getCustomMetric(std::string_view(metricName, metricNameLen)); + auto result = dnsdist::metrics::getCustomMetric(std::string_view(metricName, metricNameLen), {}); if (std::get_if(&result) != nullptr) { return 0.; } diff --git a/pdns/dnsdistdist/dnsdist-lua-ffi.hh b/pdns/dnsdistdist/dnsdist-lua-ffi.hh index 1369c2a07cd1..644114d69971 100644 --- a/pdns/dnsdistdist/dnsdist-lua-ffi.hh +++ b/pdns/dnsdistdist/dnsdist-lua-ffi.hh @@ -23,27 +23,31 @@ #include "dnsdist.hh" -extern "C" { +extern "C" +{ #include "dnsdist-lua-ffi-interface.h" } #include "ext/luawrapper/include/LuaContext.hpp" // dnsdist_ffi_dnsquestion_t is a lightuserdata -template<> -struct LuaContext::Pusher { - static const int minSize = 1; - static const int maxSize = 1; - - static PushedObject push(lua_State* state, dnsdist_ffi_dnsquestion_t* ptr) noexcept { - lua_pushlightuserdata(state, ptr); - return PushedObject{state, 1}; - } +template <> +struct LuaContext::Pusher +{ + static const int minSize = 1; + static const int maxSize = 1; + + static PushedObject push(lua_State* state, dnsdist_ffi_dnsquestion_t* ptr) noexcept + { + lua_pushlightuserdata(state, ptr); + return PushedObject{state, 1}; + } }; struct dnsdist_ffi_dnsquestion_t { - dnsdist_ffi_dnsquestion_t(DNSQuestion* dq_): dq(dq_) + dnsdist_ffi_dnsquestion_t(DNSQuestion* dq_) : + dq(dq_) { } @@ -63,20 +67,23 @@ struct dnsdist_ffi_dnsquestion_t }; // dnsdist_ffi_dnsresponse_t is a lightuserdata -template<> -struct LuaContext::Pusher { - static const int minSize = 1; - static const int maxSize = 1; - - static PushedObject push(lua_State* state, dnsdist_ffi_dnsresponse_t* ptr) noexcept { - lua_pushlightuserdata(state, ptr); - return PushedObject{state, 1}; - } +template <> +struct LuaContext::Pusher +{ + static const int minSize = 1; + static const int maxSize = 1; + + static PushedObject push(lua_State* state, dnsdist_ffi_dnsresponse_t* ptr) noexcept + { + lua_pushlightuserdata(state, ptr); + return PushedObject{state, 1}; + } }; struct dnsdist_ffi_dnsresponse_t { - dnsdist_ffi_dnsresponse_t(DNSResponse* dr_): dr(dr_) + dnsdist_ffi_dnsresponse_t(DNSResponse* dr_) : + dr(dr_) { } @@ -85,20 +92,23 @@ struct dnsdist_ffi_dnsresponse_t }; // dnsdist_ffi_server_t is a lightuserdata -template<> -struct LuaContext::Pusher { - static const int minSize = 1; - static const int maxSize = 1; - - static PushedObject push(lua_State* state, dnsdist_ffi_server_t* ptr) noexcept { - lua_pushlightuserdata(state, ptr); - return PushedObject{state, 1}; - } +template <> +struct LuaContext::Pusher +{ + static const int minSize = 1; + static const int maxSize = 1; + + static PushedObject push(lua_State* state, dnsdist_ffi_server_t* ptr) noexcept + { + lua_pushlightuserdata(state, ptr); + return PushedObject{state, 1}; + } }; struct dnsdist_ffi_server_t { - dnsdist_ffi_server_t(const std::shared_ptr& server_): server(server_) + dnsdist_ffi_server_t(const std::shared_ptr& server_) : + server(server_) { } @@ -106,23 +116,26 @@ struct dnsdist_ffi_server_t }; // dnsdist_ffi_servers_list_t is a lightuserdata -template<> -struct LuaContext::Pusher { - static const int minSize = 1; - static const int maxSize = 1; - - static PushedObject push(lua_State* state, dnsdist_ffi_servers_list_t* ptr) noexcept { - lua_pushlightuserdata(state, ptr); - return PushedObject{state, 1}; - } +template <> +struct LuaContext::Pusher +{ + static const int minSize = 1; + static const int maxSize = 1; + + static PushedObject push(lua_State* state, dnsdist_ffi_servers_list_t* ptr) noexcept + { + lua_pushlightuserdata(state, ptr); + return PushedObject{state, 1}; + } }; struct dnsdist_ffi_servers_list_t { - dnsdist_ffi_servers_list_t(const ServerPolicy::NumberedServerVector& servers_): servers(servers_) + dnsdist_ffi_servers_list_t(const ServerPolicy::NumberedServerVector& servers_) : + servers(servers_) { ffiServers.reserve(servers.size()); - for (const auto& server: servers) { + for (const auto& server : servers) { ffiServers.push_back(dnsdist_ffi_server_t(server.second)); } } @@ -132,20 +145,23 @@ struct dnsdist_ffi_servers_list_t }; // dnsdist_ffi_network_message_t is a lightuserdata -template<> -struct LuaContext::Pusher { - static const int minSize = 1; - static const int maxSize = 1; - - static PushedObject push(lua_State* state, dnsdist_ffi_network_message_t* ptr) noexcept { - lua_pushlightuserdata(state, ptr); - return PushedObject{state, 1}; - } +template <> +struct LuaContext::Pusher +{ + static const int minSize = 1; + static const int maxSize = 1; + + static PushedObject push(lua_State* state, dnsdist_ffi_network_message_t* ptr) noexcept + { + lua_pushlightuserdata(state, ptr); + return PushedObject{state, 1}; + } }; struct dnsdist_ffi_network_message_t { - dnsdist_ffi_network_message_t(const std::string& payload_ ,const std::string& from_, uint16_t endpointID_): payload(payload_), from(from_), endpointID(endpointID_) + dnsdist_ffi_network_message_t(const std::string& payload_, const std::string& from_, uint16_t endpointID_) : + payload(payload_), from(from_), endpointID(endpointID_) { } diff --git a/pdns/dnsdistdist/dnsdist-lua-inspection.cc b/pdns/dnsdistdist/dnsdist-lua-inspection.cc index 2ad3999f7c2e..26d37f712254 100644 --- a/pdns/dnsdistdist/dnsdist-lua-inspection.cc +++ b/pdns/dnsdistdist/dnsdist-lua-inspection.cc @@ -19,7 +19,9 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include #include +#include #include "dnsdist.hh" #include "dnsdist-console.hh" @@ -811,12 +813,18 @@ void setupLuaInspection(LuaContext& luaCtx) boost::format fmt("%-35s\t%+11s"); g_outputBuffer.clear(); auto entries = *dnsdist::metrics::g_stats.entries.read_lock(); - sort(entries.begin(), entries.end(), + + // Filter entries to just the ones without label, for clearer output + std::vector> unlabeledEntries; + std::copy_if(entries.begin(), entries.end(), std::back_inserter(unlabeledEntries), [](const decltype(entries)::value_type& triple) { return triple.d_labels.empty(); }); + + sort(unlabeledEntries.begin(), unlabeledEntries.end(), [](const decltype(entries)::value_type& lhs, const decltype(entries)::value_type& rhs) { return lhs.d_name < rhs.d_name; }); boost::format flt(" %9.1f"); - for (const auto& entry : entries) { + for (const auto& entryRef : unlabeledEntries) { + const auto& entry = entryRef.get(); string second; if (const auto& val = std::get_if(&entry.d_value)) { second = std::to_string((*val)->load()); @@ -828,7 +836,7 @@ void setupLuaInspection(LuaContext& luaCtx) second = std::to_string((*func)(entry.d_name)); } - if (leftcolumn.size() < entries.size() / 2) { + if (leftcolumn.size() < unlabeledEntries.size() / 2) { leftcolumn.push_back((fmt % entry.d_name % second).str()); } else { diff --git a/pdns/dnsdistdist/dnsdist-lua.cc b/pdns/dnsdistdist/dnsdist-lua.cc index 09818d982107..d97c8329e0de 100644 --- a/pdns/dnsdistdist/dnsdist-lua.cc +++ b/pdns/dnsdistdist/dnsdist-lua.cc @@ -85,6 +85,9 @@ using std::thread; +using update_metric_opts_t = LuaAssociativeTable>>; +using declare_metric_opts_t = LuaAssociativeTable>; + static boost::tribool s_noLuaSideEffect; /* this is a best effort way to prevent logging calls with no side-effects in the output of delta() @@ -1264,7 +1267,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) }); luaCtx.writeFunction("getPoolServers", [](const string& pool) { - //coverity[auto_causes_copy] + // coverity[auto_causes_copy] const auto poolServers = getDownstreamCandidates(pool); return *poolServers; }); @@ -1873,9 +1876,9 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) // 1 2 3 4 ret << (fmt % "Name" % "Cache" % "ServerPolicy" % "Servers") << endl; - //coverity[auto_causes_copy] + // coverity[auto_causes_copy] const auto defaultPolicyName = dnsdist::configuration::getCurrentRuntimeConfiguration().d_lbPolicy->getName(); - //coverity[auto_causes_copy] + // coverity[auto_causes_copy] const auto pools = dnsdist::configuration::getCurrentRuntimeConfiguration().d_pools; for (const auto& entry : pools) { const string& name = entry.first; @@ -3410,8 +3413,22 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) newThread.detach(); }); - luaCtx.writeFunction("declareMetric", [](const std::string& name, const std::string& type, const std::string& description, boost::optional customName) { - auto result = dnsdist::metrics::declareCustomMetric(name, type, description, customName ? std::optional(*customName) : std::nullopt); + luaCtx.writeFunction("declareMetric", [](const std::string& name, const std::string& type, const std::string& description, boost::optional> opts) { + bool withLabels = false; + std::optional customName = std::nullopt; + if (opts) { + auto* optCustomName = boost::get(&opts.get()); + if (optCustomName != nullptr) { + customName = std::optional(*optCustomName); + } + if (!customName) { + boost::optional vars = {boost::get(opts.get())}; + getOptionalValue(vars, "customName", customName); + getOptionalValue(vars, "withLabels", withLabels); + checkAllParametersConsumed("declareMetric", vars); + } + } + auto result = dnsdist::metrics::declareCustomMetric(name, type, description, customName, withLabels); if (result) { g_outputBuffer += *result + "\n"; errlog("Error in declareMetric: %s", *result); @@ -3419,8 +3436,21 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) } return true; }); - luaCtx.writeFunction("incMetric", [](const std::string& name, boost::optional step) { - auto result = dnsdist::metrics::incrementCustomCounter(name, step ? *step : 1); + // NOLINTNEXTLINE(performance-unnecessary-value-param) + luaCtx.writeFunction("incMetric", [](const std::string& name, boost::optional> opts) { + auto incOpts = opts.get_value_or(1); + uint64_t step = 1; + std::unordered_map labels; + if (auto* custom_step = boost::get(&incOpts)) { + step = *custom_step; + } + else { + boost::optional vars = {boost::get(incOpts)}; + getOptionalValue(vars, "step", step); + getOptionalValue>(vars, "labels", labels); + checkAllParametersConsumed("incMetric", vars); + } + auto result = dnsdist::metrics::incrementCustomCounter(name, step, labels); if (const auto* errorStr = std::get_if(&result)) { g_outputBuffer = *errorStr + "'\n"; errlog("Error in incMetric: %s", *errorStr); @@ -3428,8 +3458,21 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) } return std::get(result); }); - luaCtx.writeFunction("decMetric", [](const std::string& name, boost::optional step) { - auto result = dnsdist::metrics::decrementCustomCounter(name, step ? *step : 1); + // NOLINTNEXTLINE(performance-unnecessary-value-param) + luaCtx.writeFunction("decMetric", [](const std::string& name, boost::optional> opts) { + auto decOpts = opts.get_value_or(1); + uint64_t step = 1; + std::unordered_map labels; + if (auto* custom_step = boost::get(&decOpts)) { + step = *custom_step; + } + else { + boost::optional vars = {boost::get(decOpts)}; + getOptionalValue(vars, "step", step); + getOptionalValue>(vars, "labels", labels); + checkAllParametersConsumed("decMetric", vars); + } + auto result = dnsdist::metrics::decrementCustomCounter(name, step, labels); if (const auto* errorStr = std::get_if(&result)) { g_outputBuffer = *errorStr + "'\n"; errlog("Error in decMetric: %s", *errorStr); @@ -3437,8 +3480,13 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) } return std::get(result); }); - luaCtx.writeFunction("setMetric", [](const std::string& name, const double value) -> double { - auto result = dnsdist::metrics::setCustomGauge(name, value); + luaCtx.writeFunction("setMetric", [](const std::string& name, const double value, boost::optional opts) -> double { + std::unordered_map labels; + if (opts) { + getOptionalValue>(opts, "labels", labels); + } + checkAllParametersConsumed("setMetric", opts); + auto result = dnsdist::metrics::setCustomGauge(name, value, labels); if (const auto* errorStr = std::get_if(&result)) { g_outputBuffer = *errorStr + "'\n"; errlog("Error in setMetric: %s", *errorStr); @@ -3446,8 +3494,13 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) } return std::get(result); }); - luaCtx.writeFunction("getMetric", [](const std::string& name) { - auto result = dnsdist::metrics::getCustomMetric(name); + luaCtx.writeFunction("getMetric", [](const std::string& name, boost::optional opts) { + std::unordered_map labels; + if (opts) { + getOptionalValue>(opts, "labels", labels); + } + checkAllParametersConsumed("getMetric", opts); + auto result = dnsdist::metrics::getCustomMetric(name, labels); if (const auto* errorStr = std::get_if(&result)) { g_outputBuffer = *errorStr + "'\n"; errlog("Error in getMetric: %s", *errorStr); diff --git a/pdns/dnsdistdist/dnsdist-metrics.cc b/pdns/dnsdistdist/dnsdist-metrics.cc index 434088df4e2f..293be6d95c4d 100644 --- a/pdns/dnsdistdist/dnsdist-metrics.cc +++ b/pdns/dnsdistdist/dnsdist-metrics.cc @@ -19,7 +19,10 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include +#include #include +#include #include "dnsdist-metrics.hh" #include "dnsdist.hh" @@ -67,107 +70,107 @@ struct MutableGauge mutable pdns::stat_double_t d_value{0}; }; -static SharedLockGuarded>> s_customCounters; -static SharedLockGuarded>> s_customGauges; +static SharedLockGuarded, std::less<>>> s_customCounters; +static SharedLockGuarded, std::less<>>> s_customGauges; Stats::Stats() : - entries{std::vector{ - {"responses", &responses}, - {"servfail-responses", &servfailResponses}, - {"queries", &queries}, - {"frontend-nxdomain", &frontendNXDomain}, - {"frontend-servfail", &frontendServFail}, - {"frontend-noerror", &frontendNoError}, - {"acl-drops", &aclDrops}, - {"rule-drop", &ruleDrop}, - {"rule-nxdomain", &ruleNXDomain}, - {"rule-refused", &ruleRefused}, - {"rule-servfail", &ruleServFail}, - {"rule-truncated", &ruleTruncated}, - {"self-answered", &selfAnswered}, - {"downstream-timeouts", &downstreamTimeouts}, - {"downstream-send-errors", &downstreamSendErrors}, - {"trunc-failures", &truncFail}, - {"no-policy", &noPolicy}, - {"latency0-1", &latency0_1}, - {"latency1-10", &latency1_10}, - {"latency10-50", &latency10_50}, - {"latency50-100", &latency50_100}, - {"latency100-1000", &latency100_1000}, - {"latency-slow", &latencySlow}, - {"latency-avg100", &latencyAvg100}, - {"latency-avg1000", &latencyAvg1000}, - {"latency-avg10000", &latencyAvg10000}, - {"latency-avg1000000", &latencyAvg1000000}, - {"latency-tcp-avg100", &latencyTCPAvg100}, - {"latency-tcp-avg1000", &latencyTCPAvg1000}, - {"latency-tcp-avg10000", &latencyTCPAvg10000}, - {"latency-tcp-avg1000000", &latencyTCPAvg1000000}, - {"latency-dot-avg100", &latencyDoTAvg100}, - {"latency-dot-avg1000", &latencyDoTAvg1000}, - {"latency-dot-avg10000", &latencyDoTAvg10000}, - {"latency-dot-avg1000000", &latencyDoTAvg1000000}, - {"latency-doh-avg100", &latencyDoHAvg100}, - {"latency-doh-avg1000", &latencyDoHAvg1000}, - {"latency-doh-avg10000", &latencyDoHAvg10000}, - {"latency-doh-avg1000000", &latencyDoHAvg1000000}, - {"latency-doq-avg100", &latencyDoQAvg100}, - {"latency-doq-avg1000", &latencyDoQAvg1000}, - {"latency-doq-avg10000", &latencyDoQAvg10000}, - {"latency-doq-avg1000000", &latencyDoQAvg1000000}, - {"latency-doh3-avg100", &latencyDoH3Avg100}, - {"latency-doh3-avg1000", &latencyDoH3Avg1000}, - {"latency-doh3-avg10000", &latencyDoH3Avg10000}, - {"latency-doh3-avg1000000", &latencyDoH3Avg1000000}, - {"uptime", uptimeOfProcess}, - {"real-memory-usage", getRealMemoryUsage}, - {"special-memory-usage", getSpecialMemoryUsage}, - {"udp-in-errors", [](const std::string&) { return udpErrorStats("udp-in-errors"); }}, - {"udp-noport-errors", [](const std::string&) { return udpErrorStats("udp-noport-errors"); }}, - {"udp-recvbuf-errors", [](const std::string&) { return udpErrorStats("udp-recvbuf-errors"); }}, - {"udp-sndbuf-errors", [](const std::string&) { return udpErrorStats("udp-sndbuf-errors"); }}, - {"udp-in-csum-errors", [](const std::string&) { return udpErrorStats("udp-in-csum-errors"); }}, - {"udp6-in-errors", [](const std::string&) { return udp6ErrorStats("udp6-in-errors"); }}, - {"udp6-recvbuf-errors", [](const std::string&) { return udp6ErrorStats("udp6-recvbuf-errors"); }}, - {"udp6-sndbuf-errors", [](const std::string&) { return udp6ErrorStats("udp6-sndbuf-errors"); }}, - {"udp6-noport-errors", [](const std::string&) { return udp6ErrorStats("udp6-noport-errors"); }}, - {"udp6-in-csum-errors", [](const std::string&) { return udp6ErrorStats("udp6-in-csum-errors"); }}, - {"tcp-listen-overflows", [](const std::string&) { return tcpErrorStats("ListenOverflows"); }}, - {"noncompliant-queries", &nonCompliantQueries}, - {"noncompliant-responses", &nonCompliantResponses}, - {"proxy-protocol-invalid", &proxyProtocolInvalid}, - {"rdqueries", &rdQueries}, - {"empty-queries", &emptyQueries}, - {"cache-hits", &cacheHits}, - {"cache-misses", &cacheMisses}, - {"cpu-iowait", getCPUIOWait}, - {"cpu-steal", getCPUSteal}, - {"cpu-sys-msec", getCPUTimeSystem}, - {"cpu-user-msec", getCPUTimeUser}, - {"fd-usage", getOpenFileDescriptors}, - {"dyn-blocked", &dynBlocked}, + entries{std::vector{ + {"responses", "", &responses}, + {"servfail-responses", "", &servfailResponses}, + {"queries", "", &queries}, + {"frontend-nxdomain", "", &frontendNXDomain}, + {"frontend-servfail", "", &frontendServFail}, + {"frontend-noerror", "", &frontendNoError}, + {"acl-drops", "", &aclDrops}, + {"rule-drop", "", &ruleDrop}, + {"rule-nxdomain", "", &ruleNXDomain}, + {"rule-refused", "", &ruleRefused}, + {"rule-servfail", "", &ruleServFail}, + {"rule-truncated", "", &ruleTruncated}, + {"self-answered", "", &selfAnswered}, + {"downstream-timeouts", "", &downstreamTimeouts}, + {"downstream-send-errors", "", &downstreamSendErrors}, + {"trunc-failures", "", &truncFail}, + {"no-policy", "", &noPolicy}, + {"latency0-1", "", &latency0_1}, + {"latency1-10", "", &latency1_10}, + {"latency10-50", "", &latency10_50}, + {"latency50-100", "", &latency50_100}, + {"latency100-1000", "", &latency100_1000}, + {"latency-slow", "", &latencySlow}, + {"latency-avg100", "", &latencyAvg100}, + {"latency-avg1000", "", &latencyAvg1000}, + {"latency-avg10000", "", &latencyAvg10000}, + {"latency-avg1000000", "", &latencyAvg1000000}, + {"latency-tcp-avg100", "", &latencyTCPAvg100}, + {"latency-tcp-avg1000", "", &latencyTCPAvg1000}, + {"latency-tcp-avg10000", "", &latencyTCPAvg10000}, + {"latency-tcp-avg1000000", "", &latencyTCPAvg1000000}, + {"latency-dot-avg100", "", &latencyDoTAvg100}, + {"latency-dot-avg1000", "", &latencyDoTAvg1000}, + {"latency-dot-avg10000", "", &latencyDoTAvg10000}, + {"latency-dot-avg1000000", "", &latencyDoTAvg1000000}, + {"latency-doh-avg100", "", &latencyDoHAvg100}, + {"latency-doh-avg1000", "", &latencyDoHAvg1000}, + {"latency-doh-avg10000", "", &latencyDoHAvg10000}, + {"latency-doh-avg1000000", "", &latencyDoHAvg1000000}, + {"latency-doq-avg100", "", &latencyDoQAvg100}, + {"latency-doq-avg1000", "", &latencyDoQAvg1000}, + {"latency-doq-avg10000", "", &latencyDoQAvg10000}, + {"latency-doq-avg1000000", "", &latencyDoQAvg1000000}, + {"latency-doh3-avg100", "", &latencyDoH3Avg100}, + {"latency-doh3-avg1000", "", &latencyDoH3Avg1000}, + {"latency-doh3-avg10000", "", &latencyDoH3Avg10000}, + {"latency-doh3-avg1000000", "", &latencyDoH3Avg1000000}, + {"uptime", "", uptimeOfProcess}, + {"real-memory-usage", "", getRealMemoryUsage}, + {"special-memory-usage", "", getSpecialMemoryUsage}, + {"udp-in-errors", "", [](const std::string&) { return udpErrorStats("udp-in-errors"); }}, + {"udp-noport-errors", "", [](const std::string&) { return udpErrorStats("udp-noport-errors"); }}, + {"udp-recvbuf-errors", "", [](const std::string&) { return udpErrorStats("udp-recvbuf-errors"); }}, + {"udp-sndbuf-errors", "", [](const std::string&) { return udpErrorStats("udp-sndbuf-errors"); }}, + {"udp-in-csum-errors", "", [](const std::string&) { return udpErrorStats("udp-in-csum-errors"); }}, + {"udp6-in-errors", "", [](const std::string&) { return udp6ErrorStats("udp6-in-errors"); }}, + {"udp6-recvbuf-errors", "", [](const std::string&) { return udp6ErrorStats("udp6-recvbuf-errors"); }}, + {"udp6-sndbuf-errors", "", [](const std::string&) { return udp6ErrorStats("udp6-sndbuf-errors"); }}, + {"udp6-noport-errors", "", [](const std::string&) { return udp6ErrorStats("udp6-noport-errors"); }}, + {"udp6-in-csum-errors", "", [](const std::string&) { return udp6ErrorStats("udp6-in-csum-errors"); }}, + {"tcp-listen-overflows", "", [](const std::string&) { return tcpErrorStats("ListenOverflows"); }}, + {"noncompliant-queries", "", &nonCompliantQueries}, + {"noncompliant-responses", "", &nonCompliantResponses}, + {"proxy-protocol-invalid", "", &proxyProtocolInvalid}, + {"rdqueries", "", &rdQueries}, + {"empty-queries", "", &emptyQueries}, + {"cache-hits", "", &cacheHits}, + {"cache-misses", "", &cacheMisses}, + {"cpu-iowait", "", getCPUIOWait}, + {"cpu-steal", "", getCPUSteal}, + {"cpu-sys-msec", "", getCPUTimeSystem}, + {"cpu-user-msec", "", getCPUTimeUser}, + {"fd-usage", "", getOpenFileDescriptors}, + {"dyn-blocked", "", &dynBlocked}, #ifndef DISABLE_DYNBLOCKS - {"dyn-block-nmg-size", [](const std::string&) { return dnsdist::DynamicBlocks::getClientAddressDynamicRules().size(); }}, + {"dyn-block-nmg-size", "", [](const std::string&) { return dnsdist::DynamicBlocks::getClientAddressDynamicRules().size(); }}, #endif /* DISABLE_DYNBLOCKS */ - {"security-status", &securityStatus}, - {"doh-query-pipe-full", &dohQueryPipeFull}, - {"doh-response-pipe-full", &dohResponsePipeFull}, - {"doq-response-pipe-full", &doqResponsePipeFull}, - {"doh3-response-pipe-full", &doh3ResponsePipeFull}, - {"outgoing-doh-query-pipe-full", &outgoingDoHQueryPipeFull}, - {"tcp-query-pipe-full", &tcpQueryPipeFull}, - {"tcp-cross-protocol-query-pipe-full", &tcpCrossProtocolQueryPipeFull}, - {"tcp-cross-protocol-response-pipe-full", &tcpCrossProtocolResponsePipeFull}, + {"security-status", "", &securityStatus}, + {"doh-query-pipe-full", "", &dohQueryPipeFull}, + {"doh-response-pipe-full", "", &dohResponsePipeFull}, + {"doq-response-pipe-full", "", &doqResponsePipeFull}, + {"doh3-response-pipe-full", "", &doh3ResponsePipeFull}, + {"outgoing-doh-query-pipe-full", "", &outgoingDoHQueryPipeFull}, + {"tcp-query-pipe-full", "", &tcpQueryPipeFull}, + {"tcp-cross-protocol-query-pipe-full", "", &tcpCrossProtocolQueryPipeFull}, + {"tcp-cross-protocol-response-pipe-full", "", &tcpCrossProtocolResponsePipeFull}, // Latency histogram - {"latency-sum", &latencySum}, - {"latency-count", &latencyCount}, + {"latency-sum", "", &latencySum}, + {"latency-count", "", &latencyCount}, }} { } struct Stats g_stats; -std::optional declareCustomMetric(const std::string& name, const std::string& type, const std::string& description, std::optional customName) +std::optional declareCustomMetric(const std::string& name, const std::string& type, const std::string& description, std::optional customName, bool withLabels) { if (!std::regex_match(name, std::regex("^[a-z0-9-]+$"))) { return std::string("Unable to declare metric '") + std::string(name) + std::string("': invalid name\n"); @@ -176,18 +179,24 @@ std::optional declareCustomMetric(const std::string& name, const st const std::string finalCustomName(customName ? *customName : ""); if (type == "counter") { auto customCounters = s_customCounters.write_lock(); - auto itp = customCounters->insert({name, MutableCounter()}); + auto itp = customCounters->emplace(name, std::map()); if (itp.second) { - g_stats.entries.write_lock()->emplace_back(Stats::EntryPair{name, &(*customCounters)[name].d_value}); + if (!withLabels) { + auto counter = itp.first->second.emplace("", MutableCounter()); + g_stats.entries.write_lock()->emplace_back(Stats::EntryTriple{name, "", &counter.first->second.d_value}); + } dnsdist::prometheus::PrometheusMetricDefinition def{name, type, description, finalCustomName}; dnsdist::webserver::addMetricDefinition(def); } } else if (type == "gauge") { auto customGauges = s_customGauges.write_lock(); - auto itp = customGauges->insert({name, MutableGauge()}); + auto itp = customGauges->emplace(name, std::map()); if (itp.second) { - g_stats.entries.write_lock()->emplace_back(Stats::EntryPair{name, &(*customGauges)[name].d_value}); + if (!withLabels) { + auto gauge = itp.first->second.emplace("", MutableGauge()); + g_stats.entries.write_lock()->emplace_back(Stats::EntryTriple{name, "", &gauge.first->second.d_value}); + } dnsdist::prometheus::PrometheusMetricDefinition def{name, type, description, finalCustomName}; dnsdist::webserver::addMetricDefinition(def); } @@ -198,57 +207,107 @@ std::optional declareCustomMetric(const std::string& name, const st return std::nullopt; } -std::variant incrementCustomCounter(const std::string_view& name, uint64_t step) +static string prometheusLabelValueEscape(const string& value) { - auto customCounters = s_customCounters.read_lock(); + string ret; + + for (char lblchar : value) { + if (lblchar == '"' || lblchar == '\\') { + ret += '\\'; + ret += lblchar; + } + else if (lblchar == '\n') { + ret += '\\'; + ret += 'n'; + } + else { + ret += lblchar; + } + } + return ret; +} + +static std::string generateCombinationOfLabels(const std::unordered_map& labels) +{ + auto ordered = std::map(labels.begin(), labels.end()); + return std::accumulate(ordered.begin(), ordered.end(), std::string(), [](const std::string& acc, const std::pair& label) { + return acc + (acc.empty() ? std::string() : ",") + label.first + "=" + "\"" + prometheusLabelValueEscape(label.second) + "\""; + }); +} + +template +static T& initializeOrGetMetric(const std::string_view& name, std::map& metricEntries, const std::unordered_map& labels) +{ + auto combinationOfLabels = generateCombinationOfLabels(labels); + auto metricEntry = metricEntries.find(combinationOfLabels); + if (metricEntry == metricEntries.end()) { + metricEntry = metricEntries.emplace(std::piecewise_construct, std::forward_as_tuple(combinationOfLabels), std::forward_as_tuple()).first; + g_stats.entries.write_lock()->emplace_back(Stats::EntryTriple{std::string(name), combinationOfLabels, &metricEntry->second.d_value}); + } + return metricEntry->second; +} + +std::variant incrementCustomCounter(const std::string_view& name, uint64_t step, const std::unordered_map& labels) +{ + auto customCounters = s_customCounters.write_lock(); auto metric = customCounters->find(name); if (metric != customCounters->end()) { - metric->second.d_value += step; - return metric->second.d_value.load(); + auto& metricEntry = initializeOrGetMetric(name, metric->second, labels); + metricEntry.d_value += step; + return metricEntry.d_value.load(); } return std::string("Unable to increment custom metric '") + std::string(name) + "': no such counter"; } -std::variant decrementCustomCounter(const std::string_view& name, uint64_t step) +std::variant decrementCustomCounter(const std::string_view& name, uint64_t step, const std::unordered_map& labels) { - auto customCounters = s_customCounters.read_lock(); + auto customCounters = s_customCounters.write_lock(); auto metric = customCounters->find(name); if (metric != customCounters->end()) { - metric->second.d_value -= step; - return metric->second.d_value.load(); + auto& metricEntry = initializeOrGetMetric(name, metric->second, labels); + metricEntry.d_value -= step; + return metricEntry.d_value.load(); } return std::string("Unable to decrement custom metric '") + std::string(name) + "': no such counter"; } -std::variant setCustomGauge(const std::string_view& name, const double value) +std::variant setCustomGauge(const std::string_view& name, const double value, const std::unordered_map& labels) { - auto customGauges = s_customGauges.read_lock(); + auto customGauges = s_customGauges.write_lock(); auto metric = customGauges->find(name); if (metric != customGauges->end()) { - metric->second.d_value = value; + auto& metricEntry = initializeOrGetMetric(name, metric->second, labels); + metricEntry.d_value = value; return value; } return std::string("Unable to set metric '") + std::string(name) + "': no such gauge"; } -std::variant getCustomMetric(const std::string_view& name) +std::variant getCustomMetric(const std::string_view& name, const std::unordered_map& labels) { { auto customCounters = s_customCounters.read_lock(); auto counter = customCounters->find(name); if (counter != customCounters->end()) { - return static_cast(counter->second.d_value.load()); + auto combinationOfLabels = generateCombinationOfLabels(labels); + auto metricEntry = counter->second.find(combinationOfLabels); + if (metricEntry != counter->second.end()) { + return static_cast(metricEntry->second.d_value.load()); + } } } { auto customGauges = s_customGauges.read_lock(); auto gauge = customGauges->find(name); if (gauge != customGauges->end()) { - return gauge->second.d_value.load(); + auto combinationOfLabels = generateCombinationOfLabels(labels); + auto metricEntry = gauge->second.find(combinationOfLabels); + if (metricEntry != gauge->second.end()) { + return metricEntry->second.d_value.load(); + } } } return std::string("Unable to get metric '") + std::string(name) + "': no such metric"; } - } diff --git a/pdns/dnsdistdist/dnsdist-metrics.hh b/pdns/dnsdistdist/dnsdist-metrics.hh index 47a3fb84078b..6cab90527284 100644 --- a/pdns/dnsdistdist/dnsdist-metrics.hh +++ b/pdns/dnsdistdist/dnsdist-metrics.hh @@ -25,6 +25,7 @@ #include #include #include +#include #include #include "lock.hh" @@ -34,11 +35,11 @@ namespace dnsdist::metrics { using Error = std::string; -[[nodiscard]] std::optional declareCustomMetric(const std::string& name, const std::string& type, const std::string& description, std::optional customName); -[[nodiscard]] std::variant incrementCustomCounter(const std::string_view& name, uint64_t step); -[[nodiscard]] std::variant decrementCustomCounter(const std::string_view& name, uint64_t step); -[[nodiscard]] std::variant setCustomGauge(const std::string_view& name, const double value); -[[nodiscard]] std::variant getCustomMetric(const std::string_view& name); +[[nodiscard]] std::optional declareCustomMetric(const std::string& name, const std::string& type, const std::string& description, std::optional customName, bool withLabels); +[[nodiscard]] std::variant incrementCustomCounter(const std::string_view& name, uint64_t step, const std::unordered_map& labels); +[[nodiscard]] std::variant decrementCustomCounter(const std::string_view& name, uint64_t step, const std::unordered_map& labels); +[[nodiscard]] std::variant setCustomGauge(const std::string_view& name, const double value, const std::unordered_map& labels); +[[nodiscard]] std::variant getCustomMetric(const std::string_view& name, const std::unordered_map& labels); using pdns::stat_t; @@ -89,13 +90,14 @@ struct Stats pdns::stat_double_t latencyDoH3Avg100{0}, latencyDoH3Avg1000{0}, latencyDoH3Avg10000{0}, latencyDoH3Avg1000000{0}; using statfunction_t = std::function; using entry_t = std::variant; - struct EntryPair + struct EntryTriple { std::string d_name; + std::string d_labels; entry_t d_value; }; - SharedLockGuarded> entries; + SharedLockGuarded> entries; }; extern struct Stats g_stats; diff --git a/pdns/dnsdistdist/dnsdist-web.cc b/pdns/dnsdistdist/dnsdist-web.cc index 5cdfead09cd0..26c34edc4d14 100644 --- a/pdns/dnsdistdist/dnsdist-web.cc +++ b/pdns/dnsdistdist/dnsdist-web.cc @@ -500,6 +500,10 @@ static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp) prometheusMetricName = metricDetails.customName; } + if (!entry.d_labels.empty()) { + prometheusMetricName += "{" + entry.d_labels + "}"; + } + // for these we have the help and types encoded in the sources // but we need to be careful about labels in custom metrics std::string helpName = prometheusMetricName.substr(0, prometheusMetricName.find('{')); @@ -927,6 +931,9 @@ static void addStatsToJSONObject(Json::object& obj) if (entry.d_name == "special-memory-usage") { continue; // Too expensive for get-all } + if (!entry.d_labels.empty()) { + continue; // Skip labeled metrics to prevent duplicates + } if (const auto& val = std::get_if(&entry.d_value)) { obj.emplace(entry.d_name, (double)(*val)->load()); } @@ -1908,7 +1915,7 @@ void setMaxConcurrentConnections(size_t max) void WebserverThread(Socket sock) { setThreadName("dnsdist/webserv"); - //coverity[auto_causes_copy] + // coverity[auto_causes_copy] const auto local = *dnsdist::configuration::getCurrentRuntimeConfiguration().d_webServerAddress; infolog("Webserver launched on %s", local.toStringWithPort()); diff --git a/pdns/dnsdistdist/docs/reference/custommetrics.rst b/pdns/dnsdistdist/docs/reference/custommetrics.rst index b2f5b517be3d..d688a0a1c16c 100644 --- a/pdns/dnsdistdist/docs/reference/custommetrics.rst +++ b/pdns/dnsdistdist/docs/reference/custommetrics.rst @@ -10,13 +10,19 @@ Then you can update those at runtime using the following functions, depending on * manipulate counters using :func:`incMetric` and :func:`decMetric` * update a gauge using :func:`setMetric` -.. function:: declareMetric(name, type, description [, prometheusName]) -> bool +.. function:: declareMetric(name, type, description [, prometheusName|options]) -> bool .. versionadded:: 1.8.0 .. versionchanged:: 1.8.1 This function can now be used at runtime, instead of only at configuration time. + .. versionchanged:: 2.0.0 + This function now takes options, with ``withLabels`` option added. ``prometheusName`` can now be provided in options. + + .. note:: + Labels are only available for prometheus. Metrics with labels are otherwise ignored. + Re-declaring an existing metric with the same name and type will not reset it. Re-declaring with the same name but a different type will cause one of them to be masked. @@ -25,51 +31,103 @@ Then you can update those at runtime using the following functions, depending on :param str name: The name of the metric, lowercase alphanumerical characters and dashes (-) only :param str type: The desired type in ``gauge`` or ``counter`` :param str description: The description of the metric - :param str prometheusName: The name to use in the prometheus metrics, if supplied. Otherwise the regular name will be used, prefixed with ``dnsdist_`` and ``-`` replaced by ``_``. + :param str prometheusName: The name to use in the prometheus metrics, if supplied. Otherwise the regular name will be used, prefixed with ``dnsdist_`` and ``-`` replaced by ``_`` + :param table options: A table with key: value pairs with metric options. + + Options: -.. function:: incMetric(name [, step]) -> int + * ``name``: str - The name to use in the prometheus metrics, if supplied. Otherwise the regular name will be used, prefixed with ``dnsdist_`` and ``-`` replaced by ``_`` + * ``withLabels=false``: bool - If set to true, labels will be expected when updating this metric and it will not be automatically created without labels. Defaults to ``false``, which automatically creates this metric without labels with default value. + +.. function:: incMetric(name [, step|options]) -> int .. versionadded:: 1.8.0 .. versionchanged:: 1.8.1 Optional ``step`` parameter added. + .. versionchanged:: 2.0.0 + This function now takes options, with ``labels`` option added. ``step`` can now be provided in options. + + .. note:: + Labels are only available for prometheus. Metrics with labels are otherwise ignored. + Increment counter by one (or more, see the ``step`` parameter), will issue an error if the metric is not declared or not a ``counter``. Returns the new value. :param str name: The name of the metric - :param int step: By how much the counter should be incremented, default to 1. + :param int step: By how much the counter should be incremented, default to 1 + :param table options: A table with key: value pairs with metric options. + + Options: + + * ``step``: int - By how much the counter should be incremented, default to 1 + * ``labels={}``: table - Set of key: value pairs with labels and their values that should be used to increment the metric. Different combinations of labels have different metric values. -.. function:: decMetric(name) -> int +.. function:: decMetric(name [, step|options]) -> int .. versionadded:: 1.8.0 .. versionchanged:: 1.8.1 Optional ``step`` parameter added. + .. versionchanged:: 2.0.0 + This function now takes options, with ``labels`` option added. ``step`` can now be provided in options. + + .. note:: + Labels are only available for prometheus. Metrics with labels are otherwise ignored. + Decrement counter by one (or more, see the ``step`` parameter), will issue an error if the metric is not declared or not a ``counter``. Returns the new value. :param str name: The name of the metric :param int step: By how much the counter should be decremented, default to 1. + :param table options: A table with key: value pairs with metric options. -.. function:: getMetric(name) -> double + Options: + + * ``step``: int - By how much the counter should be decremented, default to 1 + * ``labels={}``: table - Set of key: value pairs with labels and their values that should be used to decrement the metric. Different combinations of labels have different metric values. + +.. function:: getMetric(name [, options]) -> double .. versionadded:: 1.8.0 + .. versionchanged:: 2.0.0 + This function now takes options, with ``labels`` option added. + + .. note:: + Labels are only available for prometheus. Metrics with labels are otherwise ignored. + Get metric value. :param str name: The name of the metric + :param table options: A table with key: value pairs with metric options. + + Options: -.. function:: setMetric(name, value) -> double + * ``labels={}``: table - Set of key: value pairs with labels and their values that should be used to read the metric. Different combinations of labels have different metric values. + +.. function:: setMetric(name, value [, options]) -> double .. versionadded:: 1.8.0 + .. versionchanged:: 2.0.0 + This function now takes options, with ``labels`` option added. + + .. note:: + Labels are only available for prometheus. Metrics with labels are otherwise ignored. + Set the new value, will issue an error if the metric is not declared or not a ``gauge``. Return the new value. :param str name: The name of the metric :param double value: The new value + :param table options: A table with key: value pairs with metric options. + + Options: + + * ``labels={}``: table - Set of key: value pairs with labels and their values that should be used to set the metric. Different combinations of labels have different metric values. diff --git a/regression-tests.dnsdist/test_API.py b/regression-tests.dnsdist/test_API.py index dd0e9fde85a7..dfdda817f8a3 100644 --- a/regression-tests.dnsdist/test_API.py +++ b/regression-tests.dnsdist/test_API.py @@ -878,6 +878,10 @@ class TestAPICustomStatistics(APITestsBase): declareMetric("my-custom-metric", "counter", "Number of statistics") declareMetric("my-other-metric", "counter", "Another number of statistics") declareMetric("my-gauge", "gauge", "Current memory usage") + declareMetric("my-labeled-gauge", "gauge", "Custom gauge with labels", { withLabels = true }) + setMetric("my-labeled-gauge", 123, { labels = { foo = "bar" } }) + declareMetric("my-labeled-counter", "counter", "Custom counter with labels", { withLabels = true }) + incMetric("my-labeled-counter", { labels = { foo = "bar" } }) setWebserverConfig({password="%s", apiKey="%s"}) """ @@ -899,3 +903,8 @@ def testCustomStats(self): for key in expected: self.assertIn(key, content) self.assertTrue(content[key] >= 0) + + unexpected = ['my-labeled-gauge', 'my-labeled-counter'] + + for key in unexpected: + self.assertNotIn(key, content) diff --git a/regression-tests.dnsdist/test_Prometheus.py b/regression-tests.dnsdist/test_Prometheus.py index f6d334038b0f..c4559f5d9b4e 100644 --- a/regression-tests.dnsdist/test_Prometheus.py +++ b/regression-tests.dnsdist/test_Prometheus.py @@ -30,8 +30,9 @@ class TestPrometheus(DNSDistTest): declareMetric('custom-metric3', 'counter', 'Custom counter', 'custom_prometheus_name') -- test prometheus labels in custom metrics - declareMetric('custom-metric-foo-x-bar-y-xyz', 'counter', 'Custom counter with labels', 'custom_metric_foo{x="bar",y="xyz"}') - declareMetric('custom-metric-foo-x-baz-y-abc', 'counter', 'Custom counter with labels', 'custom_metric_foo{x="baz",y="abc"}') + declareMetric('custom-metric-foo', 'counter', 'Custom counter with labels', { withLabels = true }) + incMetric('custom-metric-foo', { labels = { x = 'bar', y = 'xyz' } }) + incMetric('custom-metric-foo', { labels = { x = 'baz', y = 'abc' } }) """ def checkPrometheusContentBasic(self, content): @@ -47,12 +48,16 @@ def checkPrometheusContentBasic(self, content): tokens = line.split(' ') self.assertEqual(len(tokens), 2) if not line.startswith('dnsdist_') and not line.startswith('custom_'): - raise AssertionError('Expecting prometheus metric to be prefixed by \'dnsdist_\', got: "%s"' % (line)) + raise AssertionError( + 'Expecting prometheus metric to be prefixed by \'dnsdist_\', got: "%s"' % (line)) - def checkMetric(self, content, name, expectedType, expectedValue): + def checkMetric(self, content, name, expectedType, expectedValue, expectedLabels=""): typeFound = False helpFound = False valueFound = False + labelsFound = False + if expectedLabels == "": + labelsFound = True for line in content.splitlines(): if name in str(line): tokens = line.split(' ') @@ -70,10 +75,15 @@ def checkMetric(self, content, name, expectedType, expectedValue): if tokens[0] == name: valueFound = True self.assertEqual(int(tokens[1]), expectedValue) + elif tokens[0] == name + expectedLabels: + valueFound = True + labelsFound = True + self.assertEqual(int(tokens[1]), expectedValue) self.assertTrue(typeFound) self.assertTrue(helpFound) self.assertTrue(valueFound) + self.assertTrue(labelsFound) def checkPrometheusContentPromtool(self, content): output = None @@ -106,3 +116,5 @@ def testMetrics(self): self.checkMetric(r.text, 'dnsdist_custom_metric1', 'counter', 1) self.checkMetric(r.text, 'dnsdist_custom_metric2', 'gauge', 0) self.checkMetric(r.text, 'custom_prometheus_name', 'counter', 0) + self.checkMetric(r.text, 'dnsdist_custom_metric_foo', 'counter', 1, '{x="bar",y="xyz"}') + self.checkMetric(r.text, 'dnsdist_custom_metric_foo', 'counter', 1, '{x="baz",y="abc"}')