Blade Agent 长期在线监控与函数级性能定位方案
本方案的核心目标不是“接一个 Jaeger”,而是建立一套长期在线的性能监控与排障闭环: 平时能看趋势和告警,慢请求能定位到阶段,高 CPU / 卡顿能定位到具体函数和调用栈。
定位慢函数、高 CPU 函数、阻塞调用栈。这个能力不能后置,也不能只靠 trace。
Prometheus + Grafana 负责长期指标、趋势、告警和入口 dashboard。
Tempo / Jaeger 负责单次慢请求 trace,定位慢在哪个系统阶段。
Pyroscope / py-spy 负责 flamegraph,定位具体慢函数和高 CPU 调用栈。
最重要的判断
Trace 只能告诉我们 projection.serialize_patch、
history.append 或
socket.emit_patch 这样的阶段慢;
真正定位到 json.dumps、
model_dump、
tokenizer、文件 flush、同步 IO、React render 等具体函数,必须依赖 profiling。
因此 continuous profiling 是 P0,不是可选增强。
1. 最终监控结果会是什么样子
1.1 Grafana 首页:长期在线视图
| 区域 | 展示内容 | 回答的问题 |
|---|---|---|
| 系统健康 | running chat 数、在线 session、错误率、CPU、内存、event loop block、进程重启次数 | 系统当前是否健康,是否出现全局卡顿或资源压力 |
| Chat 延迟 | chat p50/p95/p99、慢请求比例、各阶段耗时占比 | 响应变慢是偶发还是整体退化,慢在哪个架构阶段 |
| 流式体验 | TTFT、上游 token/s、用户可见 token/s、stream slowdown ratio、首个 patch 延迟、socket emit lag | 模型输出很快但用户看到很慢时,损耗发生在哪一段 |
| 函数级热点 | Pyroscope Top CPU functions、Top wall-time stacks、按服务/进程/时间窗口筛选的 flamegraph | 高 CPU、卡顿、慢 span 内部到底是哪段函数在耗时 |
| 工具与沙盒 | 工具调用次数、失败率、p95 耗时、sandbox command 耗时、文件读写耗时 | 是否是工具、沙盒、文件系统拖慢 agent |
1.2 一次慢请求的下钻路径
Grafana 告警 / 慢请求列表
-> 点击 trace_id 打开 Tempo/Jaeger
-> trace 显示 chat.run 下 projection.serialize_patch 很慢
-> 点击同一时间窗口的 Pyroscope flamegraph
-> 定位具体函数:
pydantic/main.py:model_dump
json/encoder.py:iterencode
blade_agent/host/projections/builder.py:apply_event
server/bridge.py:_emit_patch_payload
-> 用 trace_id 到 Loki 查看上下文日志
-> 明确修复点:减少 patch payload、避免重复 model_dump、批量合并 delta
这里的关键是:Tempo/Jaeger 负责把范围缩小到 span,Pyroscope/py-spy 负责定位具体函数。
2. 工具分工
| 工具 | 定位能力 | 优先级 |
|---|---|---|
| Prometheus + Grafana | 长期指标、趋势、告警、容量判断、慢请求入口 | P0 |
| OpenTelemetry | 统一埋点 API:trace、metrics、上下文传播、trace_id/session_id/chat_run_id 关联 | P0 |
| Tempo / Jaeger | 单次请求 trace,定位慢在哪个阶段或 span | P0 |
| Pyroscope | 长期 continuous profiling,定位慢函数、高 CPU 函数、wall time 调用栈 | P0 |
| py-spy | 盒子环境或现场临时采样,生成 dump/flamegraph,用于无常驻 profiler 场景 | P0 |
| Loki / 结构化日志 | 错误上下文、请求摘要、trace_id 关联、版本和环境信息 | P1 |
| Langfuse / Helicone | LLM 语义层:prompt、completion、usage、model、tool call、agent loop 语义分析 | P1 |
3. 埋点边界:span 不打到每个函数
业务代码里只对架构边界打 span,不给每个函数手写 span。函数级定位由 profiler 采样完成。
chat.run
session.load_context
prompt.build
llm.stream
llm.first_token
projection.apply_event
projection.serialize_patch
socket.emit_patch
history.append
tool.run
- 一次请求经过哪些阶段
- 每个阶段耗时多少
- 哪个 span 慢或报错
- trace_id 串起日志和 LLM 语义记录
- 哪个函数 CPU 占比最高
- 哪个调用栈 wall time 最高
- 是否卡在序列化、tokenizer、同步 IO、锁等待
- 高 CPU 是否来自 Python、Go CLI、前端渲染或 sandbox 进程
4. Stream Telemetry:专门监控 token/s 损耗
“模型 API 返回层 100 token/s,但用户界面只有 50 token/s”必须用专门的流式指标定位。它不应该靠 trace 页面肉眼猜。
upstream_ttft_ms
upstream_tokens_per_sec
backend_first_delta_ms
backend_first_patch_ms
backend_patch_count
socket_emit_lag_ms
frontend_receive_lag_ms
frontend_apply_lag_ms
frontend_visible_tokens_per_sec
stream_slowdown_ratio = upstream_tokens_per_sec / frontend_visible_tokens_per_sec
| 异常现象 | 优先怀疑 |
|---|---|
| 上游 token/s 低 | 模型、LLM 网关、本机推理引擎资源竞争 |
| 上游快,backend patch 慢 | projection build、JSON 序列化、history append、event loop blocking |
| backend patch 快,frontend visible 慢 | Socket backlog、store apply、markdown 渲染、React render |
5. 两类部署环境
5.1 服务器环境:完整在线分析栈
| 组件 | 优先级 | 说明 |
|---|---|---|
| Prometheus + Grafana | P0 | 主监控和告警入口 |
| Tempo / Jaeger | P0 | 慢请求 trace 下钻 |
| Pyroscope | P0 | 持续 profiling,定位慢函数和高 CPU 函数 |
| Loki | P1 | 结构化日志和 trace_id 关联 |
| Langfuse | P1 | LLM 语义层,避免把 prompt/completion 重复存到 trace 后端 |
5.2 盒子环境:不部署常驻分析栈,但必须保留 P0 诊断证据
盒子环境资源紧张,不建议常驻 Grafana/Tempo/Pyroscope/Loki。但 P0 函数级定位不能消失,必须通过 py-spy 和诊断包保留证据。
| 能力 | 盒子实现 |
|---|---|
| 长期指标 | FastAPI 暴露 /metrics,巡检脚本定期抓取 Prometheus 文本快照并滚动落盘 |
| Trace 证据 | 慢请求和错误请求写入 diagnostic JSONL span 文件,正常请求低采样 |
| 函数级定位 | 镜像预装 py-spy;高 CPU 或慢请求现场执行 record/dump,生成 flamegraph 放入诊断包 |
| 日志 | JSONL 本地滚动,带 trace_id/session_id/chat_run_id/version |
| 导出 | 一条命令打包最近 N 天 metrics、spans、logs、profiles、manifest |
6. 盒子诊断包形态
blade-agent-diagnostics-2026-07-09.tar.gz
metrics/
metrics-2026-07-09.prom
traces/
spans-2026-07-09.jsonl
logs/
server.jsonl
agent.jsonl
profiles/
py-spy-2026-07-09.svg
py-spy-2026-07-09.raw
manifest.json
这个包带回服务器环境后,可以导入内部 Grafana/Tempo/Pyroscope 或用脚本离线分析。最低要求是:即使盒子现场没有常驻分析栈,也能回答“当时哪个请求慢、慢在哪个阶段、哪个函数在耗 CPU”。
7. 落地顺序
- P0:函数级定位能力先落地:服务器部署 Pyroscope;盒子镜像预装 py-spy,并设计 profile 文件进入诊断包。
- P0:基础 metrics 与 dashboard:chat 延迟、错误率、并发、CPU、event loop block、stream slowdown ratio。
- P0:OTel 架构边界 trace:chat.run、prompt.build、llm.stream、projection.serialize_patch、socket.emit_patch、tool.run。
- P0:Stream Telemetry:打通后端 delta/patch 和前端 receive/apply/render 指标。
- P1:结构化日志和 trace_id 互跳:Loki 或本地 JSONL 日志补上下文。
- P1:Langfuse/Helicone 语义层关联:通过 trace_id/chat_run_id 关联 LLM 语义记录。
- P1:盒子诊断包导出命令:打包 metrics、spans、logs、profiles、manifest,支持脱敏。
8. 最终结论
P0 能力:
1. 长期指标和告警:Prometheus + Grafana
2. 慢请求阶段定位:OpenTelemetry + Tempo/Jaeger
3. 慢函数 / 高 CPU 函数定位:Pyroscope / py-spy
4. 流式 token/s 损耗定位:Stream Telemetry
部署分档:
服务器环境:完整在线分析栈,Grafana -> Trace -> Pyroscope -> Logs 闭环
盒子环境:不常驻分析栈,但必须保留 metrics/spans/logs/profiles 诊断证据
一句话:监控的结果不是“有一个 trace 页面”,而是能在长期运行中持续回答: 系统是否变慢、哪次请求慢、慢在哪个阶段、哪个函数导致慢或高 CPU。