From: Remi Gacogne Date: Wed, 15 Feb 2023 11:31:06 +0000 (+0100) Subject: dnsdist: Add support for custom prometheus names in custom metrics X-Git-Tag: dnsdist-1.8.0-rc1~12^2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=e78fc5bde10b2ce1c98a64a723bf5accbedef55c;p=thirdparty%2Fpdns.git dnsdist: Add support for custom prometheus names in custom metrics --- diff --git a/pdns/dnsdist-console.cc b/pdns/dnsdist-console.cc index fd71c052b5..86647243ad 100644 --- a/pdns/dnsdist-console.cc +++ b/pdns/dnsdist-console.cc @@ -490,6 +490,8 @@ const std::vector g_consoleKeywords{ { "clearRules", true, "", "remove all current rules" }, { "controlSocket", true, "addr", "open a control socket on this address / connect to this address in client mode" }, { "ContinueAction", true, "action", "execute the specified action and continue the processing of the remaining rules, regardless of the return of the action" }, + { "declareMetric", true, "name, type, description [, prometheusName]", "Declare a custom metric" }, + { "decMetric", true, "name", "Decrement a custom metric" }, { "DelayAction", true, "milliseconds", "delay the response by the specified amount of milliseconds (UDP-only)" }, { "DelayResponseAction", true, "milliseconds", "delay the response by the specified amount of milliseconds (UDP-only)" }, { "delta", true, "", "shows all commands entered that changed the configuration" }, @@ -526,6 +528,7 @@ const std::vector g_consoleKeywords{ { "getListOfNetworkInterfaces", true, "", "returns the list of network interfaces present on the system, as strings" }, { "getListOfRangesOfNetworkInterface", true, "itf", "returns the list of network ranges configured on a given network interface, as strings" }, { "getMACAddress", true, "IP addr", "return the link-level address (MAC) corresponding to the supplied neighbour IP address, if known by the kernel" }, + { "getMetric", true, "name", "Get the value of a custom metric" }, { "getOutgoingTLSSessionCacheSize", true, "", "returns the number of TLS sessions (for outgoing connections) currently cached" }, { "getPool", true, "name", "return the pool named `name`, or \"\" for the default pool" }, { "getPoolServers", true, "pool", "return servers part of this pool" }, @@ -553,6 +556,7 @@ const std::vector g_consoleKeywords{ { "HTTPStatusAction", true, "status, reason, body", "return an HTTP response"}, { "inClientStartup", true, "", "returns true during console client parsing of configuration" }, { "includeDirectory", true, "path", "include configuration files from `path`" }, + { "incMetric", true, "name", "Increment a custom metric" }, { "KeyValueLookupKeyQName", true, "[wireFormat]", "Return a new KeyValueLookupKey object that, when passed to KeyValueStoreLookupAction or KeyValueStoreLookupRule, will return the qname of the query, either in wire format (default) or in plain text if 'wireFormat' is false" }, { "KeyValueLookupKeySourceIP", true, "[v4Mask [, v6Mask [, includePort]]]", "Return a new KeyValueLookupKey object that, when passed to KeyValueStoreLookupAction or KeyValueStoreLookupRule, will return the (possibly bitmasked) source IP of the client in network byte-order." }, { "KeyValueLookupKeySuffix", true, "[minLabels [,wireFormat]]", "Return a new KeyValueLookupKey object that, when passed to KeyValueStoreLookupAction or KeyValueStoreLookupRule, will return a vector of keys based on the labels of the qname in DNS wire format or plain text" }, @@ -688,6 +692,7 @@ const std::vector g_consoleKeywords{ { "setMaxTCPQueriesPerConnection", true, "n", "set the maximum number of queries in an incoming TCP connection. 0 means unlimited" }, { "setMaxTCPQueuedConnections", true, "n", "set the maximum number of TCP connections queued (waiting to be picked up by a client thread)" }, { "setMaxUDPOutstanding", true, "n", "set the maximum number of outstanding UDP queries to a given backend server. This can only be set at configuration time and defaults to 65535" }, + { "setMetric", true, "name, value", "Set the value of a custom metric to the supplied value" }, { "setPayloadSizeOnSelfGeneratedAnswers", true, "payloadSize", "set the UDP payload size advertised via EDNS on self-generated responses" }, { "setPoolServerPolicy", true, "policy, pool", "set the server selection policy for this pool to that policy" }, { "setPoolServerPolicyLua", true, "name, function, pool", "set the server selection policy for this pool to one named 'name' and provided by 'function'" }, diff --git a/pdns/dnsdist-lua.cc b/pdns/dnsdist-lua.cc index c917368fc4..624c73d267 100644 --- a/pdns/dnsdist-lua.cc +++ b/pdns/dnsdist-lua.cc @@ -2905,7 +2905,7 @@ 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) { + luaCtx.writeFunction("declareMetric", [](const std::string& name, const std::string& type, const std::string& description, boost::optional customName) { if (!checkConfigurationTime("declareMetric")) { return false; } @@ -2918,14 +2918,14 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) auto itp = g_stats.customCounters.emplace(name, 0); if (itp.second) { g_stats.entries.emplace_back(name, &g_stats.customCounters[name]); - addMetricDefinition(name, "counter", description); + addMetricDefinition(name, "counter", description, customName ? *customName : ""); } } else if (type == "gauge") { auto itp = g_stats.customGauges.emplace(name, 0.); if (itp.second) { g_stats.entries.emplace_back(name, &g_stats.customGauges[name]); - addMetricDefinition(name, "gauge", description); + addMetricDefinition(name, "gauge", description, customName ? *customName : ""); } } else { diff --git a/pdns/dnsdist-web.cc b/pdns/dnsdist-web.cc index 24546351a2..e8b51066b7 100644 --- a/pdns/dnsdist-web.cc +++ b/pdns/dnsdist-web.cc @@ -212,9 +212,9 @@ std::map MetricDefinitionStorage::metrics{ }; #endif /* DISABLE_PROMETHEUS */ -bool addMetricDefinition(const std::string& name, const std::string& type, const std::string& description) { +bool addMetricDefinition(const std::string& name, const std::string& type, const std::string& description, const std::string& customName) { #ifndef DISABLE_PROMETHEUS - return MetricDefinitionStorage::addMetricDefinition(name, type, description); + return MetricDefinitionStorage::addMetricDefinition(name, type, description, customName); #else return true; #endif /* DISABLE_PROMETHEUS */ @@ -480,14 +480,20 @@ static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp) continue; } - // Prometheus suggest using '_' instead of '-' - std::string prometheusTypeName = s_metricDefinitions.getPrometheusStringMetricType(metricDetails.prometheusType); - - if (prometheusTypeName == "") { + const std::string prometheusTypeName = s_metricDefinitions.getPrometheusStringMetricType(metricDetails.prometheusType); + if (prometheusTypeName.empty()) { vinfolog("Unknown Prometheus type for %s", metricName); continue; } - std::string prometheusMetricName = "dnsdist_" + boost::replace_all_copy(metricName, "-", "_"); + + // Prometheus suggest using '_' instead of '-' + std::string prometheusMetricName; + if (metricDetails.customName.empty()) { + prometheusMetricName = "dnsdist_" + boost::replace_all_copy(metricName, "-", "_"); + } + else { + prometheusMetricName = metricDetails.customName; + } // for these we have the help and types encoded in the sources: output << "# HELP " << prometheusMetricName << " " << metricDetails.description << "\n"; diff --git a/pdns/dnsdistdist/dnsdist-prometheus.hh b/pdns/dnsdistdist/dnsdist-prometheus.hh index 6a02078c6c..15567bff04 100644 --- a/pdns/dnsdistdist/dnsdist-prometheus.hh +++ b/pdns/dnsdistdist/dnsdist-prometheus.hh @@ -30,13 +30,15 @@ enum class PrometheusMetricType: uint8_t { // Keeps additional information about metrics struct MetricDefinition { - MetricDefinition(PrometheusMetricType _prometheusType, const std::string& _description): description(_description), prometheusType(_prometheusType) { + MetricDefinition(PrometheusMetricType _prometheusType, const std::string& _description, const std::string& customName_ = ""): description(_description), customName(customName_), prometheusType(_prometheusType) { } MetricDefinition() = default; // Metric description std::string description; + // Custom name, if any + std::string customName; // Metric type for Prometheus PrometheusMetricType prometheusType{PrometheusMetricType::counter}; }; @@ -54,7 +56,7 @@ struct MetricDefinitionStorage { return true; }; - static bool addMetricDefinition(const std::string& name, const std::string& type, const std::string& description) { + static bool addMetricDefinition(const std::string& name, const std::string& type, const std::string& description, const std::string& customName) { static const std::map namesToTypes = { {"counter", PrometheusMetricType::counter}, {"gauge", PrometheusMetricType::gauge}, @@ -63,7 +65,7 @@ struct MetricDefinitionStorage { if (realtype == namesToTypes.end()) { return false; } - metrics.emplace(name, MetricDefinition{realtype->second, description}); + metrics.emplace(name, MetricDefinition{realtype->second, description, customName}); return true; } diff --git a/pdns/dnsdistdist/dnsdist-web.hh b/pdns/dnsdistdist/dnsdist-web.hh index 0a8a0bcab9..3497d65a96 100644 --- a/pdns/dnsdistdist/dnsdist-web.hh +++ b/pdns/dnsdistdist/dnsdist-web.hh @@ -16,6 +16,6 @@ void dnsdistWebserverThread(int sock, const ComboAddress& local); void registerBuiltInWebHandlers(); void clearWebHandlers(); -bool addMetricDefinition(const std::string& name, const std::string& type, const std::string& description); +bool addMetricDefinition(const std::string& name, const std::string& type, const std::string& description, const std::string& customPrometheusName); std::string getWebserverConfig(); diff --git a/pdns/dnsdistdist/docs/reference/custommetrics.rst b/pdns/dnsdistdist/docs/reference/custommetrics.rst index 0e44c345bd..7cf5fa1a6f 100644 --- a/pdns/dnsdistdist/docs/reference/custommetrics.rst +++ b/pdns/dnsdistdist/docs/reference/custommetrics.rst @@ -10,7 +10,7 @@ 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) -> bool +.. function:: declareMetric(name, type, description [, prometheusName]) -> bool .. versionadded:: 1.8.0 @@ -19,6 +19,7 @@ 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 name: 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 ``_``. .. function:: incMetric(name) -> int diff --git a/regression-tests.dnsdist/test_Prometheus.py b/regression-tests.dnsdist/test_Prometheus.py index 61e4ee5ccc..a868752339 100644 --- a/regression-tests.dnsdist/test_Prometheus.py +++ b/regression-tests.dnsdist/test_Prometheus.py @@ -21,6 +21,13 @@ class TestPrometheus(DNSDistTest): setWebserverConfig({password="%s", apiKey="%s"}) pc = newPacketCache(100, {maxTTL=86400, minTTL=1}) getPool(""):setCache(pc) + + -- test custom metrics as well + declareMetric('custom-metric1', 'counter', 'Custom counter') + incMetric('custom-metric1') + declareMetric('custom-metric2', 'gauge', 'Custom gauge') + -- and custom names + declareMetric('custom-metric3', 'counter', 'Custom counter', 'custom_prometheus_name') """ def checkPrometheusContentBasic(self, content): @@ -35,9 +42,35 @@ class TestPrometheus(DNSDistTest): elif not line.startswith('#'): tokens = line.split(' ') self.assertEqual(len(tokens), 2) - if not line.startswith('dnsdist_'): + if not line.startswith('dnsdist_') and not line.startswith('custom_prometheus_name'): raise AssertionError('Expecting prometheus metric to be prefixed by \'dnsdist_\', got: "%s"' % (line)) + def checkMetric(self, content, name, expectedType, expectedValue): + typeFound = False + helpFound = False + valueFound = False + for line in content.splitlines(): + if name in str(line): + tokens = line.split(' ') + if line.startswith('# HELP'): + self.assertGreaterEqual(len(tokens), 4) + if tokens[2] == name: + helpFound = True + elif line.startswith('# TYPE'): + self.assertEqual(len(tokens), 4) + if tokens[2] == name: + typeFound = True + self.assertEqual(tokens[3], expectedType) + elif not line.startswith('#'): + self.assertEqual(len(tokens), 2) + if tokens[0] == name: + valueFound = True + self.assertEqual(int(tokens[1]), expectedValue) + + self.assertTrue(typeFound) + self.assertTrue(helpFound) + self.assertTrue(valueFound) + def checkPrometheusContentPromtool(self, content): output = None try: @@ -66,3 +99,6 @@ class TestPrometheus(DNSDistTest): self.assertEqual(r.status_code, 200) self.checkPrometheusContentBasic(r.text) self.checkPrometheusContentPromtool(r.content) + 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)