Skip to content

Commit 7dd039c

Browse files
authored
feat(resolver): add blocky_client_response_total metric (#2222)
1 parent dcdd952 commit 7dd039c

4 files changed

Lines changed: 54 additions & 9 deletions

File tree

docs/prometheus_grafana.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Following metrics will be exported:
1717
| blocky_query_total | Counter of total queries, partitioned by client and DNS request type (A, AAAA, PTR, etc) |
1818
| blocky_request_duration_seconds | Histogram of request duration, partitioned by response type (Blocked, cached, etc) |
1919
| blocky_response_total | Counter of responses, partitioned by response type (Blocked, cached, etc), DNS response code, and reason |
20+
| blocky_client_response_total | Counter of responses, partitioned by client and response type (Blocked, cached, etc) |
2021
| blocky_blocking_enabled | Boolean 1 if blocking is enabled, 0 otherwise |
2122
| blocky_cache_entries | Gauge of entries in cache |
2223
| blocky_cache_hits_total | Counter of the number of cache hits |
@@ -42,6 +43,15 @@ Following metrics will be exported:
4243
matched rule (e.g. `BLOCKED (ads: *.docler.com)`) is still available in the [query log](configuration.md#query-log).
4344
This avoids unbounded metric cardinality when large deny lists are used.
4445

46+
!!! note "`client` label cardinality"
47+
48+
The `client` label (used by `blocky_query_total` and `blocky_client_response_total`) is derived
49+
from a reverse DNS lookup and is **not** bounded by configuration — it grows with the number of
50+
distinct devices Blocky has seen. On networks with a stable, limited set of devices (a typical
51+
home LAN) this stays small, but on networks with high device turnover (e.g. public or guest Wi-Fi)
52+
the set of `client` label values can grow effectively unbounded over time. Consider this before
53+
scraping/retaining these metrics on such networks.
54+
4555
### Grafana dashboard
4656

4757
Example [Grafana](https://grafana.com/) dashboard

metrics/metrics_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ func AssertRegistryComplete(t *testing.T, reg *prometheus.Registry) {
5757
"blocky_query_total",
5858
"blocky_request_duration_seconds",
5959
"blocky_response_total",
60+
"blocky_client_response_total",
6061
// these should be default
6162
"blocky_error_total",
6263
"blocky_blocking_enabled",

resolver/metrics_resolver.go

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,11 @@ type MetricsResolver struct {
3131
NextResolver
3232
typed
3333

34-
totalQueries *prometheus.CounterVec
35-
totalResponse *prometheus.CounterVec
36-
totalErrors prometheus.Counter
37-
durationHistogram *prometheus.HistogramVec
34+
totalQueries *prometheus.CounterVec
35+
totalResponse *prometheus.CounterVec
36+
totalClientResponse *prometheus.CounterVec
37+
totalErrors prometheus.Counter
38+
durationHistogram *prometheus.HistogramVec
3839
}
3940

4041
// Resolve resolves the passed request
@@ -45,8 +46,10 @@ func (r *MetricsResolver) Resolve(ctx context.Context, request *model.Request) (
4546
return response, err
4647
}
4748

49+
clientLabel := strings.Join(request.ClientNames, ",")
50+
4851
r.totalQueries.With(prometheus.Labels{
49-
labelClient: strings.Join(request.ClientNames, ","),
52+
labelClient: clientLabel,
5053
labelType: dns.TypeToString[request.Req.Question[0].Qtype],
5154
}).Inc()
5255

@@ -59,6 +62,11 @@ func (r *MetricsResolver) Resolve(ctx context.Context, request *model.Request) (
5962

6063
r.durationHistogram.WithLabelValues(responseType).Observe(reqDuration.Seconds())
6164

65+
r.totalClientResponse.With(prometheus.Labels{
66+
labelClient: clientLabel,
67+
labelResponseType: responseType,
68+
}).Inc()
69+
6270
if err != nil {
6371
r.totalErrors.Inc()
6472
} else {
@@ -87,10 +95,11 @@ func NewMetricsResolver(cfg config.Metrics) *MetricsResolver {
8795
configurable: withConfig(&cfg),
8896
typed: withType("metrics"),
8997

90-
durationHistogram: durationHistogram(),
91-
totalQueries: totalQueriesMetric(),
92-
totalResponse: totalResponseMetric(),
93-
totalErrors: totalErrorMetric(),
98+
durationHistogram: durationHistogram(),
99+
totalQueries: totalQueriesMetric(),
100+
totalResponse: totalResponseMetric(),
101+
totalClientResponse: totalClientResponseMetric(),
102+
totalErrors: totalErrorMetric(),
94103
}
95104

96105
m.registerMetrics()
@@ -102,6 +111,7 @@ func (r *MetricsResolver) registerMetrics() {
102111
metrics.RegisterMetric(r.durationHistogram)
103112
metrics.RegisterMetric(r.totalQueries)
104113
metrics.RegisterMetric(r.totalResponse)
114+
metrics.RegisterMetric(r.totalClientResponse)
105115
metrics.RegisterMetric(r.totalErrors)
106116
}
107117

@@ -143,3 +153,12 @@ func totalResponseMetric() *prometheus.CounterVec {
143153
}, []string{labelReason, labelResponseCode, labelResponseType},
144154
)
145155
}
156+
157+
func totalClientResponseMetric() *prometheus.CounterVec {
158+
return prometheus.NewCounterVec(
159+
prometheus.CounterOpts{
160+
Name: "blocky_client_response_total",
161+
Help: "Number of total responses per client and response type",
162+
}, []string{labelClient, labelResponseType},
163+
)
164+
}

resolver/metrics_resolver_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,14 @@ var _ = Describe("MetricResolver", func() {
7474
Expect(err).Should(Succeed())
7575

7676
Expect(testutil.ToFloat64(cnt)).Should(BeNumerically("==", 1))
77+
78+
clientCnt, err := sut.totalClientResponse.GetMetricWith(prometheus.Labels{
79+
"client": "client",
80+
"response_type": "RESOLVED",
81+
})
82+
Expect(err).Should(Succeed())
83+
Expect(testutil.ToFloat64(clientCnt)).Should(BeNumerically("==", 1))
84+
7785
m.AssertExpectations(GinkgoT())
7886
})
7987
})
@@ -136,6 +144,13 @@ var _ = Describe("MetricResolver", func() {
136144
Expect(err).Should(HaveOccurred())
137145

138146
Expect(testutil.ToFloat64(sut.totalErrors)).Should(BeNumerically("==", 1))
147+
148+
clientCnt, err := sut.totalClientResponse.GetMetricWith(prometheus.Labels{
149+
"client": "client",
150+
"response_type": "err",
151+
})
152+
Expect(err).Should(Succeed())
153+
Expect(testutil.ToFloat64(clientCnt)).Should(BeNumerically("==", 1))
139154
})
140155
})
141156
})

0 commit comments

Comments
 (0)