]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Implement a FFI method to declare a new custom metric
authorRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 16 Jun 2023 11:39:16 +0000 (13:39 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 27 Jun 2023 14:03:32 +0000 (16:03 +0200)
pdns/dnsdist-lua.cc
pdns/dnsdistdist/Makefile.am
pdns/dnsdistdist/dnsdist-lua-ffi-interface.h
pdns/dnsdistdist/dnsdist-lua-ffi.cc
pdns/dnsdistdist/dnsdist-metrics.cc [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-metrics.hh [new file with mode: 0644]
pdns/dnsdistdist/docs/reference/custommetrics.rst
pdns/dnsdistdist/test-dnsdist-lua-ffi.cc

index 0510d9a1afff602685a92917b079f8ee485d8c21..aea5a1b66e40bc96c46263e7fdc9cc6704cfd008 100644 (file)
@@ -47,6 +47,7 @@
 #ifdef LUAJIT_VERSION
 #include "dnsdist-lua-ffi.hh"
 #endif /* LUAJIT_VERSION */
+#include "dnsdist-metrics.hh"
 #include "dnsdist-nghttp2.hh"
 #include "dnsdist-proxy-protocol.hh"
 #include "dnsdist-rings.hh"
@@ -2914,89 +2915,49 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
   });
 
   luaCtx.writeFunction("declareMetric", [](const std::string& name, const std::string& type, const std::string& description, boost::optional<std::string> customName) {
-    if (!std::regex_match(name, std::regex("^[a-z0-9-]+$"))) {
-      g_outputBuffer = "Unable to declare metric '" + name + "': invalid name\n";
-      errlog("Unable to declare metric '%s': invalid name", name);
-      return false;
-    }
-    if (type == "counter") {
-      auto customCounters = g_stats.customCounters.write_lock();
-      auto itp = customCounters->insert({name, DNSDistStats::MutableCounter()});
-      if (itp.second) {
-        g_stats.entries.write_lock()->emplace_back(DNSDistStats::EntryPair{name, &(*customCounters)[name].d_value});
-        addMetricDefinition(name, "counter", description, customName ? *customName : "");
-      }
-    }
-    else if (type == "gauge") {
-      auto customGauges = g_stats.customGauges.write_lock();
-      auto itp = customGauges->insert({name, DNSDistStats::MutableGauge()});
-      if (itp.second) {
-        g_stats.entries.write_lock()->emplace_back(DNSDistStats::EntryPair{name, &(*customGauges)[name].d_value});
-        addMetricDefinition(name, "gauge", description, customName ? *customName : "");
-      }
-    }
-    else {
-      g_outputBuffer = "declareMetric unknown type '" + type + "'\n";
-      errlog("Unable to declareMetric '%s': no such type '%s'", name, type);
+    auto result = dnsdist::metrics::declareCustomMetric(name, type, description, customName ? std::optional<std::string>(*customName) : std::nullopt);
+    if (result) {
+      g_outputBuffer += *result + "\n";
+      errlog("%s", *result);
       return false;
     }
     return true;
   });
   luaCtx.writeFunction("incMetric", [](const std::string& name, boost::optional<uint64_t> step) {
-    auto customCounters = g_stats.customCounters.read_lock();
-    auto metric = customCounters->find(name);
-    if (metric != customCounters->end()) {
-      if (step) {
-        metric->second.d_value += *step;
-        return metric->second.d_value.load();
-      }
-      return ++(metric->second.d_value);
+    auto result = dnsdist::metrics::incrementCustomCounter(name, step ? *step : 1);
+    if (const auto* errorStr = std::get_if<dnsdist::metrics::Error>(&result)) {
+      g_outputBuffer = *errorStr + "'\n";
+      errlog("%s", *errorStr);
+      return static_cast<uint64_t>(0);
     }
-    g_outputBuffer = "incMetric no such metric '" + name + "'\n";
-    errlog("Unable to incMetric: no such name '%s'", name);
-    return (uint64_t)0;
+    return std::get<uint64_t>(result);
   });
-  luaCtx.writeFunction("decMetric", [](const std::string& name) {
-    auto customCounters = g_stats.customCounters.read_lock();
-    auto metric = customCounters->find(name);
-    if (metric != customCounters->end()) {
-      return --(metric->second.d_value);
+  luaCtx.writeFunction("decMetric", [](const std::string& name, boost::optional<uint64_t> step) {
+    auto result = dnsdist::metrics::decrementCustomCounter(name, step ? *step : 1);
+    if (const auto* errorStr = std::get_if<dnsdist::metrics::Error>(&result)) {
+      g_outputBuffer = *errorStr + "'\n";
+      errlog("%s", *errorStr);
+      return static_cast<uint64_t>(0);
     }
-    g_outputBuffer = "decMetric no such metric '" + name + "'\n";
-    errlog("Unable to decMetric: no such name '%s'", name);
-    return (uint64_t)0;
+    return std::get<uint64_t>(result);
   });
   luaCtx.writeFunction("setMetric", [](const std::string& name, const double value) -> double {
-    {
-      auto customGauges = g_stats.customGauges.read_lock();
-      auto metric = customGauges->find(name);
-      if (metric != customGauges->end()) {
-        metric->second.d_value = value;
-        return value;
-      }
+    auto result = dnsdist::metrics::setCustomGauge(name, value);
+    if (const auto* errorStr = std::get_if<dnsdist::metrics::Error>(&result)) {
+      g_outputBuffer = *errorStr + "'\n";
+      errlog("%s", *errorStr);
+      return 0.;
     }
-    g_outputBuffer = "setMetric no such metric '" + name + "'\n";
-    errlog("Unable to setMetric: no such name '%s'", name);
-    return 0.;
+    return std::get<double>(result);
   });
   luaCtx.writeFunction("getMetric", [](const std::string& name) {
-    {
-      auto customCounters = g_stats.customCounters.read_lock();
-      auto counter = customCounters->find(name);
-      if (counter != customCounters->end()) {
-        return (double)counter->second.d_value.load();
-      }
-    }
-    {
-      auto customGauges = g_stats.customGauges.read_lock();
-      auto gauge = customGauges->find(name);
-      if (gauge != customGauges->end()) {
-        return gauge->second.d_value.load();
-      }
+    auto result = dnsdist::metrics::getCustomMetric(name);
+    if (const auto* errorStr = std::get_if<dnsdist::metrics::Error>(&result)) {
+      g_outputBuffer = *errorStr + "'\n";
+      errlog("%s", *errorStr);
+      return 0.;
     }
-    g_outputBuffer = "getMetric no such metric '" + name + "'\n";
-    errlog("Unable to getMetric: no such name '%s'", name);
-    return 0.;
+    return std::get<double>(result);
   });
 }
 
index e9c7ca2086cdf13828d732824e908aa2f639909d..aaac2e20666614f0b500b01f228fd73c5be04f6f 100644 (file)
@@ -175,6 +175,7 @@ dnsdist_SOURCES = \
        dnsdist-lua-web.cc \
        dnsdist-lua.cc dnsdist-lua.hh \
        dnsdist-mac-address.cc dnsdist-mac-address.hh \
+       dnsdist-metrics.cc dnsdist-metrics.hh \
        dnsdist-nghttp2.cc dnsdist-nghttp2.hh \
        dnsdist-prometheus.hh \
        dnsdist-protobuf.cc dnsdist-protobuf.hh \
@@ -269,6 +270,7 @@ testrunner_SOURCES = \
        dnsdist-lua-network.cc dnsdist-lua-network.hh \
        dnsdist-lua-vars.cc \
        dnsdist-mac-address.cc dnsdist-mac-address.hh \
+       dnsdist-metrics.cc dnsdist-metrics.hh \
        dnsdist-nghttp2.cc dnsdist-nghttp2.hh \
        dnsdist-protocols.cc dnsdist-protocols.hh \
        dnsdist-proxy-protocol.cc dnsdist-proxy-protocol.hh \
index 5e393a7b948a54ce8f4fb09d236e08302d8a6c4c..e7cc8fe873335b3f43dd2079c4bd00383b5cad56 100644 (file)
@@ -228,6 +228,7 @@ uint16_t dnsdist_ffi_dnspacket_get_record_content_offset(const dnsdist_ffi_dnspa
 size_t dnsdist_ffi_dnspacket_get_name_at_offset_raw(const char* packet, size_t packetSize, size_t offset, char* name, size_t nameSize) __attribute__ ((visibility ("default")));
 void dnsdist_ffi_dnspacket_free(dnsdist_ffi_dnspacket_t*) __attribute__ ((visibility ("default")));
 
+bool dnsdist_ffi_metric_declare(const char* name, size_t nameLen, const char* type, const char* description, const char* customName) __attribute__ ((visibility ("default")));
 void dnsdist_ffi_metric_inc(const char* metricName, size_t metricNameLen) __attribute__ ((visibility ("default")));
 void dnsdist_ffi_metric_inc_by(const char* metricName, size_t metricNameLen, uint64_t value) __attribute__ ((visibility ("default")));
 void dnsdist_ffi_metric_dec(const char* metricName, size_t metricNameLen) __attribute__ ((visibility ("default")));
index f42a01b6e10f7a7a93c33046abf0937800109fb1..bf46aadece445a3c0f3eff705da613f80b70dcf1 100644 (file)
@@ -25,6 +25,7 @@
 #include "dnsdist-ecs.hh"
 #include "dnsdist-lua-ffi.hh"
 #include "dnsdist-mac-address.hh"
+#include "dnsdist-metrics.hh"
 #include "dnsdist-lua-network.hh"
 #include "dnsdist-lua.hh"
 #include "dnsdist-ecs.hh"
@@ -1601,60 +1602,57 @@ void dnsdist_ffi_dnspacket_free(dnsdist_ffi_dnspacket_t* packet)
   }
 }
 
+bool dnsdist_ffi_metric_declare(const char* name, size_t nameLen, const char* type, const char* description, const char* customName)
+{
+  if (name == nullptr || nameLen == 0 || type == nullptr || description == nullptr) {
+    return false;
+  }
+  auto result = dnsdist::metrics::declareCustomMetric(name, type, description, customName ? std::optional<std::string>(customName) : std::nullopt);
+  if (result) {
+    return false;
+  }
+  return true;
+}
+
 void dnsdist_ffi_metric_inc(const char* metricName, size_t metricNameLen)
 {
-  auto customCounters = g_stats.customCounters.read_lock();
-  auto metric = customCounters->find(std::string_view(metricName, metricNameLen));
-  if (metric != customCounters->end()) {
-    ++metric->second.d_value;
+  auto result = dnsdist::metrics::incrementCustomCounter(std::string_view(metricName, metricNameLen), 1U);
+  if (const auto* errorStr = std::get_if<dnsdist::metrics::Error>(&result)) {
+    return;
   }
 }
 
 void dnsdist_ffi_metric_inc_by(const char* metricName, size_t metricNameLen, uint64_t value)
 {
-  auto customCounters = g_stats.customCounters.write_lock();
-  auto metric = customCounters->find(std::string_view(metricName, metricNameLen));
-  if (metric != customCounters->end()) {
-    metric->second.d_value += value;
+  auto result = dnsdist::metrics::incrementCustomCounter(std::string_view(metricName, metricNameLen), value);
+  if (const auto* errorStr = std::get_if<dnsdist::metrics::Error>(&result)) {
+    return;
   }
 }
 
 void dnsdist_ffi_metric_dec(const char* metricName, size_t metricNameLen)
 {
-  auto customCounters = g_stats.customCounters.read_lock();
-  auto metric = customCounters->find(std::string_view(metricName, metricNameLen));
-  if (metric != customCounters->end()) {
-    --metric->second.d_value;
+  auto result = dnsdist::metrics::decrementCustomCounter(std::string_view(metricName, metricNameLen), 1U);
+  if (const auto* errorStr = std::get_if<dnsdist::metrics::Error>(&result)) {
+    return;
   }
 }
 
 void dnsdist_ffi_metric_set(const char* metricName, size_t metricNameLen, double value)
 {
-  auto customGauges = g_stats.customGauges.read_lock();
-  auto metric = customGauges->find(std::string_view(metricName, metricNameLen));
-  if (metric != customGauges->end()) {
-    metric->second.d_value = value;
+  auto result = dnsdist::metrics::setCustomGauge(std::string_view(metricName, metricNameLen), value);
+  if (const auto* errorStr = std::get_if<dnsdist::metrics::Error>(&result)) {
+    return;
   }
 }
 
 double dnsdist_ffi_metric_get(const char* metricName, size_t metricNameLen, bool isCounter)
 {
-  auto name = std::string_view(metricName, metricNameLen);
-  if (isCounter) {
-    auto customCounters = g_stats.customCounters.read_lock();
-    auto counter = customCounters->find(name);
-    if (counter != customCounters->end()) {
-      return (double)counter->second.d_value.load();
-    }
-  }
-  else {
-    auto customGauges = g_stats.customGauges.read_lock();
-    auto gauge = customGauges->find(name);
-    if (gauge != customGauges->end()) {
-      return gauge->second.d_value.load();
-    }
+  auto result = dnsdist::metrics::getCustomMetric(std::string_view(metricName, metricNameLen));
+  if (const auto* errorStr = std::get_if<dnsdist::metrics::Error>(&result)) {
+    return 0.;
   }
-  return 0.;
+  return std::get<double>(result);
 }
 
 const char* dnsdist_ffi_network_message_get_payload(const dnsdist_ffi_network_message_t* msg)
diff --git a/pdns/dnsdistdist/dnsdist-metrics.cc b/pdns/dnsdistdist/dnsdist-metrics.cc
new file mode 100644 (file)
index 0000000..0eed935
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include <regex>
+
+#include "dnsdist-metrics.hh"
+#include "dnsdist.hh"
+#include "dnsdist-web.hh"
+
+namespace dnsdist::metrics {
+
+std::optional<std::string> declareCustomMetric(const std::string& name, const std::string& type, const std::string& description, std::optional<std::string> customName)
+{
+  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");
+  }
+
+  if (type == "counter") {
+    auto customCounters = g_stats.customCounters.write_lock();
+    auto itp = customCounters->insert({name, DNSDistStats::MutableCounter()});
+    if (itp.second) {
+      g_stats.entries.write_lock()->emplace_back(DNSDistStats::EntryPair{name, &(*customCounters)[name].d_value});
+      addMetricDefinition(name, "counter", description, customName ? *customName : "");
+    }
+  }
+  else if (type == "gauge") {
+    auto customGauges = g_stats.customGauges.write_lock();
+    auto itp = customGauges->insert({name, DNSDistStats::MutableGauge()});
+    if (itp.second) {
+      g_stats.entries.write_lock()->emplace_back(DNSDistStats::EntryPair{name, &(*customGauges)[name].d_value});
+      addMetricDefinition(name, "gauge", description, customName ? *customName : "");
+    }
+  }
+  else {
+    return std::string("Unable to declare metric: unknown type '") + type + "'";
+  }
+  return std::nullopt;
+}
+
+std::variant<uint64_t, Error> incrementCustomCounter(const std::string_view& name, uint64_t step)
+{
+  auto customCounters = g_stats.customCounters.read_lock();
+  auto metric = customCounters->find(name);
+  if (metric != customCounters->end()) {
+    if (step) {
+      metric->second.d_value += step;
+      return metric->second.d_value.load();
+    }
+    return ++(metric->second.d_value);
+  }
+  return std::string("Unable to increment custom metric '") + std::string(name) + "': no such metric";
+}
+
+std::variant<uint64_t, Error> decrementCustomCounter(const std::string_view& name, uint64_t step)
+{
+  auto customCounters = g_stats.customCounters.read_lock();
+  auto metric = customCounters->find(name);
+  if (metric != customCounters->end()) {
+    if (step) {
+      metric->second.d_value -= step;
+      return metric->second.d_value.load();
+    }
+    return --(metric->second.d_value);
+  }
+  return std::string("Unable to decrement custom metric '") + std::string(name) + "': no such metric";
+}
+
+std::variant<double, Error> setCustomGauge(const std::string_view& name, const double value)
+{
+  auto customGauges = g_stats.customGauges.read_lock();
+  auto metric = customGauges->find(name);
+  if (metric != customGauges->end()) {
+    metric->second.d_value = value;
+    return value;
+  }
+
+  return std::string("Unable to set metric '") + std::string(name) + "': no such metric";
+}
+
+std::variant<double, Error> getCustomMetric(const std::string_view& name)
+{
+  {
+    auto customCounters = g_stats.customCounters.read_lock();
+    auto counter = customCounters->find(name);
+    if (counter != customCounters->end()) {
+      return static_cast<double>(counter->second.d_value.load());
+    }
+  }
+  {
+    auto customGauges = g_stats.customGauges.read_lock();
+    auto gauge = customGauges->find(name);
+    if (gauge != customGauges->end()) {
+      return gauge->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
new file mode 100644 (file)
index 0000000..88a812e
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include <cinttypes>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <variant>
+
+namespace dnsdist::metrics
+{
+  using Error = std::string;
+
+  [[nodiscard]] std::optional<Error> declareCustomMetric(const std::string& name, const std::string& type, const std::string& description, std::optional<std::string> customName);
+  [[nodiscard]] std::variant<uint64_t, Error> incrementCustomCounter(const std::string_view& name, uint64_t step);
+  [[nodiscard]] std::variant<uint64_t, Error> decrementCustomCounter(const std::string_view& name, uint64_t step);
+  [[nodiscard]] std::variant<double, Error> setCustomGauge(const std::string_view& name, const double value);
+  [[nodiscard]] std::variant<double, Error> getCustomMetric(const std::string_view& name);
+}
index e0af5f51c27705accfc774c0b2afd213072fc02f..0f656b7f9c9e5920a401ce5c5265aa18f29a0d0e 100644 (file)
@@ -41,10 +41,14 @@ Then you can update those at runtime using the following functions, depending on
 
   .. versionadded:: 1.8.0
 
-  Decrement counter by one, will issue an error if the metric is not declared or not a ``counter``
+  .. versionchanged:: 1.8.1
+    Optional ``step`` parameter added.
+
+  Decrement counter by one (or more, see the ``step`` parameter), will issue an error if the metric is not declared or not a ``counter``
   Return 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.
 
 .. function:: getMetric(name) -> double
 
index e6f0e1e21c231115c012d4d7833a4936afb42ba7..8a5a06763bff8991ebf0a912cbaf474234fd4498 100644 (file)
 #include "dnsparser.hh"
 #include "dnswriter.hh"
 
+bool addMetricDefinition(const std::string& name, const std::string& type, const std::string& description, const std::string& customPrometheusName)
+{
+  return true;
+}
+
 BOOST_AUTO_TEST_SUITE(test_dnsdist_lua_ffi)
 
 BOOST_AUTO_TEST_CASE(test_Query)