mirror of
https://github.com/EZ-Api/ez-api.git
synced 2026-01-13 17:47:51 +00:00
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:
@@ -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
|
||||
}
|
||||
|
||||
@@ -50,3 +50,32 @@ func TestStatsService_MasterRealtimeStats(t *testing.T) {
|
||||
t.Fatalf("unexpected stats: %+v", stats)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatsService_MasterRealtimeSnapshot(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mr := miniredis.RunT(t)
|
||||
rdb := redis.NewClient(&redis.Options{Addr: mr.Addr()})
|
||||
svc := NewStatsService(rdb)
|
||||
|
||||
mr.Set("master:stats:12:requests", "9")
|
||||
mr.Set("master:stats:12:tokens", "18")
|
||||
mr.HSet("master:rate:12", "qps", "3")
|
||||
mr.HSet("master:rate:12", "limit", "10")
|
||||
mr.HSet("master:rate:12", "blocked", "1")
|
||||
mr.HSet("master:rate:12", "updated_at", "1700000100")
|
||||
|
||||
stats, err := svc.GetMasterRealtimeSnapshot(context.Background(), 12)
|
||||
if err != nil {
|
||||
t.Fatalf("GetMasterRealtimeSnapshot: %v", err)
|
||||
}
|
||||
if stats.Requests != 9 || stats.Tokens != 18 {
|
||||
t.Fatalf("unexpected totals: %+v", stats)
|
||||
}
|
||||
if stats.QPS != 3 || stats.QPSLimit != 10 || !stats.RateLimited {
|
||||
t.Fatalf("unexpected rate stats: %+v", stats)
|
||||
}
|
||||
if stats.UpdatedAt == nil || !stats.UpdatedAt.Equal(time.Unix(1700000100, 0).UTC()) {
|
||||
t.Fatalf("unexpected updated_at: %+v", stats.UpdatedAt)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user