mirror of
https://github.com/lukaszraczylo/traefik-llmgateway.git
synced 2026-10-17 05:37:43 +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:
@@ -1306,3 +1306,48 @@ func TestHandleTargetProxy_TargetHealth_A2A_UsesAgentKind(t *testing.T) {
|
||||
t.Errorf("mcp/alpha state = %q, want unknown (must not be conflated with the agent of the same name)", gw.targetHealth.stateOf(targetKindMCP, "alpha"))
|
||||
}
|
||||
}
|
||||
|
||||
// --- admin-redesign WP-A step 8: target x caller ---
|
||||
|
||||
// TestHandleTargetProxy_CountsTargetCaller_OnlyWithAdminStats proves
|
||||
// handleTargetProxy threads the authenticated caller's own name
|
||||
// (u.name) into countTargetRequestBy — a proxied MCP request must move
|
||||
// targetCallerScopeID("mcp", "alpha", "alice")'s own req counter when
|
||||
// admin stats are on, and must not when they are off.
|
||||
func TestHandleTargetProxy_CountsTargetCaller_OnlyWithAdminStats(t *testing.T) {
|
||||
for _, adminOn := range []bool{false, true} {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
|
||||
cfg := CreateConfig()
|
||||
cfg.Providers = map[string]*ProviderConfig{"openai": {Type: "openai", APIKey: "k"}}
|
||||
cfg.MCPServers = map[string]*TargetConfig{"alpha": {URL: srv.URL}}
|
||||
cfg.Groups = map[string]*GroupConfig{"default": {}}
|
||||
cfg.Users = &UsersConfig{Inline: []*UserConfig{{Name: "alice", Group: "default", APIKey: "sk-alice"}}}
|
||||
if adminOn {
|
||||
cfg.Admin = &AdminConfig{Enabled: true}
|
||||
}
|
||||
h, err := New(context.Background(), http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}), cfg, "llmgw")
|
||||
if err != nil {
|
||||
srv.Close()
|
||||
t.Fatalf("New: %v", err)
|
||||
}
|
||||
gw := h.(*Gateway)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/mcp/alpha/tools/list", nil)
|
||||
req.Header.Set("Authorization", "Bearer sk-alice")
|
||||
h.ServeHTTP(httptest.NewRecorder(), req)
|
||||
srv.Close()
|
||||
|
||||
id := targetCallerScopeID(targetKindMCP, "alpha", "alice")
|
||||
got, ok := gw.limiter.getCounter(kindTargetCaller, id, metricReq, windowDay, time.Now())
|
||||
want := int64(0)
|
||||
if adminOn {
|
||||
want = 1
|
||||
}
|
||||
if !ok || got != want {
|
||||
t.Errorf("adminOn=%v: tcaller req/day = %d (ok=%v), want %d", adminOn, got, ok, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user