feat(usage): 改进请求追踪与缓存预热

- 区分请求重试与故障转移:新增 has_retry 标识亲和缓存重试
- 修正候选 TTFB 计算:记录候选自身的首字节时间而非全局时间
- Streaming 状态同步 rate_multiplier,支持按 API 格式配置
- 新增缓存预热服务:启动时预热仪表盘统计、热力图、每日统计
- 优化缓存 TTL 配置:仪表盘统计 2 分钟、热力图和每日统计 10 分钟
- 自动刷新间隔从 10 秒调整为 5 秒
This commit is contained in:
fawney19
2026-01-15 11:45:46 +08:00
parent 24b65d76b5
commit 514bf7e3ed
15 changed files with 386 additions and 39 deletions

View File

@@ -432,6 +432,7 @@ class BaseMessageHandler:
endpoint_id = ctx.endpoint_id
key_id = ctx.key_id
first_byte_time_ms = ctx.first_byte_time_ms
api_format = ctx.api_format
# 如果 provider 为空,记录警告(不应该发生,但用于调试)
if not provider:
@@ -455,6 +456,7 @@ class BaseMessageHandler:
provider_endpoint_id=endpoint_id,
provider_api_key_id=key_id,
first_byte_time_ms=first_byte_time_ms,
api_format=api_format,
)
finally:
db.close()

View File

@@ -1432,6 +1432,16 @@ class CliMessageHandlerBase(BaseMessageHandler):
if ctx.attempt_id:
from src.services.request.candidate import RequestCandidateService
# 计算候选自身的 TTFB
candidate_first_byte_time_ms: Optional[int] = None
if ctx.first_byte_time_ms is not None:
candidate_first_byte_time_ms = RequestCandidateService.calculate_candidate_ttfb(
db=bg_db,
candidate_id=ctx.attempt_id,
request_start_time=self.start_time,
global_first_byte_time_ms=ctx.first_byte_time_ms,
)
# 根据状态码决定是成功还是失败
# 499 = 客户端断开连接,应标记为失败
# 503 = 服务不可用(如流中断),应标记为失败
@@ -1443,8 +1453,8 @@ class CliMessageHandlerBase(BaseMessageHandler):
"chunk_count": ctx.chunk_count,
"data_count": ctx.data_count,
}
if ctx.first_byte_time_ms is not None:
extra_data["first_byte_time_ms"] = ctx.first_byte_time_ms
if candidate_first_byte_time_ms is not None:
extra_data["first_byte_time_ms"] = candidate_first_byte_time_ms
RequestCandidateService.mark_candidate_failed(
db=bg_db,
candidate_id=ctx.attempt_id,
@@ -1462,8 +1472,8 @@ class CliMessageHandlerBase(BaseMessageHandler):
"chunk_count": ctx.chunk_count,
"data_count": ctx.data_count,
}
if ctx.first_byte_time_ms is not None:
extra_data["first_byte_time_ms"] = ctx.first_byte_time_ms
if candidate_first_byte_time_ms is not None:
extra_data["first_byte_time_ms"] = candidate_first_byte_time_ms
RequestCandidateService.mark_candidate_success(
db=bg_db,
candidate_id=ctx.attempt_id,

View File

@@ -132,7 +132,7 @@ class StreamTelemetryRecorder:
)
# 更新候选记录状态
await self._update_candidate_status(bg_db, ctx, response_time_ms)
await self._update_candidate_status(bg_db, ctx, response_time_ms, start_time)
finally:
if bg_db:
@@ -234,6 +234,7 @@ class StreamTelemetryRecorder:
db: Session,
ctx: StreamContext,
response_time_ms: int,
request_start_time: float,
) -> None:
"""更新候选记录状态"""
if not ctx.attempt_id:
@@ -246,7 +247,14 @@ class StreamTelemetryRecorder:
"data_count": ctx.data_count,
}
if ctx.first_byte_time_ms is not None:
extra_data["first_byte_time_ms"] = ctx.first_byte_time_ms
# 计算候选自身的 TTFB
first_byte_time_ms = RequestCandidateService.calculate_candidate_ttfb(
db=db,
candidate_id=ctx.attempt_id,
request_start_time=request_start_time,
global_first_byte_time_ms=ctx.first_byte_time_ms,
)
extra_data["first_byte_time_ms"] = first_byte_time_ms
if ctx.is_success():
RequestCandidateService.mark_candidate_success(