| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129 |
- package repository
- import (
- "context"
- "fmt"
- "strings"
- "time"
- "github.com/Wei-Shaw/sub2api/internal/service"
- )
- func (r *opsRepository) GetRealtimeTrafficSummary(ctx context.Context, filter *service.OpsDashboardFilter) (*service.OpsRealtimeTrafficSummary, error) {
- if r == nil || r.db == nil {
- return nil, fmt.Errorf("nil ops repository")
- }
- if filter == nil {
- return nil, fmt.Errorf("nil filter")
- }
- if filter.StartTime.IsZero() || filter.EndTime.IsZero() {
- return nil, fmt.Errorf("start_time/end_time required")
- }
- start := filter.StartTime.UTC()
- end := filter.EndTime.UTC()
- if start.After(end) {
- return nil, fmt.Errorf("start_time must be <= end_time")
- }
- window := end.Sub(start)
- if window <= 0 {
- return nil, fmt.Errorf("invalid time window")
- }
- if window > time.Hour {
- return nil, fmt.Errorf("window too large")
- }
- usageJoin, usageWhere, usageArgs, next := buildUsageWhere(filter, start, end, 1)
- errorWhere, errorArgs, _ := buildErrorWhere(filter, start, end, next)
- q := `
- WITH usage_buckets AS (
- SELECT
- date_trunc('minute', ul.created_at) AS bucket,
- COALESCE(COUNT(*), 0) AS success_count,
- COALESCE(SUM(input_tokens + output_tokens + cache_creation_tokens + cache_read_tokens), 0) AS token_sum
- FROM usage_logs ul
- ` + usageJoin + `
- ` + usageWhere + `
- GROUP BY 1
- ),
- error_buckets AS (
- SELECT
- date_trunc('minute', created_at) AS bucket,
- COALESCE(COUNT(*), 0) AS error_count
- FROM ops_error_logs
- ` + errorWhere + `
- AND COALESCE(status_code, 0) >= 400
- GROUP BY 1
- ),
- combined AS (
- SELECT
- COALESCE(u.bucket, e.bucket) AS bucket,
- COALESCE(u.success_count, 0) AS success_count,
- COALESCE(u.token_sum, 0) AS token_sum,
- COALESCE(e.error_count, 0) AS error_count,
- COALESCE(u.success_count, 0) + COALESCE(e.error_count, 0) AS request_total
- FROM usage_buckets u
- FULL OUTER JOIN error_buckets e ON u.bucket = e.bucket
- )
- SELECT
- COALESCE(SUM(success_count), 0) AS success_total,
- COALESCE(SUM(error_count), 0) AS error_total,
- COALESCE(SUM(token_sum), 0) AS token_total,
- COALESCE(MAX(request_total), 0) AS peak_requests_per_min,
- COALESCE(MAX(token_sum), 0) AS peak_tokens_per_min
- FROM combined`
- args := append(usageArgs, errorArgs...)
- var successCount int64
- var errorTotal int64
- var tokenConsumed int64
- var peakRequestsPerMin int64
- var peakTokensPerMin int64
- if err := r.db.QueryRowContext(ctx, q, args...).Scan(
- &successCount,
- &errorTotal,
- &tokenConsumed,
- &peakRequestsPerMin,
- &peakTokensPerMin,
- ); err != nil {
- return nil, err
- }
- windowSeconds := window.Seconds()
- if windowSeconds <= 0 {
- windowSeconds = 1
- }
- requestCountTotal := successCount + errorTotal
- qpsAvg := roundTo1DP(float64(requestCountTotal) / windowSeconds)
- tpsAvg := roundTo1DP(float64(tokenConsumed) / windowSeconds)
- // Keep "current" consistent with the dashboard overview semantics: last 1 minute.
- // This remains "within the selected window" since end=start+window.
- qpsCurrent, tpsCurrent, err := r.queryCurrentRates(ctx, filter, end)
- if err != nil {
- return nil, err
- }
- qpsPeak := roundTo1DP(float64(peakRequestsPerMin) / 60.0)
- tpsPeak := roundTo1DP(float64(peakTokensPerMin) / 60.0)
- return &service.OpsRealtimeTrafficSummary{
- StartTime: start,
- EndTime: end,
- Platform: strings.TrimSpace(filter.Platform),
- GroupID: filter.GroupID,
- QPS: service.OpsRateSummary{
- Current: qpsCurrent,
- Peak: qpsPeak,
- Avg: qpsAvg,
- },
- TPS: service.OpsRateSummary{
- Current: tpsCurrent,
- Peak: tpsPeak,
- Avg: tpsAvg,
- },
- }, nil
- }
|