Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
7d9a757a
Commit
7d9a757a
authored
Dec 24, 2025
by
shaw
Browse files
feat(dashboard): 添加 RPM/TPM 性能指标
在 Dashboard 中用 RPM/TPM 卡片替换原来的"今日缓存"卡片, 实时显示最近1分钟的请求数和 Token 吞吐量。
parent
bbf4024d
Changes
8
Show whitespace changes
Inline
Side-by-side
backend/internal/pkg/usagestats/usage_log_types.go
View file @
7d9a757a
...
...
@@ -42,6 +42,10 @@ type DashboardStats struct {
// 系统运行统计
AverageDurationMs
float64
`json:"average_duration_ms"`
// 平均响应时间
// 性能指标
Rpm
int64
`json:"rpm"`
// 最近1分钟的请求数
Tpm
int64
`json:"tpm"`
// 最近1分钟的Token数
}
// TrendDataPoint represents a single point in trend data
...
...
@@ -115,6 +119,10 @@ type UserDashboardStats struct {
// 性能统计
AverageDurationMs
float64
`json:"average_duration_ms"`
// 性能指标
Rpm
int64
`json:"rpm"`
// 最近1分钟的请求数
Tpm
int64
`json:"tpm"`
// 最近1分钟的Token数
}
// UsageLogFilters represents filters for usage log queries
...
...
backend/internal/repository/usage_log_repo.go
View file @
7d9a757a
...
...
@@ -19,6 +19,29 @@ func NewUsageLogRepository(db *gorm.DB) *UsageLogRepository {
return
&
UsageLogRepository
{
db
:
db
}
}
// getPerformanceStats 获取 RPM 和 TPM(可选按用户过滤)
func
(
r
*
UsageLogRepository
)
getPerformanceStats
(
ctx
context
.
Context
,
userID
int64
)
(
rpm
,
tpm
int64
)
{
oneMinuteAgo
:=
time
.
Now
()
.
Add
(
-
1
*
time
.
Minute
)
var
perfStats
struct
{
RequestCount
int64
`gorm:"column:request_count"`
TokenCount
int64
`gorm:"column:token_count"`
}
db
:=
r
.
db
.
WithContext
(
ctx
)
.
Model
(
&
model
.
UsageLog
{})
.
Select
(
`
COUNT(*) as request_count,
COALESCE(SUM(input_tokens + output_tokens), 0) as token_count
`
)
.
Where
(
"created_at >= ?"
,
oneMinuteAgo
)
if
userID
>
0
{
db
=
db
.
Where
(
"user_id = ?"
,
userID
)
}
db
.
Scan
(
&
perfStats
)
return
perfStats
.
RequestCount
,
perfStats
.
TokenCount
}
func
(
r
*
UsageLogRepository
)
Create
(
ctx
context
.
Context
,
log
*
model
.
UsageLog
)
error
{
return
r
.
db
.
WithContext
(
ctx
)
.
Create
(
log
)
.
Error
}
...
...
@@ -230,6 +253,9 @@ func (r *UsageLogRepository) GetDashboardStats(ctx context.Context) (*DashboardS
stats
.
TodayCost
=
todayStats
.
TodayCost
stats
.
TodayActualCost
=
todayStats
.
TodayActualCost
// 性能指标:RPM 和 TPM(最近1分钟,全局)
stats
.
Rpm
,
stats
.
Tpm
=
r
.
getPerformanceStats
(
ctx
,
0
)
return
&
stats
,
nil
}
...
...
@@ -544,6 +570,9 @@ func (r *UsageLogRepository) GetUserDashboardStats(ctx context.Context, userID i
stats
.
TodayCost
=
todayStats
.
TodayCost
stats
.
TodayActualCost
=
todayStats
.
TodayActualCost
// 性能指标:RPM 和 TPM(最近1分钟,仅统计该用户的请求)
stats
.
Rpm
,
stats
.
Tpm
=
r
.
getPerformanceStats
(
ctx
,
userID
)
return
&
stats
,
nil
}
...
...
frontend/src/api/usage.ts
View file @
7d9a757a
...
...
@@ -35,6 +35,8 @@ export interface UserDashboardStats {
today_cost
:
number
;
// 今日标准计费
today_actual_cost
:
number
;
// 今日实际扣除
average_duration_ms
:
number
;
rpm
:
number
;
// 最近1分钟的请求数
tpm
:
number
;
// 最近1分钟的Token数
}
export
interface
TrendParams
{
...
...
frontend/src/i18n/locales/en.ts
View file @
7d9a757a
...
...
@@ -151,6 +151,7 @@ export default {
todayTokens
:
'
Today Tokens
'
,
totalTokens
:
'
Total Tokens
'
,
cacheToday
:
'
Cache (Today)
'
,
performance
:
'
Performance
'
,
avgResponse
:
'
Avg Response
'
,
averageTime
:
'
Average time
'
,
timeRange
:
'
Time Range
'
,
...
...
@@ -420,6 +421,7 @@ export default {
todayTokens
:
'
Today Tokens
'
,
totalTokens
:
'
Total Tokens
'
,
cacheToday
:
'
Cache (Today)
'
,
performance
:
'
Performance
'
,
avgResponse
:
'
Avg Response
'
,
active
:
'
active
'
,
ok
:
'
ok
'
,
...
...
frontend/src/i18n/locales/zh.ts
View file @
7d9a757a
...
...
@@ -151,6 +151,7 @@ export default {
todayTokens
:
'
今日 Token
'
,
totalTokens
:
'
累计 Token
'
,
cacheToday
:
'
今日缓存
'
,
performance
:
'
性能指标
'
,
avgResponse
:
'
平均响应
'
,
averageTime
:
'
平均时间
'
,
timeRange
:
'
时间范围
'
,
...
...
@@ -432,6 +433,7 @@ export default {
input
:
'
输入
'
,
output
:
'
输出
'
,
cacheToday
:
'
今日缓存
'
,
performance
:
'
性能指标
'
,
avgResponse
:
'
平均响应
'
,
averageTime
:
'
平均时间
'
,
timeRange
:
'
时间范围
'
,
...
...
frontend/src/types/index.ts
View file @
7d9a757a
...
...
@@ -517,6 +517,10 @@ export interface DashboardStats {
// 系统运行统计
average_duration_ms
:
number
;
// 平均响应时间
uptime
:
number
;
// 系统运行时间(秒)
// 性能指标
rpm
:
number
;
// 最近1分钟的请求数
tpm
:
number
;
// 最近1分钟的Token数
}
export
interface
UsageStatsResponse
{
...
...
frontend/src/views/admin/DashboardView.vue
View file @
7d9a757a
...
...
@@ -117,20 +117,24 @@
</div>
</div>
<!--
Cache Tokens
-->
<!--
Performance (RPM/TPM)
-->
<div
class=
"card p-4"
>
<div
class=
"flex items-center gap-3"
>
<div
class=
"p-2 rounded-lg bg-
cyan
-100 dark:bg-
cyan
-900/30"
>
<svg
class=
"w-5 h-5 text-
cyan
-600 dark:text-
cyan
-400"
fill=
"none"
stroke=
"currentColor"
viewBox=
"0 0 24 24"
>
<path
stroke-linecap=
"round"
stroke-linejoin=
"round"
stroke-width=
"2"
d=
"M1
9.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m.75 12l3 3m0 0l3-3m-3 3v-6m-1.5-9H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9
z"
/>
<div
class=
"p-2 rounded-lg bg-
violet
-100 dark:bg-
violet
-900/30"
>
<svg
class=
"w-5 h-5 text-
violet
-600 dark:text-
violet
-400"
fill=
"none"
stroke=
"currentColor"
viewBox=
"0 0 24 24"
>
<path
stroke-linecap=
"round"
stroke-linejoin=
"round"
stroke-width=
"2"
d=
"M1
3 10V3L4 14h7v7l9-11h-7
z"
/>
</svg>
</div>
<div>
<p
class=
"text-xs font-medium text-gray-500 dark:text-gray-400"
>
{{
t
(
'
admin.dashboard.cacheToday
'
)
}}
</p>
<p
class=
"text-xl font-bold text-gray-900 dark:text-white"
>
{{
formatTokens
(
stats
.
today_cache_read_tokens
)
}}
</p>
<p
class=
"text-xs text-gray-500 dark:text-gray-400"
>
{{
t
(
'
common.create
'
)
}}
:
{{
formatTokens
(
stats
.
today_cache_creation_tokens
)
}}
</p>
<div
class=
"flex-1"
>
<p
class=
"text-xs font-medium text-gray-500 dark:text-gray-400"
>
{{
t
(
'
admin.dashboard.performance
'
)
}}
</p>
<div
class=
"flex items-baseline gap-2"
>
<p
class=
"text-xl font-bold text-gray-900 dark:text-white"
>
{{
formatTokens
(
stats
.
rpm
)
}}
</p>
<span
class=
"text-xs text-gray-500 dark:text-gray-400"
>
RPM
</span>
</div>
<div
class=
"flex items-baseline gap-2"
>
<p
class=
"text-sm font-semibold text-violet-600 dark:text-violet-400"
>
{{
formatTokens
(
stats
.
tpm
)
}}
</p>
<span
class=
"text-xs text-gray-500 dark:text-gray-400"
>
TPM
</span>
</div>
</div>
</div>
</div>
...
...
@@ -378,7 +382,8 @@ const userTrendChartData = computed(() => {
})
// Format helpers
const
formatTokens
=
(
value
:
number
):
string
=>
{
const
formatTokens
=
(
value
:
number
|
undefined
):
string
=>
{
if
(
value
===
undefined
||
value
===
null
)
return
'
0
'
if
(
value
>=
1
_000_000_000
)
{
return
`
${(
value
/
1
_000_000_000
).
toFixed
(
2
)}
B`
}
else
if
(
value
>=
1
_000_000
)
{
...
...
frontend/src/views/user/DashboardView.vue
View file @
7d9a757a
...
...
@@ -119,20 +119,24 @@
</div>
</div>
<!--
Cache Tokens
-->
<!--
Performance (RPM/TPM)
-->
<div
class=
"card p-4"
>
<div
class=
"flex items-center gap-3"
>
<div
class=
"p-2 rounded-lg bg-
cyan
-100 dark:bg-
cyan
-900/30"
>
<svg
class=
"w-5 h-5 text-
cyan
-600 dark:text-
cyan
-400"
fill=
"none"
stroke=
"currentColor"
viewBox=
"0 0 24 24"
>
<path
stroke-linecap=
"round"
stroke-linejoin=
"round"
stroke-width=
"2"
d=
"M1
9.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m.75 12l3 3m0 0l3-3m-3 3v-6m-1.5-9H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9
z"
/>
<div
class=
"p-2 rounded-lg bg-
violet
-100 dark:bg-
violet
-900/30"
>
<svg
class=
"w-5 h-5 text-
violet
-600 dark:text-
violet
-400"
fill=
"none"
stroke=
"currentColor"
viewBox=
"0 0 24 24"
>
<path
stroke-linecap=
"round"
stroke-linejoin=
"round"
stroke-width=
"2"
d=
"M1
3 10V3L4 14h7v7l9-11h-7
z"
/>
</svg>
</div>
<div>
<p
class=
"text-xs font-medium text-gray-500 dark:text-gray-400"
>
{{
t
(
'
dashboard.cacheToday
'
)
}}
</p>
<p
class=
"text-xl font-bold text-gray-900 dark:text-white"
>
{{
formatTokens
(
stats
.
today_cache_read_tokens
)
}}
</p>
<p
class=
"text-xs text-gray-500 dark:text-gray-400"
>
{{
t
(
'
common.create
'
)
}}
:
{{
formatTokens
(
stats
.
today_cache_creation_tokens
)
}}
</p>
<div
class=
"flex-1"
>
<p
class=
"text-xs font-medium text-gray-500 dark:text-gray-400"
>
{{
t
(
'
dashboard.performance
'
)
}}
</p>
<div
class=
"flex items-baseline gap-2"
>
<p
class=
"text-xl font-bold text-gray-900 dark:text-white"
>
{{
formatTokens
(
stats
.
rpm
)
}}
</p>
<span
class=
"text-xs text-gray-500 dark:text-gray-400"
>
RPM
</span>
</div>
<div
class=
"flex items-baseline gap-2"
>
<p
class=
"text-sm font-semibold text-violet-600 dark:text-violet-400"
>
{{
formatTokens
(
stats
.
tpm
)
}}
</p>
<span
class=
"text-xs text-gray-500 dark:text-gray-400"
>
TPM
</span>
</div>
</div>
</div>
</div>
...
...
@@ -584,7 +588,8 @@ const trendChartData = computed(() => {
})
// Format helpers
const
formatTokens
=
(
value
:
number
):
string
=>
{
const
formatTokens
=
(
value
:
number
|
undefined
):
string
=>
{
if
(
value
===
undefined
||
value
===
null
)
return
'
0
'
if
(
value
>=
1
_000_000_000
)
{
return
`
${(
value
/
1
_000_000_000
).
toFixed
(
2
)}
B`
}
else
if
(
value
>=
1
_000_000
)
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment