]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Allow declaring custom metrics at runtime
authorRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 15 Jun 2023 12:17:03 +0000 (14:17 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 27 Jun 2023 14:03:31 +0000 (16:03 +0200)
Also fixes a bug in the prometheus HELP and TYPE messages for custom
metrics with labels, and adds a method to increment a counter by more
than one.

pdns/dnsdist-carbon.cc
pdns/dnsdist-lua-inspection.cc
pdns/dnsdist-lua.cc
pdns/dnsdist-web.cc
pdns/dnsdist.hh
pdns/dnsdistdist/dnsdist-lua-ffi-interface.h
pdns/dnsdistdist/dnsdist-lua-ffi.cc
pdns/dnsdistdist/docs/reference/custommetrics.rst
regression-tests.dnsdist/test_Advanced.py

index 90cdd5acbc8d2ede802cb4aaa7b1cc649295035b..4280f05f31212a0ade3101669f67bbac14d6e198 100644 (file)
@@ -51,21 +51,24 @@ static bool doOneCarbonExport(const Carbon::Endpoint& endpoint)
 
     const time_t now = time(nullptr);
 
-    for (const auto& e : g_stats.entries) {
-      str << namespace_name << "." << hostname << "." << instance_name << "." << e.first << ' ';
-      if (const auto& val = boost::get<pdns::stat_t*>(&e.second)) {
-        str << (*val)->load();
-      }
-      else if (const auto& adval = boost::get<pdns::stat_t_trait<double>*>(&e.second)) {
-        str << (*adval)->load();
-      }
-      else if (const auto& dval = boost::get<double*>(&e.second)) {
-        str << **dval;
-      }
-      else if (const auto& func = boost::get<DNSDistStats::statfunction_t>(&e.second)) {
-        str << (*func)(e.first);
+    {
+      auto entries = g_stats.entries.read_lock();
+      for (const auto& entry : *entries) {
+        str << namespace_name << "." << hostname << "." << instance_name << "." << entry.d_name << ' ';
+        if (const auto& val = boost::get<pdns::stat_t*>(&entry.d_value)) {
+          str << (*val)->load();
+        }
+        else if (const auto& adval = boost::get<pdns::stat_t_trait<double>*>(&entry.d_value)) {
+          str << (*adval)->load();
+        }
+        else if (const auto& dval = boost::get<double*>(&entry.d_value)) {
+          str << **dval;
+        }
+        else if (const auto& func = boost::get<DNSDistStats::statfunction_t>(&entry.d_value)) {
+          str << (*func)(entry.d_name);
+        }
+        str << ' ' << now << "\r\n";
       }
-      str << ' ' << now << "\r\n";
     }
 
     auto states = g_dstates.getLocal();
index d6755b00cd3b8a13af919108db18f4077c09e9d3..27628c33eaa0c4922f4f0e7b32f3b2297ba8ffca 100644 (file)
@@ -737,32 +737,32 @@ void setupLuaInspection(LuaContext& luaCtx)
 
       boost::format fmt("%-35s\t%+11s");
       g_outputBuffer.clear();
-      auto entries = g_stats.entries;
+      auto entries = *g_stats.entries.read_lock();
       sort(entries.begin(), entries.end(),
-          [](const decltype(entries)::value_type& a, const decltype(entries)::value_type& b) {
-            return a.first < b.first;
-          });
+           [](const decltype(entries)::value_type& a, const decltype(entries)::value_type& b) {
+             return a.d_name < b.d_name;
+           });
       boost::format flt("    %9.1f");
-      for (const auto& e : entries) {
+      for (const auto& entry : entries) {
         string second;
-        if (const auto& val = boost::get<pdns::stat_t*>(&e.second)) {
+        if (const auto& val = boost::get<pdns::stat_t*>(&entry.d_value)) {
           second = std::to_string((*val)->load());
         }
-        else if (const auto& adval = boost::get<pdns::stat_t_trait<double>*>(&e.second)) {
+        else if (const auto& adval = boost::get<pdns::stat_t_trait<double>*>(&entry.d_value)) {
           second = (flt % (*adval)->load()).str();
         }
-        else if (const auto& dval = boost::get<double*>(&e.second)) {
+        else if (const auto& dval = boost::get<double*>(&entry.d_value)) {
           second = (flt % (**dval)).str();
         }
-        else if (const auto& func = boost::get<DNSDistStats::statfunction_t>(&e.second)) {
-          second = std::to_string((*func)(e.first));
+        else if (const auto& func = boost::get<DNSDistStats::statfunction_t>(&entry.d_value)) {
+          second = std::to_string((*func)(entry.d_name));
         }
 
-        if (leftcolumn.size() < g_stats.entries.size()/2) {
-          leftcolumn.push_back((fmt % e.first % second).str());
+        if (leftcolumn.size() < entries.size() / 2) {
+          leftcolumn.push_back((fmt % entry.d_name % second).str());
         }
         else {
-          rightcolumn.push_back((fmt % e.first % second).str());
+          rightcolumn.push_back((fmt % entry.d_name % second).str());
         }
       }
 
index f0345ebf2ebfaaf93ba5d6209bce0a62ca771cec..0510d9a1afff602685a92917b079f8ee485d8c21 100644 (file)
@@ -1828,9 +1828,14 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
   luaCtx.writeFunction<LuaAssociativeTable<uint64_t>()>("getStatisticsCounters", []() {
     setLuaNoSideEffect();
     std::unordered_map<string, uint64_t> res;
-    for (const auto& entry : g_stats.entries) {
-      if (const auto& val = boost::get<pdns::stat_t*>(&entry.second))
-        res[entry.first] = (*val)->load();
+    {
+      auto entries = g_stats.entries.read_lock();
+      res.reserve(entries->size());
+      for (const auto& entry : *entries) {
+        if (const auto& val = boost::get<pdns::stat_t*>(&entry.d_value)) {
+          res[entry.d_name] = (*val)->load();
+        }
+      }
     }
     return res;
   });
@@ -2909,25 +2914,24 @@ 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 (!checkConfigurationTime("declareMetric")) {
-      return false;
-    }
     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 itp = g_stats.customCounters.emplace(name, 0);
+      auto customCounters = g_stats.customCounters.write_lock();
+      auto itp = customCounters->insert({name, DNSDistStats::MutableCounter()});
       if (itp.second) {
-        g_stats.entries.emplace_back(name, &g_stats.customCounters[name]);
+        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 itp = g_stats.customGauges.emplace(name, 0.);
+      auto customGauges = g_stats.customGauges.write_lock();
+      auto itp = customGauges->insert({name, DNSDistStats::MutableGauge()});
       if (itp.second) {
-        g_stats.entries.emplace_back(name, &g_stats.customGauges[name]);
+        g_stats.entries.write_lock()->emplace_back(DNSDistStats::EntryPair{name, &(*customGauges)[name].d_value});
         addMetricDefinition(name, "gauge", description, customName ? *customName : "");
       }
     }
@@ -2938,43 +2942,56 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     }
     return true;
   });
-  luaCtx.writeFunction("incMetric", [](const std::string& name) {
-    auto metric = g_stats.customCounters.find(name);
-    if (metric != g_stats.customCounters.end()) {
-      return ++(metric->second);
+  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);
     }
     g_outputBuffer = "incMetric no such metric '" + name + "'\n";
     errlog("Unable to incMetric: no such name '%s'", name);
     return (uint64_t)0;
   });
   luaCtx.writeFunction("decMetric", [](const std::string& name) {
-    auto metric = g_stats.customCounters.find(name);
-    if (metric != g_stats.customCounters.end()) {
-      return --(metric->second);
+    auto customCounters = g_stats.customCounters.read_lock();
+    auto metric = customCounters->find(name);
+    if (metric != customCounters->end()) {
+      return --(metric->second.d_value);
     }
     g_outputBuffer = "decMetric no such metric '" + name + "'\n";
     errlog("Unable to decMetric: no such name '%s'", name);
     return (uint64_t)0;
   });
-  luaCtx.writeFunction("setMetric", [](const std::string& name, const double& value) {
-    auto metric = g_stats.customGauges.find(name);
-    if (metric != g_stats.customGauges.end()) {
-      metric->second = value;
-      return value;
+  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;
+      }
     }
     g_outputBuffer = "setMetric no such metric '" + name + "'\n";
     errlog("Unable to setMetric: no such name '%s'", name);
     return 0.;
   });
   luaCtx.writeFunction("getMetric", [](const std::string& name) {
-    auto counter = g_stats.customCounters.find(name);
-    if (counter != g_stats.customCounters.end()) {
-      return (double)counter->second.load();
+    {
+      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 gauge = g_stats.customGauges.find(name);
-      if (gauge != g_stats.customGauges.end()) {
-        return gauge->second.load();
+    {
+      auto customGauges = g_stats.customGauges.read_lock();
+      auto gauge = customGauges->find(name);
+      if (gauge != customGauges->end()) {
+        return gauge->second.d_value.load();
       }
     }
     g_outputBuffer = "getMetric no such metric '" + name + "'\n";
index 21cdfd05b3a7da370257817542e69f5959d6d7da..c7fd3812c23c5c4dad5674fce1b296de06f029af 100644 (file)
@@ -467,53 +467,58 @@ static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp)
 
   std::ostringstream output;
   static const std::set<std::string> metricBlacklist = { "special-memory-usage", "latency-count", "latency-sum" };
-  for (const auto& e : g_stats.entries) {
-    const auto& metricName = std::get<0>(e);
+  {
+    auto entries = g_stats.entries.read_lock();
+    for (const auto& entry : *entries) {
+      const auto& metricName = entry.d_name;
 
-    if (metricBlacklist.count(metricName) != 0) {
-      continue;
-    }
+      if (metricBlacklist.count(metricName) != 0) {
+        continue;
+      }
 
-    MetricDefinition metricDetails;
-    if (!s_metricDefinitions.getMetricDetails(metricName, metricDetails)) {
-      vinfolog("Do not have metric details for %s", metricName);
-      continue;
-    }
+      MetricDefinition metricDetails;
+      if (!s_metricDefinitions.getMetricDetails(metricName, metricDetails)) {
+        vinfolog("Do not have metric details for %s", metricName);
+        continue;
+      }
 
-    const std::string prometheusTypeName = s_metricDefinitions.getPrometheusStringMetricType(metricDetails.prometheusType);
-    if (prometheusTypeName.empty()) {
-      vinfolog("Unknown Prometheus type for %s", metricName);
-      continue;
-    }
+      const std::string prometheusTypeName = s_metricDefinitions.getPrometheusStringMetricType(metricDetails.prometheusType);
+      if (prometheusTypeName.empty()) {
+        vinfolog("Unknown Prometheus type for %s", metricName);
+        continue;
+      }
 
-    // Prometheus suggest using '_' instead of '-'
-    std::string prometheusMetricName;
-    if (metricDetails.customName.empty()) {
-      prometheusMetricName = "dnsdist_" + boost::replace_all_copy(metricName, "-", "_");
-    }
-    else {
-      prometheusMetricName = metricDetails.customName;
-    }
+      // 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";
-    output << "# TYPE " << prometheusMetricName << " " << prometheusTypeName << "\n";
-    output << prometheusMetricName << " ";
+      // 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('{'));
+      output << "# HELP " << helpName << " " << metricDetails.description    << "\n";
+      output << "# TYPE " << helpName << " " << prometheusTypeName << "\n";
+      output << prometheusMetricName << " ";
 
-    if (const auto& val = boost::get<pdns::stat_t*>(&std::get<1>(e))) {
-      output << (*val)->load();
-    }
-    else if (const auto& adval = boost::get<pdns::stat_t_trait<double>*>(&std::get<1>(e))) {
-      output << (*adval)->load();
-    }
-    else if (const auto& dval = boost::get<double*>(&std::get<1>(e))) {
-      output << **dval;
-    }
-    else if (const auto& func = boost::get<DNSDistStats::statfunction_t>(&std::get<1>(e))) {
-      output << (*func)(std::get<0>(e));
-    }
+      if (const auto& val = boost::get<pdns::stat_t*>(&entry.d_value)) {
+        output << (*val)->load();
+      }
+      else if (const auto& adval = boost::get<pdns::stat_t_trait<double>*>(&entry.d_value)) {
+        output << (*adval)->load();
+      }
+      else if (const auto& dval = boost::get<double*>(&entry.d_value)) {
+        output << **dval;
+      }
+      else if (const auto& func = boost::get<DNSDistStats::statfunction_t>(&entry.d_value)) {
+        output << (*func)(entry.d_name);
+      }
 
-    output << "\n";
+      output << "\n";
+    }
   }
 
   // Latency histogram buckets
@@ -890,18 +895,19 @@ using namespace json11;
 
 static void addStatsToJSONObject(Json::object& obj)
 {
-  for (const auto& e : g_stats.entries) {
-    if (e.first == "special-memory-usage") {
+  auto entries = g_stats.entries.read_lock();
+  for (const auto& entry : *entries) {
+    if (entry.d_name == "special-memory-usage") {
       continue; // Too expensive for get-all
     }
-    if (const auto& val = boost::get<pdns::stat_t*>(&e.second)) {
-      obj.emplace(e.first, (double)(*val)->load());
-    } else if (const auto& adval = boost::get<pdns::stat_t_trait<double>*>(&e.second)) {
-      obj.emplace(e.first, (*adval)->load());
-    } else if (const auto& dval = boost::get<double*>(&e.second)) {
-      obj.emplace(e.first, (**dval));
-    } else if (const auto& func = boost::get<DNSDistStats::statfunction_t>(&e.second)) {
-      obj.emplace(e.first, (double)(*func)(e.first));
+    if (const auto& val = boost::get<pdns::stat_t*>(&entry.d_value)) {
+      obj.emplace(entry.d_name, (double)(*val)->load());
+    } else if (const auto& adval = boost::get<pdns::stat_t_trait<double>*>(&entry.d_value)) {
+      obj.emplace(entry.d_name, (*adval)->load());
+    } else if (const auto& dval = boost::get<double*>(&entry.d_value)) {
+      obj.emplace(entry.d_name, (**dval));
+    } else if (const auto& func = boost::get<DNSDistStats::statfunction_t>(&entry.d_value)) {
+      obj.emplace(entry.d_name, (double)(*func)(entry.d_name));
     }
   }
 }
@@ -1329,37 +1335,41 @@ static void handleStatsOnly(const YaHTTP::Request& req, YaHTTP::Response& resp)
   resp.status = 200;
 
   Json::array doc;
-  for(const auto& item : g_stats.entries) {
-    if (item.first == "special-memory-usage")
-      continue; // Too expensive for get-all
+  {
+    auto entries = g_stats.entries.read_lock();
+    for (const auto& item : *entries) {
+      if (item.d_name == "special-memory-usage") {
+        continue; // Too expensive for get-all
+      }
 
-    if (const auto& val = boost::get<pdns::stat_t*>(&item.second)) {
-      doc.push_back(Json::object {
-          { "type", "StatisticItem" },
-          { "name", item.first },
-          { "value", (double)(*val)->load() }
-        });
-    }
-    else if (const auto& adval = boost::get<pdns::stat_t_trait<double>*>(&item.second)) {
-      doc.push_back(Json::object {
-          { "type", "StatisticItem" },
-          { "name", item.first },
-          { "value", (*adval)->load() }
-        });
-    }
-    else if (const auto& dval = boost::get<double*>(&item.second)) {
-      doc.push_back(Json::object {
-          { "type", "StatisticItem" },
-          { "name", item.first },
-          { "value", (**dval) }
-        });
-    }
-    else if (const auto& func = boost::get<DNSDistStats::statfunction_t>(&item.second)) {
-      doc.push_back(Json::object {
-          { "type", "StatisticItem" },
-          { "name", item.first },
-          { "value", (double)(*func)(item.first) }
-        });
+      if (const auto& val = boost::get<pdns::stat_t*>(&item.d_value)) {
+        doc.push_back(Json::object {
+            { "type", "StatisticItem" },
+            { "name", item.d_name },
+            { "value", (double)(*val)->load() }
+          });
+      }
+      else if (const auto& adval = boost::get<pdns::stat_t_trait<double>*>(&item.d_value)) {
+        doc.push_back(Json::object {
+            { "type", "StatisticItem" },
+            { "name", item.d_name },
+            { "value", (*adval)->load() }
+          });
+      }
+      else if (const auto& dval = boost::get<double*>(&item.d_value)) {
+        doc.push_back(Json::object {
+            { "type", "StatisticItem" },
+            { "name", item.d_name },
+            { "value", (**dval) }
+          });
+      }
+      else if (const auto& func = boost::get<DNSDistStats::statfunction_t>(&item.d_value)) {
+        doc.push_back(Json::object {
+            { "type", "StatisticItem" },
+            { "name", item.d_name },
+            { "value", (double)(*func)(item.d_name) }
+          });
+      }
     }
   }
   Json my_json = doc;
index e55205599fb2e2139aa75b25d7606f7c0faed719..1f7b4e8704e2611da757628f910a222cc1b5ad91 100644 (file)
@@ -369,10 +369,15 @@ struct DNSDistStats
   double latencyTCPAvg100{0}, latencyTCPAvg1000{0}, latencyTCPAvg10000{0}, latencyTCPAvg1000000{0};
   double latencyDoTAvg100{0}, latencyDoTAvg1000{0}, latencyDoTAvg10000{0}, latencyDoTAvg1000000{0};
   double latencyDoHAvg100{0}, latencyDoHAvg1000{0}, latencyDoHAvg10000{0}, latencyDoHAvg1000000{0};
-  typedef std::function<uint64_t(const std::string&)> statfunction_t;
-  typedef boost::variant<stat_t*, pdns::stat_t_trait<double>*, double*, statfunction_t> entry_t;
+  using statfunction_t =  std::function<uint64_t(const std::string&)>;
+  using entry_t = boost::variant<stat_t*, pdns::stat_t_trait<double>*, double*, statfunction_t>;
+  struct EntryPair
+  {
+    std::string d_name;
+    entry_t d_value;
+  };
 
-  std::vector<std::pair<std::string, entry_t>> entries{
+  SharedLockGuarded<std::vector<EntryPair>> entries{std::vector<EntryPair>{
     {"responses", &responses},
     {"servfail-responses", &servfailResponses},
     {"queries", &queries},
@@ -450,9 +455,27 @@ struct DNSDistStats
     // Latency histogram
     {"latency-sum", &latencySum},
     {"latency-count", &latencyCount},
+  }};
+  struct MutableCounter
+  {
+    MutableCounter() = default;
+    MutableCounter(MutableCounter&& rhs): d_value(rhs.d_value.load())
+    {
+    }
+
+    mutable stat_t d_value{0};
+  };
+  struct MutableGauge
+  {
+    MutableGauge() = default;
+    MutableGauge(MutableGauge&& rhs): d_value(rhs.d_value.load())
+    {
+    }
+
+    mutable pdns::stat_t_trait<double> d_value{0};
   };
-  std::map<std::string, stat_t, std::less<>> customCounters;
-  std::map<std::string, pdns::stat_t_trait<double>, std::less<>> customGauges;
+  SharedLockGuarded<std::map<std::string, MutableCounter, std::less<>>> customCounters;
+  SharedLockGuarded<std::map<std::string, MutableGauge, std::less<>>> customGauges;
 };
 
 extern struct DNSDistStats g_stats;
index c8d93978156fb5160c659ccec09105c03b2edda0..5e393a7b948a54ce8f4fb09d236e08302d8a6c4c 100644 (file)
@@ -229,6 +229,7 @@ size_t dnsdist_ffi_dnspacket_get_name_at_offset_raw(const char* packet, size_t p
 void dnsdist_ffi_dnspacket_free(dnsdist_ffi_dnspacket_t*) __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")));
 void dnsdist_ffi_metric_set(const char* metricName, size_t metricNameLen, double value) __attribute__ ((visibility ("default")));
 double dnsdist_ffi_metric_get(const char* metricName, size_t metricNameLen, bool isCounter) __attribute__ ((visibility ("default")));
index c9fa8407ef80ef3632c1d8c66ca21927e5a0c5bc..f42a01b6e10f7a7a93c33046abf0937800109fb1 100644 (file)
@@ -1603,25 +1603,37 @@ void dnsdist_ffi_dnspacket_free(dnsdist_ffi_dnspacket_t* packet)
 
 void dnsdist_ffi_metric_inc(const char* metricName, size_t metricNameLen)
 {
-  auto metric = g_stats.customCounters.find(std::string_view(metricName, metricNameLen));
-  if (metric != g_stats.customCounters.end()) {
-    ++metric->second;
+  auto customCounters = g_stats.customCounters.read_lock();
+  auto metric = customCounters->find(std::string_view(metricName, metricNameLen));
+  if (metric != customCounters->end()) {
+    ++metric->second.d_value;
+  }
+}
+
+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;
   }
 }
 
 void dnsdist_ffi_metric_dec(const char* metricName, size_t metricNameLen)
 {
-  auto metric = g_stats.customCounters.find(std::string_view(metricName, metricNameLen));
-  if (metric != g_stats.customCounters.end()) {
-    --metric->second;
+  auto customCounters = g_stats.customCounters.read_lock();
+  auto metric = customCounters->find(std::string_view(metricName, metricNameLen));
+  if (metric != customCounters->end()) {
+    --metric->second.d_value;
   }
 }
 
 void dnsdist_ffi_metric_set(const char* metricName, size_t metricNameLen, double value)
 {
-  auto metric = g_stats.customGauges.find(std::string_view(metricName, metricNameLen));
-  if (metric != g_stats.customGauges.end()) {
-    metric->second = 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;
   }
 }
 
@@ -1629,15 +1641,17 @@ double dnsdist_ffi_metric_get(const char* metricName, size_t metricNameLen, bool
 {
   auto name = std::string_view(metricName, metricNameLen);
   if (isCounter) {
-    auto counter = g_stats.customCounters.find(name);
-    if (counter != g_stats.customCounters.end()) {
-      return (double)counter->second.load();
+    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 gauge = g_stats.customGauges.find(name);
-    if (gauge != g_stats.customGauges.end()) {
-      return gauge->second.load();
+    auto customGauges = g_stats.customGauges.read_lock();
+    auto gauge = customGauges->find(name);
+    if (gauge != customGauges->end()) {
+      return gauge->second.d_value.load();
     }
   }
   return 0.;
index 7cf5fa1a6fc41eaa57c80410391b8f260f6b7dd6..e0af5f51c27705accfc774c0b2afd213072fc02f 100644 (file)
@@ -1,9 +1,9 @@
 Custom Metrics
 =====================================
 
-You can define at configuration time your own metrics that can be updated using Lua.
+You can define at your own metrics that can be updated using Lua.
 
-The first step is to declare a new metric using :func:`declareMetric`.
+The first step is to declare a new metric using :func:`declareMetric`. In 1.8.0 the declaration had to be done at configuration time, but since 1.8.1 it can be done at any point.
 
 Then you can update those at runtime using the following functions, depending on the metric type:
 
@@ -14,6 +14,9 @@ Then you can update those at runtime using the following functions, depending on
 
   .. versionadded:: 1.8.0
 
+  .. versionchanged:: 1.8.1
+    This function can now be used at runtime, instead of only at configuration time.
+
   Return true if declaration was successful
 
   :param str name: The name of the metric, lowercase alphanumerical characters and dashes (-) only
@@ -21,14 +24,18 @@ Then you can update those at runtime using the following functions, depending on
   :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
+.. function:: incMetric(name [, step]) -> int
 
   .. versionadded:: 1.8.0
 
-  Increment 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.
+
+  Increment 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 incremented, default to 1.
 
 .. function:: decMetric(name) -> int
 
@@ -55,3 +62,4 @@ Then you can update those at runtime using the following functions, depending on
   Return the new value
 
   :param str name: The name of the metric
+  :param double value: The new value
index 5c3efbb5016d46060440a261c73de04f10d5f3e7..186138cbd5106c2b1a7b887350a138d49c7e02fa 100644 (file)
@@ -597,18 +597,19 @@ class TestCustomMetrics(DNSDistTest):
       initialCounter = getMetric("my-custom-counter")
       initialGauge = getMetric("my-custom-counter")
       incMetric("my-custom-counter")
+      incMetric("my-custom-counter", 41)
       setMetric("my-custom-gauge", initialGauge + 1.3)
-      if getMetric("my-custom-counter") ~= (initialCounter + 1) or getMetric("my-custom-gauge") ~= (initialGauge + 1.3) then
+      if getMetric("my-custom-counter") ~= (initialCounter + 42) or getMetric("my-custom-gauge") ~= (initialGauge + 1.3) then
         return DNSAction.Spoof, '1.2.3.5'
       end
       return DNSAction.Spoof, '4.3.2.1'
     end
 
     function declareNewMetric(dq)
-      if declareMetric("new-runtime-metric", "counter", "Metric declaration at runtime should fail") then
-        return DNSAction.Spoof, '1.2.3.4'
+      if declareMetric("new-runtime-metric", "counter", "Metric declaration at runtime should work fine") then
+        return DNSAction.None
       end
-      return DNSAction.None
+      return DNSAction.Spoof, '1.2.3.4'
     end
 
     declareMetric("my-custom-counter", "counter", "Number of tests run")