ops_snapshot_v2_handler.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. package admin
  2. import (
  3. "encoding/json"
  4. "net/http"
  5. "strconv"
  6. "strings"
  7. "time"
  8. "github.com/Wei-Shaw/sub2api/internal/pkg/response"
  9. "github.com/Wei-Shaw/sub2api/internal/service"
  10. "github.com/gin-gonic/gin"
  11. "golang.org/x/sync/errgroup"
  12. )
  13. var opsDashboardSnapshotV2Cache = newSnapshotCache(30 * time.Second)
  14. type opsDashboardSnapshotV2Response struct {
  15. GeneratedAt string `json:"generated_at"`
  16. Overview *service.OpsDashboardOverview `json:"overview"`
  17. ThroughputTrend *service.OpsThroughputTrendResponse `json:"throughput_trend"`
  18. ErrorTrend *service.OpsErrorTrendResponse `json:"error_trend"`
  19. }
  20. type opsDashboardSnapshotV2CacheKey struct {
  21. StartTime string `json:"start_time"`
  22. EndTime string `json:"end_time"`
  23. Platform string `json:"platform"`
  24. GroupID *int64 `json:"group_id"`
  25. QueryMode service.OpsQueryMode `json:"mode"`
  26. BucketSecond int `json:"bucket_second"`
  27. }
  28. // GetDashboardSnapshotV2 returns ops dashboard core snapshot in one request.
  29. // GET /api/v1/admin/ops/dashboard/snapshot-v2
  30. func (h *OpsHandler) GetDashboardSnapshotV2(c *gin.Context) {
  31. if h.opsService == nil {
  32. response.Error(c, http.StatusServiceUnavailable, "Ops service not available")
  33. return
  34. }
  35. if err := h.opsService.RequireMonitoringEnabled(c.Request.Context()); err != nil {
  36. response.ErrorFrom(c, err)
  37. return
  38. }
  39. startTime, endTime, err := parseOpsTimeRange(c, "1h")
  40. if err != nil {
  41. response.BadRequest(c, err.Error())
  42. return
  43. }
  44. filter := &service.OpsDashboardFilter{
  45. StartTime: startTime,
  46. EndTime: endTime,
  47. Platform: strings.TrimSpace(c.Query("platform")),
  48. QueryMode: parseOpsQueryMode(c),
  49. }
  50. if v := strings.TrimSpace(c.Query("group_id")); v != "" {
  51. id, err := strconv.ParseInt(v, 10, 64)
  52. if err != nil || id <= 0 {
  53. response.BadRequest(c, "Invalid group_id")
  54. return
  55. }
  56. filter.GroupID = &id
  57. }
  58. bucketSeconds := pickThroughputBucketSeconds(endTime.Sub(startTime))
  59. keyRaw, _ := json.Marshal(opsDashboardSnapshotV2CacheKey{
  60. StartTime: startTime.UTC().Format(time.RFC3339),
  61. EndTime: endTime.UTC().Format(time.RFC3339),
  62. Platform: filter.Platform,
  63. GroupID: filter.GroupID,
  64. QueryMode: filter.QueryMode,
  65. BucketSecond: bucketSeconds,
  66. })
  67. cacheKey := string(keyRaw)
  68. if cached, ok := opsDashboardSnapshotV2Cache.Get(cacheKey); ok {
  69. if cached.ETag != "" {
  70. c.Header("ETag", cached.ETag)
  71. c.Header("Vary", "If-None-Match")
  72. if ifNoneMatchMatched(c.GetHeader("If-None-Match"), cached.ETag) {
  73. c.Status(http.StatusNotModified)
  74. return
  75. }
  76. }
  77. c.Header("X-Snapshot-Cache", "hit")
  78. response.Success(c, cached.Payload)
  79. return
  80. }
  81. var (
  82. overview *service.OpsDashboardOverview
  83. trend *service.OpsThroughputTrendResponse
  84. errTrend *service.OpsErrorTrendResponse
  85. )
  86. g, gctx := errgroup.WithContext(c.Request.Context())
  87. g.Go(func() error {
  88. f := *filter
  89. result, err := h.opsService.GetDashboardOverview(gctx, &f)
  90. if err != nil {
  91. return err
  92. }
  93. overview = result
  94. return nil
  95. })
  96. g.Go(func() error {
  97. f := *filter
  98. result, err := h.opsService.GetThroughputTrend(gctx, &f, bucketSeconds)
  99. if err != nil {
  100. return err
  101. }
  102. trend = result
  103. return nil
  104. })
  105. g.Go(func() error {
  106. f := *filter
  107. result, err := h.opsService.GetErrorTrend(gctx, &f, bucketSeconds)
  108. if err != nil {
  109. return err
  110. }
  111. errTrend = result
  112. return nil
  113. })
  114. if err := g.Wait(); err != nil {
  115. response.ErrorFrom(c, err)
  116. return
  117. }
  118. resp := &opsDashboardSnapshotV2Response{
  119. GeneratedAt: time.Now().UTC().Format(time.RFC3339),
  120. Overview: overview,
  121. ThroughputTrend: trend,
  122. ErrorTrend: errTrend,
  123. }
  124. cached := opsDashboardSnapshotV2Cache.Set(cacheKey, resp)
  125. if cached.ETag != "" {
  126. c.Header("ETag", cached.ETag)
  127. c.Header("Vary", "If-None-Match")
  128. }
  129. c.Header("X-Snapshot-Cache", "miss")
  130. response.Success(c, resp)
  131. }