Files
HomeAgent/docs/zh/kernel-performance.md
JianFeeeee c53d1da2a6 docs: 内核性能实测基线 + payload 透传/保留键设计
## docs/zh/kernel-performance.md(新增)

全部是实测数据,不是估算,每条标出测量方法与复现命令。

冷启动 15.4s(5 层 17 模块 + 22 插件),耗时分布:

	第 0→1 层  3.1s  20%   内存/ONNX/文档存储初始化
	第 1→4 层  2.4s  16%   LLM/知识库/agent 装配
	第 4→完成  9.9s  64%   22 个插件子进程建链

★ 与早期记录(1538–1709ms)的差异要说明:那是**纯内核**装配时间,
不含插件。两者口径不同,混用会得出"启动变慢 10 倍"的错误结论。

其余指标:RSS 26MB / 插件进程 12 个(13 个按需加载)/ 三层工具超时
(30s ctx → max(声明,参数) → 10m 硬上限)/ 上下文预算实测值 /
`offload_busy_after` 1m。

含三个实测踩过的性能陷阱:长工具执行期间 stop 无法生效、
同阶段重复注册静默覆盖、before_output 双触发路径。

## kernel-modularization-design.md 新增 2.7 节

注入 payload 的透传机制与保留键硬约束。

保留键必须覆盖两类:注入路径自己写入的(no_memory/context_policy/…)
与内核读取的语义键(interrupt/stop/command/…)。插件写保留键会被
跳过并记日志 —— 允许覆盖等于允许伪造"这条不记记忆"或"这条立即打断",
那是权限提升。

同时记录 output_send__* 的 stage 语义:before_output/after_output
有两条触发路径,output.go 那条才是"消息真发出去"的时刻,而它历史上
连 Extra 都没初始化(实测 extra_keys=0)。

## 设计过程中的一次返工

曾加 InjectOptions.Meta,等于把同一件事做两遍且只服务注入路径。
用户指出后改为复用 InputEvent.Payload 的扩展口,所有注入路径共用。
2026-10-18 18:36:26 +08:00

202 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 内核性能指标(实测基线)
本文记录**实测**数据,不是估算。每条都标出测量方法与采集时间,
便于复现与追回归。
> 采集环境:`main` @ `ecffb8a`,Linux amd64,Go 1.27.1,
> 生产实例 `/home/newqqagent`(21.4GB 数据目录),
> 22 个外部插件 + 5 层 / 17 模块内核。
> 采集时间:2026-10-18 18:12(冷启动)。
---
## 1. 冷启动
`systemctl restart homeagent` → `内核装配完成`。
| 阶段 | 时刻 | 累计 | 说明 |
| --- | --- | --- | --- |
| 启动 | 18:12:15 | 0s | 依赖图加载(`depgraph.bin`,digest `2067c913`) |
| 第 0 层完成 | 18:12:15 | ~0.1s | 2 个:`config`, `kernel-api` |
| 第 1 层完成 | 18:12:18 | **3.1s** | 9 个:`doc-store`, `knowledge-migration`, `lua`, `memory`, `migration`, `multimodal`, `onnx-parser`, `supervisor`, `tracker` |
| 第 2 层完成 | 18:12:18 | 3.2s | 3 个:`knowledge-store`, `llm`, `text-memory` |
| 第 3 层完成 | 18:12:18 | 3.3s | 1 个:`stage-host` |
| 第 4 层完成 | 18:12:20 | **5.5s** | 2 个:`agent`, `plugin-host` |
| 装配完成 | 18:12:30 | **15.4s** | 含 22 个外部插件建链 |
峰值并发 4(原为 17 步串行)。
### 耗时分布
```text
第 0→1 层 3.1s ████████████████ 20% 内存/ONNX/文档存储初始化
第 1→4 层 2.4s ████████████ 16% LLM/知识库/agent 装配
第 4→完成 9.9s ████████████████ 64% 22 个插件子进程建链
```text
**结论**:启动时间主要由**插件建链**决定(64%),不是内核模块本身。
每多一个 proc 插件约 +0.45s。
> ⚠️ 与早期记录(1538–1709ms)的差异:那是**纯内核**(5 层 17 模块)
> 的装配时间,不含插件。两者口径不同,不要混用。
### 复现
```bash
systemctl restart homeagent
journalctl -u homeagent --since "1 min ago" \
| grep -E "启动顺序来自|第 [0-9] 层完成|内核装配完成"
```text
---
## 2. 资源占用
稳态(启动完成、空闲):
| 指标 | 值 |
| --- | --- |
| 内核 RSS | **26 MB** |
| 内核 VSZ | 2104 MB(含 Go 保留地址空间,非常驻) |
| 插件子进程数 | 12(其余按需加载) |
| 二进制体积 | 84 MB(未 strip,含 debug_info) |
> 12 而非 22:13 个插件声明了 `auto_start: false`(按需加载),
> 只有被 `plugin_enable` 拉起时才起进程。
---
## 3. 超时与容量
这些是**硬约束**,决定"一个任务最多能占用多久"。
### 工具超时(三层)
| 层 | 值 | 位置 |
| --- | --- | --- |
| `cmd_run` 自身 ctx | 默认 **30s**,可由参数调 | `internal/plugins/cmd/plugin.go` |
| 内核工具等待 | `max(声明值, 参数值)` | `internal/agent/core/toolcall.go:37` |
| 硬上限 | **10m** | `toolcall.go:28` `maxToolTimeout` |
计算规则(`resolveToolTimeout`):
```text
base = 60s(默认)
↓ 若 ToolDef 声明了 Timeout
base = 声明值
↓ 若调用参数带 timeout 且 > base
base = 参数值
↓ 若 base > 10m
base = 10m
```text
**实测验证**:
```text
cmd_run command="sleep 200"(不传 timeout)
→ 工具 cmd_run 失败: 命令执行超时(30s)
```text
⚠️ **参数可以合法绕过默认值**:模型传 `timeout="2m"` 时,
`cmd_run` 的 30s 保护被提高到 2 分钟。这是设计允许的(长命令确有需要),
但也意味着**单个工具最长可占用 10 分钟**,期间调度器只能等。
### 上下文预算
运行时日志(`task.go:599`):
```text
tool call loop start, max_ctx=1048576 target=600000 fixed=3004 mem=198998 ctx=397
```text
| 项 | 值 | 含义 |
| --- | --- | --- |
| `max_ctx` | 1048576 | 模型上下文上限(1M) |
| `target` | 600000 | 目标使用量(60 万,留 40% 余量) |
| `fixed` | 3004 | 固定开销(system prompt 等) |
| `mem` | 198998 | 记忆注入占用 |
| `ctx` | 397 | 当前对话占用 |
### 记住-遗忘阈值
| 键 | 值 | 说明 |
|---|---|---|
| `core.agent.offload_busy_after` | **1m** | 忙超过此时长则转投 offload 子 agent |
---
## 4. 安全层开销
| 项 | 值 |
| --- | --- |
| 密钥来源 | `embedded`(构建期注入,指纹 `0db8e9c5`) |
| 变量条数 | 8 |
| 加密算法 | AES-GCM(`enc:v1:` 前缀) |
| 出向/入向扫描 | 每次工具调用与工具返回各一次(`security.Patterns`) |
| 日志脱敏 | `scrubForLog`(幂等,nil-store 安全) |
扫描是**纯内存正则匹配**,未观测到成为瓶颈(工具调用耗时由工具本身主导)。
---
## 5. 插件建链
| 指标 | 值 |
| --- | --- |
| 建链数(本次启动) | 11 |
| `loaded` 事件 | 28 |
| 单插件建链均摊 | ~0.45s(9.9s / 22) |
协议版本:`sdk=1.4.0 core=1.4.0`。
---
## 6. 已知的性能陷阱
以下都是**实测踩过**的,不是理论风险。
### 6.1 长工具执行期间 `stop` 无法生效
`stop` 请求只在 `pre_action` 被消费:
```text
[agent] stop requested by webui (queued=1 will be short-circuited at pre-action)
```text
若 agent 正卡在 `sleep 150` 这类长命令里,**没有 pre_action 可以插入** ⇒
停止按钮要等到当前工具跑完才生效。
现场(2026-10-18):`task#3` 跑了 10 分钟,期间 `task#4`~`task#8`
全部堵在队列,两次 stop 请求都无效,最后只能重启。
### 6.2 同阶段重复注册 = 静默覆盖
插件侧 `stageHandlers` 是 `map[阶段名]handler`,同阶段注册两次后者覆盖前者。
除了功能丢失,还意味着**权限门可能被覆盖掉**。hmapdev 在构建期告警。
### 6.3 `before_output` 有两条触发路径
一轮结束 vs 单次 `output_send__`。误用会让"正在输入"漏触发,
或让状态归位过于频繁(干活期间反复跳回空闲)。
详见 SDK README「阶段钩子」一节。
---
## 7. 复现与回归
```bash
# 冷启动
systemctl restart homeagent && sleep 20
journalctl -u homeagent --since "1 min ago" | grep "内核装配完成"
# 资源
ps -o rss= -p $(pgrep -f "homed" | head -1) | awk '{printf "RSS: %.0f MB\n", $1/1024}'
# 插件进程
pgrep -c -f "plugin.bin"
# 工具超时(应 30s 失败)
# 经 agent 调:cmd_run command="sleep 200"
```text