mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-31 00:24:38 +00:00
Merge pull request #7001 from SavitarC/feat/ops-token-stats-all-platforms
feat(ops): 扩展 Token 请求统计至所有平台
This commit is contained in:
@@ -219,7 +219,8 @@ func (h *OpsHandler) GetDashboardErrorDistribution(c *gin.Context) {
|
||||
response.Success(c, data)
|
||||
}
|
||||
|
||||
// GetDashboardOpenAITokenStats returns OpenAI token efficiency stats grouped by model.
|
||||
// GetDashboardOpenAITokenStats returns token efficiency stats for all platforms, grouped by model.
|
||||
// The endpoint name is retained for compatibility; platform and group_id optionally narrow the scope.
|
||||
// GET /api/v1/admin/ops/dashboard/openai-token-stats
|
||||
func (h *OpsHandler) GetDashboardOpenAITokenStats(c *gin.Context) {
|
||||
if h.opsService == nil {
|
||||
|
||||
@@ -32,7 +32,6 @@ func (r *opsRepository) GetOpenAITokenStats(ctx context.Context, filter *service
|
||||
}
|
||||
|
||||
join, where, baseArgs, next := buildUsageWhere(dashboardFilter, dashboardFilter.StartTime, dashboardFilter.EndTime, 1)
|
||||
where += " AND ul.model LIKE 'gpt%'"
|
||||
|
||||
baseCTE := `
|
||||
WITH stats AS (
|
||||
|
||||
@@ -2,6 +2,8 @@ package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql/driver"
|
||||
"regexp"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -10,6 +12,81 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestOpsRepositoryGetOpenAITokenStats_PlatformScope(t *testing.T) {
|
||||
start := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
end := start.Add(time.Hour)
|
||||
groupID := int64(9)
|
||||
tests := []struct {
|
||||
name string
|
||||
platform string
|
||||
groupID *int64
|
||||
models []string
|
||||
}{
|
||||
{name: "all platforms", models: []string{"claude-sonnet-4", "deepseek-chat", "gemini-2.5-pro", "gpt-4o", "o3"}},
|
||||
{name: "anthropic group", platform: "anthropic", groupID: &groupID, models: []string{"claude-sonnet-4"}},
|
||||
{name: "gemini", platform: "gemini", models: []string{"gemini-2.5-pro"}},
|
||||
{name: "openai reasoning model", platform: "openai", models: []string{"o3"}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
db, mock := newSQLMock(t)
|
||||
repo := &opsRepository{db: db}
|
||||
filter := &service.OpsOpenAITokenStatsFilter{
|
||||
TimeRange: "1h",
|
||||
StartTime: start,
|
||||
EndTime: end,
|
||||
Platform: tt.platform,
|
||||
GroupID: tt.groupID,
|
||||
TopN: 20,
|
||||
}
|
||||
|
||||
// Match the complete filtering clause in both queries so an implicit
|
||||
// model restriction cannot silently narrow the selected platform scope.
|
||||
where := "WHERE ul.created_at >= $1 AND ul.created_at < $2"
|
||||
args := []driver.Value{start, end}
|
||||
if tt.groupID != nil {
|
||||
where += " AND ul.group_id = $3"
|
||||
args = append(args, *tt.groupID)
|
||||
}
|
||||
if tt.platform != "" {
|
||||
placeholder := "$3"
|
||||
if tt.groupID != nil {
|
||||
placeholder = "$4"
|
||||
}
|
||||
where += " AND COALESCE(NULLIF(g.platform,''), a.platform) = " + placeholder
|
||||
args = append(args, tt.platform)
|
||||
}
|
||||
queryPrefix := regexp.QuoteMeta(where) + `\s+GROUP BY ul.model\s+\)`
|
||||
mock.ExpectQuery(queryPrefix + `\s+SELECT COUNT\(\*\) FROM stats`).
|
||||
WithArgs(args...).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(len(tt.models)))
|
||||
|
||||
rows := sqlmock.NewRows([]string{
|
||||
"model", "request_count", "avg_tokens_per_sec", "avg_first_token_ms",
|
||||
"total_output_tokens", "avg_duration_ms", "requests_with_first_token",
|
||||
})
|
||||
for _, model := range tt.models {
|
||||
rows.AddRow(model, int64(2), 10.0, 100.0, int64(20), int64(1000), int64(2))
|
||||
}
|
||||
mock.ExpectQuery(queryPrefix + `\s+SELECT model,`).
|
||||
WithArgs(append(args, 20)...).
|
||||
WillReturnRows(rows)
|
||||
|
||||
resp, err := repo.GetOpenAITokenStats(context.Background(), filter)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.platform, resp.Platform)
|
||||
require.Equal(t, tt.groupID, resp.GroupID)
|
||||
require.Equal(t, int64(len(tt.models)), resp.Total)
|
||||
require.Len(t, resp.Items, len(tt.models))
|
||||
for i, model := range tt.models {
|
||||
require.Equal(t, model, resp.Items[i].Model)
|
||||
}
|
||||
require.NoError(t, mock.ExpectationsWereMet())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpsRepositoryGetOpenAITokenStats_PaginationMode(t *testing.T) {
|
||||
db, mock := newSQLMock(t)
|
||||
repo := &opsRepository{db: db}
|
||||
|
||||
Reference in New Issue
Block a user