fix(usage): repair aggregate usage statistics

This commit is contained in:
fawney19
2026-05-06 01:13:23 +08:00
parent f1358dd845
commit 3264857e4a
10 changed files with 736 additions and 121 deletions

View File

@@ -950,7 +950,18 @@ pub(super) async fn handle_dashboard_stats_get(
/ today_totals.requests as f64
* 100.0
};
let cost_savings =
let today_cost_savings =
match dashboard_load_cache_savings(state, today_range, user_filter).await {
Ok(value) => value,
Err(err) => {
return build_auth_error_response(
http::StatusCode::INTERNAL_SERVER_ERROR,
format!("dashboard today cache savings lookup failed: {err:?}"),
false,
);
}
};
let period_cost_savings =
match dashboard_load_cache_savings(state, summary_range, user_filter).await {
Ok(value) => value,
Err(err) => {
@@ -977,7 +988,7 @@ pub(super) async fn handle_dashboard_stats_get(
{
"name": "今日费用",
"value": dashboard_format_usd(today_totals.total_cost_usd),
"subValue": format!("节省 {}", dashboard_format_usd(cost_savings.max(0.0))),
"subValue": format!("节省 {}", dashboard_format_usd(today_cost_savings.max(0.0))),
"icon": "DollarSign",
},
{
@@ -1007,7 +1018,7 @@ pub(super) async fn handle_dashboard_stats_get(
"cost_stats": {
"total_cost": dashboard_round_f64(period_totals.total_cost_usd, 4),
"total_actual_cost": dashboard_round_f64(period_totals.actual_total_cost_usd, 4),
"cost_savings": cost_savings,
"cost_savings": period_cost_savings,
},
"cache_stats": cache_stats,
"users": {

View File

@@ -17,6 +17,7 @@ use axum::routing::{any, get};
use axum::{extract::Request, Router};
use chrono::Utc;
use http::StatusCode;
use serde_json::json;
use super::super::{
build_router_with_state, sample_currently_usable_auth_snapshot, sample_provider, start_server,
@@ -1235,6 +1236,7 @@ async fn gateway_handles_admin_stats_cost_savings_locally_with_trusted_admin_pri
usage_row.cache_creation_cost_usd = 0.001;
usage_row.cache_read_cost_usd = 0.002;
usage_row.output_price_per_1m = Some(50.0);
usage_row.request_metadata = Some(json!({ "input_price_per_1m": 30.0 }));
let usage_repository = Arc::new(InMemoryUsageReadRepository::seed(vec![usage_row]));
@@ -1261,8 +1263,8 @@ async fn gateway_handles_admin_stats_cost_savings_locally_with_trusted_admin_pri
assert_eq!(payload["cache_read_tokens"], 100);
assert_eq!(payload["cache_read_cost"], 0.002);
assert_eq!(payload["cache_creation_cost"], 0.001);
assert_eq!(payload["estimated_full_cost"], 0.005);
assert_eq!(payload["cache_savings"], 0.003);
assert_eq!(payload["estimated_full_cost"], 0.003);
assert_eq!(payload["cache_savings"], 0.001);
assert_eq!(*upstream_hits.lock().expect("mutex should lock"), 0);
gateway_handle.abort();

View File

@@ -321,6 +321,7 @@ async fn gateway_handles_admin_dashboard_stats_locally_without_proxying_upstream
openai_usage.cache_read_input_tokens = 800;
openai_usage.cache_read_cost_usd = 0.01;
openai_usage.output_price_per_1m = Some(100.0);
openai_usage.request_metadata = Some(json!({ "input_price_per_1m": 20.0 }));
let mut claude_usage = sample_user_usage_audit(
"usage-dashboard-admin-2",
@@ -342,6 +343,27 @@ async fn gateway_handles_admin_dashboard_stats_locally_without_proxying_upstream
claude_usage.cache_read_input_tokens = 200;
claude_usage.cache_read_cost_usd = 0.005;
claude_usage.output_price_per_1m = Some(100.0);
claude_usage.request_metadata = Some(json!({ "input_price_per_1m": 20.0 }));
let mut prior_usage = sample_user_usage_audit(
"usage-dashboard-admin-4",
"req-dashboard-admin-4",
"user-auth-1",
"gpt-5",
"openai",
"completed",
now - chrono::Duration::days(1),
);
prior_usage.input_tokens = 2_000;
prior_usage.output_tokens = 500;
prior_usage.total_tokens = 2_500;
prior_usage.cache_creation_input_tokens = 0;
prior_usage.cache_creation_ephemeral_5m_input_tokens = 0;
prior_usage.cache_creation_ephemeral_1h_input_tokens = 0;
prior_usage.cache_read_input_tokens = 1_000;
prior_usage.cache_read_cost_usd = 0.01;
prior_usage.output_price_per_1m = Some(100.0);
prior_usage.request_metadata = Some(json!({ "input_price_per_1m": 30.0 }));
let mut streaming_usage = sample_user_usage_audit(
"usage-dashboard-admin-3",
@@ -358,6 +380,7 @@ async fn gateway_handles_admin_dashboard_stats_locally_without_proxying_upstream
let usage_repository = Arc::new(InMemoryUsageReadRepository::seed(vec![
openai_usage,
claude_usage,
prior_usage,
streaming_usage,
]));
let user_repository = Arc::new(
@@ -487,7 +510,11 @@ async fn gateway_handles_admin_dashboard_stats_locally_without_proxying_upstream
.await;
let response = reqwest::Client::new()
.get(format!("{gateway_url}/api/dashboard/stats"))
.get(format!(
"{gateway_url}/api/dashboard/stats?start_date={}&end_date={}",
(now - chrono::Duration::days(1)).date_naive(),
now.date_naive(),
))
.header("authorization", format!("Bearer {access_token}"))
.header("x-client-device-id", "device-dashboard-stats-admin")
.header("user-agent", "AetherTest/1.0")
@@ -500,8 +527,8 @@ async fn gateway_handles_admin_dashboard_stats_locally_without_proxying_upstream
assert_eq!(payload["today"]["requests"], 2);
assert_eq!(payload["today"]["tokens"], 17_450);
assert_eq!(payload["today"]["cost"], json!(2.5));
assert_eq!(payload["cost_stats"]["cost_savings"], json!(0.085));
assert_eq!(payload["stats"][2]["subValue"], json!("节省 $0.09"));
assert_eq!(payload["cost_stats"]["cost_savings"], json!(0.025));
assert_eq!(payload["stats"][2]["subValue"], json!("节省 $0.01"));
assert_eq!(payload["stats"][0]["value"], json!("2"));
assert_eq!(payload["stats"][1]["value"], json!("17.4K"));
assert_eq!(
@@ -561,34 +588,40 @@ async fn gateway_handles_dashboard_daily_stats_locally_without_proxying_upstream
"refresh-dashboard-daily-stats",
now,
);
let mut today_openai_usage = sample_user_usage_audit(
"usage-dashboard-daily-1",
"req-dashboard-daily-1",
"user-auth-1",
"gpt-5",
"openai",
"completed",
now - chrono::Duration::hours(1),
);
today_openai_usage.total_tokens = 160;
let mut today_claude_usage = sample_user_usage_audit(
"usage-dashboard-daily-2",
"req-dashboard-daily-2",
"user-auth-2",
"claude-3-7",
"claude",
"completed",
now - chrono::Duration::hours(2),
);
today_claude_usage.total_tokens = 160;
let mut prior_usage = sample_user_usage_audit(
"usage-dashboard-daily-3",
"req-dashboard-daily-3",
"user-auth-3",
"gpt-5",
"openai",
"completed",
now - chrono::Duration::days(1) - chrono::Duration::hours(2),
);
prior_usage.total_tokens = 160;
let usage_repository = Arc::new(InMemoryUsageReadRepository::seed(vec![
sample_user_usage_audit(
"usage-dashboard-daily-1",
"req-dashboard-daily-1",
"user-auth-1",
"gpt-5",
"openai",
"completed",
now - chrono::Duration::hours(1),
),
sample_user_usage_audit(
"usage-dashboard-daily-2",
"req-dashboard-daily-2",
"user-auth-2",
"claude-3-7",
"claude",
"completed",
now - chrono::Duration::hours(2),
),
sample_user_usage_audit(
"usage-dashboard-daily-3",
"req-dashboard-daily-3",
"user-auth-3",
"gpt-5",
"openai",
"completed",
now - chrono::Duration::days(1) - chrono::Duration::hours(2),
),
today_openai_usage,
today_claude_usage,
prior_usage,
]));
let (gateway_url, upstream_hits, gateway_handle, upstream_handle) =
@@ -636,6 +669,7 @@ async fn gateway_handles_dashboard_daily_stats_locally_without_proxying_upstream
assert_eq!(daily_stats[0]["unique_providers"], 1);
assert_eq!(daily_stats[1]["date"], json!(now.date_naive().to_string()));
assert_eq!(daily_stats[1]["requests"], 2);
assert_eq!(daily_stats[1]["tokens"], 320);
assert_eq!(daily_stats[1]["unique_models"], 2);
assert_eq!(daily_stats[1]["unique_providers"], 2);
assert_eq!(