feat(ratelimit): track per-window token usage and utilization

Poll /api/oauth/usage every 5 min and extract utilization from
/v1/messages response headers for real-time updates. Track proxy
tokens in/out per rate limit window (5h/7d), resetting on window
change. Expose as OTel observable gauges for Grafana dashboards.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alexander
2026-04-14 12:51:31 +02:00
parent 76aeeb6be1
commit fac9578975
7 changed files with 364 additions and 13 deletions
+15 -2
View File
@@ -14,6 +14,7 @@ import (
"github.com/fujin/anthropic-proxy/internal/config"
"github.com/fujin/anthropic-proxy/internal/logging"
"github.com/fujin/anthropic-proxy/internal/proxy"
"github.com/fujin/anthropic-proxy/internal/ratelimit"
"github.com/fujin/anthropic-proxy/internal/server"
"github.com/fujin/anthropic-proxy/internal/telemetry"
"github.com/rs/zerolog/log"
@@ -25,8 +26,17 @@ func run() error {
return fmt.Errorf("load config: %w", err)
}
// Create usage tracker (started later once credential is loaded)
var credForTracker *auth.Credential
tracker := ratelimit.NewTracker(func() string {
if credForTracker == nil {
return ""
}
return credForTracker.Token()
})
// Initialize telemetry (metrics always active; OTLP export when endpoint set)
telemetryShutdown, logBridge, err := telemetry.Setup(context.Background(), cfg.Telemetry)
telemetryShutdown, logBridge, err := telemetry.Setup(context.Background(), cfg.Telemetry, tracker)
if err != nil {
return fmt.Errorf("telemetry setup: %w", err)
}
@@ -85,6 +95,8 @@ func run() error {
log.Info().Str("credential", cred.Email).Msg("credential loaded")
credForTracker = cred
pool := auth.NewPool([]*auth.Credential{cred})
ctx, cancel := context.WithCancel(context.Background())
@@ -92,6 +104,7 @@ func run() error {
pool.RefreshExpiring(context.Background())
auth.StartBackgroundRefresh(ctx, pool)
tracker.Start(ctx)
var profile *proxy.SniffedProfile
if cfg.ClaudeBinary != "" {
@@ -103,7 +116,7 @@ func run() error {
}
log.Info().Int("port", cfg.Port).Msg("starting server")
srv := server.New(cfg, pool, profile)
srv := server.New(cfg, pool, profile, tracker)
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)