feat(stats): 新增聚合读路径与回填机制并重构 dashboard/usage 读取链路

- 新增 stats_user_summary 及 user_daily_provider/api_format/cost_savings 等聚合表
- 扩展 stats_daily/hourly 有效 token 与响应时间等字段,maintenance runtime 同步写入
- 新增 backfill 模块与 --apply-backfills 命令补齐历史聚合数据
- 重写 dashboard_filters、usage_heatmap、user_rollups 查询改走聚合表
- 同步更新 baseline_v2.sql 与 migration 集,README/dev.sh 补充回填用法
This commit is contained in:
fawney19
2026-04-22 17:29:39 +08:00
parent 063ef02306
commit a5e6bd3b62
45 changed files with 15323 additions and 1039 deletions

View File

@@ -222,6 +222,7 @@ pub(super) fn sample_monitoring_export_api_key(
None,
false,
0,
0,
0.0,
false,
)

View File

@@ -1,10 +1,13 @@
use crate::handlers::admin::request::AdminAppState;
use crate::query::usage_heatmap::{
list_usage_heatmap_aggregate_rows, read_stats_daily_cutoff_date,
};
use crate::GatewayError;
use aether_admin::observability::stats::round_to;
use aether_admin::observability::usage::{
admin_usage_data_unavailable_response, ADMIN_USAGE_DATA_UNAVAILABLE_DETAIL,
};
use aether_data_contracts::repository::usage::UsageDailyHeatmapQuery;
use aether_data_contracts::repository::usage::{StoredUsageDailySummary, UsageDailyHeatmapQuery};
use axum::{
body::Body,
response::{IntoResponse, Response},
@@ -34,17 +37,13 @@ pub(super) async fn build_admin_usage_heatmap_response(
)
.unwrap_or_default();
let summaries = state
.summarize_usage_daily_heatmap(&UsageDailyHeatmapQuery {
created_from_unix_secs,
user_id: None,
admin_mode: true,
})
.await?;
let summaries =
build_admin_heatmap_summaries(state, created_from_unix_secs, start_date, today).await?;
let grouped: BTreeMap<String, _> = summaries.into_iter().map(|s| (s.date.clone(), s)).collect();
let mut max_requests = 0_u64;
let mut active_days = 0_u64;
let mut cursor = start_date;
let mut days = Vec::new();
while cursor <= today {
@@ -61,6 +60,9 @@ pub(super) async fn build_admin_usage_heatmap_response(
(0, 0, 0.0, 0.0)
};
max_requests = max_requests.max(requests);
if requests > 0 {
active_days = active_days.saturating_add(1);
}
days.push(json!({
"date": date_str,
"requests": requests,
@@ -77,8 +79,59 @@ pub(super) async fn build_admin_usage_heatmap_response(
"start_date": start_date.to_string(),
"end_date": today.to_string(),
"total_days": days.len(),
"active_days": active_days,
"max_requests": max_requests,
"days": days,
}))
.into_response())
}
async fn build_admin_heatmap_summaries(
state: &AdminAppState<'_>,
created_from_unix_secs: u64,
start_date: chrono::NaiveDate,
today: chrono::NaiveDate,
) -> Result<Vec<StoredUsageDailySummary>, GatewayError> {
let query = UsageDailyHeatmapQuery {
created_from_unix_secs,
user_id: None,
admin_mode: true,
};
let Some(pool) = state.app().postgres_pool() else {
return state.summarize_usage_daily_heatmap(&query).await;
};
let Some(cutoff_date) = read_stats_daily_cutoff_date(&pool).await? else {
return state.summarize_usage_daily_heatmap(&query).await;
};
let cutoff_day = cutoff_date.date_naive().min(today);
let mut summaries =
list_usage_heatmap_aggregate_rows(&pool, start_date, cutoff_day, None).await?;
let raw_start_date = start_date.max(cutoff_day);
if raw_start_date <= today {
let raw_start_of_day = raw_start_date
.and_hms_opt(0, 0, 0)
.expect("heatmap day start should be valid");
let raw_created_from_unix_secs = u64::try_from(
chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset(
raw_start_of_day,
chrono::Utc,
)
.timestamp(),
)
.unwrap_or_default();
summaries.extend(
state
.summarize_usage_daily_heatmap(&UsageDailyHeatmapQuery {
created_from_unix_secs: raw_created_from_unix_secs,
user_id: None,
admin_mode: true,
})
.await?,
);
}
summaries.sort_by(|left, right| left.date.cmp(&right.date));
Ok(summaries)
}