mirror of
https://github.com/coredns/coredns.git
synced 2025-12-02 08:34:07 -05:00
fix(metrics): preserve request size from plugins (#7313)
The rewrite plugin modifies DNS messages, affecting the request size observed in the coredns_dns_request_size_bytes metric. This change captures the original request size before any plugins can modify it. It adds a functional options pattern to Report() to pass this information while maintaining API compatibility. Tests have been added to verify the fix prevents rewrite from affecting the request size metrics. Docs included. Signed-off-by: Ville Vesilehto <ville@vesilehto.fi>
This commit is contained in:
@@ -16,7 +16,7 @@ the following metrics are exported:
|
||||
* `coredns_panics_total{}` - total number of panics.
|
||||
* `coredns_dns_requests_total{server, zone, view, proto, family, type}` - total query count.
|
||||
* `coredns_dns_request_duration_seconds{server, zone, view, type}` - duration to process each query.
|
||||
* `coredns_dns_request_size_bytes{server, zone, view, proto}` - size of the request in bytes.
|
||||
* `coredns_dns_request_size_bytes{server, zone, view, proto}` - size of the request in bytes. Uses the original size before any plugin rewrites.
|
||||
* `coredns_dns_do_requests_total{server, view, zone}` - queries that have the DO bit set
|
||||
* `coredns_dns_response_size_bytes{server, zone, view, proto}` - response size in bytes.
|
||||
* `coredns_dns_responses_total{server, zone, view, rcode, plugin}` - response per zone, rcode and plugin.
|
||||
|
||||
@@ -16,6 +16,9 @@ import (
|
||||
func (m *Metrics) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
||||
state := request.Request{W: w, Req: r}
|
||||
|
||||
// Capture the original request size before any plugins modify it
|
||||
originalSize := r.Len()
|
||||
|
||||
qname := state.QName()
|
||||
zone := plugin.Zones(m.ZoneNames()).Matches(qname)
|
||||
if zone == "" {
|
||||
@@ -34,7 +37,9 @@ func (m *Metrics) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg
|
||||
rc = status
|
||||
}
|
||||
plugin := m.authoritativePlugin(rw.Caller)
|
||||
vars.Report(WithServer(ctx), state, zone, WithView(ctx), rcode.ToString(rc), plugin, rw.Len, rw.Start)
|
||||
// Pass the original request size to vars.Report
|
||||
vars.Report(WithServer(ctx), state, zone, WithView(ctx), rcode.ToString(rc), plugin,
|
||||
rw.Len, rw.Start, vars.WithOriginalReqSize(originalSize))
|
||||
|
||||
return status, err
|
||||
}
|
||||
|
||||
@@ -6,10 +6,34 @@ import (
|
||||
"github.com/coredns/coredns/request"
|
||||
)
|
||||
|
||||
// ReportOptions is a struct that contains available options for the Report function.
|
||||
type ReportOptions struct {
|
||||
OriginalReqSize int
|
||||
}
|
||||
|
||||
// ReportOption defines a function that modifies ReportOptions
|
||||
type ReportOption func(*ReportOptions)
|
||||
|
||||
// WithOriginalReqSize returns an option to set the original request size
|
||||
func WithOriginalReqSize(size int) ReportOption {
|
||||
return func(opts *ReportOptions) {
|
||||
opts.OriginalReqSize = size
|
||||
}
|
||||
}
|
||||
|
||||
// Report reports the metrics data associated with request. This function is exported because it is also
|
||||
// called from core/dnsserver to report requests hitting the server that should not be handled and are thus
|
||||
// not sent down the plugin chain.
|
||||
func Report(server string, req request.Request, zone, view, rcode, plugin string, size int, start time.Time) {
|
||||
func Report(server string, req request.Request, zone, view, rcode, plugin string,
|
||||
size int, start time.Time, opts ...ReportOption) {
|
||||
options := ReportOptions{
|
||||
OriginalReqSize: 0,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
// Proto and Family.
|
||||
net := req.Proto()
|
||||
fam := "1"
|
||||
@@ -27,7 +51,13 @@ func Report(server string, req request.Request, zone, view, rcode, plugin string
|
||||
RequestDuration.WithLabelValues(server, zone, view).Observe(time.Since(start).Seconds())
|
||||
|
||||
ResponseSize.WithLabelValues(server, zone, view, net).Observe(float64(size))
|
||||
RequestSize.WithLabelValues(server, zone, view, net).Observe(float64(req.Len()))
|
||||
|
||||
reqSize := req.Len()
|
||||
if options.OriginalReqSize > 0 {
|
||||
reqSize = options.OriginalReqSize
|
||||
}
|
||||
|
||||
RequestSize.WithLabelValues(server, zone, view, net).Observe(float64(reqSize))
|
||||
|
||||
ResponseRcode.WithLabelValues(server, zone, view, rcode, plugin).Inc()
|
||||
}
|
||||
|
||||
101
plugin/metrics/vars/report_test.go
Normal file
101
plugin/metrics/vars/report_test.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package vars
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/coredns/coredns/plugin/test"
|
||||
"github.com/coredns/coredns/request"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/prometheus/client_golang/prometheus/testutil"
|
||||
)
|
||||
|
||||
func TestReportWithOptions(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
question string
|
||||
qtype uint16
|
||||
edns0 bool
|
||||
do bool
|
||||
originalSize int
|
||||
useOriginal bool
|
||||
}{
|
||||
{
|
||||
name: "A record without DO bit",
|
||||
question: "example.org.",
|
||||
qtype: dns.TypeA,
|
||||
edns0: true,
|
||||
do: false,
|
||||
originalSize: 0,
|
||||
useOriginal: false,
|
||||
},
|
||||
{
|
||||
name: "A record with DO bit",
|
||||
question: "example.org.",
|
||||
qtype: dns.TypeA,
|
||||
edns0: true,
|
||||
do: true,
|
||||
originalSize: 0,
|
||||
useOriginal: false,
|
||||
},
|
||||
{
|
||||
name: "A record with original size",
|
||||
question: "example.org.",
|
||||
qtype: dns.TypeA,
|
||||
edns0: false,
|
||||
do: false,
|
||||
originalSize: 42,
|
||||
useOriginal: true,
|
||||
},
|
||||
{
|
||||
name: "A record bogus qtype",
|
||||
question: "example.org.",
|
||||
qtype: 0, // does not exist
|
||||
edns0: false,
|
||||
do: false,
|
||||
originalSize: 42,
|
||||
useOriginal: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion(tc.question, tc.qtype)
|
||||
if tc.edns0 {
|
||||
m.SetEdns0(4096, tc.do)
|
||||
}
|
||||
|
||||
w := &test.ResponseWriter{}
|
||||
state := request.Request{W: w, Req: m}
|
||||
|
||||
if state.Do() != tc.do {
|
||||
t.Errorf("DO bit detection failed, got %v, want %v", state.Do(), tc.do)
|
||||
}
|
||||
|
||||
qType := qTypeString(tc.qtype)
|
||||
expectedType := dns.Type(tc.qtype).String()
|
||||
if qType != expectedType && qType != "other" {
|
||||
t.Errorf("qTypeString(%d) = %s, want %s or 'other'", tc.qtype, qType, expectedType)
|
||||
}
|
||||
|
||||
var opts []ReportOption
|
||||
if tc.useOriginal {
|
||||
opts = append(opts, WithOriginalReqSize(tc.originalSize))
|
||||
}
|
||||
|
||||
net := state.Proto()
|
||||
fam := "1"
|
||||
|
||||
countBefore := testutil.ToFloat64(RequestCount.WithLabelValues("dns://:53", "example.org.", "", net, fam, qType))
|
||||
|
||||
Report("dns://:53", state, "example.org.", "", "NOERROR", "test", 100, time.Now(), opts...)
|
||||
|
||||
countAfter := testutil.ToFloat64(RequestCount.WithLabelValues("dns://:53", "example.org.", "", net, fam, qType))
|
||||
if countAfter <= countBefore {
|
||||
t.Errorf("RequestCount was not incremented. Before: %f, After: %f", countBefore, countAfter)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user