File size: 4,610 Bytes
b034029
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package api

import (
	"net/http"
	"time"

	"cpa-usage-keeper/internal/redact"
	"cpa-usage-keeper/internal/service"
	"github.com/gin-gonic/gin"
)

type usageAnalysisResponse struct {
	APIs   []usageAnalysisAPIPayload   `json:"apis"`
	Models []usageAnalysisModelPayload `json:"models"`
}

type usageAnalysisAPIPayload struct {
	APIKey          string                      `json:"api_key"`
	DisplayName     string                      `json:"display_name"`
	TotalRequests   int64                       `json:"total_requests"`
	SuccessCount    int64                       `json:"success_count"`
	FailureCount    int64                       `json:"failure_count"`
	InputTokens     int64                       `json:"input_tokens"`
	OutputTokens    int64                       `json:"output_tokens"`
	ReasoningTokens int64                       `json:"reasoning_tokens"`
	CachedTokens    int64                       `json:"cached_tokens"`
	TotalTokens     int64                       `json:"total_tokens"`
	Models          []usageAnalysisModelPayload `json:"models"`
}

type usageAnalysisModelPayload struct {
	Model              string `json:"model"`
	TotalRequests      int64  `json:"total_requests"`
	SuccessCount       int64  `json:"success_count"`
	FailureCount       int64  `json:"failure_count"`
	InputTokens        int64  `json:"input_tokens"`
	OutputTokens       int64  `json:"output_tokens"`
	ReasoningTokens    int64  `json:"reasoning_tokens"`
	CachedTokens       int64  `json:"cached_tokens"`
	TotalTokens        int64  `json:"total_tokens"`
	TotalLatencyMS     int64  `json:"total_latency_ms"`
	LatencySampleCount int64  `json:"latency_sample_count"`
}

func registerUsageAnalysisRoute(router gin.IRoutes, usageProvider service.UsageProvider) {
	router.GET("/usage/analysis", func(c *gin.Context) {
		if usageProvider == nil {
			c.JSON(http.StatusOK, usageAnalysisResponse{APIs: []usageAnalysisAPIPayload{}, Models: []usageAnalysisModelPayload{}})
			return
		}

		filter, err := parseUsageFilterQuery(c.Request, time.Now().UTC())
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}

		analysis, err := usageProvider.GetUsageAnalysis(c.Request.Context(), filter)
		if err != nil {
			writeInternalError(c, "get usage analysis failed", err)
			return
		}

		c.JSON(http.StatusOK, buildUsageAnalysisPayload(analysis))
	})
}

func buildUsageAnalysisPayload(snapshot *service.UsageAnalysisSnapshot) usageAnalysisResponse {
	if snapshot == nil {
		return usageAnalysisResponse{APIs: []usageAnalysisAPIPayload{}, Models: []usageAnalysisModelPayload{}}
	}

	apis := make([]usageAnalysisAPIPayload, 0, len(snapshot.APIs))
	for _, api := range snapshot.APIs {
		models := make([]usageAnalysisModelPayload, 0, len(api.Models))
		for _, model := range api.Models {
			models = append(models, usageAnalysisModelPayload{
				Model:              model.Model,
				TotalRequests:      model.TotalRequests,
				SuccessCount:       model.SuccessCount,
				FailureCount:       model.FailureCount,
				InputTokens:        model.InputTokens,
				OutputTokens:       model.OutputTokens,
				ReasoningTokens:    model.ReasoningTokens,
				CachedTokens:       model.CachedTokens,
				TotalTokens:        model.TotalTokens,
				TotalLatencyMS:     model.TotalLatencyMS,
				LatencySampleCount: model.LatencySampleCount,
			})
		}
		apiKey := redact.APIAlias(api.APIKey)
		displayName := redact.APIKeyDisplayName(api.APIKey)
		apis = append(apis, usageAnalysisAPIPayload{
			APIKey:          apiKey,
			DisplayName:     displayName,
			TotalRequests:   api.TotalRequests,
			SuccessCount:    api.SuccessCount,
			FailureCount:    api.FailureCount,
			InputTokens:     api.InputTokens,
			OutputTokens:    api.OutputTokens,
			ReasoningTokens: api.ReasoningTokens,
			CachedTokens:    api.CachedTokens,
			TotalTokens:     api.TotalTokens,
			Models:          models,
		})
	}

	models := make([]usageAnalysisModelPayload, 0, len(snapshot.Models))
	for _, model := range snapshot.Models {
		models = append(models, usageAnalysisModelPayload{
			Model:              model.Model,
			TotalRequests:      model.TotalRequests,
			SuccessCount:       model.SuccessCount,
			FailureCount:       model.FailureCount,
			InputTokens:        model.InputTokens,
			OutputTokens:       model.OutputTokens,
			ReasoningTokens:    model.ReasoningTokens,
			CachedTokens:       model.CachedTokens,
			TotalTokens:        model.TotalTokens,
			TotalLatencyMS:     model.TotalLatencyMS,
			LatencySampleCount: model.LatencySampleCount,
		})
	}

	return usageAnalysisResponse{APIs: apis, Models: models}
}