chore: export AI Gateway metrics under new branding + keep old as alias (#26413)

> AI Tools where used in this request.

Registers `coder_aibridged_*` and `coder_aibridgeproxyd_*` metrics under
new prefixes: `coder_ai_gateway_*` and `coder_ai_gateway_proxy_*`.
Old prefix is still exported. Will be removed in later release.

Also updated the `metricsdocgen` static fixture. Added 4
previously-undocumented metrics `key_pool_state`,
`key_pool_state_transitions_total`, `key_pool_exhaustions_total`,
`key_pool_failover_attempts` added the `client` label to the existing
interception, prompt, and token counter samples.

Updated AI Gateway documentation.
This commit is contained in:
Paweł Banaszewski
2026-06-17 13:10:53 +02:00
committed by GitHub
parent 7d95153bf4
commit f1ce1013c4
12 changed files with 575 additions and 320 deletions
+1 -1
View File
@@ -46,7 +46,7 @@ func NewMetrics(reg prometheus.Registerer) *Metrics {
ProvidersLastReloadSuccessTimestampSeconds: factory.NewGauge(prometheus.GaugeOpts{
Name: "providers_last_reload_success_timestamp_seconds",
Help: "Unix timestamp of the last provider reload that successfully refreshed the pool. A gap against coder_aibridged_providers_last_reload_timestamp_seconds means the loop is firing but the refresh function is failing.",
Help: "Unix timestamp of the last provider reload that successfully refreshed the pool. A gap against the providers_last_reload_timestamp_seconds gauge means the loop is firing but the refresh function is failing.",
}),
}
}
+52
View File
@@ -0,0 +1,52 @@
package prometheusmetrics
import "github.com/prometheus/client_golang/prometheus"
// metricAliasRegisterer exposes each collector under multiple prefixes.
type metricAliasRegisterer struct {
registerers []prometheus.Registerer
}
// NewMetricAliasRegisterer exposes collectors under canonicalPrefix and each
// alias prefix. Every exported name reads from the same collector. Alias
// prefixes are typically deprecated names scheduled for removal; see each
// call site for the specific deprecation ticket.
func NewMetricAliasRegisterer(base prometheus.Registerer, canonicalPrefix string, aliasPrefixes ...string) prometheus.Registerer {
prefixes := append([]string{canonicalPrefix}, aliasPrefixes...)
registerers := make([]prometheus.Registerer, 0, len(prefixes))
for _, prefix := range prefixes {
registerers = append(registerers, prometheus.WrapRegistererWithPrefix(prefix, base))
}
return &metricAliasRegisterer{registerers: registerers}
}
// Register registers c under each prefix and rolls back on failure.
func (m *metricAliasRegisterer) Register(c prometheus.Collector) error {
for i, registerer := range m.registerers {
if err := registerer.Register(c); err != nil {
for _, registered := range m.registerers[:i] {
registered.Unregister(c)
}
return err
}
}
return nil
}
// MustRegister registers collectors and panics on the first failure.
func (m *metricAliasRegisterer) MustRegister(cs ...prometheus.Collector) {
for _, c := range cs {
if err := m.Register(c); err != nil {
panic(err)
}
}
}
// Unregister removes c from every prefix.
func (m *metricAliasRegisterer) Unregister(c prometheus.Collector) bool {
ok := true
for _, registerer := range m.registerers {
ok = registerer.Unregister(c) && ok
}
return ok
}
@@ -0,0 +1,143 @@
package prometheusmetrics_test
import (
"testing"
"github.com/prometheus/client_golang/prometheus"
io_prometheus_client "github.com/prometheus/client_model/go"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/coderd/prometheusmetrics"
)
func TestMetricAliasRegisterer(t *testing.T) {
t.Parallel()
t.Run("EmitsCanonicalAndAliases", func(t *testing.T) {
t.Parallel()
tests := []struct {
name string
suffix string
register func(prometheus.Registerer)
}{
{
name: "counter_vec",
suffix: "requests_total",
register: func(reg prometheus.Registerer) {
counter := prometheus.NewCounterVec(prometheus.CounterOpts{
Name: "requests_total",
Help: "Total requests.",
}, []string{"route"})
reg.MustRegister(counter)
counter.WithLabelValues("/api").Add(3)
},
},
{
name: "gauge",
suffix: "inflight_requests",
register: func(reg prometheus.Registerer) {
gauge := prometheus.NewGauge(prometheus.GaugeOpts{
Name: "inflight_requests",
Help: "Inflight requests.",
})
reg.MustRegister(gauge)
gauge.Set(7)
},
},
{
name: "histogram_vec",
suffix: "request_duration_seconds",
register: func(reg prometheus.Registerer) {
histogram := prometheus.NewHistogramVec(prometheus.HistogramOpts{
Name: "request_duration_seconds",
Help: "Request duration.",
Buckets: []float64{1, 5},
}, []string{"route"})
reg.MustRegister(histogram)
histogram.WithLabelValues("/api").Observe(3)
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
base := prometheus.NewRegistry()
prefixes := []string{"canonical_", "alias_one_", "alias_two_"}
reg := prometheusmetrics.NewMetricAliasRegisterer(base, prefixes[0], prefixes[1:]...)
tc.register(reg)
families, err := base.Gather()
require.NoError(t, err)
canonical := prefixes[0] + tc.suffix
for _, aliasPrefix := range prefixes[1:] {
assertParity(t, families, canonical, aliasPrefix+tc.suffix)
}
require.Len(t, families, len(prefixes))
})
}
})
t.Run("RegisterRollsBackPartialFailure", func(t *testing.T) {
t.Parallel()
base := prometheus.NewRegistry()
counter := prometheus.NewCounter(prometheus.CounterOpts{
Name: "requests_total",
Help: "Total requests.",
})
prometheus.WrapRegistererWithPrefix("alias_two_", base).MustRegister(counter)
reg := prometheusmetrics.NewMetricAliasRegisterer(base, "canonical_", "alias_one_", "alias_two_")
err := reg.Register(counter)
require.Error(t, err)
families, err := base.Gather()
require.NoError(t, err)
require.Len(t, families, 1)
require.Equal(t, "alias_two_requests_total", families[0].GetName())
})
t.Run("Unregister", func(t *testing.T) {
t.Parallel()
base := prometheus.NewRegistry()
reg := prometheusmetrics.NewMetricAliasRegisterer(base, "canonical_", "alias_one_", "alias_two_")
counter := prometheus.NewCounter(prometheus.CounterOpts{
Name: "requests_total",
Help: "Total requests.",
})
reg.MustRegister(counter)
require.True(t, reg.Unregister(counter))
families, err := base.Gather()
require.NoError(t, err)
require.Empty(t, families)
})
}
func assertParity(t *testing.T, families []*io_prometheus_client.MetricFamily, canonical, alias string) {
t.Helper()
canonicalFamily := findMetricFamily(t, families, canonical)
aliasFamily := findMetricFamily(t, families, alias)
require.Equal(t, canonicalFamily.GetType(), aliasFamily.GetType())
require.Equal(t, canonicalFamily.GetHelp(), aliasFamily.GetHelp())
require.Equal(t, canonicalFamily.GetMetric(), aliasFamily.GetMetric())
}
func findMetricFamily(t *testing.T, families []*io_prometheus_client.MetricFamily, name string) *io_prometheus_client.MetricFamily {
t.Helper()
for _, family := range families {
if family.GetName() == name {
return family
}
}
require.Failf(t, "metric family not found", "missing metric family %q", name)
return nil
}