146 lines
3.8 KiB
Go
146 lines
3.8 KiB
Go
package admin
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
|
"github.com/gin-gonic/gin"
|
|
"golang.org/x/sync/errgroup"
|
|
)
|
|
|
|
var opsDashboardSnapshotV2Cache = newSnapshotCache(30 * time.Second)
|
|
|
|
type opsDashboardSnapshotV2Response struct {
|
|
GeneratedAt string `json:"generated_at"`
|
|
|
|
Overview *service.OpsDashboardOverview `json:"overview"`
|
|
ThroughputTrend *service.OpsThroughputTrendResponse `json:"throughput_trend"`
|
|
ErrorTrend *service.OpsErrorTrendResponse `json:"error_trend"`
|
|
}
|
|
|
|
type opsDashboardSnapshotV2CacheKey struct {
|
|
StartTime string `json:"start_time"`
|
|
EndTime string `json:"end_time"`
|
|
Platform string `json:"platform"`
|
|
GroupID *int64 `json:"group_id"`
|
|
QueryMode service.OpsQueryMode `json:"mode"`
|
|
BucketSecond int `json:"bucket_second"`
|
|
}
|
|
|
|
// GetDashboardSnapshotV2 returns ops dashboard core snapshot in one request.
|
|
// GET /api/v1/admin/ops/dashboard/snapshot-v2
|
|
func (h *OpsHandler) GetDashboardSnapshotV2(c *gin.Context) {
|
|
if h.opsService == nil {
|
|
response.Error(c, http.StatusServiceUnavailable, "Ops service not available")
|
|
return
|
|
}
|
|
if err := h.opsService.RequireMonitoringEnabled(c.Request.Context()); err != nil {
|
|
response.ErrorFrom(c, err)
|
|
return
|
|
}
|
|
|
|
startTime, endTime, err := parseOpsTimeRange(c, "1h")
|
|
if err != nil {
|
|
response.BadRequest(c, err.Error())
|
|
return
|
|
}
|
|
|
|
filter := &service.OpsDashboardFilter{
|
|
StartTime: startTime,
|
|
EndTime: endTime,
|
|
Platform: strings.TrimSpace(c.Query("platform")),
|
|
QueryMode: parseOpsQueryMode(c),
|
|
}
|
|
if v := strings.TrimSpace(c.Query("group_id")); v != "" {
|
|
id, err := strconv.ParseInt(v, 10, 64)
|
|
if err != nil || id <= 0 {
|
|
response.BadRequest(c, "Invalid group_id")
|
|
return
|
|
}
|
|
filter.GroupID = &id
|
|
}
|
|
bucketSeconds := pickThroughputBucketSeconds(endTime.Sub(startTime))
|
|
|
|
keyRaw, _ := json.Marshal(opsDashboardSnapshotV2CacheKey{
|
|
StartTime: startTime.UTC().Format(time.RFC3339),
|
|
EndTime: endTime.UTC().Format(time.RFC3339),
|
|
Platform: filter.Platform,
|
|
GroupID: filter.GroupID,
|
|
QueryMode: filter.QueryMode,
|
|
BucketSecond: bucketSeconds,
|
|
})
|
|
cacheKey := string(keyRaw)
|
|
|
|
if cached, ok := opsDashboardSnapshotV2Cache.Get(cacheKey); ok {
|
|
if cached.ETag != "" {
|
|
c.Header("ETag", cached.ETag)
|
|
c.Header("Vary", "If-None-Match")
|
|
if ifNoneMatchMatched(c.GetHeader("If-None-Match"), cached.ETag) {
|
|
c.Status(http.StatusNotModified)
|
|
return
|
|
}
|
|
}
|
|
c.Header("X-Snapshot-Cache", "hit")
|
|
response.Success(c, cached.Payload)
|
|
return
|
|
}
|
|
|
|
var (
|
|
overview *service.OpsDashboardOverview
|
|
trend *service.OpsThroughputTrendResponse
|
|
errTrend *service.OpsErrorTrendResponse
|
|
)
|
|
g, gctx := errgroup.WithContext(c.Request.Context())
|
|
g.Go(func() error {
|
|
f := *filter
|
|
result, err := h.opsService.GetDashboardOverview(gctx, &f)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
overview = result
|
|
return nil
|
|
})
|
|
g.Go(func() error {
|
|
f := *filter
|
|
result, err := h.opsService.GetThroughputTrend(gctx, &f, bucketSeconds)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
trend = result
|
|
return nil
|
|
})
|
|
g.Go(func() error {
|
|
f := *filter
|
|
result, err := h.opsService.GetErrorTrend(gctx, &f, bucketSeconds)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
errTrend = result
|
|
return nil
|
|
})
|
|
if err := g.Wait(); err != nil {
|
|
response.ErrorFrom(c, err)
|
|
return
|
|
}
|
|
|
|
resp := &opsDashboardSnapshotV2Response{
|
|
GeneratedAt: time.Now().UTC().Format(time.RFC3339),
|
|
Overview: overview,
|
|
ThroughputTrend: trend,
|
|
ErrorTrend: errTrend,
|
|
}
|
|
|
|
cached := opsDashboardSnapshotV2Cache.Set(cacheKey, resp)
|
|
if cached.ETag != "" {
|
|
c.Header("ETag", cached.ETag)
|
|
c.Header("Vary", "If-None-Match")
|
|
}
|
|
c.Header("X-Snapshot-Cache", "miss")
|
|
response.Success(c, resp)
|
|
}
|