feat: 提前记录 pending 用量、优化流遥测时序与前端活跃请求发现机制

- 将 record_pending 调用移至执行开始前(sync/stream 两路),确保请求在执行前即有 pending 记录
- stream_pump 在收到第一个数据块前优先 yield 遥测帧,保证 ttfb 早于 data 帧到达
- stream execution 增加 should_refresh_stream_usage_telemetry,在遥测帧携带新 ttfb/elapsed 时及时更新 record_stream_started
- access_log 对高频轮询路径(usage/active、usage/records 等)降级为 TRACE 日志,减少日志噪音
- 前端新增 reconcileActiveRequestDiscovery 工具函数及 discoverActiveRequests 逻辑,活跃请求发现与全局自动刷新解耦,空闲时降频为 5 秒扫描
- RequestDetailDrawer 调整:进行中请求不再自动开启轮询,由用户手动触发;刷新按钮 title 动态适配状态
This commit is contained in:
fawney19
2026-04-10 20:58:01 +08:00
parent d5b8583d6b
commit 46f1507d44
13 changed files with 846 additions and 52 deletions

View File

@@ -70,6 +70,10 @@ pub(crate) async fn execute_execution_runtime_stream(
mut report_context: Option<serde_json::Value>,
) -> Result<Option<Response<Body>>, GatewayError> {
ensure_execution_request_candidate_slot(state, &mut plan, &mut report_context).await;
state
.usage_runtime
.record_pending(state.data.as_ref(), &plan, report_context.as_ref())
.await;
let plan_request_id_for_log = short_request_id(plan.request_id.as_str());
#[cfg(not(test))]
{
@@ -235,6 +239,19 @@ where
read_next_frame(lines).await
}
fn should_refresh_stream_usage_telemetry(
previous: Option<&ExecutionTelemetry>,
next: &ExecutionTelemetry,
) -> bool {
let previous_ttfb = previous.and_then(|telemetry| telemetry.ttfb_ms);
let previous_elapsed = previous.and_then(|telemetry| telemetry.elapsed_ms);
let next_ttfb = next.ttfb_ms;
let next_elapsed = next.elapsed_ms;
(next_ttfb.is_some() && next_ttfb != previous_ttfb)
|| (next_elapsed.is_some() && next_elapsed != previous_elapsed)
}
async fn probe_local_stream_success_failover_text<R>(
buffered_frames: &mut VecDeque<StreamFrame>,
lines: &mut FramedRead<R, LinesCodec>,
@@ -740,10 +757,6 @@ async fn execute_stream_from_frame_stream(
}
let candidate_started_unix_secs = current_request_candidate_unix_ms();
state
.usage_runtime
.record_pending(state.data.as_ref(), &plan, report_context.as_ref())
.await;
state
.usage_runtime
.record_stream_started(
@@ -794,7 +807,8 @@ async fn execute_stream_from_frame_stream(
tokio::spawn(async move {
let mut provider_buffered_body = provider_prefetched_body_for_report;
let mut buffered_body = prefetched_body_for_report;
let mut telemetry: Option<ExecutionTelemetry> = initial_telemetry;
let mut telemetry: Option<ExecutionTelemetry> = initial_telemetry.clone();
let mut usage_stream_telemetry: Option<ExecutionTelemetry> = initial_telemetry;
let reached_eof = initial_reached_eof;
let mut downstream_dropped = false;
let mut terminal_failure: Option<StreamFailureReport> = None;
@@ -928,7 +942,25 @@ async fn execute_stream_from_frame_stream(
StreamFramePayload::Telemetry {
telemetry: frame_telemetry,
} => {
telemetry = Some(frame_telemetry);
let should_refresh_stream_usage = should_refresh_stream_usage_telemetry(
usage_stream_telemetry.as_ref(),
&frame_telemetry,
);
telemetry = Some(frame_telemetry.clone());
if should_refresh_stream_usage {
state_for_report
.usage_runtime
.record_stream_started(
state_for_report.data.as_ref(),
&plan_for_report,
report_context_owned.as_ref(),
status_code,
&headers_for_report,
Some(&frame_telemetry),
)
.await;
usage_stream_telemetry = Some(frame_telemetry);
}
}
StreamFramePayload::Eof { .. } => {
break;