merge: task/v5tool2-v11 合入 v2-on-ui
任务 v5tool2(1 个提交)经 task-worktree 回收: - feat(tools): v5 判读工具 v1.1——MISS 降级诊断行,违规只看 stall 与嫌疑段
This commit is contained in:
+17
-8
@@ -277,16 +277,25 @@ $cli = if ($env:DTU_STLINK_CLI) { $env:DTU_STLINK_CLI } else { (Get-Command ST-L
|
||||
真停滞」判别口径落 `../子系统设计/控制台单写者日志设计说明.md` 文末附录——
|
||||
再现静默先按三本账(`dbg_console_dropped/_tx_fault/_tx_timeout`)+ syslog 丢弃
|
||||
账对照定性,再决定是否开卡;销项条件 = N-17 修复版 V5 复跑(e02_probe + FTP
|
||||
≥30 min)运行期 stall 与非开机 `MISS start` 双零。
|
||||
- **判读工具(2026-10-25 起随单交付)**:
|
||||
≥30 min)运行期 `task stall` 与真停滞嫌疑静默段双零(2026-10-25 v1.1 起
|
||||
`MISS start` 降级为诊断行不计销项——见下方判读工具与
|
||||
`../子系统设计/看门狗设计说明.md` §6.1)。
|
||||
- **判读工具 v1.1(2026-10-25 大窗判读 F1 起)**:
|
||||
`'.venv/Scripts/python.exe' scripts/v5_log_gap_report.py <serial-assistant 日志路径>`
|
||||
——自动列 `>10s` 静默段(`--gap-threshold-s` 可调),每段带判定(NO-SOURCE /
|
||||
PROBE-WINDOW / BOOT-WINDOW / TRUE-STALL-SUSPECT)与贴边证据行;内置
|
||||
`task stall` 计数与开机良性豁免(§9 第 8 条口径);退出码 0=双零达标 /
|
||||
1=违规 / 2=日志无有效时戳;`--json` 机读、`--report-only` 只出报告、
|
||||
`--selftest` 内嵌合成样例自检(无台架可跑)。口径唯一来源 =
|
||||
`../子系统设计/控制台单写者日志设计说明.md` 文末附录 + §9 第 8 条——改口径
|
||||
先改文档再改脚本(自检断言会拦漂移)。
|
||||
PROBE-WINDOW / BOOT-WINDOW / TRUE-STALL-SUSPECT)与贴边证据行。违规口径 =
|
||||
`task stall` 行 >0 **或** 真停滞嫌疑段 >0;退出码 0=达标 / 1=违规 /
|
||||
2=日志无有效时戳;`--json` 机读、`--report-only` 只出报告、`--selftest`
|
||||
内嵌合成样例自检(无台架可跑)。**v1.1 变更(判读口径,先文档后脚本)**:
|
||||
① `MISS start` 降级为**诊断行**(看门狗 v1.5 按位分账后它只是复合缺席边沿,
|
||||
不代表停喂,`../子系统设计/看门狗设计说明.md` §6.1)——报告改出归因诊断:
|
||||
开机良性 / 探针窗内 / 窗外运行期 + 窗外每分钟速率 top;v1.0 把运行期 MISS
|
||||
计违规属收紧过头(2026-10-25 台架 70min 窗 1262 条经逐窗归因实为风暴复合边沿
|
||||
与传输窗抖动),予以纠正;② `task stall` 行语义 = v1.5 起「同一任务 800ms
|
||||
单串真停摆」(行文本不变,`../子系统设计/看门狗设计说明.md` §5.3),每行都是
|
||||
真猎物;开机豁免口径(§9 第 8 条)保留、仅作用于 MISS 诊断分类。口径唯一
|
||||
来源 = `../子系统设计/控制台单写者日志设计说明.md` 文末附录 + §9 第 8 条 +
|
||||
看门狗设计说明——改口径先改文档再改脚本(自检断言会拦漂移)。
|
||||
|
||||
**执行步骤**:长跑(§4.2 V5)期间周期性打点 + 对照 `CH395F` 无关的 SD2506 侧日志判别卡点。
|
||||
|
||||
|
||||
@@ -443,7 +443,11 @@ def main(argv=None):
|
||||
g = ap.add_mutually_exclusive_group()
|
||||
g.add_argument("--stamp", action="store_true", help="用新鲜 .map 盖同码戳(@build 后)")
|
||||
g.add_argument("--local", action="store_true", help="本地/ci_local 自查:工作树须与测量同码")
|
||||
g.add_argument("--ci", action="store_true", help="CI 模式:同码性 + 已提交数字的 92% 红线")
|
||||
# ⚠ help 字符串里 % 必须写成 %%(argparse 会对 help 做 %-插值;裸 % + 中文
|
||||
# 如"92% 红"在**无参/非法参的 print_help 路径**上才炸:ValueError
|
||||
# unsupported format character(2026-10-25 道F task-22 顺手修,直调崩溃)
|
||||
g.add_argument("--ci", action="store_true",
|
||||
help="CI 模式:同码性 + 已提交数字的 92%% 红线")
|
||||
g.add_argument("--show", action="store_true", help="打印当前指纹与凭证状态")
|
||||
ap.add_argument("--map", default=str(ram_baseline.MAP_DEFAULT),
|
||||
help=".map 路径(--stamp 用)")
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
「丢弃账 / 故障账」留痕行 = 真停滞嫌疑;零命中 = 无源(系统安静,正常形态)。
|
||||
- 开机良性豁免:docs/测试说明/实机测试实施指南.md §9 第 8 条——开机屏障期
|
||||
(reset cause / armed / netTask: started 等锚点后一窗)偶见的单抽样 MISS start
|
||||
属固有交错,不计运行期违规;连续 task stall 行才是真停摆。
|
||||
属固有交错;且自 v1.1 起 MISS start 整体降级为**诊断行**(复合缺席边沿,不代表
|
||||
停喂,v2-on-ui 看门狗 v1.5 按位分账语义)——违规判定只看 task stall 行与
|
||||
真停滞嫌疑段,见下。
|
||||
|
||||
输入契约(serial-assistant 落盘日志 = 纯文本完整行;两种时钟源自动择一):
|
||||
- 首选采集相对钟:行首 [+NNNms](@test/采集管线相对时戳;相对钟回退 >5s、或出现
|
||||
@@ -23,13 +25,21 @@
|
||||
'.venv/Scripts/python.exe' scripts/v5_log_gap_report.py <日志路径>
|
||||
[--gap-threshold-s 10] [--boot-exempt-s 10] [--post-window-s 8]
|
||||
[--jump-cap-s 3600] [--top 20] [--json] [--report-only] [--selftest]
|
||||
输出 = 汇总(stall 计数 / 运行期 MISS / >=阈值 静默段清单:每段带判定与贴边
|
||||
证据行);--json 出机读整包;--selftest 跑内嵌合成样例(无需台架日志)。
|
||||
退出码:0=达标(零 stall、零运行期 MISS、零真停滞嫌疑段);1=有违规;
|
||||
2=文件不可读/无有效时戳行;3=用法错误;--report-only 分析成功即退 0。
|
||||
输出 = 汇总(stall 计数 / MISS 归因诊断[探针窗内/窗外 + 每分钟速率] / >=阈值
|
||||
静默段清单:每段带判定与贴边证据行);--json 出机读整包;--selftest 跑内嵌
|
||||
合成样例(无需台架日志)。
|
||||
退出码:0=达标(零 stall、零真停滞嫌疑段;MISS 为诊断行不参与判定,v1.1 起);
|
||||
1=有违规;2=文件不可读/无有效时戳行;3=用法错误;--report-only 分析成功即退 0。
|
||||
|
||||
创建日期:2026-10-25(道F 批 2 支援 task-19:探针窗心跳缺口修复后的长跑复跑
|
||||
判读工具;台架执行归 lead,本工具只做主机侧判读)。
|
||||
|
||||
v1.1(2026-10-25,道F task-22 / 大窗判读 F1):违规口径 = stall>0 或真停滞嫌疑段
|
||||
(MISS start 降级为诊断行并出归因:探针窗内/窗外 + 每分钟速率;v1.0 曾把运行期
|
||||
MISS 计违规,经 2026-10-25 台架 70min 窗逐窗归因属收紧过头,予以纠正)。stall 行
|
||||
语义随看门狗 v1.5 按位分账变化(行文本不变):每行 = 同一任务 800ms 单串,见
|
||||
docs/子系统设计/看门狗设计说明.md §5.3/§6.1;使用文档
|
||||
docs/测试说明/实机测试实施指南.md §7.2。
|
||||
"""
|
||||
|
||||
import argparse
|
||||
@@ -174,6 +184,7 @@ def analyze(lines, mode, args):
|
||||
if not points:
|
||||
return None
|
||||
cs = {"stall": 0, "miss": 0, "miss_benign": 0, "miss_runtime": 0,
|
||||
"miss_probe_win": 0, "miss_outside": 0, "miss_minutes": {},
|
||||
"evidence": 0}
|
||||
gaps = []
|
||||
be = args.boot_exempt_s
|
||||
@@ -195,6 +206,14 @@ def analyze(lines, mode, args):
|
||||
cs["miss_benign"] += 1
|
||||
else:
|
||||
cs["miss_runtime"] += 1
|
||||
# v1.1 诊断归因:探针窗内 = 重建风暴的复合边沿(正常形态),
|
||||
# 窗外 = 值得看一眼的缺席;两者都不参与违规判定
|
||||
if any(op - 2.0 <= ts <= cl + 2.0 for op, cl in pwins):
|
||||
cs["miss_probe_win"] += 1
|
||||
else:
|
||||
cs["miss_outside"] += 1
|
||||
mi = int(ts // 60)
|
||||
cs["miss_minutes"][mi] = cs["miss_minutes"].get(mi, 0) + 1
|
||||
for i in range(1, len(sp)):
|
||||
t0 = sp[i - 1][0]
|
||||
t1 = sp[i][0]
|
||||
@@ -224,20 +243,35 @@ def analyze(lines, mode, args):
|
||||
"before": _clip(sp[i - 1][1][-1].text),
|
||||
"after": _clip(sp[i][1][0].text),
|
||||
"evidence_lines": evl})
|
||||
# v1.1 违规口径(道F task-22,2026-10-25 大窗判读 F1):
|
||||
# violation := stall>0(看门狗 v1.5 按位分账后每行 = 同一任务连续缺席 >=8
|
||||
# 窗的单任务真停摆,docs/子系统设计/看门狗设计说明.md §5.3)
|
||||
# 或 真停滞嫌疑段 >0。MISS start 自 v1.1 起降级为**诊断行**(复合缺席边沿,
|
||||
# 不代表停喂,docs/子系统设计/看门狗设计说明.md §6.1):v1.0 把运行期 MISS
|
||||
# 计入违规属把判读口径收紧过头(2026-10-25 台架 70min 窗 1262 条运行期 MISS
|
||||
# 经逐窗归因实为风暴复合边沿 + 传输窗抖动,非心跳丢失),改出下方归因诊断。
|
||||
violations = []
|
||||
if cs["stall"] > 0:
|
||||
n1 = cs["stall"]
|
||||
violations.append(f"task stall 行 {n1} 条(判据 = 0;连续 stall 行 = 真停摆)")
|
||||
if cs["miss_runtime"] > 0:
|
||||
n2 = cs["miss_runtime"]
|
||||
violations.append(f"运行期(非开机豁免窗)MISS start {n2} 条")
|
||||
violations.append(f"task stall 行 {n1} 条(判据 = 0;v1.5 按位分账后 "
|
||||
f"每行 = 同一任务 800ms 单串真停摆)")
|
||||
nsus = sum(1 for g in gaps if g["verdict"] == "TRUE-STALL-SUSPECT")
|
||||
if nsus:
|
||||
violations.append(f"真停滞嫌疑静默段 {nsus} 处(贴边命中丢弃/故障账)")
|
||||
topmin = sorted(cs["miss_minutes"].items(), key=lambda kv: -kv[1])[:5]
|
||||
mdiag = {
|
||||
"total": cs["miss"],
|
||||
"boot_benign": cs["miss_benign"],
|
||||
"runtime": cs["miss_runtime"],
|
||||
"in_probe_window": cs["miss_probe_win"],
|
||||
"outside_windows": cs["miss_outside"],
|
||||
"runtime_top_minutes": [{"minute": mi, "count": nc} for mi, nc in topmin],
|
||||
"semantics": "v1.1 诊断行:MISS start = 复合缺席边沿,不代表停喂,不参与违规",
|
||||
}
|
||||
return {"mode": mode, "points": len(points), "sessions": len(sessions),
|
||||
"meta": meta, "counters": cs, "threshold_s": args.gap_threshold_s,
|
||||
"gaps_over_threshold": gaps, "violations": violations,
|
||||
"verdict_pass": not violations}
|
||||
"gaps_over_threshold": gaps, "miss_diag": mdiag,
|
||||
"violations": violations, "verdict_pass": not violations}
|
||||
|
||||
|
||||
def render(rep, args):
|
||||
@@ -257,6 +291,17 @@ def render(rep, args):
|
||||
out.append(f"[计数] task stall={c['stall']} | MISS start 总={c['miss']}"
|
||||
f"(开机良性={c['miss_benign']} / 运行期={c['miss_runtime']})"
|
||||
f"| 账目留痕行={c['evidence']}")
|
||||
mdg = rep["miss_diag"]
|
||||
tot, bb = mdg["total"], mdg["boot_benign"]
|
||||
ipw, outw = mdg["in_probe_window"], mdg["outside_windows"]
|
||||
out.append(f"[MISS 诊断(非违规)] 总={tot} 开机良性={bb} 探针窗内={ipw}"
|
||||
f" 窗外运行期={outw}")
|
||||
if mdg["runtime_top_minutes"]:
|
||||
parts = []
|
||||
for mm in mdg["runtime_top_minutes"]:
|
||||
parts.append("第%d分x%d" % (mm["minute"], mm["count"]))
|
||||
out.append(" 窗外 MISS 每分钟速率 top:" + " ".join(parts)
|
||||
+ "(分钟号=所选时钟源时基的第几分钟)")
|
||||
gaps = rep["gaps_over_threshold"]
|
||||
out.append(f"[静默] >= {thr:.0f}s 共 {len(gaps)} 段:")
|
||||
for k, g in enumerate(gaps[: args.top], 1):
|
||||
@@ -274,15 +319,17 @@ def render(rep, args):
|
||||
for v in rep["violations"]:
|
||||
out.append(" - " + v)
|
||||
else:
|
||||
out.append("结论: PASS(零 stall / 零运行期 MISS / 零真停滞嫌疑段;"
|
||||
"NO-SOURCE 段属正常空闲,不构成违规)")
|
||||
out.append("结论: PASS(零 stall / 零真停滞嫌疑段;MISS 为诊断行、"
|
||||
"NO-SOURCE 为正常空闲,均不构成违规)")
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
def selftest():
|
||||
fails = []
|
||||
nchk = [0]
|
||||
|
||||
def check(cond, name):
|
||||
nchk[0] += 1
|
||||
if not cond:
|
||||
fails.append(name)
|
||||
|
||||
@@ -331,6 +378,29 @@ def selftest():
|
||||
check(by.get("PROBE-WINDOW(no-source)") == 1, f"探针窗段=1(得 {by})")
|
||||
check(by.get("NO-SOURCE") == 2, f"无源段=2(得 {by})")
|
||||
check(rep["verdict_pass"] is False, "含 stall 的样例应判 FAIL")
|
||||
# v1.1 回归钉:零 stall + 运行期 MISS 存在 = PASS(MISS 已降级为诊断行),
|
||||
# 且归因正确:探针窗内 / 窗外各自计数、每分钟速率分桶
|
||||
rel2 = "\n".join([
|
||||
"[+100ms] reset cause: POR",
|
||||
"[+5000ms] [2026-01-01 00:00:05][NET] link up",
|
||||
"[+40000ms] [2026-01-01 00:00:40][TS] no TIME packet in 30s",
|
||||
"[+45000ms] MISS start snap=0x000001F6 req=0x000001F7 t=45000",
|
||||
"[+47000ms] [2026-01-01 00:00:47][NET] ftp session end",
|
||||
"[+50000ms] [INFO][LIS] sock1 auto-relisten ok (drop)",
|
||||
"[+70000ms] MISS start snap=0x000001E7 req=0x000001F7 t=70000",
|
||||
"[+72000ms] [2026-01-01 00:01:12][UI] tick",
|
||||
]) + "\n"
|
||||
lines3, mode3 = parse_stream(rel2)
|
||||
rep3 = analyze(lines3, mode3, A())
|
||||
c3, d3 = rep3["counters"], rep3["miss_diag"]
|
||||
check(rep3["verdict_pass"] is True,
|
||||
f"零 stall + 运行期 MISS 应判 PASS(v1.1 降级口径)(得 {rep3['violations']})")
|
||||
check(d3["runtime"] == 2 and d3["total"] == 2,
|
||||
f"MISS 运行期=2(得 {d3['runtime']}/{d3['total']})")
|
||||
check(d3["in_probe_window"] == 1 and d3["outside_windows"] == 1,
|
||||
f"探针窗内/窗外 = 1/1(得 {d3['in_probe_window']}/{d3['outside_windows']})")
|
||||
check(sum(x["count"] for x in d3["runtime_top_minutes"]) == 2,
|
||||
"窗外+窗内 MISS 分钟桶总量=2")
|
||||
abslog = "\n".join([
|
||||
"[2026-01-01 00:00:00] boot banner",
|
||||
"[----/--/-- --:--:--][APP] reset cause: POR",
|
||||
@@ -350,8 +420,8 @@ def selftest():
|
||||
for f in fails:
|
||||
print("SELFTEST FAIL: " + f)
|
||||
return 1
|
||||
print("SELFTEST PASS 15/15(rel+abs 双钟源、会话切分、四类判定、"
|
||||
"良性豁免、时钟跳变边界)")
|
||||
print(f"SELFTEST PASS {nchk[0]}/{nchk[0]}(rel+abs 双钟源、会话切分、四类"
|
||||
"判定、良性豁免、时钟跳变边界、v1.1 MISS 诊断降级与归因)")
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user