From 3d2a7f357e4c5ce03d0e54f3571361945d541102 Mon Sep 17 00:00:00 2001 From: lzt404 <2596933790@qq.com> Date: Tue, 28 Jul 2026 15:07:30 +0800 Subject: [PATCH] feat: add TPS and cache hit rate to usage overview --- src-tauri/src/usage.rs | 38 ++++++++++++++++++++++++++++------ src/i18n/resources.ts | 8 +++++++ src/pages/UsageRecordsPage.tsx | 5 ++++- src/styles.css | 5 +++-- 4 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src-tauri/src/usage.rs b/src-tauri/src/usage.rs index e93a7f3..9e1d88a 100644 --- a/src-tauri/src/usage.rs +++ b/src-tauri/src/usage.rs @@ -184,7 +184,9 @@ pub(crate) struct UsageOverview { total_tokens: u64, rpm: f64, tpm: f64, + tps: f64, average_latency_ms: f64, + cache_hit_rate: f64, timeline: Vec, } @@ -718,6 +720,7 @@ fn is_generated_usage_event(value: &Value) -> bool { "output_tokens", "reasoning_tokens", "cached_tokens", + "cache_tokens", "cache_read_tokens", "cache_creation_tokens", "total_tokens", @@ -745,13 +748,17 @@ fn normalize_usage_record(value: Value, config: &GuiConfigFile) -> Result 0 AND latency_ms > 0 + THEN CAST(output_tokens AS REAL) * 1000.0 / latency_ms + END), 0.0), MIN(timestamp_ms), MAX(timestamp_ms) FROM usage_events{} @@ -898,8 +909,9 @@ fn load_usage_overview( row.get::<_, i64>(7)?, row.get::<_, i64>(8)?, row.get::<_, i64>(9)?, - row.get::<_, Option>(10)?, + row.get::<_, f64>(10)?, row.get::<_, Option>(11)?, + row.get::<_, Option>(12)?, )) }, ) @@ -954,7 +966,12 @@ fn load_usage_overview( overview.success_count as f64 * 100.0 / overview.total_requests as f64; overview.average_latency_ms = from_sql_i64(summary.9) as f64 / overview.total_requests as f64; - let minutes = query_window_minutes(query, summary.10, summary.11); + overview.tps = summary.10; + if overview.input_tokens > 0 { + overview.cache_hit_rate = + (overview.cache_read_tokens as f64 / overview.input_tokens as f64).min(1.0); + } + let minutes = query_window_minutes(query, summary.11, summary.12); overview.rpm = overview.total_requests as f64 / minutes; overview.tpm = overview.total_tokens as f64 / minutes; } @@ -1483,7 +1500,13 @@ mod tests { "api_key": "secret-client-key", "response_headers": { "authorization": ["secret-upstream-token"] }, "model": "gpt-test", - "tokens": { "input_tokens": 10, "output_tokens": 20 } + "tokens": { + "input_tokens": 10, + "output_tokens": 20, + "cached_tokens": 7, + "cache_read_tokens": 5, + "cache_creation_tokens": 2 + } }), &config, ) @@ -1493,6 +1516,7 @@ mod tests { assert!(!rendered.contains("secret-client-key")); assert!(!rendered.contains("secret-upstream-token")); assert_eq!(record.tokens.total_tokens, 30); + assert_eq!(record.tokens.cache_read_tokens, 5); assert!(!record.api_key_hash.is_empty()); } @@ -1614,6 +1638,8 @@ mod tests { assert_eq!(overview.total_requests, 1); assert_eq!(overview.failure_count, 1); + assert_eq!(overview.tps, 200.0); + assert_eq!(overview.cache_hit_rate, 0.2); assert_eq!(analysis.models[0].key, "gpt-b"); assert_eq!(events.total, 1); assert_eq!(events.items[0].id, "request-2"); diff --git a/src/i18n/resources.ts b/src/i18n/resources.ts index a67ce26..35d0193 100644 --- a/src/i18n/resources.ts +++ b/src/i18n/resources.ts @@ -127,6 +127,10 @@ export const zhCN = { 'usage.stat.successRate': '成功率', 'usage.stat.reasoningMeta': '思考 {tokens} Token', 'usage.stat.averageLatency': '平均延迟', + 'usage.stat.tps': 'TPS', + 'usage.stat.performanceMeta': '{rpm} RPM · {tpm} TPM · 平均延迟 {latency} ms', + 'usage.stat.cacheHitRate': '缓存命中率', + 'usage.stat.cacheHitMeta': '命中 {hit} / 输入 {input} Token', 'usage.trend.title': '请求与 Token 趋势', 'usage.trend.description': '按系统本地小时聚合', 'usage.token.title': 'Token 构成', @@ -942,6 +946,10 @@ export const en: Record = { 'usage.stat.successRate': 'Success Rate', 'usage.stat.reasoningMeta': 'Reasoning {tokens} tokens', 'usage.stat.averageLatency': 'Average Latency', + 'usage.stat.tps': 'TPS', + 'usage.stat.performanceMeta': '{rpm} RPM · {tpm} TPM · Avg latency {latency} ms', + 'usage.stat.cacheHitRate': 'Cache Hit Rate', + 'usage.stat.cacheHitMeta': '{hit} hit / {input} input tokens', 'usage.trend.title': 'Request and Token Trend', 'usage.trend.description': 'Grouped by local system hour', 'usage.token.title': 'Token Breakdown', diff --git a/src/pages/UsageRecordsPage.tsx b/src/pages/UsageRecordsPage.tsx index bc1590e..efc80a1 100644 --- a/src/pages/UsageRecordsPage.tsx +++ b/src/pages/UsageRecordsPage.tsx @@ -35,7 +35,9 @@ type UsageOverview = { totalTokens: number; rpm: number; tpm: number; + tps: number; averageLatencyMs: number; + cacheHitRate: number; timeline: TimelinePoint[]; }; @@ -342,7 +344,8 @@ function OverviewView({ overview }: { overview: UsageOverview }) { { icon: Activity, label: t('usage.stat.requests'), value: compactNumber(overview.totalRequests), meta: t('usage.stat.requestMeta', { success: overview.successCount, failed: overview.failureCount }) }, { icon: Sparkles, label: t('usage.stat.tokens'), value: compactNumber(overview.totalTokens), meta: t('usage.stat.tokenMeta', { input: compactNumber(overview.inputTokens), output: compactNumber(overview.outputTokens) }) }, { icon: ShieldCheck, label: t('usage.stat.successRate'), value: `${overview.successRate.toFixed(1)}%`, meta: t('usage.stat.reasoningMeta', { tokens: compactNumber(overview.reasoningTokens) }) }, - { icon: Clock3, label: t('usage.stat.averageLatency'), value: `${Math.round(overview.averageLatencyMs)} ms`, meta: `${overview.rpm.toFixed(2)} RPM · ${compactNumber(overview.tpm)} TPM` }, + { icon: Clock3, label: t('usage.stat.tps'), value: `${overview.tps.toFixed(1)} TPS`, meta: t('usage.stat.performanceMeta', { rpm: overview.rpm.toFixed(2), tpm: compactNumber(overview.tpm), latency: Math.round(overview.averageLatencyMs) }) }, + { icon: Database, label: t('usage.stat.cacheHitRate'), value: `${(overview.cacheHitRate * 100).toFixed(1)}%`, meta: t('usage.stat.cacheHitMeta', { hit: compactNumber(overview.cacheReadTokens), input: compactNumber(overview.inputTokens) }) }, ]; return
{cards.map(({ icon: Icon, label, value, meta }) =>
{label}{value}{meta}
)}
diff --git a/src/styles.css b/src/styles.css index 1eb3870..2bae300 100644 --- a/src/styles.css +++ b/src/styles.css @@ -5218,7 +5218,7 @@ input:focus-visible { .usage-stat-grid { display: grid; grid-column: 1 / -1; - grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr)); + grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 10px; } @@ -5249,8 +5249,9 @@ input:focus-visible { overflow: hidden; color: #8a847c; font-size: 10px; + line-height: 1.45; text-overflow: ellipsis; - white-space: nowrap; + white-space: normal; } .usage-trend-panel,