]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Add support for custom prometheus names in custom metrics 12553/head
authorRemi Gacogne <remi.gacogne@powerdns.com>
Wed, 15 Feb 2023 11:31:06 +0000 (12:31 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Wed, 15 Feb 2023 11:31:06 +0000 (12:31 +0100)
pdns/dnsdist-console.cc
pdns/dnsdist-lua.cc
pdns/dnsdist-web.cc
pdns/dnsdistdist/dnsdist-prometheus.hh
pdns/dnsdistdist/dnsdist-web.hh
pdns/dnsdistdist/docs/reference/custommetrics.rst
regression-tests.dnsdist/test_Prometheus.py

index fd71c052b56c8315c4ab41a62d40eae4a880b21a..86647243ad4e806351f366dcfb84e822c9282d36 100644 (file)
@@ -490,6 +490,8 @@ const std::vector<ConsoleKeyword> 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<ConsoleKeyword> 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<ConsoleKeyword> 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<ConsoleKeyword> 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'" },
index c917368fc44eea5a2baf126fc80ab8d3129a1459..624c73d26701e6aade4deb72a1d879ea64c9a714 100644 (file)
@@ -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<std::string> 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 {
index 24546351a251c9b8da85a7d5d8bfbafa5c533b12..e8b51066b78596a26b0560e82aeb5d67230a989a 100644 (file)
@@ -212,9 +212,9 @@ std::map<std::string, MetricDefinition> 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";
index 6a02078c6c8f5585de6dc4992804d66dbb7f8611..15567bff044893f408758f2ef90a11b949bf12fb 100644 (file)
@@ -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<std::string, PrometheusMetricType> 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;
   }
 
index 0a8a0bcab9a1f348c360f6eb4f9fe5a82e736a72..3497d65a96b2dc6b5c84034b9dc6f86da1ac91b5 100644 (file)
@@ -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();
index 0e44c345bd5c80f121755e34db9dfa6e5d144fe8..7cf5fa1a6fc41eaa57c80410391b8f260f6b7dd6 100644 (file)
@@ -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
 
index 61e4ee5ccc9530169a276b0fc6614a066f8b9022..a868752339d9e2e5de1215e31033a33c9084a601 100644 (file)
@@ -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)