# 内核二进制压力测试(kernel-stress) 对本仓库**编译出来的真实内核**做压力测试 —— 与 `go test` 的区别是:它跑真二进制、 真插件加载、真 unix socket 协议,因此能抓到只在集成面上出现的问题 (已有战绩:根 agent `DataDir` 漏接线、插件通道没登记为 inputch、create 后子不开工)。 ## 为什么必须放在私有 netns 里 生产实例占着 `*:8080 / *:9890 / *:9876`,而插件的监听都设了 `SO_REUSEADDR`: 同机再起一个实例会**在 127.0.0.1 上与之并存绑定**(实测抢到过 `127.0.0.1:9890` 约 1 分钟)。 `unshare -n` 后实例只有 `lo`,结构上不可能碰到生产端口。 unix socket 是文件系统对象,跨 netns 仍可驱动,所以驱动脚本在 netns 外也能用。 ## 前置 ```bash go build -o /tmp/homed-stress ./cmd/homed # 被压的内核 export GOCACHE=/tmp/gocache GOPATH=/tmp/gopath TMPDIR=/var/tmp/gotmp ``` ## 用法 ```bash # 1) 准备数据目录 + 把 LLM 指向本地 mock(无外网也能跑,且快、可控) DATA=/var/tmp/kstress mkdir -p $DATA # 先跑一次实例建出 config.db,再写入下面这些键(也可直接复用现成目录): # core.llm.provider=mock # core.llm.sources.mock.base_url=http://127.0.0.1:9099/v1 # core.llm.sources.mock.model=mock api_key=mock adapter=openai # core.llm.sources.mock.adapter_path=adapters/openai.lua priority=100 # core.defaults.llm_endpoints=http://127.0.0.1:9099/v1/models # 探活端点(探活用 HEAD!) # core.defaults.rollback.max_retries=100000 auto_rollback=false # 并把 core.llm.sources.deepseek* 删掉(netns 里它不可达,会让 agent 被判 degraded → rollback 循环) # 2) 起 mock LLM + 内核(都在同一个私有 netns 里) MOCK_DELAY_MS=300 MOCK_CHUNKS=8 ./launch.sh /tmp/homed-stress # 3) 取认证密钥(cli 插件回落到 webui.api_key) export KCLI_KEY=$(sqlite3 $DATA/config.db "select value from config_webui where key='api_key';") # 4) 压 ./kcli.py $DATA/cli.sock stats full # 看内核状态(含 scheduler 计数) ./stress.py $DATA/cli.sock "$KCLI_KEY" 16 12 4 20 dense 0.05 # 16 连接×12 输入 + 4 线程×20 中断 ./stress.py $DATA/cli.sock "$KCLI_KEY" 12 12 1 25 0.0 3.0 # 稀疏中断 ⇒ 压抢占/挂起/恢复 ./stress.py $DATA/cli.sock "$KCLI_KEY" 1 1 0 0 resident 0.0 # 驻留子全链路(mock 见 !resident 标记) ``` ## 远程设备(agent ↔ 设备)端到端 设备网关在实例的 netns 内监听 `127.0.0.1:9890`,所以设备客户端要**进同一个 netns** 跑: ```bash TOKEN=$(sqlite3 $DATA/config.db "select value from config_remotedevice where key='ws_token';") nsenter -t $(cat $DATA/pid) -n python3 ./devclient.py --port 9890 --token "$TOKEN" \ --id pydev-1 --caps cmd --seconds 30 --out /var/tmp/push.txt ``` 然后让 agent 主动发一条(mock 里 `!push` 会回一个 `output_send__device/pydev-1` 的工具调用): ```bash # 经 CLI socket 发 "!push",设备侧应收到 {"op":"push", ...} ``` 设备上线/下线会在内核里登记/注销输出通道 `device/`,可用 `/kernel` 的 channels 观察 (在线时出现、掉线后消失)。 > **设计口径**:agent → 设备**必须**是 agent 的主动调用(`output_send__device/`); > 设备的上报(`op=event`)虽然会被注入成输入,但 agent 的回复**不会**被插件自动转回设备 > —— 全仓只有 **webui 与 cli** 两个交互界面"主动转发"(把最终回复渲染成气泡/终端输出), > 其它通道(qq、设备等)一律要求显式 `output_send__<通道>`。 ## 读结果 | 指标 | 含义 | |---|---| | `executed` / `rejected` | 任务执行数 / 被拒数(压力下应为 0) | | 峰值 `峰值_队列` / `峰值_待处理中断` | 采样到的最大排队深度 / 待处理中断数 | | `suspended` / `resumed` / `preempted` | 抢占三件套。**中断要放稀**才会打在高优先级任务上:密集中断会互相同级(L4 vs L4)不抢占,数值会很低 | | `max_suspend_depth` | 中断栈结构上界(= 中断级数 4) | ## 已知坑 - 探活用 **HEAD**(`internal/network/monitor.go`):mock 必须实现 `do_HEAD`,否则 501 → 判不可达 → agent degraded → rollback 循环(会 reset 合并目录)。 - CLI 协议**每条连接串行**处理(`handleChat` 阻塞到本次响应产出):单连接狂发只会排成一条线,造不出队列压力 —— 必须多连接。 - 认证:连接后先发 `/auth `,否则一切命令返回 `unauthorized`。 - `mockllm.py` 的 `!resident` / `!notify` 标记用来让 mock 回工具调用,从而在真内核里驱动 `resident_agents` / `notify_parent`。