From: Pieter Lexis Date: Tue, 13 Jan 2026 13:25:35 +0000 (+0100) Subject: feat(dnsdist): Add optional `instance` label to Prometheus metrics X-Git-Tag: rec-5.4.0-beta1~41^2~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f408fe11fc9d28a863bf19821ec0db1602a02fa4;p=thirdparty%2Fpdns.git feat(dnsdist): Add optional `instance` label to Prometheus metrics The label's value is based on the server_id, which is the hostname by default. --- diff --git a/pdns/dnsdistdist/dnsdist-configuration-yaml.cc b/pdns/dnsdistdist/dnsdist-configuration-yaml.cc index 3b9836c594..0a99dc39b9 100644 --- a/pdns/dnsdistdist/dnsdist-configuration-yaml.cc +++ b/pdns/dnsdistdist/dnsdist-configuration-yaml.cc @@ -891,6 +891,7 @@ static void loadWebServer(const dnsdist::rust::settings::WebserverConfiguration& } config.d_apiRequiresAuthentication = webConfig.api_requires_authentication; + config.d_prometheusAddInstanceLabel = webConfig.prometheus_add_instance; config.d_dashboardRequiresAuthentication = webConfig.dashboard_requires_authentication; config.d_statsRequireAuthentication = webConfig.stats_require_authentication; dnsdist::webserver::setMaxConcurrentConnections(webConfig.max_concurrent_connections); diff --git a/pdns/dnsdistdist/dnsdist-configuration.hh b/pdns/dnsdistdist/dnsdist-configuration.hh index 67b7906b1b..a190d8a8db 100644 --- a/pdns/dnsdistdist/dnsdist-configuration.hh +++ b/pdns/dnsdistdist/dnsdist-configuration.hh @@ -157,6 +157,7 @@ struct RuntimeConfiguration bool d_apiRequiresAuthentication{true}; bool d_dashboardRequiresAuthentication{true}; bool d_statsRequireAuthentication{true}; + bool d_prometheusAddInstanceLabel{false}; bool d_truncateTC{false}; bool d_fixupCase{false}; bool d_queryCountEnabled{false}; diff --git a/pdns/dnsdistdist/dnsdist-console-completion.cc b/pdns/dnsdistdist/dnsdist-console-completion.cc index 4ab848cd60..2424808e10 100644 --- a/pdns/dnsdistdist/dnsdist-console-completion.cc +++ b/pdns/dnsdistdist/dnsdist-console-completion.cc @@ -309,7 +309,7 @@ static std::vector s_consoleKeywor {"setVerbose", true, "bool", "set whether log messages at the verbose level will be logged"}, {"setVerboseHealthChecks", true, "bool", "set whether health check errors will be logged"}, {"setVerboseLogDestination", true, "destination file", "Set a destination file to write the 'verbose' log messages to, instead of sending them to syslog and/or the standard output"}, - {"setWebserverConfig", true, "[{password=string, apiKey=string, customHeaders, statsRequireAuthentication}]", "Updates webserver configuration"}, + {"setWebserverConfig", true, "[{password=string, apiKey=string, customHeaders, statsRequireAuthentication, prometheusAddInstanceLabel=bool}]", "Updates webserver configuration"}, {"setWeightedBalancingFactor", true, "factor", "Set the balancing factor for bounded-load weighted policies (whashed, wrandom)"}, {"setWHashedPerturbation", true, "value", "Set the hash perturbation value to be used in the whashed policy instead of a random one, allowing to have consistent whashed results on different instance"}, {"show", true, "string", "outputs `string`"}, diff --git a/pdns/dnsdistdist/dnsdist-lua.cc b/pdns/dnsdistdist/dnsdist-lua.cc index 5fc7a147a3..64c2098702 100644 --- a/pdns/dnsdistdist/dnsdist-lua.cc +++ b/pdns/dnsdistdist/dnsdist-lua.cc @@ -1103,6 +1103,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) LuaAssociativeTable headers; bool statsRequireAuthentication{true}; bool apiRequiresAuthentication{true}; + bool prometheusAddInstance{false}; bool dashboardRequiresAuthentication{true}; bool hashPlaintextCredentials = false; getOptionalValue(vars, "hashPlaintextCredentials", hashPlaintextCredentials); @@ -1137,6 +1138,10 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) config.d_statsRequireAuthentication = statsRequireAuthentication; } + if (getOptionalValue(vars, "prometheusAddInstanceLabel", prometheusAddInstance) > 0) { + config.d_prometheusAddInstanceLabel = prometheusAddInstance; + } + if (getOptionalValue(vars, "apiRequiresAuthentication", apiRequiresAuthentication) > 0) { config.d_apiRequiresAuthentication = apiRequiresAuthentication; } diff --git a/pdns/dnsdistdist/dnsdist-settings-definitions.yml b/pdns/dnsdistdist/dnsdist-settings-definitions.yml index bfcb87ca68..6e1d0a8210 100644 --- a/pdns/dnsdistdist/dnsdist-settings-definitions.yml +++ b/pdns/dnsdistdist/dnsdist-settings-definitions.yml @@ -410,6 +410,9 @@ key_value_stores: description: "List of lookup keys" webserver: + changes: + - version: 2.1.0 + content: "Added the ``prometheus_add_instance`` option" parameters: - name: "listen_addresses" type: "Vec" @@ -435,6 +438,10 @@ webserver: type: "bool" default: "true" description: "Whether access to the statistics (/metrics and /jsonstat endpoints) requires a valid password or API key" + - name: "prometheus_add_instance" + type: "bool" + default: "false" + description: "Add the 'instance' label with the value of general.server_id to all prometheus metrics" - name: "dashboard_requires_authentication" type: "bool" default: "true" diff --git a/pdns/dnsdistdist/dnsdist-web.cc b/pdns/dnsdistdist/dnsdist-web.cc index 3f90fb8e67..836d3d3f23 100644 --- a/pdns/dnsdistdist/dnsdist-web.cc +++ b/pdns/dnsdistdist/dnsdist-web.cc @@ -159,6 +159,7 @@ std::string getConfig() out << "API requires authentication: " << (config.d_apiRequiresAuthentication ? "yes" : "no") << endl; out << "Dashboard requires authentication: " << (config.d_dashboardRequiresAuthentication ? "yes" : "no") << endl; out << "Statistics require authentication: " << (config.d_statsRequireAuthentication ? "yes" : "no") << endl; + out << "Add instance to Prometheus labels: " << (config.d_prometheusAddInstanceLabel ? "yes" : "no") << endl; out << "Password: " << (config.d_webPassword ? "set" : "unset") << endl; out << "API key: " << (config.d_webAPIKey ? "set" : "unset") << endl; out << "API writable: " << (config.d_apiReadWrite ? "yes" : "no") << endl; @@ -455,11 +456,11 @@ static json11::Json::array someResponseRulesToJson(const std::vector& someRes #ifndef DISABLE_PROMETHEUS template -static void addRulesToPrometheusOutput(std::ostringstream& output, const std::vector& rules) +static void addRulesToPrometheusOutput(std::ostringstream& output, const std::vector& rules, const std::string instanceLabelWithComma) { for (const auto& entry : rules) { std::string identifier = !entry.d_name.empty() ? entry.d_name : boost::uuids::to_string(entry.d_id); - output << "dnsdist_rule_hits{id=\"" << identifier << "\"} " << entry.d_rule->d_matches << "\n"; + output << "dnsdist_rule_hits{id=\"" << identifier << "\"" << instanceLabelWithComma << "} " << entry.d_rule->d_matches << "\n"; } } @@ -491,6 +492,11 @@ static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp) resp.status = 200; std::ostringstream output; + std::string instanceLabel; // MUST be empty when instance label is not requested + { + auto rtc = dnsdist::configuration::getCurrentRuntimeConfiguration(); + instanceLabel = rtc.d_prometheusAddInstanceLabel ? "instance=\"" + rtc.d_server_id + "\"" : ""; + } static const std::set metricBlacklist = {"special-memory-usage", "latency-count", "latency-sum"}; { auto entries = dnsdist::metrics::g_stats.entries.read_lock(); @@ -523,8 +529,18 @@ static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp) prometheusMetricName = metricDetails.customName; } - if (!entry.d_labels.empty()) { - prometheusMetricName += "{" + entry.d_labels + "}"; + if (!entry.d_labels.empty() || !instanceLabel.empty()) { + prometheusMetricName += "{"; + if (!entry.d_labels.empty()) { + prometheusMetricName += entry.d_labels; + if (!instanceLabel.empty()) { + prometheusMetricName += ","; + } + } + if (!instanceLabel.empty()) { + prometheusMetricName += instanceLabel; + } + prometheusMetricName += "}"; } // for these we have the help and types encoded in the sources @@ -551,10 +567,16 @@ static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp) } } + std::string instanceLabelPlusComma; + std::string instanceLabelPlusBrackets; + if (!instanceLabel.empty()) { + instanceLabelPlusComma = "," + instanceLabel; + instanceLabelPlusBrackets = "{" + instanceLabel + "}"; + } // Latency histogram buckets output << "# HELP dnsdist_latency Histogram of responses by latency (in milliseconds)\n"; output << "# TYPE dnsdist_latency histogram\n"; - addHistogramToPrometheusOutput(output, dnsdist::metrics::g_stats, "dnsdist_latency", ""); + addHistogramToPrometheusOutput(output, dnsdist::metrics::g_stats, "dnsdist_latency", instanceLabelPlusBrackets); const string statesbase = "dnsdist_server_"; @@ -639,8 +661,8 @@ static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp) std::replace(serverName.begin(), serverName.end(), '.', '_'); - const std::string label = boost::str(boost::format(R"({server="%1%",address="%2%"})") - % serverName % state->d_config.remote.toStringWithPort()); + const std::string label = boost::str(boost::format(R"({server="%1%",address="%2%"%3%})") + % serverName % state->d_config.remote.toStringWithPort() % (instanceLabel.empty() ? "" : instanceLabelPlusComma)); output << statesbase << "status" << label << " " << (state->isUp() ? "1" : "0") << "\n"; output << statesbase << "queries" << label << " " << state->queries.load() << "\n"; @@ -738,8 +760,8 @@ static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp) threadNumber = dupPair.first->second; ++(dupPair.first->second); } - const std::string label = boost::str(boost::format(R"({frontend="%1%",proto="%2%",thread="%3%"} )") - % frontName % proto % threadNumber); + const std::string label = boost::str(boost::format(R"({frontend="%1%",proto="%2%",thread="%3%"%4%} )") + % frontName % proto % threadNumber % (instanceLabel.empty() ? "" : instanceLabelPlusComma)); output << frontsbase << "queries" << label << front->queries.load() << "\n"; output << frontsbase << "noncompliantqueries" << label << front->nonCompliantQueries.load() << "\n"; @@ -761,11 +783,11 @@ static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp) output << frontsbase << "tlsunknownticketkeys" << label << front->tlsUnknownTicketKey.load() << "\n"; output << frontsbase << "tlsinactiveticketkeys" << label << front->tlsInactiveTicketKey.load() << "\n"; - output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",tls="tls10"} )" << front->tls10queries.load() << "\n"; - output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",tls="tls11"} )" << front->tls11queries.load() << "\n"; - output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",tls="tls12"} )" << front->tls12queries.load() << "\n"; - output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",tls="tls13"} )" << front->tls13queries.load() << "\n"; - output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",tls="unknown"} )" << front->tlsUnknownqueries.load() << "\n"; + output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",tls="tls10"} )" << (instanceLabel.empty() ? "" : instanceLabelPlusComma) << front->tls10queries.load() << "\n"; + output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",tls="tls11"} )" << (instanceLabel.empty() ? "" : instanceLabelPlusComma) << front->tls11queries.load() << "\n"; + output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",tls="tls12"} )" << (instanceLabel.empty() ? "" : instanceLabelPlusComma) << front->tls12queries.load() << "\n"; + output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",tls="tls13"} )" << (instanceLabel.empty() ? "" : instanceLabelPlusComma) << front->tls13queries.load() << "\n"; + output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",tls="unknown"} )" << (instanceLabel.empty() ? "" : instanceLabelPlusComma) << front->tlsUnknownqueries.load() << "\n"; const TLSErrorCounters* errorCounters = nullptr; if (front->tlsFrontend != nullptr) { @@ -776,14 +798,14 @@ static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp) } if (errorCounters != nullptr) { - output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",error="dhKeyTooSmall"} )" << errorCounters->d_dhKeyTooSmall << "\n"; - output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",error="inappropriateFallBack"} )" << errorCounters->d_inappropriateFallBack << "\n"; - output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",error="noSharedCipher"} )" << errorCounters->d_noSharedCipher << "\n"; - output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",error="unknownCipherType"} )" << errorCounters->d_unknownCipherType << "\n"; - output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",error="unknownKeyExchangeType"} )" << errorCounters->d_unknownKeyExchangeType << "\n"; - output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",error="unknownProtocol"} )" << errorCounters->d_unknownProtocol << "\n"; - output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",error="unsupportedEC"} )" << errorCounters->d_unsupportedEC << "\n"; - output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",error="unsupportedProtocol"} )" << errorCounters->d_unsupportedProtocol << "\n"; + output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << (instanceLabel.empty() ? "" : instanceLabelPlusComma) << R"(",error="dhKeyTooSmall"} )" << errorCounters->d_dhKeyTooSmall << "\n"; + output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << (instanceLabel.empty() ? "" : instanceLabelPlusComma) << R"(",error="inappropriateFallBack"} )" << errorCounters->d_inappropriateFallBack << "\n"; + output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << (instanceLabel.empty() ? "" : instanceLabelPlusComma) << R"(",error="noSharedCipher"} )" << errorCounters->d_noSharedCipher << "\n"; + output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << (instanceLabel.empty() ? "" : instanceLabelPlusComma) << R"(",error="unknownCipherType"} )" << errorCounters->d_unknownCipherType << "\n"; + output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << (instanceLabel.empty() ? "" : instanceLabelPlusComma) << R"(",error="unknownKeyExchangeType"} )" << errorCounters->d_unknownKeyExchangeType << "\n"; + output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << (instanceLabel.empty() ? "" : instanceLabelPlusComma) << R"(",error="unknownProtocol"} )" << errorCounters->d_unknownProtocol << "\n"; + output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << (instanceLabel.empty() ? "" : instanceLabelPlusComma) << R"(",error="unsupportedEC"} )" << errorCounters->d_unsupportedEC << "\n"; + output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << (instanceLabel.empty() ? "" : instanceLabelPlusComma) << R"(",error="unsupportedProtocol"} )" << errorCounters->d_unsupportedProtocol << "\n"; } } } @@ -817,7 +839,7 @@ static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp) threadNumber = dupPair.first->second; ++(dupPair.first->second); } - const std::string addrlabel = boost::str(boost::format(R"(frontend="%1%",thread="%2%")") % frontName % threadNumber); + const std::string addrlabel = boost::str(boost::format(R"(frontend="%1%",thread="%2%"%3%)") % frontName % threadNumber % (instanceLabel.empty() ? "" : instanceLabelPlusComma)); const std::string label = "{" + addrlabel + "} "; output << frontsbase << "http_connects" << label << doh->d_httpconnects << "\n"; @@ -881,7 +903,7 @@ static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp) if (poolName.empty()) { poolName = "_default_"; } - const string label = "{pool=\"" + poolName + "\"}"; + const string label = "{pool=\"" + poolName + "\"" + (instanceLabel.empty() ? "" : instanceLabelPlusComma) + "}"; const auto& pool = entry.second; output << "dnsdist_pool_servers" << label << " " << pool.countServers(false) << "\n"; output << "dnsdist_pool_active_servers" << label << " " << pool.countServers(true) << "\n"; @@ -907,11 +929,11 @@ static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp) const auto& chains = dnsdist::configuration::getCurrentRuntimeConfiguration().d_ruleChains; for (const auto& chainDescription : dnsdist::rules::getRuleChainDescriptions()) { const auto& chain = dnsdist::rules::getRuleChain(chains, chainDescription.identifier); - addRulesToPrometheusOutput(output, chain); + addRulesToPrometheusOutput(output, chain, instanceLabelPlusComma); } for (const auto& chainDescription : dnsdist::rules::getResponseRuleChainDescriptions()) { const auto& chain = dnsdist::rules::getResponseRuleChain(chains, chainDescription.identifier); - addRulesToPrometheusOutput(output, chain); + addRulesToPrometheusOutput(output, chain, instanceLabelPlusComma); } #ifndef DISABLE_DYNBLOCKS @@ -920,7 +942,7 @@ static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp) auto topNetmasksByReason = DynBlockMaintenance::getHitsForTopNetmasks(); for (const auto& entry : topNetmasksByReason) { for (const auto& netmask : entry.second) { - output << "dnsdist_dynblocks_nmg_top_offenders_hits_per_second{reason=\"" << entry.first << "\",netmask=\"" << netmask.first.toString() << "\"} " << netmask.second << "\n"; + output << "dnsdist_dynblocks_nmg_top_offenders_hits_per_second{reason=\"" << entry.first << "\",netmask=\"" << netmask.first.toString() << "\"" << (instanceLabel.empty() ? "" : instanceLabelPlusComma) <<"} " << netmask.second << "\n"; } } @@ -929,14 +951,14 @@ static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp) auto topSuffixesByReason = DynBlockMaintenance::getHitsForTopSuffixes(); for (const auto& entry : topSuffixesByReason) { for (const auto& suffix : entry.second) { - output << "dnsdist_dynblocks_smt_top_offenders_hits_per_second{reason=\"" << entry.first << "\",suffix=\"" << suffix.first.toString() << "\"} " << suffix.second << "\n"; + output << "dnsdist_dynblocks_smt_top_offenders_hits_per_second{reason=\"" << entry.first << "\",suffix=\"" << suffix.first.toString() << "\"" << (instanceLabel.empty() ? "" : instanceLabelPlusComma) << "} " << suffix.second << "\n"; } } #endif /* DISABLE_DYNBLOCKS */ output << "# HELP dnsdist_info " << "Info from dnsdist, value is always 1" << "\n"; output << "# TYPE dnsdist_info " << "gauge" << "\n"; - output << "dnsdist_info{version=\"" << VERSION << "\"} " << "1" << "\n"; + output << "dnsdist_info{version=\"" << VERSION << "\"" << (instanceLabel.empty() ? "" : instanceLabelPlusComma) << "} " << "1" << "\n"; resp.body = output.str(); resp.headers["Content-Type"] = "text/plain; version=0.0.4"; diff --git a/pdns/dnsdistdist/docs/reference/config.rst b/pdns/dnsdistdist/docs/reference/config.rst index 65e82ae8fe..221a195c6a 100644 --- a/pdns/dnsdistdist/docs/reference/config.rst +++ b/pdns/dnsdistdist/docs/reference/config.rst @@ -467,6 +467,9 @@ Webserver configuration .. versionchanged:: 1.8.0 ``apiRequiresAuthentication``, ``dashboardRequiresAuthentication`` optional parameters added. + .. versionchanged:: 2.1.0 + ``prometheusAddInstanceLabel`` optional parameter added. + Setup webserver configuration. See :func:`webserver` and :doc:`../guides/webserver`. :param table options: A table with key: value pairs with webserver options. @@ -480,6 +483,7 @@ Webserver configuration * ``apiRequiresAuthentication``: bool - Whether access to the API (/api endpoints) require a valid API key. Defaults to true. * ``dashboardRequiresAuthentication``: bool - Whether access to the internal dashboard requires a valid password. Defaults to true. * ``statsRequireAuthentication``: bool - Whether access to the statistics (/metrics and /jsonstat endpoints) require a valid password or API key. Defaults to true. + * ``prometheusAddInstanceLabel``: bool - Whether to add an instance label to every metric. The value of this label is set by :func:`setServerID`. Defaults to false. * ``maxConcurrentConnections``: int - The maximum number of concurrent web connections, or 0 which means an unlimited number. Defaults to 100. * ``hashPlaintextCredentials``: bool - Whether passwords and API keys provided in plaintext should be hashed during startup, to prevent the plaintext versions from staying in memory. Doing so increases significantly the cost of verifying credentials. Defaults to false. diff --git a/regression-tests.dnsdist/test_Prometheus.py b/regression-tests.dnsdist/test_Prometheus.py index c300f876d9..ee781d7b65 100644 --- a/regression-tests.dnsdist/test_Prometheus.py +++ b/regression-tests.dnsdist/test_Prometheus.py @@ -158,3 +158,73 @@ class TestPrometheus(DNSDistTest): self.checkMetric( r.text, "dnsdist_custom_metric_foo", "counter", 1, '{x="baz",y="abc"}' ) + + +class TestPrometheusWithInstance(TestPrometheus): + instance_name = "my-id" + _config_template = """ + newServer{address="127.0.0.1:%d"} + webserver("127.0.0.1:%d") + setServerID("my-id") + setWebserverConfig({password="%s", apiKey="%s", prometheusAddInstanceLabel=true}) + 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') + + -- test prometheus labels in custom metrics + 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 checkPrometheusContentWithInstance(self, content): + for line in content.splitlines(): + if not line.startswith("#"): + tokens = line.split(" ") + self.assertIn(f'instance="{self.instance_name}"', tokens[0]) + + def testMetrics(self): + """ + Prometheus: Retrieve metrics + """ + url = "http://127.0.0.1:" + str(self._webServerPort) + "/metrics" + r = requests.get( + url, + auth=("whatever", self._webServerBasicAuthPassword), + timeout=self._webTimeout, + ) + self.assertTrue(r) + self.assertEqual(r.status_code, 200) + self.checkPrometheusContentBasic(r.text) + self.checkPrometheusContentWithInstance(r.text) + + self.checkPrometheusContentPromtool(r.content) + self.checkMetric( + r.text, "dnsdist_custom_metric1", "counter", 1, '{instance="my-id"}' + ) + self.checkMetric( + r.text, "dnsdist_custom_metric2", "gauge", 0, '{instance="my-id"}' + ) + self.checkMetric( + r.text, "custom_prometheus_name", "counter", 0, '{instance="my-id"}' + ) + self.checkMetric( + r.text, + "dnsdist_custom_metric_foo", + "counter", + 1, + '{x="bar",y="xyz",instance="my-id"}', + ) + self.checkMetric( + r.text, + "dnsdist_custom_metric_foo", + "counter", + 1, + '{x="baz",y="abc",instance="my-id"}', + )