]> 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>
Mon, 14 Aug 2023 14:08:42 +0000 (16:08 +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.

(cherry picked from commit 54c1bc22f3ae1af76253efa7ba859601d6d6c45e)

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 74514ab963a0c2b375b612731e2c48857b32daf5..f72301a4506ee380843dccb05e90eac381d06be5 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 86b3e0b5918c324128aa1cd99a2ff2c818ff50d7..4cc46ef654f43f373132e55cc74ca15e680fcddd 100644 (file)
@@ -700,32 +700,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 6e3252f809a384c79c2234e3c01f9e6b51db6f26..adf2490ddeb38a409b4c76b56b27817a03b6b8f5 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 e8b51066b78596a26b0560e82aeb5d67230a989a..06cad38ef0030e094eb45bd46c09267e1d069829 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 472a729ba17088ba3ea5002d59dda03eba82f98d..ed2f7d0aae2336e45a40d38592ff260d3f2a397c 100644 (file)
@@ -368,10 +368,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},
@@ -449,9 +454,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 1a110e483dad7ba7162a7bf2f0a3a4a149ebbf9c..a5997946deccbffe2b2c2e0d69753898f42f5c96 100644 (file)
@@ -594,18 +594,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")