Files
wandering1 f754d59218 docs(测试说明): 按用途分六个子目录并同步全库引用
分层:流程与规范/ 单元测试/ 板级测试/ 专项验证/ 测试工具/ 测试数据/,
顶层只留新增的目录导航 README.md(归层判据 + 硬约束 + 本次处置记录)。

- 搬迁 28 个路径:测试数据三个目录 58 个文件为纯改名(R100,内容逐字节不变)
- 全库引用同步 124 个文件:AGENTS / docs 索引 / skills / 门禁注释 / scripts
- 门禁面:scripts/check_test_traceability.py 的 glob 改 rglob
  (分层后不递归扫描=追溯门禁直接瞎掉)
- scripts/rename_ref.py:收尾的 ℹ 提示在 GBK 代码页抛 UnicodeEncodeError,
  让进程以退出码 1 收场而改写其实已落盘(RC 与真实结果相反);改为把
  stdout/stderr reconfigure 成 UTF-8,与 check_doc_links.py 等同法
- docs/文档规范.md §1.1 补「子目录文件过多时再分一层」;§3.6 补 rename_ref
  搬整目录的两个实测坑:① 会改写目录内部文件的内容 ② 内部文件的 ../ 出链
  不补层级(本次 23 个被误改的证据文件已按 HEAD 原始字节逐字节还原)
- docs/README.md §6 改为与六个子目录一一对应,并补登原先漏登记的
  「启动日志原子性复验流程」一行
- .cubemx_survival_baseline.json:App/util/task_watchdog.c 的注释路径也随分层更新,
  该文件是存活审计的 watchdog-module 漂移锚点,按脚本自己的提示跑 --write 重建基线
  (实测重建后仅 watchdog-module 一条哈希变化)
- 铁律 4 例外(本次在任务树里改了单写者文件):AGENTS.md 与
  .pre-commit-config.yaml 只被 rename_ref 改写路径字面量(后者全在注释行),
  任务表块与钩子声明一字未动

验证:check_doc_links.py --sections-strict 覆盖全部 88 个改动 .md 全绿;
check_test_traceability.py 输出与主树逐行一致;196 个文本文件无 BOM、无新增
CR、无 U+FFFD、无行尾空白;陈旧引用面由主树 25 处降到 6 处(无新增断链)。
2026-10-30 07:16:07 +08:00

121 lines
7.8 KiB
Markdown
Raw Permalink 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.
# M2 链路延迟实测数据(Phase 13,安静档)
本目录保存 M2(Phase 13:芯片 + 队列时延基线)的**原始实测产物**。判定阈值与解读见
[`../../专项验证/链路延迟测试说明.md`](../../专项验证/链路延迟测试说明.md) §6.2;缺陷总表见 `docs/测试说明/专项验证/链路延迟测试说明.md` §8。
## 1. 运行元数据
| 项 | 值 |
|---|---|
| 日期 | 2026-09-22(设备 RTC 显示 2026-09-21,落后 1 天,仅显示差异;主机时间戳 = 设备 + 4s) |
| 台架 | COM6 @115200;目标 192.168.1.100:8080(Phase 13 独占 socket 2) |
| 固件 | `test/board/test_config.h` = `TEST_SUITE_CH395F`;`test/board/ch395f_test.h` = `ENABLE_PHASE13_TESTS`(仅此一项) |
| 构建 | `keil-build` skill(`keil_build.py`)→ **0 错误 0 警告**(测试构建与产品构建均 0/0) |
| 对端 | `.venv\Scripts\python.exe test/host/latency_peer.py --mode net`(纯标准库,`--wait 170`) |
| 会话顺序 | HELLO → L5_RECV → L5_SEND → L3 → L10 → L6 → L7 → DONE |
| 干扰 | 无(安静档,`ftp_stress=0`;未跑 `--ftp-stress`) |
| PASS 轮次 | **run12 / run13 / run15** 三次独立完整 PASS(`summary verdict=OK`、`=== Phase 13: 1/1 PASSED (failed=0) ===`、peer exit 0) |
复现命令(顺序不可反:先 `clear` 再起 peer,最后烧录):
```powershell
& '.venv\Scripts\python.exe' .dsh\skills\keil-build\scripts\keil_build.py
& '.venv\Scripts\python.exe' .dsh\skills\serial-assistant\scripts\serial_tool.py clear
& '.venv\Scripts\python.exe' test\latency_peer.py --host 192.168.1.100 --port 8080 `
--json "docs\测试说明\测试数据\m2_data\m2_quiet_runNN.json" --wait 170 # 后台作业
& '.venv\Scripts\python.exe' .dsh\skills\stlink-flash\scripts\stlink_flash.py flash
& '.venv\Scripts\python.exe' .dsh\skills\serial-assistant\scripts\serial_tool.py wait-idle `
--timeout 120 --settle-ms 10000 --max-ms 110000
# 判定:Grep .dsh\skills\serial-assistant\scripts\state\serial.log
```
判定标记:Required `App init OK` / `Network Init` / `net init: OK` / `[LAT] report complete` /
`=== Phase 13: 1/1 PASSED`;Forbidden `HardFault` / `assert` / `[ERR]` / `[FAIL]` / `verdict=FAIL`;
peer 侧退出码 **0** = PASS(收到 `0xFF` 并打印汇总,`l7_crc.fail == 0`)。
## 2. 产物清单
| 文件 | 内容 |
|---|---|
| `m2_quiet.json` | **run15**(最终代码,规范基线)peer 端原始 JSON,含 10 帧逐帧批次明细 |
| `m2_quiet_run13.json` | **run13**(PASS 复现)peer 端原始 JSON |
| `m2_quiet_run14.json` | **run14**(PASS;但该轮 MCU 侧探针日志过载,仅 peer 侧数据有效,见 §6) |
| `m2_serial_excerpt_run15.txt` | **run15** 串口证据片段 62 行(**只含烧录重启后的会话**:从 Phase 13 横幅/`run_id` 校准行到 `1/1 PASSED` + relisten) |
| `m2_serial_excerpt_run13.txt` | run13 串口日志中 `[LAT]`/`[NET]`/Phase 13 相关 87 行(含烧录前旧固件段,需自行忽略) |
## 3. 实测结果
### 3.1 MCU 侧(`[LAT]` 报告,三次 PASS)
| 段 | run12 med(min/max, n) | run13 med(min/max, n) | **run15 med(min/max, n)** | warn / crit |
|---|---|---|---|---|
| `queue_rt_recv` | 6014µs(5365/6020, 19) | 6007µs(5392/6025, 20) | **5997µs(5513/6033, 20)** | 10ms / 100ms |
| `queue_rt_send` | 986µs(836/1204, 20) | 987µs(833/1209, 20) | **987µs(838/1209, 20)** | 10ms / 100ms |
| `chunk_wait` | 235µs(215/238, 64) | 219µs(213/377, 64) | **219µs(214/381, 64)** | 10ms / 500ms(spike) |
| `logtax_line` | 4396µs | 4396µs(4390/4414) | **4396µs(4394/4422)** | 10ms / 25ms |
| `req2first_mcu` | 310µs(296/338, 10) | 318µs(263/361, 10) | **319µs(308/338, 10)** | 20ms / 200ms |
| `fullframe_mcu` | 6021µs(5986/6103, 10) | 6052µs(5966/6078, 10) | **6028µs(5991/6066, 10)** | 50ms / 500ms |
三轮均:6 段 `verdict=OK`、`spikes=0`、`summary verdict=OK`、`1/1 PASSED`。
> run12 的 `queue_rt_recv` n=19 而非 20:对端 20×100B 中相邻两包被 TCP 合并成一次收
> (完成判定按字节 2000B),属正常合并,非丢包(三轮字节数均为 2000)。
### 3.2 PC 侧端到端(peer JSON)
| 段 | run12 med(min/max) | run13 med(min/max) | run15 med(min/max) | CRC |
|---|---|---|---|---|
| L6 端到端(op→回显) | 12.5ms(5.5/16.0) | 13.8ms(5.9/16.0) | 8.9ms(6.3/16.0) | — |
| L7 首字节 | 10.9ms(7.9/16.2) | 11.8ms(7.5/17.7) | 11.3ms(7.8/16.5) | — |
| L7 全帧 3209B | 61.1ms(54.2/61.5) | 61.7ms(59.9/70.9) | 57.9ms(52.6/72.3) | 10/0 |
## 4. 关键发现:L7 尾批固定迟到 ~44~60ms
run15 逐帧明细(10/10 帧结构完全一致):
```
frame#1: first=11.620ms batches=[1024,1024,1024,137] tail_lag=+53.8ms
frame#2: first=11.327ms batches=[1024,1024,1024,137] tail_lag=+46.5ms
frame#3: first= 8.160ms batches=[1024,1024,1024,137] tail_lag=+46.5ms
frame#4: first= 7.773ms batches=[1024,1024,1024,137] tail_lag=+44.8ms
frame#5: first= 9.858ms batches=[1024,1024,1024,137] tail_lag=+53.9ms
frame#6: first=11.900ms batches=[1024,1024,1024,137] tail_lag=+43.8ms
frame#7: first=10.465ms batches=[1024,1024,1024,137] tail_lag=+44.2ms
frame#8: first=10.615ms batches=[1024,1024,1024,137] tail_lag=+43.4ms
frame#9: first=11.328ms batches=[1024,1024,1024,137] tail_lag=+60.2ms
frame#10:first=16.522ms batches=[1024,1024,1024,137] tail_lag=+55.7ms
```
- 前 3 批各 1024B 在 **~4ms** 内到齐;第 4 批 137B(=3209−3072)稳定落在 **+43~+60ms**;
- MCU 侧把整个 3209B 交给芯片只花 **6.0ms**(`fullframe_mcu`)→ 延迟**不在 MCU 发送路径**;
- 缺口在**芯片 TCP 尾段发送 / 对端 ACK 交互**(候选:Nagle 式小段等待 + 对端延迟 ACK);
- 该尾批**主导整帧时长**(total ≈ 首字节 + 尾批延迟),是远端显示"每帧多 ~50ms"的直接来源;
- **M3 待办**:Wireshark(`tcp.port==8080`)确认尾段是被芯片扣住还是被对端 ACK 拖住,再定对策
(对端 `TCP_NODELAY` / 帧长与 chunk 对齐),**未证实前不改协议**。
## 5. 结论(回答 M2 立项问题)
1. **安静档不存在 500ms 台阶**:三轮 6 段全 OK、零尖峰、p95≈med;
2. 会话级 MCU 净成本量级 **~6ms**(`queue_rt_recv`,与 netTask 轮询节拍同量级);
3. 芯片 chunk 级不是瓶颈:`chunk_wait` med 219µs ≈ 每 chunk SPI 成本(1.61ms)的 1/7;
4. 生产链路的可见延迟主要在 **PC/线路段**(L6:MCU 侧 0.32ms vs 端到端 8.9~13.8ms)
与 **L7 尾批 ~50ms**。
## 6. 测量方法学(两条必须记住的坑)
1. **探针不得在 net 热路径逐调用打日志**(run14 实证):在 `net_wait_result` 内加
`pick/block` 两行后,日志量 40KB → 1.04MB(9,644/9,779 行 = **98.6%**),115200 下
丢行/串行错位,冲掉 `[LAT]` 段证据,`wait-idle` 永远判不出空闲;并实测把
`queue_rt_send` med 从 987µs 抬到 **7685µs(7.8×)**、`queue_rt_recv` p95 抬到 21ms。
→ 该轮 MCU 侧数据**不作基线**;诊断需求改在 `[LAT]` 段报告侧聚合。
2. **首连必失败是有意为之的正常现象**:peer 首连时常在 MCU 启动期完成握手,随后芯片
空闲超时(15~30s 无数据)RST → `WinError 10054`;peer 自动 +2s 重连后会话正常
(run12/13/15 均如此)。**另注意**:首连时若 MCU 正在烧录,attempt1 由**上一版固件**
服务(日志里那段偏高的数据不属于本轮固件),只有烧录重启后的会话才计入本轮基线。
## 7. 未纳入本目录的数据
- run1~run11 为缺陷定位过程(D11~D16 见 `docs/测试说明/专项验证/链路延迟测试说明.md` §6.1),其中 run10/run11 的 L7 失败、
run9 的 170s 连接拒绝均已定位并修复;
- 压力档(`--ftp-stress` 复现)与 `--mode remo`(生产 RemoDispBus 客户端)属 M3。