• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PowerDNS / pdns / 19741624072

27 Nov 2025 03:45PM UTC coverage: 73.086% (+0.02%) from 73.065%
19741624072

Pull #16570

github

web-flow
Merge 08a2cdb1d into f94a3f63f
Pull Request #16570: rec: rewrite all unwrap calls in web.rs

38523 of 63408 branches covered (60.75%)

Branch coverage included in aggregate %.

128044 of 164496 relevant lines covered (77.84%)

6531485.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.05
/pdns/dnsdistdist/dnsdist-metrics.cc
1
/*
2
 * This file is part of PowerDNS or dnsdist.
3
 * Copyright -- PowerDNS.COM B.V. and its contributors
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of version 2 of the GNU General Public License as
7
 * published by the Free Software Foundation.
8
 *
9
 * In addition, for the avoidance of any doubt, permission is granted to
10
 * link this program with OpenSSL and to (re)distribute the binaries
11
 * produced as the result of such linking.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with this program; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
 */
22
#include <boost/algorithm/string/join.hpp>
23
#include <numeric>
24
#include <regex>
25
#include <utility>
26

27
#include "dnsdist-metrics.hh"
28
#include "dnsdist.hh"
29
#include "dnsdist-dynblocks.hh"
30
#include "dnsdist-web.hh"
31

32
namespace dnsdist::metrics
33
{
34

35
struct MutableCounter
36
{
37
  MutableCounter() = default;
17✔
38
  MutableCounter(const MutableCounter&) = delete;
39
  MutableCounter(MutableCounter&& rhs) noexcept :
40
    d_value(rhs.d_value.load())
11✔
41
  {
11✔
42
  }
11✔
43
  MutableCounter& operator=(const MutableCounter&) = delete;
44
  MutableCounter& operator=(MutableCounter&& rhs) noexcept
45
  {
×
46
    d_value = rhs.d_value.load();
×
47
    return *this;
×
48
  }
×
49
  ~MutableCounter() = default;
11✔
50

51
  mutable stat_t d_value{0};
52
};
53

54
struct MutableGauge
55
{
56
  MutableGauge() = default;
8✔
57
  MutableGauge(const MutableGauge&) = delete;
58
  MutableGauge(MutableGauge&& rhs) noexcept :
59
    d_value(rhs.d_value.load())
6✔
60
  {
6✔
61
  }
6✔
62
  MutableGauge& operator=(const MutableGauge&) = delete;
63
  MutableGauge& operator=(MutableGauge&& rhs) noexcept
64
  {
×
65
    d_value = rhs.d_value.load();
×
66
    return *this;
×
67
  }
×
68
  ~MutableGauge() = default;
6✔
69

70
  mutable pdns::stat_double_t d_value{0};
71
};
72

73
/* map of metric name -> map of labels -> metric value */
74
template <class MetricType>
75
using LabelsToMetricMap = std::map<std::string, MetricType>;
76
static SharedLockGuarded<std::map<std::string, LabelsToMetricMap<MutableCounter>, std::less<>>> s_customCounters;
77
static SharedLockGuarded<std::map<std::string, LabelsToMetricMap<MutableGauge>, std::less<>>> s_customGauges;
78

79
Stats::Stats() :
80
  entries{std::vector<EntryTriple>{
817✔
81
    {"responses", "", &responses},
817✔
82
    {"servfail-responses", "", &servfailResponses},
817✔
83
    {"queries", "", &queries},
817✔
84
    {"frontend-nxdomain", "", &frontendNXDomain},
817✔
85
    {"frontend-servfail", "", &frontendServFail},
817✔
86
    {"frontend-noerror", "", &frontendNoError},
817✔
87
    {"acl-drops", "", &aclDrops},
817✔
88
    {"rule-drop", "", &ruleDrop},
817✔
89
    {"rule-nxdomain", "", &ruleNXDomain},
817✔
90
    {"rule-refused", "", &ruleRefused},
817✔
91
    {"rule-servfail", "", &ruleServFail},
817✔
92
    {"rule-truncated", "", &ruleTruncated},
817✔
93
    {"self-answered", "", &selfAnswered},
817✔
94
    {"downstream-timeouts", "", &downstreamTimeouts},
817✔
95
    {"downstream-send-errors", "", &downstreamSendErrors},
817✔
96
    {"trunc-failures", "", &truncFail},
817✔
97
    {"no-policy", "", &noPolicy},
817✔
98
    {"latency0-1", "", &latency0_1},
817✔
99
    {"latency1-10", "", &latency1_10},
817✔
100
    {"latency10-50", "", &latency10_50},
817✔
101
    {"latency50-100", "", &latency50_100},
817✔
102
    {"latency100-1000", "", &latency100_1000},
817✔
103
    {"latency-slow", "", &latencySlow},
817✔
104
    {"latency-avg100", "", &latencyAvg100},
817✔
105
    {"latency-avg1000", "", &latencyAvg1000},
817✔
106
    {"latency-avg10000", "", &latencyAvg10000},
817✔
107
    {"latency-avg1000000", "", &latencyAvg1000000},
817✔
108
    {"latency-tcp-avg100", "", &latencyTCPAvg100},
817✔
109
    {"latency-tcp-avg1000", "", &latencyTCPAvg1000},
817✔
110
    {"latency-tcp-avg10000", "", &latencyTCPAvg10000},
817✔
111
    {"latency-tcp-avg1000000", "", &latencyTCPAvg1000000},
817✔
112
    {"latency-dot-avg100", "", &latencyDoTAvg100},
817✔
113
    {"latency-dot-avg1000", "", &latencyDoTAvg1000},
817✔
114
    {"latency-dot-avg10000", "", &latencyDoTAvg10000},
817✔
115
    {"latency-dot-avg1000000", "", &latencyDoTAvg1000000},
817✔
116
    {"latency-doh-avg100", "", &latencyDoHAvg100},
817✔
117
    {"latency-doh-avg1000", "", &latencyDoHAvg1000},
817✔
118
    {"latency-doh-avg10000", "", &latencyDoHAvg10000},
817✔
119
    {"latency-doh-avg1000000", "", &latencyDoHAvg1000000},
817✔
120
    {"latency-doq-avg100", "", &latencyDoQAvg100},
817✔
121
    {"latency-doq-avg1000", "", &latencyDoQAvg1000},
817✔
122
    {"latency-doq-avg10000", "", &latencyDoQAvg10000},
817✔
123
    {"latency-doq-avg1000000", "", &latencyDoQAvg1000000},
817✔
124
    {"latency-doh3-avg100", "", &latencyDoH3Avg100},
817✔
125
    {"latency-doh3-avg1000", "", &latencyDoH3Avg1000},
817✔
126
    {"latency-doh3-avg10000", "", &latencyDoH3Avg10000},
817✔
127
    {"latency-doh3-avg1000000", "", &latencyDoH3Avg1000000},
817✔
128
    {"uptime", "", uptimeOfProcess},
817✔
129
    {"real-memory-usage", "", getRealMemoryUsage},
817✔
130
    {"special-memory-usage", "", getSpecialMemoryUsage},
817✔
131
    {"udp-in-errors", "", [](const std::string&) { return udpErrorStats("udp-in-errors"); }},
817✔
132
    {"udp-noport-errors", "", [](const std::string&) { return udpErrorStats("udp-noport-errors"); }},
817✔
133
    {"udp-recvbuf-errors", "", [](const std::string&) { return udpErrorStats("udp-recvbuf-errors"); }},
817✔
134
    {"udp-sndbuf-errors", "", [](const std::string&) { return udpErrorStats("udp-sndbuf-errors"); }},
817✔
135
    {"udp-in-csum-errors", "", [](const std::string&) { return udpErrorStats("udp-in-csum-errors"); }},
817✔
136
    {"udp6-in-errors", "", [](const std::string&) { return udp6ErrorStats("udp6-in-errors"); }},
817✔
137
    {"udp6-recvbuf-errors", "", [](const std::string&) { return udp6ErrorStats("udp6-recvbuf-errors"); }},
817✔
138
    {"udp6-sndbuf-errors", "", [](const std::string&) { return udp6ErrorStats("udp6-sndbuf-errors"); }},
817✔
139
    {"udp6-noport-errors", "", [](const std::string&) { return udp6ErrorStats("udp6-noport-errors"); }},
817✔
140
    {"udp6-in-csum-errors", "", [](const std::string&) { return udp6ErrorStats("udp6-in-csum-errors"); }},
817✔
141
    {"tcp-listen-overflows", "", [](const std::string&) { return tcpErrorStats("ListenOverflows"); }},
817✔
142
    {"noncompliant-queries", "", &nonCompliantQueries},
817✔
143
    {"noncompliant-responses", "", &nonCompliantResponses},
817✔
144
    {"proxy-protocol-invalid", "", &proxyProtocolInvalid},
817✔
145
    {"rdqueries", "", &rdQueries},
817✔
146
    {"empty-queries", "", &emptyQueries},
817✔
147
    {"cache-hits", "", &cacheHits},
817✔
148
    {"cache-misses", "", &cacheMisses},
817✔
149
    {"cpu-iowait", "", getCPUIOWait},
817✔
150
    {"cpu-steal", "", getCPUSteal},
817✔
151
    {"cpu-sys-msec", "", getCPUTimeSystem},
817✔
152
    {"cpu-user-msec", "", getCPUTimeUser},
817✔
153
    {"fd-usage", "", getOpenFileDescriptors},
817✔
154
    {"dyn-blocked", "", &dynBlocked},
817✔
155
#ifndef DISABLE_DYNBLOCKS
816✔
156
    {"dyn-block-nmg-size", "", [](const std::string&) { return dnsdist::DynamicBlocks::getClientAddressDynamicRules().size(); }},
816✔
157
#endif /* DISABLE_DYNBLOCKS */
816✔
158
    {"security-status", "", &securityStatus},
817✔
159
    {"doh-query-pipe-full", "", &dohQueryPipeFull},
817✔
160
    {"doh-response-pipe-full", "", &dohResponsePipeFull},
817✔
161
    {"doq-response-pipe-full", "", &doqResponsePipeFull},
817✔
162
    {"doh3-response-pipe-full", "", &doh3ResponsePipeFull},
817✔
163
    {"outgoing-doh-query-pipe-full", "", &outgoingDoHQueryPipeFull},
817✔
164
    {"tcp-query-pipe-full", "", &tcpQueryPipeFull},
817✔
165
    {"tcp-cross-protocol-query-pipe-full", "", &tcpCrossProtocolQueryPipeFull},
817✔
166
    {"tcp-cross-protocol-response-pipe-full", "", &tcpCrossProtocolResponsePipeFull},
817✔
167
    // Latency histogram
168
    {"latency-sum", "", &latencySum},
817✔
169
    {"latency-count", "", &latencyCount},
817✔
170
  }}
817✔
171
{
817✔
172
}
817✔
173

174
struct Stats g_stats;
175

176
std::optional<std::string> declareCustomMetric(const std::string& name, const std::string& type, const std::string& description, std::optional<std::string> customName, bool withLabels)
177
{
24✔
178
  if (!std::regex_match(name, std::regex("^[a-z0-9-]+$"))) {
24!
179
    return std::string("Unable to declare metric '") + std::string(name) + std::string("': invalid name\n");
×
180
  }
×
181

182
  const std::string finalCustomName(customName ? *customName : "");
24✔
183
  if (type == "counter") {
24✔
184
    auto customCounters = s_customCounters.write_lock();
16✔
185
    auto itp = customCounters->emplace(name, std::map<std::string, MutableCounter>());
16✔
186
    if (itp.second) {
16✔
187
      if (!withLabels) {
15✔
188
        auto counter = itp.first->second.emplace("", MutableCounter());
11✔
189
        g_stats.entries.write_lock()->emplace_back(Stats::EntryTriple{name, "", &counter.first->second.d_value});
11✔
190
      }
11✔
191
      dnsdist::prometheus::PrometheusMetricDefinition def{name, type, description, finalCustomName};
15✔
192
      dnsdist::webserver::addMetricDefinition(def);
15✔
193
    }
15✔
194
  }
16✔
195
  else if (type == "gauge") {
8!
196
    auto customGauges = s_customGauges.write_lock();
8✔
197
    auto itp = customGauges->emplace(name, std::map<std::string, MutableGauge>());
8✔
198
    if (itp.second) {
8!
199
      if (!withLabels) {
8✔
200
        auto gauge = itp.first->second.emplace("", MutableGauge());
6✔
201
        g_stats.entries.write_lock()->emplace_back(Stats::EntryTriple{name, "", &gauge.first->second.d_value});
6✔
202
      }
6✔
203
      dnsdist::prometheus::PrometheusMetricDefinition def{name, type, description, finalCustomName};
8✔
204
      dnsdist::webserver::addMetricDefinition(def);
8✔
205
    }
8✔
206
  }
8✔
207
  else {
×
208
    return std::string("Unable to declare metric: unknown type '") + type + "'";
×
209
  }
×
210
  return std::nullopt;
24✔
211
}
24✔
212

213
static string prometheusLabelValueEscape(const string& value)
214
{
12✔
215
  string ret;
12✔
216

217
  for (char lblchar : value) {
36✔
218
    if (lblchar == '"' || lblchar == '\\') {
36!
219
      ret += '\\';
×
220
      ret += lblchar;
×
221
    }
×
222
    else if (lblchar == '\n') {
36!
223
      ret += '\\';
×
224
      ret += 'n';
×
225
    }
×
226
    else {
36✔
227
      ret += lblchar;
36✔
228
    }
36✔
229
  }
36✔
230
  return ret;
12✔
231
}
12✔
232

233
static std::string generateCombinationOfLabels(const Labels& optLabels)
234
{
24✔
235
  if (!optLabels || optLabels->get().empty()) {
24!
236
    return {};
16✔
237
  }
16✔
238
  const auto& labels = optLabels->get();
8✔
239
  auto ordered = std::map(labels.begin(), labels.end());
8✔
240
  return std::accumulate(ordered.begin(), ordered.end(), std::string(), [](const std::string& acc, const std::pair<std::string, std::string>& label) {
12✔
241
    return acc + (acc.empty() ? std::string() : ",") + label.first + "=" + "\"" + prometheusLabelValueEscape(label.second) + "\"";
12✔
242
  });
12✔
243
}
24✔
244

245
template <typename MetricType, typename MetricValueType>
246
static std::variant<MetricValueType, Error> updateMetric(const std::string_view& name, SharedLockGuarded<std::map<std::string, LabelsToMetricMap<MetricType>, std::less<>>>& metricMap, const Labels& labels, const std::function<void(const MetricType&)>& callback)
247
{
16✔
248
  auto combinationOfLabels = generateCombinationOfLabels(labels);
16✔
249
  /* be optimistic first, and see if the metric and labels exist */
250
  {
16✔
251
    auto readLockedMap = metricMap.read_lock();
16✔
252
    auto labelsMapIt = readLockedMap->find(name);
16✔
253
    if (labelsMapIt == readLockedMap->end()) {
16!
254
      if constexpr (std::is_same_v<MetricType, MutableCounter>) {
×
255
        return std::string("Unable to update custom metric '") + std::string(name) + "': no such counter";
×
256
      }
257
      else {
×
258
        return std::string("Unable to update custom metric '") + std::string(name) + "': no such gauge";
×
259
      }
×
260
    }
×
261

262
    auto& metricEntries = labelsMapIt->second;
×
263
    auto metricEntry = metricEntries.find(combinationOfLabels);
16✔
264
    if (metricEntry != metricEntries.end()) {
16✔
265
      callback(metricEntry->second);
8✔
266
      return metricEntry->second.d_value.load();
8✔
267
    }
8✔
268
  }
16✔
269

270
  /* OK, so we the metric exists (otherwise we would have returned an Error) but the label doesn't yet */
271
  {
8✔
272
    // too bad Coverity claims to understand C++ yet does not understand RAII
273
    // coverity[double_lock]
274
    auto writeLockedMap = metricMap.write_lock();
8✔
275
    auto labelsMapIt = writeLockedMap->find(name);
8✔
276
    if (labelsMapIt == writeLockedMap->end()) {
8!
277
      if constexpr (std::is_same_v<MetricType, MutableCounter>) {
×
278
        return std::string("Unable to update custom metric '") + std::string(name) + "': no such counter";
×
279
      }
280
      else {
×
281
        return std::string("Unable to update custom metric '") + std::string(name) + "': no such gauge";
×
282
      }
×
283
    }
×
284
    /* we need to check again, it might have been inserted in the meantime */
285
    auto& metricEntries = labelsMapIt->second;
×
286
    auto metricEntry = metricEntries.find(combinationOfLabels);
8✔
287
    if (metricEntry != metricEntries.end()) {
8!
288
      callback(metricEntry->second);
×
289
      return metricEntry->second.d_value.load();
×
290
    }
×
291
    metricEntry = metricEntries.emplace(std::piecewise_construct, std::forward_as_tuple(combinationOfLabels), std::forward_as_tuple()).first;
8✔
292
    g_stats.entries.write_lock()->emplace_back(Stats::EntryTriple{std::string(name), std::move(combinationOfLabels), &metricEntry->second.d_value});
8✔
293
    callback(metricEntry->second);
8✔
294
    return metricEntry->second.d_value.load();
8✔
295
  }
8✔
296
}
8✔
297

298
std::variant<uint64_t, Error> incrementCustomCounter(const std::string_view& name, uint64_t step, const Labels& labels)
299
{
12✔
300
  return updateMetric<MutableCounter, uint64_t>(name, s_customCounters, labels, [step](const MutableCounter& counter) -> void {
12✔
301
    counter.d_value += step;
12✔
302
  });
12✔
303
}
12✔
304

305
std::variant<uint64_t, Error> decrementCustomCounter(const std::string_view& name, uint64_t step, const Labels& labels)
306
{
×
307
  return updateMetric<MutableCounter, uint64_t>(name, s_customCounters, labels, [step](const MutableCounter& counter) {
×
308
    counter.d_value -= step;
×
309
  });
×
310
}
×
311

312
std::variant<double, Error> setCustomGauge(const std::string_view& name, const double value, const Labels& labels)
313
{
4✔
314
  return updateMetric<MutableGauge, double>(name, s_customGauges, labels, [value](const MutableGauge& gauge) {
4✔
315
    gauge.d_value = value;
4✔
316
  });
4✔
317
}
4✔
318

319
std::variant<double, Error> getCustomMetric(const std::string_view& name, const Labels& labels)
320
{
8✔
321
  {
8✔
322
    auto customCounters = s_customCounters.read_lock();
8✔
323
    auto counter = customCounters->find(name);
8✔
324
    if (counter != customCounters->end()) {
8✔
325
      auto combinationOfLabels = generateCombinationOfLabels(labels);
4✔
326
      auto metricEntry = counter->second.find(combinationOfLabels);
4✔
327
      if (metricEntry != counter->second.end()) {
4!
328
        return static_cast<double>(metricEntry->second.d_value.load());
4✔
329
      }
4✔
330
    }
4✔
331
  }
8✔
332
  {
4✔
333
    auto customGauges = s_customGauges.read_lock();
4✔
334
    auto gauge = customGauges->find(name);
4✔
335
    if (gauge != customGauges->end()) {
4!
336
      auto combinationOfLabels = generateCombinationOfLabels(labels);
4✔
337
      auto metricEntry = gauge->second.find(combinationOfLabels);
4✔
338
      if (metricEntry != gauge->second.end()) {
4!
339
        return metricEntry->second.d_value.load();
4✔
340
      }
4✔
341
    }
4✔
342
  }
4✔
343
  return std::string("Unable to get metric '") + std::string(name) + "': no such metric";
×
344
}
4✔
345
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc