Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions controller/perf_metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package controller
import (
"net/http"
"strconv"
"time"

perfmetrics "github.com/QuantumNous/new-api/pkg/perf_metrics"
"github.com/QuantumNous/new-api/setting/ratio_setting"
Expand Down Expand Up @@ -73,6 +74,38 @@ func GetPerfMetrics(c *gin.Context) {
})
}

func GetAdminModelPerfMetrics(c *gin.Context) {
const maxRangeSeconds = int64(30 * 24 * 60 * 60)
rawStart := c.Query("start_timestamp")
rawEnd := c.Query("end_timestamp")
if rawStart == "" || rawEnd == "" {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "start_timestamp and end_timestamp are required"})
return
}

startTs, startErr := strconv.ParseInt(rawStart, 10, 64)
endTs, endErr := strconv.ParseInt(rawEnd, 10, 64)
if startErr != nil || endErr != nil || startTs <= 0 || endTs <= startTs {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "invalid performance metric time range"})
return
}
if endTs > time.Now().Unix() {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "end_timestamp cannot be in the future"})
return
}
if endTs-startTs > maxRangeSeconds {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "performance metric time range cannot exceed 30 days"})
return
}

result, err := perfmetrics.QueryAdmin(startTs, endTs)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"success": true, "data": result})
}

func filterActiveGroups(groups []perfmetrics.GroupResult) []perfmetrics.GroupResult {
activeRatios := ratio_setting.GetGroupRatioCopy()
return lo.Filter(groups, func(g perfmetrics.GroupResult, _ int) bool {
Expand Down
42 changes: 42 additions & 0 deletions controller/perf_metrics_admin_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package controller

import (
"net/http"
"net/http/httptest"
"strconv"
"testing"
"time"

"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestGetAdminModelPerfMetricsRejectsInvalidRanges(t *testing.T) {
gin.SetMode(gin.TestMode)
now := time.Now().Unix()
tests := []struct {
name string
query string
}{
{name: "missing end", query: "?start_timestamp=100"},
{name: "non numeric", query: "?start_timestamp=bad&end_timestamp=200"},
{name: "reversed", query: "?start_timestamp=200&end_timestamp=100"},
{name: "future end", query: "?start_timestamp=100&end_timestamp=" + strconv.FormatInt(now+60, 10)},
{name: "over thirty days", query: "?start_timestamp=1&end_timestamp=2592002"},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
request, err := http.NewRequest(http.MethodGet, "/api/perf-metrics/admin/models"+tt.query, nil)
require.NoError(t, err)
ctx.Request = request

GetAdminModelPerfMetrics(ctx)

assert.Equal(t, http.StatusBadRequest, recorder.Code)
})
}
}
9 changes: 7 additions & 2 deletions model/ability.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,14 @@ func GetEnabledModels() []string {
return models
}

func GetAllEnableAbilities() []Ability {
func ListEnabledAbilities() ([]Ability, error) {
var abilities []Ability
DB.Find(&abilities, "enabled = ?", true)
err := DB.Where("enabled = ?", true).Find(&abilities).Error
return abilities, err
}

func GetAllEnableAbilities() []Ability {
abilities, _ := ListEnabledAbilities()
return abilities
}

Expand Down
49 changes: 49 additions & 0 deletions model/perf_metric.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package model

import (
"database/sql"
"time"

"gorm.io/gorm"
Expand Down Expand Up @@ -115,6 +116,54 @@ func GetPerfMetricsSummaryBucketsAll(startTs int64, endTs int64, groups []string
return summaries, err
}

type PerfMetricGroupSummary struct {
ModelName string `json:"model_name"`
Group string `json:"group"`
RequestCount int64 `json:"request_count"`
SuccessCount int64 `json:"success_count"`
TotalLatencyMs int64 `json:"total_latency_ms"`
TtftSumMs int64 `json:"ttft_sum_ms"`
TtftCount int64 `json:"ttft_count"`
OutputTokens int64 `json:"output_tokens"`
GenerationMs int64 `json:"generation_ms"`
}

func GetPerfMetricGroupSummaries(startTs int64, endTs int64) ([]PerfMetricGroupSummary, error) {
summaries := make([]PerfMetricGroupSummary, 0)
if endTs <= startTs {
return summaries, nil
}
err := DB.Model(&PerfMetric{}).
Select("model_name, "+commonGroupCol+", SUM(request_count) as request_count, SUM(success_count) as success_count, SUM(total_latency_ms) as total_latency_ms, SUM(ttft_sum_ms) as ttft_sum_ms, SUM(ttft_count) as ttft_count, SUM(output_tokens) as output_tokens, SUM(generation_ms) as generation_ms").
Where("bucket_ts >= ? AND bucket_ts < ?", startTs, endTs).
Group("model_name, " + commonGroupCol).
Having("SUM(request_count) > 0").
Find(&summaries).Error
return summaries, err
}

func GetPerfMetricAvailableRange() (*int64, *int64, error) {
var bounds struct {
Oldest sql.NullInt64
Newest sql.NullInt64
}
err := DB.Model(&PerfMetric{}).
Select("MIN(bucket_ts) as oldest, MAX(bucket_ts) as newest").
Scan(&bounds).Error
if err != nil {
return nil, nil, err
}
var oldest *int64
var newest *int64
if bounds.Oldest.Valid {
oldest = &bounds.Oldest.Int64
}
if bounds.Newest.Valid {
newest = &bounds.Newest.Int64
}
return oldest, newest, nil
}

func DeletePerfMetricsBefore(cutoffTs int64) error {
if cutoffTs <= 0 {
return nil
Expand Down
34 changes: 34 additions & 0 deletions model/perf_metric_admin_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package model

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestGetPerfMetricGroupSummariesUsesHalfOpenRange(t *testing.T) {
modelName := "admin-summary-half-open"
t.Cleanup(func() {
require.NoError(t, DB.Where("model_name = ?", modelName).Delete(&PerfMetric{}).Error)
})

rows := []PerfMetric{
{ModelName: modelName, Group: "default", BucketTs: 100, RequestCount: 2, SuccessCount: 1, TotalLatencyMs: 400, TtftSumMs: 100, TtftCount: 1, OutputTokens: 20, GenerationMs: 1000},
{ModelName: modelName, Group: "default", BucketTs: 200, RequestCount: 3, SuccessCount: 3, TotalLatencyMs: 600, TtftSumMs: 300, TtftCount: 2, OutputTokens: 30, GenerationMs: 1500},
{ModelName: modelName, Group: "default", BucketTs: 300, RequestCount: 7, SuccessCount: 7, TotalLatencyMs: 700, TtftSumMs: 700, TtftCount: 7, OutputTokens: 70, GenerationMs: 3500},
}
require.NoError(t, DB.Create(&rows).Error)

summaries, err := GetPerfMetricGroupSummaries(100, 300)

require.NoError(t, err)
require.Len(t, summaries, 1)
assert.Equal(t, int64(5), summaries[0].RequestCount)
assert.Equal(t, int64(4), summaries[0].SuccessCount)
assert.Equal(t, int64(1000), summaries[0].TotalLatencyMs)
assert.Equal(t, int64(400), summaries[0].TtftSumMs)
assert.Equal(t, int64(3), summaries[0].TtftCount)
assert.Equal(t, int64(50), summaries[0].OutputTokens)
assert.Equal(t, int64(2500), summaries[0].GenerationMs)
}
Loading
Loading