perf(metrics): implement plugin chain tracking (#7791)

Remove expensive runtime.Caller calls from metrics Recorder.WriteMsg
by tracking the responding plugin through the plugin chain instead.

- Add PluginTracker interface and pluginWriter wrapper in plugin.go
- Modify NextOrFailure to wrap ResponseWriter with plugin name
- Update metrics Recorder to implement PluginTracker
- Remove authoritativePlugin method using filepath inspection

Signed-off-by: Ville Vesilehto <ville@vesilehto.fi>
This commit is contained in:
Ville Vesilehto
2025-12-30 00:33:12 +02:00
committed by GitHub
parent b21c752d7f
commit be934b2b06
5 changed files with 263 additions and 25 deletions

View File

@@ -2,7 +2,6 @@ package metrics
import (
"context"
"path/filepath"
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/metrics/vars"
@@ -36,9 +35,9 @@ func (m *Metrics) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg
// see https://github.com/coredns/coredns/blob/master/core/dnsserver/server.go#L318
rc = status
}
plugin := m.authoritativePlugin(rw.Caller)
// Pass the original request size to vars.Report
vars.Report(WithServer(ctx), state, zone, WithView(ctx), rcode.ToString(rc), plugin,
// rw.Plugin is set automatically by the plugin chain via the PluginTracker interface
vars.Report(WithServer(ctx), state, zone, WithView(ctx), rcode.ToString(rc), rw.Plugin,
rw.Len, rw.Start, vars.WithOriginalReqSize(originalSize))
return status, err
@@ -46,17 +45,3 @@ func (m *Metrics) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg
// Name implements the Handler interface.
func (m *Metrics) Name() string { return "prometheus" }
// authoritativePlugin returns which of made the write, if none is found the empty string is returned.
func (m *Metrics) authoritativePlugin(caller [3]string) string {
// a b and c contain the full path of the caller, the plugin name 2nd last elements
// .../coredns/plugin/whoami/whoami.go --> whoami
// this is likely FS specific, so use filepath.
for _, c := range caller {
plug := filepath.Base(filepath.Dir(c))
if _, ok := m.plugins[plug]; ok {
return plug
}
}
return ""
}

View File

@@ -1,8 +1,6 @@
package metrics
import (
"runtime"
"github.com/coredns/coredns/plugin/pkg/dnstest"
"github.com/miekg/dns"
@@ -11,8 +9,9 @@ import (
// Recorder is a dnstest.Recorder specific to the metrics plugin.
type Recorder struct {
*dnstest.Recorder
// CallerN holds the string return value of the call to runtime.Caller(N+1)
Caller [3]string
// Plugin holds the name of the plugin that wrote the response.
// This is set automatically by the plugin chain via the PluginTracker interface.
Plugin string
}
// NewRecorder makes and returns a new Recorder.
@@ -21,8 +20,15 @@ func NewRecorder(w dns.ResponseWriter) *Recorder { return &Recorder{Recorder: dn
// WriteMsg records the status code and calls the
// underlying ResponseWriter's WriteMsg method.
func (r *Recorder) WriteMsg(res *dns.Msg) error {
_, r.Caller[0], _, _ = runtime.Caller(1)
_, r.Caller[1], _, _ = runtime.Caller(2)
_, r.Caller[2], _, _ = runtime.Caller(3)
return r.Recorder.WriteMsg(res)
}
// SetPlugin implements the plugin.PluginTracker interface.
func (r *Recorder) SetPlugin(name string) {
r.Plugin = name
}
// GetPlugin implements the plugin.PluginTracker interface.
func (r *Recorder) GetPlugin() string {
return r.Plugin
}

View File

@@ -23,6 +23,34 @@ func (r *inmemoryWriter) Write(buf []byte) (int, error) {
return r.ResponseWriter.Write(buf)
}
func TestRecorder_PluginTracker(t *testing.T) {
tw := inmemoryWriter{ResponseWriter: test.ResponseWriter{}}
rec := NewRecorder(&tw)
// Initially Plugin should be empty
if rec.Plugin != "" {
t.Errorf("Expected empty Plugin, got %q", rec.Plugin)
}
if rec.GetPlugin() != "" {
t.Errorf("Expected GetPlugin() to return empty string, got %q", rec.GetPlugin())
}
// SetPlugin should set the plugin name
rec.SetPlugin("whoami")
if rec.Plugin != "whoami" {
t.Errorf("Expected Plugin to be 'whoami', got %q", rec.Plugin)
}
if rec.GetPlugin() != "whoami" {
t.Errorf("Expected GetPlugin() to return 'whoami', got %q", rec.GetPlugin())
}
// SetPlugin should overwrite previous value
rec.SetPlugin("cache")
if rec.Plugin != "cache" {
t.Errorf("Expected Plugin to be 'cache', got %q", rec.Plugin)
}
}
func TestRecorder_WriteMsg(t *testing.T) {
successResp := dns.Msg{}
successResp.Answer = []dns.RR{