Observability Proposal v3

Blade Agent 长期在线监控与函数级性能定位方案

本方案的核心目标不是“接一个 Jaeger”,而是建立一套长期在线的性能监控与排障闭环: 平时能看趋势和告警,慢请求能定位到阶段,高 CPU / 卡顿能定位到具体函数和调用栈。

P0 目标

定位慢函数、高 CPU 函数、阻塞调用栈。这个能力不能后置,也不能只靠 trace。

主监控

Prometheus + Grafana 负责长期指标、趋势、告警和入口 dashboard。

请求下钻

Tempo / Jaeger 负责单次慢请求 trace,定位慢在哪个系统阶段。

函数下钻

Pyroscope / py-spy 负责 flamegraph,定位具体慢函数和高 CPU 调用栈。

最重要的判断

Trace 只能告诉我们 projection.serialize_patchhistory.appendsocket.emit_patch 这样的阶段慢; 真正定位到 json.dumpsmodel_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
Trace 负责
  • 一次请求经过哪些阶段
  • 每个阶段耗时多少
  • 哪个 span 慢或报错
  • trace_id 串起日志和 LLM 语义记录
Profiler 负责
  • 哪个函数 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. 落地顺序

  1. P0:函数级定位能力先落地:服务器部署 Pyroscope;盒子镜像预装 py-spy,并设计 profile 文件进入诊断包。
  2. P0:基础 metrics 与 dashboard:chat 延迟、错误率、并发、CPU、event loop block、stream slowdown ratio。
  3. P0:OTel 架构边界 trace:chat.run、prompt.build、llm.stream、projection.serialize_patch、socket.emit_patch、tool.run。
  4. P0:Stream Telemetry:打通后端 delta/patch 和前端 receive/apply/render 指标。
  5. P1:结构化日志和 trace_id 互跳:Loki 或本地 JSONL 日志补上下文。
  6. P1:Langfuse/Helicone 语义层关联:通过 trace_id/chat_run_id 关联 LLM 语义记录。
  7. 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。