]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Add prometheus types/descriptions to dynamic metrics.
authorJess Bees <jesse@toomanybees.com>
Mon, 27 Oct 2025 14:44:39 +0000 (10:44 -0400)
committerJess Bees <jesse@toomanybees.com>
Mon, 27 Oct 2025 14:49:31 +0000 (10:49 -0400)
This commit adds optional types and descriptions to dynamic metrics, so
they can be written to the prometheus metrics web endpoint in comments.

Adds `initMetric` function to Lua, which is similar to getMetric, but
has two additional arguments: the metric's prometheus type, and the
metric's description. Metrics that are first declared with `getMetric`
will have no type or description, and subsequent calls to `initMetric`
will have no effect (the same way that calling `getMetric` multiple
times with different prometheus metric names will have no effect).

Signed-off-by: Jess Bees <jesse@toomanybees.com>
pdns/recursordist/lua-recursor4.cc
pdns/recursordist/rec_channel.hh
pdns/recursordist/rec_channel_rec.cc
pdns/recursordist/ws-recursor.cc

index ada61fbcd6e750601e38b46bcaa4daaa449e1893..b8a030fcad8157a47775827670ff39aa1a6d0d62 100644 (file)
@@ -430,6 +430,25 @@ void RecursorLua4::postPrepareContext() // NOLINT(readability-function-cognitive
 
   d_pd.emplace_back("now", &g_now);
 
+  d_lw->writeFunction("initMetric", [](const std::string& str, const std::string& maybePrometheusName, const std::string& maybePrometheusTypeName, boost::optional<std::string> maybeDescr){
+    // Shift arguments, because it's actually the second `prometheusName` argument which is optional
+    // to match the optional `prometheusName` in `getMetric`.
+    std::string prometheusName;
+    std::string prometheusTypeName;
+    std::string prometheusDescr;
+    if (maybeDescr) {
+      prometheusName = maybePrometheusName;
+      prometheusTypeName = maybePrometheusTypeName;
+      prometheusDescr = *maybeDescr;
+    } else {
+      prometheusDescr = prometheusTypeName;
+      prometheusTypeName = prometheusName;
+      prometheusName = "";
+    }
+
+    return DynMetric{initDynMetric(str, prometheusName, prometheusTypeName, prometheusDescr)};
+  });
+
   d_lw->writeFunction("getMetric", [](const std::string& str, boost::optional<std::string> prometheusName) {
     return DynMetric{getDynMetric(str, prometheusName ? *prometheusName : "")};
     });
index 061683b67b6318a1e9b7e7141dd5d6f1aab50010..78ecd9d06d9f0b39b1bae89b74713d46c737241a 100644 (file)
@@ -104,6 +104,8 @@ struct StatsMapEntry
 {
   std::string d_prometheusName;
   std::string d_value;
+  std::optional<std::string> d_prometheusTypeName = std::nullopt;
+  std::optional<std::string> d_prometheusDescr = std::nullopt;
 };
 
 class PrefixDashNumberCompare
@@ -139,6 +141,7 @@ std::vector<ComboAddress>* pleaseGetLargeAnswerRemotes();
 std::vector<ComboAddress>* pleaseGetTimeouts();
 DNSName getRegisteredName(const DNSName& dom);
 std::atomic<unsigned long>* getDynMetric(const std::string& str, const std::string& prometheusName);
+std::atomic<unsigned long>* initDynMetric(const std::string& str, const std::string& prometheusName, const std::string& prometheusTypeName, const std::string& prometheusDescr);
 std::optional<uint64_t> getStatByName(const std::string& name);
 bool isStatDisabled(StatComponent component, const std::string& name);
 void disableStat(StatComponent component, const string& name);
index c9dcc86836af1cf551f79b6e5253798a19385e25..2610db82a3f619238c6f14588e47db9e9108118e 100644 (file)
@@ -118,6 +118,8 @@ struct dynmetrics
 {
   std::atomic<unsigned long>* d_ptr;
   std::string d_prometheusName;
+  std::optional<string> d_prometheusTypeName;
+  std::optional<string> d_prometheusDescr;
 };
 
 static LockGuarded<map<string, dynmetrics>> d_dynmetrics;
@@ -177,6 +179,11 @@ static std::string getPrometheusName(const std::string& arg)
 }
 
 std::atomic<unsigned long>* getDynMetric(const std::string& str, const std::string& prometheusName)
+{
+  return initDynMetric(str, prometheusName, "", "");
+}
+
+std::atomic<unsigned long>* initDynMetric(const std::string& str, const std::string& prometheusName, const std::string& prometheusTypeName, const std::string& prometheusDescr)
 {
   auto locked = d_dynmetrics.lock();
   auto iter = locked->find(str);
@@ -192,7 +199,16 @@ std::atomic<unsigned long>* getDynMetric(const std::string& str, const std::stri
     name = getPrometheusName(name);
   }
 
-  auto ret = dynmetrics{new std::atomic<unsigned long>(), std::move(name)};
+  std::optional<std::string> typeName;
+  if (!prometheusTypeName.empty()) {
+    typeName = std::optional(std::move(prometheusTypeName));
+  }
+  std::optional<std::string> descr;
+  if (!prometheusDescr.empty()) {
+    descr = std::optional(std::move(prometheusDescr));
+  }
+
+  auto ret = dynmetrics{new std::atomic<unsigned long>(), std::move(name), typeName, descr};
   (*locked)[str] = ret;
   return ret.d_ptr;
 }
@@ -258,7 +274,7 @@ StatsMap getAllStatsMap(StatComponent component)
   {
     for (const auto& value : *(d_dynmetrics.lock())) {
       if (disabledlistMap.count(value.first) == 0) {
-        ret.emplace(value.first, StatsMapEntry{value.second.d_prometheusName, std::to_string(*value.second.d_ptr)});
+        ret.emplace(value.first, StatsMapEntry{value.second.d_prometheusName, std::to_string(*value.second.d_ptr), value.second.d_prometheusTypeName, value.second.d_prometheusDescr});
       }
     }
   }
index 074e5256cbd57557297d72f01aa2b9b419833a6c..f2fa083fa0303d78208e337beffb4e7efa12fae5 100644 (file)
@@ -555,12 +555,15 @@ static void prometheusMetrics(HttpRequest* /* req */, HttpResponse* resp)
   for (const auto& tup : varmap) {
     std::string metricName = tup.first;
     std::string prometheusMetricName = tup.second.d_prometheusName;
+    std::string prometheusTypeName;
+    std::string prometheusDescr;
     std::string helpname = tup.second.d_prometheusName;
     MetricDefinition metricDetails;
 
     if (s_metricDefinitions.getMetricDetails(metricName, metricDetails)) {
-      std::string prometheusTypeName = MetricDefinitionStorage::getPrometheusStringMetricType(
+      prometheusTypeName = MetricDefinitionStorage::getPrometheusStringMetricType(
         metricDetails.d_prometheusType);
+      prometheusDescr = metricDetails.d_description;
 
       if (prometheusTypeName.empty()) {
         continue;
@@ -573,8 +576,20 @@ static void prometheusMetrics(HttpRequest* /* req */, HttpResponse* resp)
         // name is XXX_count, strip the _count part
         helpname = helpname.substr(0, helpname.length() - 6);
       }
+    } else {
+      if (tup.second.d_prometheusTypeName) {
+        prometheusTypeName = *tup.second.d_prometheusTypeName;
+      }
+      if (tup.second.d_prometheusDescr) {
+        prometheusDescr = *tup.second.d_prometheusDescr;
+      }
+    }
+
+    if (!prometheusTypeName.empty()) {
       output << "# TYPE " << helpname << " " << prometheusTypeName << "\n";
-      output << "# HELP " << helpname << " " << metricDetails.d_description << "\n";
+    }
+    if (!prometheusDescr.empty()) {
+      output << "# HELP " << helpname << " " << prometheusDescr << "\n";
     }
     output << prometheusMetricName << " " << tup.second.d_value << "\n";
   }