feat(api): add realtime stats endpoints for masters

Introduce StatsService integration to admin and master handlers,
exposing realtime metrics (requests, tokens, QPS, rate limit status)
via new endpoints:
- GET /admin/masters/:id/realtime
- GET /v1/realtime

Also embed realtime stats in the existing GET /admin/masters/:id
response and change GlobalQPS default to 0 with validation to
reject negative values.
This commit is contained in:
zenfun
2025-12-22 12:02:27 +08:00
parent fa7f92c6e3
commit 2c5ccd56ee
12 changed files with 404 additions and 27 deletions

View File

@@ -20,6 +20,15 @@ type RealtimeStats struct {
LastAccessedAt *time.Time
}
type MasterRealtimeSnapshot struct {
Requests int64
Tokens int64
QPS int64
QPSLimit int64
RateLimited bool
UpdatedAt *time.Time
}
func NewStatsService(rdb *redis.Client) *StatsService {
return &StatsService{rdb: rdb}
}
@@ -77,3 +86,70 @@ func (s *StatsService) GetMasterRealtimeStats(ctx context.Context, masterID uint
}
return RealtimeStats{Requests: reqs, Tokens: tokens}, nil
}
func (s *StatsService) GetMasterRealtimeSnapshot(ctx context.Context, masterID uint) (MasterRealtimeSnapshot, error) {
if s == nil || s.rdb == nil {
return MasterRealtimeSnapshot{}, fmt.Errorf("redis client is required")
}
if masterID == 0 {
return MasterRealtimeSnapshot{}, fmt.Errorf("master id required")
}
if ctx == nil {
ctx = context.Background()
}
statsKey := fmt.Sprintf("master:stats:%d", masterID)
rateKey := fmt.Sprintf("master:rate:%d", masterID)
pipe := s.rdb.Pipeline()
reqCmd := pipe.Get(ctx, statsKey+":requests")
tokCmd := pipe.Get(ctx, statsKey+":tokens")
qpsCmd := pipe.HGet(ctx, rateKey, "qps")
limitCmd := pipe.HGet(ctx, rateKey, "limit")
blockedCmd := pipe.HGet(ctx, rateKey, "blocked")
updatedCmd := pipe.HGet(ctx, rateKey, "updated_at")
if _, err := pipe.Exec(ctx); err != nil && err != redis.Nil {
return MasterRealtimeSnapshot{}, fmt.Errorf("read realtime stats: %w", err)
}
requests := readCmdInt64(reqCmd)
tokens := readCmdInt64(tokCmd)
qps := readCmdInt64(qpsCmd)
limit := readCmdInt64(limitCmd)
blocked := readCmdInt64(blockedCmd) == 1
updatedAt := readCmdTime(updatedCmd)
if limit < 0 {
limit = 0
}
return MasterRealtimeSnapshot{
Requests: requests,
Tokens: tokens,
QPS: qps,
QPSLimit: limit,
RateLimited: blocked,
UpdatedAt: updatedAt,
}, nil
}
func readCmdInt64(cmd *redis.StringCmd) int64 {
if cmd == nil {
return 0
}
v, err := cmd.Int64()
if err != nil {
return 0
}
return v
}
func readCmdTime(cmd *redis.StringCmd) *time.Time {
if cmd == nil {
return nil
}
v, err := cmd.Int64()
if err != nil || v <= 0 {
return nil
}
tm := time.Unix(v, 0).UTC()
return &tm
}