feat(dashboard): 最近请求缓存命中列 + 中文模式人民币计价 + 价格解析记忆化
- 最近请求表新增缓存命中率列(缓存读取/总输入) - useCurrency:中文界面费用按最新 USD→CNY 汇率显示 ¥,英文保持 $(open.er-api.com,缓存 12h,失败兜底 7.2) - match_price 记忆化缓存:O(记录数×5910) → O(模型种类×5910),扫描从秒级降到百毫秒级 - 仪表盘右上角显示加载耗时/数据量(get_summary 往返埋点);Rust 侧分段计时
This commit is contained in:
1 parent
de132ffb3a
commit
38a11666ac
9 files changed
+173
-28
No files matched your search
@@ -610,7 +610,26 @@ fn strip_context_suffix(model_name: &str) -> Option<String> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Memoized price resolution. `match_price` is called once per usage record
|
||||
/// during the scan, and the models.dev suffix scan is O(5910) per miss — with
|
||||
/// thousands of records sharing a handful of model names, memoizing the result
|
||||
/// turns the whole scan from O(records × 5910) into O(unique_models × 5910).
|
||||
static MATCH_PRICE_MEMO: OnceLock<Mutex<HashMap<String, (String, f64, f64, f64, bool)>>> =
|
||||
OnceLock::new();
|
||||
|
||||
/// Resolve a model's price, caching per-model results (hits and misses).
|
||||
fn match_price(model_name: &str) -> (String, f64, f64, f64, bool) {
|
||||
let key = model_name.to_ascii_lowercase();
|
||||
let memo = MATCH_PRICE_MEMO.get_or_init(|| Mutex::new(HashMap::new()));
|
||||
if let Some(hit) = memo.lock().unwrap().get(&key) {
|
||||
return hit.clone();
|
||||
}
|
||||
let result = match_price_inner(model_name);
|
||||
memo.lock().unwrap().insert(key, result.clone());
|
||||
result
|
||||
}
|
||||
|
||||
fn match_price_inner(model_name: &str) -> (String, f64, f64, f64, bool) {
|
||||
let bare = match model_name.rsplit_once('/') {
|
||||
Some((_, b)) => b,
|
||||
None => model_name,
|
||||
@@ -1537,6 +1556,7 @@ pub fn get_prices() -> PricesResult {
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_summary(home_override: Option<String>, range: Option<String>, refresh: Option<bool>) -> SummaryResult {
|
||||
let t0 = std::time::Instant::now();
|
||||
let refresh = refresh.unwrap_or(false);
|
||||
let home = resolve_kimi_home(home_override);
|
||||
let now_ms = std::time::SystemTime::now()
|
||||
@@ -1544,6 +1564,7 @@ pub fn get_summary(home_override: Option<String>, range: Option<String>, refresh
|
||||
let r = range.unwrap_or_else(|| "30d".into());
|
||||
|
||||
let (records, meta) = scan_usage_cached(&home, refresh);
|
||||
let t1 = std::time::Instant::now();
|
||||
let stats = aggregate(&records, &r, now_ms);
|
||||
let all_stats = aggregate(&records, "all", now_ms);
|
||||
let heatmap = build_heatmap(&records, now_ms);
|
||||
@@ -1568,6 +1589,15 @@ pub fn get_summary(home_override: Option<String>, range: Option<String>, refresh
|
||||
name, provider: std::env::var("KIMI_MODEL_PROVIDER").ok(), model: std::env::var("KIMI_MODEL_ID").ok(),
|
||||
});
|
||||
|
||||
let t2 = std::time::Instant::now();
|
||||
let scan_ms = t1.duration_since(t0).as_secs_f64() * 1000.0;
|
||||
let aggregate_ms = t2.duration_since(t1).as_secs_f64() * 1000.0;
|
||||
let total_ms = t2.duration_since(t0).as_secs_f64() * 1000.0;
|
||||
eprintln!(
|
||||
"[dashboard] get_summary range={} records={} scan={:.0}ms aggregate={:.0}ms total={:.0}ms",
|
||||
r, records.len(), scan_ms, aggregate_ms, total_ms
|
||||
);
|
||||
|
||||
SummaryResult {
|
||||
home: home.to_string_lossy().to_string(),
|
||||
valid: is_kimi_home(&home),
|
||||
|
||||
Reference in new issue
Block a user