Blade Agent 长期在线监控建议
本报告总结关于 Jaeger、长期监控、细粒度性能定位的讨论,目标是帮助团队建立能长期运行、能告警、也能定位慢请求根因的可观测性体系。
长期在线监控不应以 Jaeger 为核心。主干应是 Prometheus/Grafana,Trace 和 Profiler 作为定位工具。
Jaeger 能看一次慢请求的阶段耗时,但不能替代 continuous profiling 来定位具体慢函数。
Prometheus + Grafana + OpenTelemetry + Tempo/Jaeger + Pyroscope/Parca + Loki。
1. 问题定义
需要监控的不是一次性排障,而是长期在线运行中的性能状态,重点包括:
- 系统整体是否变慢,例如 chat p95、p99 延迟是否升高。
- LLM 流式输出是否在系统内部被拖慢,例如模型侧 100 token/s,用户可见只有 50 token/s。
- 慢请求具体慢在什么阶段,进一步能定位到哪个函数、调用栈、序列化、文件 IO、锁等待或 event loop blocking。
2. Jaeger 的定位
Jaeger 是 trace viewer,适合查看一次请求经过哪些 span、每个阶段花了多久。它适合做慢请求链路分析,但不适合作为长期在线监控的主系统。
| 能力 | Jaeger 是否适合 | 说明 |
|---|---|---|
| 查看单次慢请求链路 | 适合 | 能展示 chat、LLM、tool、projection、socket 等 span 的耗时关系。 |
| 长期趋势和告警 | 不适合作为主系统 | p95、p99、错误率、吞吐、并发、token/s 这类指标更适合 Prometheus/Grafana。 |
| 定位具体慢函数 | 不充分 | Trace 只能说明某个 span 慢,不能自然回答 span 内部哪条函数调用栈最耗时。 |
| 全量 token 流细粒度记录 | 不建议 | 对每个 token 或每个 delta 建 span 会产生极高数据量和噪声。 |
3. 推荐架构
建议采用分层可观测性架构,每一层负责不同问题。
| 层级 | 推荐工具 | 回答的问题 |
|---|---|---|
| 长期指标 | Prometheus + Grafana | 系统是否变慢,吞吐是否下降,错误率是否升高,是否需要告警。 |
| 请求链路 | OpenTelemetry + Tempo 或 Jaeger | 一次慢请求慢在哪个架构阶段。 |
| 函数级定位 | Pyroscope 或 Parca | 慢 span 内部具体是哪段 Python/Go/前端函数或调用栈在耗时。 |
| 日志上下文 | Loki 或结构化日志系统 | 错误、异常、请求上下文、session_id、chat_run_id 的补充证据。 |
| LLM 语义层 | Langfuse / Helicone | prompt、completion、model、usage、tool call、agent loop 的 LLM 特有分析。 |
4. 为什么不能给每个函数都打 span
“细到代码函数”这个目标是正确的,但实现方式不应该是给每个函数手写 span。
- 长期在线开销高,尤其在高频流式路径上会放大延迟。
- Trace 页面会充满低价值 span,真正慢点反而难看出来。
- 很多慢不是函数本身慢,而是 JSON 序列化、同步文件 IO、锁等待、网络 backpressure、event loop blocking。
- 函数级热点更适合用 sampling profiler 持续采样,而不是用 tracing 全量记录。
更合理的做法是:对架构边界打 span,对少数高风险热路径打手工 span,再用 continuous profiler 自动采样函数调用栈。
5. 建议的 Trace 边界
Trace 应该表达一次 chat 请求的主要阶段,而不是表达每个内部函数。
chat.run
session.load_context
prompt.build
llm.stream
llm.first_token
llm.delta_batch
projection.apply_event
projection.serialize_patch
socket.emit_patch
history.append
tool.run
这些 span 能先回答慢在哪个阶段。例如:
llm.stream慢:模型、网关或上游 API 慢。projection.serialize_patch慢:可能是 projection 结构过大、model_dump、JSON 序列化成本高。socket.emit_patch慢:可能是 Socket.IO backpressure、room fanout、event loop 被阻塞。history.append慢:可能是 JSONL 写入、flush、磁盘 IO。
6. Stream Telemetry 设计
对于“模型 100 token/s,用户可见 50 token/s”的问题,需要专门记录流式链路指标。
建议长期记录的指标
upstream_ttft_ms
upstream_tokens_per_sec
backend_first_patch_ms
backend_patch_count
backend_patch_chars_per_sec
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
这些指标进入 Prometheus/Grafana 后,可以长期观察趋势并做告警;当指标异常时,再通过 trace 和 profiler 下钻到具体请求与具体函数。
7. Profiler 的必要性
Trace 能告诉你 projection.serialize_patch 慢,但 profiler 才能告诉你它为什么慢。
| 慢阶段 | Profiler 要回答的问题 |
|---|---|
projection.serialize_patch |
是否卡在 Pydantic model_dump、JSON 序列化、深层结构复制。 |
prompt.build |
是否卡在 tokenizer、模板渲染、历史裁剪、文件读取。 |
history.append |
是否卡在同步磁盘 IO、flush、锁等待。 |
socket.emit_patch |
是否 event loop 被阻塞、payload 太大、发送队列积压。 |
| 前端可见速度慢 | 是否 store update、diff、markdown 渲染或 React render 成本过高。 |
8. 落地顺序
- 先建立 Prometheus metrics endpoint 和 Grafana dashboard,覆盖 chat 延迟、错误率、并发、LLM token/s、stream slowdown ratio。
- 基于 OpenTelemetry 增加架构边界 span,导出到 Tempo 或 Jaeger。
- 接入 Pyroscope 或 Parca,启用 Python 后端的 continuous profiling。
- 前端增加 performance mark,记录 socket receive、store apply、render visible 的时间差。
- 将 trace_id、session_id、chat_run_id 写入结构化日志,方便从 dashboard 跳转到 trace、profile 和日志。
- 对慢请求和错误请求提高 trace 采样率;普通请求低采样,避免长期存储爆量。
9. 最终建议
长期在线监控应以指标为主,以 trace 和 profiler 为下钻工具:
Prometheus/Grafana -> 发现趋势、告警、容量问题
OpenTelemetry Trace -> 定位一次慢请求慢在哪个阶段
Pyroscope/Parca -> 定位慢阶段里具体哪个函数或调用栈耗时
Loki/结构化日志 -> 补充错误和上下文
Langfuse/Helicone -> 保留 LLM 语义层分析