From 010dc1e2b75ee1be11b061b17a38f56670f4a3f9 Mon Sep 17 00:00:00 2001 From: rpb-ant Date: Wed, 8 Apr 2026 14:38:19 -0400 Subject: [PATCH] Allow selectively exporting all Go runtime metrics (#7990) Signed-off-by: Ryan Brewster --- plugin/metrics/README.md | 10 +++++++++- plugin/metrics/setup.go | 20 +++++++++++++++++++- plugin/metrics/setup_test.go | 9 +++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/plugin/metrics/README.md b/plugin/metrics/README.md index f15cef58a..dc57c3bac 100644 --- a/plugin/metrics/README.md +++ b/plugin/metrics/README.md @@ -55,7 +55,9 @@ This plugin can only be used once per Server Block. ## Syntax ~~~ -prometheus [ADDRESS] +prometheus [ADDRESS] { + runtime_metrics +} ~~~ For each zone that you want to see metrics for. @@ -63,6 +65,12 @@ For each zone that you want to see metrics for. It optionally takes a bind address to which the metrics are exported; the default listens on `localhost:9153`. The metrics path is fixed to `/metrics`. +* `runtime_metrics` exports the full Go [runtime/metrics](https://pkg.go.dev/runtime/metrics) + set — notably `go_cpu_classes_*` for CPU attribution (GC mark/assist/pause vs user code) + and `go_sched_latencies_seconds` for goroutine scheduling delay. Adds roughly 100 scalars + and 8 histograms. This is a process-wide latch: enabling it in any server block enables it + for all, and it stays enabled across reloads until restart. + ## Examples Use an alternative listening address: diff --git a/plugin/metrics/setup.go b/plugin/metrics/setup.go index a9d683061..a7b41720d 100644 --- a/plugin/metrics/setup.go +++ b/plugin/metrics/setup.go @@ -3,6 +3,7 @@ package metrics import ( "net" "runtime" + "sync" "github.com/coredns/caddy" "github.com/coredns/coredns/core/dnsserver" @@ -10,11 +11,19 @@ import ( "github.com/coredns/coredns/plugin" "github.com/coredns/coredns/plugin/metrics/vars" "github.com/coredns/coredns/plugin/pkg/uniq" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/collectors" ) var ( u = uniq.New() registry = newReg() + + // There is one Go runtime per process, so this is a latch: the first server + // block to enable runtime_metrics swaps the collector for everyone, and the + // swap persists across reloads until process restart. + runtimeMetricsOnce sync.Once ) func init() { plugin.Register("prometheus", setup) } @@ -96,9 +105,18 @@ func parse(c *caddy.Controller) (*Metrics, error) { return met, c.ArgErr() } - // Parse TLS block if present for c.NextBlock() { switch c.Val() { + case "runtime_metrics": + if len(c.RemainingArgs()) != 0 { + return nil, c.ArgErr() + } + runtimeMetricsOnce.Do(func() { + prometheus.Unregister(collectors.NewGoCollector()) + prometheus.MustRegister(collectors.NewGoCollector( + collectors.WithGoCollectorRuntimeMetrics(collectors.MetricsAll), + )) + }) case "tls": if met.tlsConfigPath != "" { return nil, c.Err("tls block already specified") diff --git a/plugin/metrics/setup_test.go b/plugin/metrics/setup_test.go index f4d097256..a93245a24 100644 --- a/plugin/metrics/setup_test.go +++ b/plugin/metrics/setup_test.go @@ -15,8 +15,17 @@ func TestPrometheusParse(t *testing.T) { // oks {`prometheus`, false, "localhost:9153"}, {`prometheus localhost:53`, false, "localhost:53"}, + {`prometheus { + runtime_metrics + }`, false, "localhost:9153"}, + {`prometheus localhost:53 { + runtime_metrics + }`, false, "localhost:53"}, // fails {`prometheus {}`, true, ""}, + {`prometheus { + runtime_metrics extra_arg + }`, true, ""}, {`prometheus /foo`, true, ""}, {`prometheus a b c`, true, ""}, }