Files
NotesAgentic/docs/development/Agent与任务压测报告.md
T

13 KiB
Raw Blame History

Agent 与任务压测报告

日期:2026-09-06。范围:当前第二阶段 Agent Runtime、任务 API、任务列表及 Trace 组件。测试在 Windows 11、Python 3.12.6、独立无头 Chrome 下执行,前端为 Vite 开发模式,纸间时光版本 1.8.1。

1. 结论

功能正确性检查通过:Agent 工具调用、事件序号与回放、终态持久化恢复、容量拒绝、取消、权限等待、故障隔离,以及任务增删改查和分页均得到预期结果。性能仍有三项明确问题:

优先级 问题 本轮证据 建议方向
P1 任务列表未加载后续页;Agent 历史列表也没有后续分页入口 100、1000 条任务的实际页面均只请求 /api/tasks 并显示 50 条;Agent store 同样仅调用默认分页一次,接口默认 50 增加服务端筛选与分页/加载更多,显示总量,防止统计仅基于已加载项
P1 高并发 Agent 阻塞事件循环 200 并发的完成延迟 P95 约 10.55 秒;进程内心跳最大延迟约 3.56 秒 分析同步持久化,设计有界执行调度及顺序写入,保留事件落盘、终态与回放一致性
P1 大 Trace 的树形筛选长时间阻塞 2000 事件树形搜索约 483617 ms10000 事件约 25.91 秒 为匹配的 sequence/tool/model ID 建 Set,避免逐节点重复遍历事件;再评估分段显示及增量更新

本次新增的是可复现压测与报告,没有把上述生产问题标记为已修复。页面滚动与树形搜索是不同瓶颈:10000 事件的滚动 P95 约 40 ms,并不能说明搜索交互也流畅。

2. 隔离与方法

  • 后端脚本在导入任何 app 模块前,将 APP_DATA_DIRAPP_DB_PATHAPP_VAULT_PATH 指向临时目录,结束后清理;不读写用户 Vault、现有任务及 Provider 凭据。
  • Agent 使用内置 Mock Provider,每轮模型调用注入 50 ms 异步等待,正常样本包含一次 system.echo 工具调用和两轮模型调用;不调用真实厂商,也不衡量回答质量或真实模型吞吐。
  • 进程内运行直接调用真实 Runtime、SQLite 和任务 ASGI 路由。任务 HTTP 复核另起独立 Uvicorn 进程,用真实回环连接执行,避免将 ASGI 驱动的连续无让出执行误解为网络服务延迟。
  • 前端挂载真实 TasksView、TraceTimeline 和 Pinia store。压测页拦截全部 fetch,不向真实后端发出请求。任务接口夹具保留默认 50 条分页,先测实际加载条数,再注入完整列表测试渲染上限,两者分开记录。
  • Trace 由确定性模型开始/完成、工具调用/结果事件组成,带调用与父节点 ID;测量首次渲染、时间线过滤、树形切换、匹配全部节点的树形搜索。
  • 滚动用 CDP 派发 120 次滚轮事件,记录真实滚动容器及 maxScrollTop,防止对未滚动页面统计帧间隔。Trace 测试容器显式解除应用根节点的裁剪,由独立滚动区承担真实 Agent 页滚动容器的职责。

本轮每个前端配置测一次;开发编译、后台负载、缓存和浏览器调度都会影响结果,数据用于发现瓶颈,不作为生产环境 SLA。首次渲染计时不包含模块下载,包含 Vue 更新及两个 animation frame。

3. Agent 后端

3.1 正常工具调用与回放

并发数 运行数 完成延迟中位数(ms 完成延迟 P95ms 场景心跳最大延迟(ms
1 20 146.20 170.13 26.84
10 20 405.75 505.74 136.71
50 50 2234.45 2304.42 798.84
200 200 10262.47 10554.52 3559.97

290 个正常运行全部完成,工具返回内容逐项一致;实时订阅事件序号连续,after_sequence=2 回放与原事件后缀一致。新的 Runtime 实例可以从 SQLite 恢复这些终态运行。订阅者集合最终清空,进程内保留记录不超过 200。

“并发”是同时提交的协程数,不代表独立 CPU 工作线程。当前 queued 是启动前状态,Runtime 并没有把超额活跃运行无限排入执行队列。场景心跳还包含提交、持久化回放与恢复校验,不能将其全部归为模型执行耗时;50、200 并发场景的心跳采样数仅 8 个,表中因此列最大值而非宣称稳定分位数。

代码定位:AgentRuntime._publish() 同步调用 AgentTraceRepository.append_event(),后者逐事件连接 SQLite 并提交事务;connect() 还加载扩展和检查迁移。这是需要进一步拆分测量的阻塞路径,本轮未完成各子步骤 CPU/磁盘成本归因。

3.2 容量、取消与故障注入

  • 保持 200 个模型调用活跃,第 201 次创建得到 AgentCapacityError;随后取消全部 200 个运行,全部进入 cancelled,后台任务均结束。
  • 20 个运行等待权限:10 个允许后完成,10 个等待中取消,订阅者均释放。
  • 20 个混合运行:5 个正常完成、5 个 Provider 异常、5 个模型超时、5 个工具超时,均获得预期状态或错误码;工具执行器剩余数量为 0。
  • 正常回放和故障场景合计创建 530 个运行,临时数据库最终约 2.95 MB。工具超时被记录为 ToolResult 错误,后续 Mock 模型仍可能完成运行,不将它误算为整个 Run 必然失败。

这里的“恢复”是新 Runtime 读取终态持久化数据,未模拟 OS 强杀时的写入中断。SSE 通过 Runtime 的事件生成器验证序号及断点回放,没有进行真实网络慢读者/断网压力测试。

4. 任务 API

进程内分别对 100、1000 条任务执行创建、逐页读取、更新为 done、删除,所有 ID 完整,最终总量为 0。默认接口每页 50 条,显式每页 100 条遍历可以取回全部数据。

ASGITransport 的 1000 条突发操作中,心跳出现约 14 秒延迟:测试客户端与同步路由处于同一事件循环,连续就绪协程缺少网络等待,不能将其视作真实 HTTP 健康检查延迟。因此增加独立 Uvicorn + 回环 HTTP 复核:

操作 数量 P95ms 最大值(ms
创建 1000 116.45 134.91
更新 1000 159.29 193.37
删除 1000 156.43 190.10
分页/收尾查询 11 20.96 20.96
并行健康检查 117 157.00 195.07

HTTP 客户端并发 20,总耗时约 18.31 秒;健康检查零失败,分页无重复/遗漏,最终任务数为 0。此处是本机单次结果,未覆盖跨网络、长期持久负载或多进程同时写同一数据库。

5. 前端

5.1 任务列表

100、1000 条数据的实际加载阶段都只有一次 /api/tasks 请求,store 中均为 50 条。以下是额外注入完整 1000 条数据后的结果,不是生产页面当前可以加载 1000 条的证明。

主题 完整列表渲染(ms 完成状态筛选(ms
默认浅色 117.4 44.1
默认深色 137.8 47.8
纸间时光 1.8.1 150.0 59.5

1000 条列表约 11007 个 DOM 节点,实际滚动最远达到 81000 px;筛选 done 得到 334 条,与夹具期望一致。100 条样本筛选得到 34 条,也一致。默认浅色的 1000 条滚动 P95 约 30.2 ms,纸间时光约 50.1 ms;样本数少,暂不据此认定新的单一 CSS 根因。

5.2 Agent Trace

事件数/主题 首次渲染(ms 时间线搜索(ms 树形切换(ms 树形全匹配搜索(ms
2000 / 默认浅色 283.8 97.4 64.0 617.2
2000 / 默认深色 289.0 74.5 62.9 483.0
2000 / 纸间时光 337.3 86.3 62.1 540.1
10000 / 纸间时光 1803.9 582.8 317.5 25914.8

200 事件的树形搜索约 20–29 ms。2000 事件时间线约 20542 个 DOM 节点,10000 事件约 102542 个。查询“压力测试 1”分别命中 444、4444 条事件,与直接检查输入数据的结果一致;树形搜索“压力测试”匹配所有事件,会自动展开匹配子树。

代码定位:TraceTimeline.filteredTree 为每个节点调用 filteredEvents.some(...)。节点数和匹配事件数一起增加时,会产生近似二次增长的匹配工作,随后还需构造并渲染展开子树。建议先用匹配 ID 集合消除嵌套扫描,再测 DOM 更新成本;不要仅用防抖隐藏单次 25 秒阻塞。

10000 是扩大数据规模的诊断测试,高于 Runtime 默认内存事件保留量 2000;它直接向组件传入事件,不代表当前单次 API 页或实时 store 默认会收到这一数量。也未覆盖逐事件 SSE 增量刷新的端到端成本。

6. 复现与原始数据

# 后端:独立临时数据,离线 Mock
backend/.venv/Scripts/python.exe backend/scripts/agent-task-stress.py --output .local-plans/agent-task-backend.json
# 真实 HTTP:独立 Uvicorn、随机回环端口
backend/.venv/Scripts/python.exe backend/scripts/task-http-stress.py --count 1000 --concurrency 20 --output .local-plans/task-http.json
# 前端:先启动独立 Vite,另一个终端执行后续命令
npm --prefix frontend run dev -- --port 5175 --strictPort
backend/.venv/Scripts/python.exe frontend/tests/performance/run-stress.py --url 'http://127.0.0.1:5175/tests/performance/agent-task.html?kind=tasks&theme=paper-moments' --scroll --sizes 100 1000 --runs 1 --output .local-plans/task-ui.json
backend/.venv/Scripts/python.exe frontend/tests/performance/run-stress.py --url 'http://127.0.0.1:5175/tests/performance/agent-task.html?kind=trace&theme=paper-moments' --scroll --sizes 200 2000 10000 --runs 1 --output .local-plans/trace-ui.json

URL 的 theme 可换为 light、dark。测试应串行运行,避免不同负载相互争用;10k Trace 树形筛选可能长时间占用测试浏览器。浏览器使用临时用户目录,不接管用户当前浏览器。

附加回归:test_agent_core.pytest_api.py 共 26 项通过。脚本中的状态、序号、输出、分页与筛选断言均通过。未提交或推送本次材料。

7. 修复后复测(2026-09-06

以上保留首次压测基线。本节对应后台 Trace 批量写入、任务后台写入、完整列表加载、Trace 集合匹配/分页和统一日志接入后的实现。样本开启新操作日志,仍只调用隔离 Mock。

项目 修复前 修复后
200 并发 Agent 完成 P95 10,554.52 ms 1,233.12 ms
200 并发测量区间事件循环最大延迟 3,559.97 ms 509.00 ms
纸间时光 10k Trace 首次渲染 1,803.9 ms 94.4 ms
纸间时光 10k Trace 树形全匹配筛选 25,914.8 ms 189.2 ms
纸间时光 10k Trace 首屏 DOM 节点 约 102,542 1,852
1,000 条任务首次实际加载 50 条 1,000 条(10 次 API 请求)

Agent 测量区间还包含同步读回、校验和重启恢复,最大心跳延迟不是纯执行阶段指标。290 次普通运行、200 个容量/取消场景、20 个权限场景、20 个失败/超时场景全部通过,订阅者和工具执行器释放。不能声称消除了所有主线程工作。

Trace 搜索检查完整数据,只有渲染分页。10k 样本滚动 frame gap P95 为 10.1 ms,无 longtask;时间线筛选 284.9 ms,树形切换 100.9 ms。这是单次本机样本,不代表所有硬件与实时 SSE 输入。

1,000 条任务界面每页 100 条;筛选后数据总数 334,当前页 100。筛选 18.7 ms,滚动 frame gap P95 为 30 ms,无 longtask。断言同时检查完整数据量和分页渲染数量。

真实 HTTP 测试 1,000 条任务、20 并发,CRUD 全部成功,分页完整,最终任务数 0:

指标 修复前 修复后
health P95 157.00 ms 38.64 ms
创建 P95 116.45 ms 157.68 ms
更新 P95 159.29 ms 208.68 ms
删除 P95 156.43 ms 205.45 ms
总耗时 18.31 s 24.51 s

后台排队和操作记录改善了无关请求的响应,但这组样本写入吞吐下降,不能描述为 CRUD 全面提速。后续可以评估任务事务批量化与连接初始化开销;本次没有降低 SQLite 持久化级别。