mirror of
https://github.com/lukaszraczylo/traefik-llmgateway.git
synced 2026-10-18 05:47:38 +00:00
feat(admin): add fleet statistics and admin read endpoints
Counters (always on when admin is enabled): - last seen per user and group, written asynchronously after admission, throttled to one write per scope per minute per replica - hourly provider and model attempts, failures and timeouts - failovers per provider, rejections per hour, and unpriced (402) refusals - cache hits, misses and cost saved - request counts per target and caller Opt-in counters under admin.stats (both default off): - userModel: usage per user and model - latency: latency histograms per provider and model All counters ride the existing Redis batches. A test measures three round trips per request with defaults and with every statistic on. A cache hit takes two. A model request is no longer counted for failed failover candidates. New admin endpoints: - usage/series and usage/totals - performance, with percentiles from the histograms - catalog, with the price billing actually uses and its source - consumers, with key source, grants and model access - config, with secrets redacted The overview gains features and a pricing summary. Usage gains lastSeen. usage/models gains offset and more detail fields. Reads are chunked at 1600 keys per round trip and capped at 64000 keys per request. Claude-Session: https://claude.ai/code/session_01V5jYJNxpDFdJkyyExpADrv
This commit is contained in:
@@ -1083,3 +1083,66 @@ func TestSendStartupTelemetry_OnceAcrossConcurrentCalls(t *testing.T) {
|
||||
}
|
||||
wg.Wait() // -race proves the Once is the only synchronisation needed
|
||||
}
|
||||
|
||||
// --- admin-redesign WP-A: AdminConfig.Stats + newConfiguredLimiter wiring ---
|
||||
|
||||
// TestNewConfiguredLimiter_StatsFlags_DeriveFromAdminConfig proves
|
||||
// newConfiguredLimiter derives statsAdmin/statsUserModel/statsLatency
|
||||
// purely from Config.Admin — statsUserModel/statsLatency are ANDed with
|
||||
// statsAdmin at construction (limiter.statsUserModel's own doc comment,
|
||||
// limits.go), so a call site need only check the one field, never both.
|
||||
func TestNewConfiguredLimiter_StatsFlags_DeriveFromAdminConfig(t *testing.T) {
|
||||
cases := []struct {
|
||||
admin *AdminConfig
|
||||
name string
|
||||
wantStatsAdmin bool
|
||||
wantStatsUserModel bool
|
||||
wantStatsLatency bool
|
||||
}{
|
||||
{name: "nil Admin: every flag off", admin: nil},
|
||||
{name: "Admin disabled: every flag off", admin: &AdminConfig{Enabled: false}},
|
||||
{name: "Admin enabled, nil Stats: statsAdmin on, sub-flags off",
|
||||
admin: &AdminConfig{Enabled: true}, wantStatsAdmin: true},
|
||||
{name: "Admin enabled, Stats zero value: statsAdmin on, sub-flags off",
|
||||
admin: &AdminConfig{Enabled: true, Stats: &AdminStatsConfig{}}, wantStatsAdmin: true},
|
||||
{name: "Admin enabled, both sub-flags on",
|
||||
admin: &AdminConfig{Enabled: true, Stats: &AdminStatsConfig{UserModel: true, Latency: true}},
|
||||
wantStatsAdmin: true, wantStatsUserModel: true, wantStatsLatency: true},
|
||||
{name: "Admin disabled but Stats set: every flag off (statsAdmin gates the sub-flags too)",
|
||||
admin: &AdminConfig{Enabled: false, Stats: &AdminStatsConfig{UserModel: true, Latency: true}}},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
cfg := &Config{Admin: c.admin}
|
||||
l := newConfiguredLimiter(cfg, nil) // nil client: fallback-only path, still must derive the flags
|
||||
if l.statsAdmin != c.wantStatsAdmin {
|
||||
t.Errorf("statsAdmin = %v, want %v", l.statsAdmin, c.wantStatsAdmin)
|
||||
}
|
||||
if l.statsUserModel != c.wantStatsUserModel {
|
||||
t.Errorf("statsUserModel = %v, want %v", l.statsUserModel, c.wantStatsUserModel)
|
||||
}
|
||||
if l.statsLatency != c.wantStatsLatency {
|
||||
t.Errorf("statsLatency = %v, want %v", l.statsLatency, c.wantStatsLatency)
|
||||
}
|
||||
if l.lastSeen == nil {
|
||||
t.Error("lastSeen is nil; newLimiter must always construct one")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewLimiter_DirectConstruction_EveryStatsFlagDefaultsFalse proves a
|
||||
// limiter built directly via newLimiter (as most of this package's own
|
||||
// tests do, bypassing newConfiguredLimiter/Config entirely) defaults every
|
||||
// admin-stats flag to false — additive and default-preserving: existing
|
||||
// tests must observe byte-identical behavior to before this task unless
|
||||
// they opt in explicitly.
|
||||
func TestNewLimiter_DirectConstruction_EveryStatsFlagDefaultsFalse(t *testing.T) {
|
||||
l := newLimiter(nil, true)
|
||||
if l.statsAdmin || l.statsUserModel || l.statsLatency {
|
||||
t.Errorf("statsAdmin=%v statsUserModel=%v statsLatency=%v, want all false", l.statsAdmin, l.statsUserModel, l.statsLatency)
|
||||
}
|
||||
if l.lastSeen == nil {
|
||||
t.Error("lastSeen is nil; newLimiter must always construct one, regardless of stats config")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user