From c10efea12cd6221d1c59ca796a39a03b931727ae Mon Sep 17 00:00:00 2001 From: Zyy Date: Fri, 23 Oct 2026 21:06:11 +0800 Subject: [PATCH] perf(dashboard): deduplicate histogram samples and trim unused trend fields Group identical positive samples before numeric logarithm evaluation while retaining histogram boundaries and weights. Remove nine unused trend fields and synchronize frontend types, preview data, and API documentation. Validate against the 30-million-row Docker dataset, matching retained statistics and reducing uncached today-summary median latency from 623 ms to 319 ms. Run 133 related tests, Clippy, formatting, frontend checks, and browser tab checks. --- .../src/admin/observability/presenter.rs | 24 ------------------- .../src/admin/observability/wire.rs | 9 ------- .../observability/analytics/aggregate.rs | 6 +++-- .../gateway-store/tests/postgres/analytics.rs | 10 ++++---- docs/api.md | 4 ++++ frontend/src/api/modules/dashboard.ts | 9 ------- .../src/views/theme/fixtures/dashboard.ts | 9 ------- 7 files changed, 14 insertions(+), 57 deletions(-) diff --git a/backend/crates/gateway-api/src/admin/observability/presenter.rs b/backend/crates/gateway-api/src/admin/observability/presenter.rs index 2d27d016b..fcb949803 100644 --- a/backend/crates/gateway-api/src/admin/observability/presenter.rs +++ b/backend/crates/gateway-api/src/admin/observability/presenter.rs @@ -686,20 +686,10 @@ pub(crate) fn trend_point_view( tokens_value: point.metrics.total_tokens, errors: format_compact_number(point.service_failure_count), errors_value: point.service_failure_count, - latency: display_duration(point.average_latency_ms), latency_value: point.average_latency_ms, - max_latency: display_duration(point.metrics.max_latency_ms), - max_latency_value: point.metrics.max_latency_ms, - min_latency: display_duration(point.metrics.min_latency_ms), - min_latency_value: point.metrics.min_latency_ms, success_rate: success_rate_value .map_or_else(|| "—".to_owned(), |value| format!("{value:.1}%")), success_rate_value, - first_token_p50_ms: point - .metrics - .first_token_latency_percentiles - .p50_ms - .map(|value| value.as_f64()), first_token_p95_ms: point .metrics .first_token_latency_percentiles @@ -711,20 +701,6 @@ pub(crate) fn trend_point_view( .p95_ms .map(|value| value.as_f64()), output_throughput_p50: point.metrics.output_throughput_p50, - admission_decision_p95_ms: point - .metrics - .admission_decision_percentiles - .p95_ms - .map(|value| value.as_f64()), - account_selection_wait_p95_ms: point - .metrics - .account_selection_wait_percentiles - .p95_ms - .map(|value| value.as_f64()), - capacity_utilization: point - .metrics - .capacity_utilization_p95_basis_points - .map(basis_points_rate), } } diff --git a/backend/crates/gateway-api/src/admin/observability/wire.rs b/backend/crates/gateway-api/src/admin/observability/wire.rs index e518755fc..4e4579374 100644 --- a/backend/crates/gateway-api/src/admin/observability/wire.rs +++ b/backend/crates/gateway-api/src/admin/observability/wire.rs @@ -306,21 +306,12 @@ pub struct TrendPointView { pub tokens_value: u64, pub errors: String, pub errors_value: u64, - pub latency: String, pub latency_value: Option, - pub max_latency: String, - pub max_latency_value: Option, - pub min_latency: String, - pub min_latency_value: Option, pub success_rate: String, pub success_rate_value: Option, - pub first_token_p50_ms: Option, pub first_token_p95_ms: Option, pub latency_p95_ms: Option, pub output_throughput_p50: Option, - pub admission_decision_p95_ms: Option, - pub account_selection_wait_p95_ms: Option, - pub capacity_utilization: Option, } /// 趋势指标的稳定身份,展示名称由管理端本地化 diff --git a/backend/crates/gateway-store/src/postgres/observability/analytics/aggregate.rs b/backend/crates/gateway-store/src/postgres/observability/analytics/aggregate.rs index c076b39f7..4da5cbe13 100644 --- a/backend/crates/gateway-store/src/postgres/observability/analytics/aggregate.rs +++ b/backend/crates/gateway-store/src/postgres/observability/analytics/aggregate.rs @@ -89,8 +89,10 @@ pub(super) fn push_fact_aggregation_selected( } query.push(") sample(metric,value) where value is not null), sample_stats as (select output_bucket,name,metric,count(*) as count,sum(value) as sum,min(value) as minimum,max(value) as maximum,count(*) filter(where value=0) as zero"); query.push(" from samples group by output_bucket,name,metric)"); - // 对数桶定义与 Histogram::add 相同,零值单独保留,不对零取对数 - query.push(", bin_counts as (select output_bucket,name,metric,floor(log(2::numeric,value::numeric)*16)::integer as bin,count(*) as count from samples where value>0 group by output_bucket,name,metric,bin), merged_bins as (select output_bucket,name,metric,jsonb_object_agg(bin,count) as bins from bin_counts group by output_bucket,name,metric)"); + // 先合并同值样本,避免逐条重复计算 numeric 对数,保持原有桶边界和样本权重 + // 零值仍由 sample_stats 单独保留,不参与对数计算 + query.push(", sample_values as materialized (select output_bucket,name,metric,value,count(*) as count from samples where value>0 group by output_bucket,name,metric,value)"); + query.push(", bin_counts as (select output_bucket,name,metric,floor(log(2::numeric,value::numeric)*16)::integer as bin,sum(count)::bigint as count from sample_values group by output_bucket,name,metric,bin), merged_bins as (select output_bucket,name,metric,jsonb_object_agg(bin,count) as bins from bin_counts group by output_bucket,name,metric)"); query.push(", merged_distributions as (select s.output_bucket,s.name,jsonb_object_agg(s.metric,jsonb_build_object('count',s.count,'sum',s.sum,'minimum',s.minimum,'maximum',s.maximum,'zero',s.zero,'bins',"); query.push("coalesce(b.bins,'{}'::jsonb)"); query.push(")) as distributions"); diff --git a/backend/crates/gateway-store/tests/postgres/analytics.rs b/backend/crates/gateway-store/tests/postgres/analytics.rs index 1c4fb8124..32ccc03cd 100644 --- a/backend/crates/gateway-store/tests/postgres/analytics.rs +++ b/backend/crates/gateway-store/tests/postgres/analytics.rs @@ -1692,16 +1692,18 @@ async fn daily_rank_counts_follow_rebuilds_rollbacks_deletions_and_dirty_days() } #[tokio::test] -async fn sql_edge_histograms_match_persisted_power_of_two_boundaries() { +async fn sql_edge_histograms_preserve_repeated_samples_and_power_of_two_boundaries() { let Some(database) = TestDatabase::create("analytics_sql_histogram_boundaries").await else { return; }; let start = DateTime::parse_from_rfc3339("2026-09-01T10:00:00Z") .unwrap() .with_timezone(&Utc); - for (index, value) in [0_i64, 1, 2, 4, 8, 16, 31, 32, 33, 64, 128, 1024, 1_048_576] - .into_iter() - .enumerate() + for (index, value) in [ + 0_i64, 0, 1, 1, 1, 2, 2, 4, 8, 16, 31, 32, 32, 33, 64, 128, 1024, 1_048_576, + ] + .into_iter() + .enumerate() { let id = format!("sql_hist_{index}"); seed( diff --git a/docs/api.md b/docs/api.md index 55797f414..1fa94f310 100644 --- a/docs/api.md +++ b/docs/api.md @@ -2022,6 +2022,10 @@ errorCode, errorMessage, startedAt, completedAt, expiresAt, createdAt, updatedAt 图表颜色、系列身份和选中状态使用 `metric`,不根据译文或数组位置推断指标 +`points[]` 提供趋势图与首页卡片使用的请求数、Tokens、缓存比例、错误数、成功率、 +`latencyValue`、首 Token P95、总延迟 P95 和输出吞吐 P50,以及时间标签与展示值 +内部调度分位数和容量分布通过用量洞察查询 + ### Dashboard 客户端身份 `wireProfiles[].attributes[]` 返回稳定的 `key` 和 `value`,字段名称由前端本地化 diff --git a/frontend/src/api/modules/dashboard.ts b/frontend/src/api/modules/dashboard.ts index a395e90d8..7bfd49658 100644 --- a/frontend/src/api/modules/dashboard.ts +++ b/frontend/src/api/modules/dashboard.ts @@ -26,21 +26,12 @@ export interface DashboardTrendPoint { tokensValue: number errors: string errorsValue: number - latency: string latencyValue: number | null - maxLatency: string - maxLatencyValue: number | null - minLatency: string - minLatencyValue: number | null successRate: string successRateValue: number | null - firstTokenP50Ms: number | null firstTokenP95Ms: number | null latencyP95Ms: number | null outputThroughputP50: number | null - admissionDecisionP95Ms: number | null - accountSelectionWaitP95Ms: number | null - capacityUtilization: number | null } export type DashboardTrendMetric diff --git a/frontend/src/views/theme/fixtures/dashboard.ts b/frontend/src/views/theme/fixtures/dashboard.ts index 472530e81..82d249039 100644 --- a/frontend/src/views/theme/fixtures/dashboard.ts +++ b/frontend/src/views/theme/fixtures/dashboard.ts @@ -81,21 +81,12 @@ function previewTrendPoint(requestsValue: number, index: number): DashboardTrend tokensValue: inputTokensValue + outputTokensValue, errors: errorsValue.toLocaleString('zh-CN'), errorsValue, - latency: `${410 + index * 7} ms`, latencyValue: 410 + index * 7, - maxLatency: `${760 + index * 12} ms`, - maxLatencyValue: 760 + index * 12, - minLatency: `${180 + index * 3} ms`, - minLatencyValue: 180 + index * 3, successRate: `${successRateValue.toFixed(1)}%`, successRateValue, - firstTokenP50Ms: 210 + index * 3, firstTokenP95Ms: 420 + index * 5, latencyP95Ms: 760 + index * 12, outputThroughputP50: 52 + (index % 5), - admissionDecisionP95Ms: 2 + (index % 3), - accountSelectionWaitP95Ms: 8 + (index % 4), - capacityUtilization: 0.42 + (index % 6) * 0.04, } }