ops_repo_realtime_traffic.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. package repository
  2. import (
  3. "context"
  4. "fmt"
  5. "strings"
  6. "time"
  7. "github.com/Wei-Shaw/sub2api/internal/service"
  8. )
  9. func (r *opsRepository) GetRealtimeTrafficSummary(ctx context.Context, filter *service.OpsDashboardFilter) (*service.OpsRealtimeTrafficSummary, error) {
  10. if r == nil || r.db == nil {
  11. return nil, fmt.Errorf("nil ops repository")
  12. }
  13. if filter == nil {
  14. return nil, fmt.Errorf("nil filter")
  15. }
  16. if filter.StartTime.IsZero() || filter.EndTime.IsZero() {
  17. return nil, fmt.Errorf("start_time/end_time required")
  18. }
  19. start := filter.StartTime.UTC()
  20. end := filter.EndTime.UTC()
  21. if start.After(end) {
  22. return nil, fmt.Errorf("start_time must be <= end_time")
  23. }
  24. window := end.Sub(start)
  25. if window <= 0 {
  26. return nil, fmt.Errorf("invalid time window")
  27. }
  28. if window > time.Hour {
  29. return nil, fmt.Errorf("window too large")
  30. }
  31. usageJoin, usageWhere, usageArgs, next := buildUsageWhere(filter, start, end, 1)
  32. errorWhere, errorArgs, _ := buildErrorWhere(filter, start, end, next)
  33. q := `
  34. WITH usage_buckets AS (
  35. SELECT
  36. date_trunc('minute', ul.created_at) AS bucket,
  37. COALESCE(COUNT(*), 0) AS success_count,
  38. COALESCE(SUM(input_tokens + output_tokens + cache_creation_tokens + cache_read_tokens), 0) AS token_sum
  39. FROM usage_logs ul
  40. ` + usageJoin + `
  41. ` + usageWhere + `
  42. GROUP BY 1
  43. ),
  44. error_buckets AS (
  45. SELECT
  46. date_trunc('minute', created_at) AS bucket,
  47. COALESCE(COUNT(*), 0) AS error_count
  48. FROM ops_error_logs
  49. ` + errorWhere + `
  50. AND COALESCE(status_code, 0) >= 400
  51. GROUP BY 1
  52. ),
  53. combined AS (
  54. SELECT
  55. COALESCE(u.bucket, e.bucket) AS bucket,
  56. COALESCE(u.success_count, 0) AS success_count,
  57. COALESCE(u.token_sum, 0) AS token_sum,
  58. COALESCE(e.error_count, 0) AS error_count,
  59. COALESCE(u.success_count, 0) + COALESCE(e.error_count, 0) AS request_total
  60. FROM usage_buckets u
  61. FULL OUTER JOIN error_buckets e ON u.bucket = e.bucket
  62. )
  63. SELECT
  64. COALESCE(SUM(success_count), 0) AS success_total,
  65. COALESCE(SUM(error_count), 0) AS error_total,
  66. COALESCE(SUM(token_sum), 0) AS token_total,
  67. COALESCE(MAX(request_total), 0) AS peak_requests_per_min,
  68. COALESCE(MAX(token_sum), 0) AS peak_tokens_per_min
  69. FROM combined`
  70. args := append(usageArgs, errorArgs...)
  71. var successCount int64
  72. var errorTotal int64
  73. var tokenConsumed int64
  74. var peakRequestsPerMin int64
  75. var peakTokensPerMin int64
  76. if err := r.db.QueryRowContext(ctx, q, args...).Scan(
  77. &successCount,
  78. &errorTotal,
  79. &tokenConsumed,
  80. &peakRequestsPerMin,
  81. &peakTokensPerMin,
  82. ); err != nil {
  83. return nil, err
  84. }
  85. windowSeconds := window.Seconds()
  86. if windowSeconds <= 0 {
  87. windowSeconds = 1
  88. }
  89. requestCountTotal := successCount + errorTotal
  90. qpsAvg := roundTo1DP(float64(requestCountTotal) / windowSeconds)
  91. tpsAvg := roundTo1DP(float64(tokenConsumed) / windowSeconds)
  92. // Keep "current" consistent with the dashboard overview semantics: last 1 minute.
  93. // This remains "within the selected window" since end=start+window.
  94. qpsCurrent, tpsCurrent, err := r.queryCurrentRates(ctx, filter, end)
  95. if err != nil {
  96. return nil, err
  97. }
  98. qpsPeak := roundTo1DP(float64(peakRequestsPerMin) / 60.0)
  99. tpsPeak := roundTo1DP(float64(peakTokensPerMin) / 60.0)
  100. return &service.OpsRealtimeTrafficSummary{
  101. StartTime: start,
  102. EndTime: end,
  103. Platform: strings.TrimSpace(filter.Platform),
  104. GroupID: filter.GroupID,
  105. QPS: service.OpsRateSummary{
  106. Current: qpsCurrent,
  107. Peak: qpsPeak,
  108. Avg: qpsAvg,
  109. },
  110. TPS: service.OpsRateSummary{
  111. Current: tpsCurrent,
  112. Peak: tpsPeak,
  113. Avg: tpsAvg,
  114. },
  115. }, nil
  116. }