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

@@ -17,14 +17,15 @@ type AdminHandler struct {
logDB *gorm.DB
masterService *service.MasterService
syncService *service.SyncService
statsService *service.StatsService
logPartitioner *service.LogPartitioner
}
func NewAdminHandler(db *gorm.DB, logDB *gorm.DB, masterService *service.MasterService, syncService *service.SyncService, partitioner *service.LogPartitioner) *AdminHandler {
func NewAdminHandler(db *gorm.DB, logDB *gorm.DB, masterService *service.MasterService, syncService *service.SyncService, statsService *service.StatsService, partitioner *service.LogPartitioner) *AdminHandler {
if logDB == nil {
logDB = db
}
return &AdminHandler{db: db, logDB: logDB, masterService: masterService, syncService: syncService, logPartitioner: partitioner}
return &AdminHandler{db: db, logDB: logDB, masterService: masterService, syncService: syncService, statsService: statsService, logPartitioner: partitioner}
}
func (h *AdminHandler) logDBConn() *gorm.DB {
@@ -68,8 +69,9 @@ func (h *AdminHandler) CreateMaster(c *gin.Context) {
if req.MaxChildKeys == 0 {
req.MaxChildKeys = 5
}
if req.GlobalQPS == 0 {
req.GlobalQPS = 3
if req.GlobalQPS < 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "global_qps must be >= 0"})
return
}
master, rawMasterKey, err := h.masterService.CreateMaster(req.Name, req.Group, req.MaxChildKeys, req.GlobalQPS)
@@ -94,17 +96,18 @@ func (h *AdminHandler) CreateMaster(c *gin.Context) {
}
type MasterView struct {
ID uint `json:"id"`
Name string `json:"name"`
Group string `json:"group"`
DefaultNamespace string `json:"default_namespace"`
Namespaces string `json:"namespaces"`
Epoch int64 `json:"epoch"`
Status string `json:"status"`
MaxChildKeys int `json:"max_child_keys"`
GlobalQPS int `json:"global_qps"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
ID uint `json:"id"`
Name string `json:"name"`
Group string `json:"group"`
DefaultNamespace string `json:"default_namespace"`
Namespaces string `json:"namespaces"`
Epoch int64 `json:"epoch"`
Status string `json:"status"`
MaxChildKeys int `json:"max_child_keys"`
GlobalQPS int `json:"global_qps"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
Realtime *MasterRealtimeView `json:"realtime,omitempty"`
}
func toMasterView(m model.Master) MasterView {
@@ -176,7 +179,16 @@ func (h *AdminHandler) GetMaster(c *gin.Context) {
c.JSON(http.StatusNotFound, gin.H{"error": "master not found"})
return
}
c.JSON(http.StatusOK, toMasterView(m))
view := toMasterView(m)
if h.statsService != nil {
if stats, err := h.statsService.GetMasterRealtimeSnapshot(c.Request.Context(), m.ID); err == nil {
if stats.QPSLimit == 0 && m.GlobalQPS > 0 {
stats.QPSLimit = int64(m.GlobalQPS)
}
view.Realtime = toMasterRealtimeView(stats)
}
}
c.JSON(http.StatusOK, view)
}
type UpdateMasterRequest struct {
@@ -233,7 +245,11 @@ func (h *AdminHandler) UpdateMaster(c *gin.Context) {
if req.MaxChildKeys != nil && *req.MaxChildKeys > 0 {
update["max_child_keys"] = *req.MaxChildKeys
}
if req.GlobalQPS != nil && *req.GlobalQPS > 0 {
if req.GlobalQPS != nil {
if *req.GlobalQPS < 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "global_qps must be >= 0"})
return
}
update["global_qps"] = *req.GlobalQPS
}
if len(update) == 0 && !req.PropagateToKeys {