入库台架工具:真实路径冻结/中止/重连复现与判读脚本,含已撤回台架归档(带README)
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
RETRACTED 2026-09-22 bench artifacts (TC-NET-604 'freeze' campaign).
|
||||
Their 'STALL/WEDGE' verdicts were artifacts: (1) in-firmware non-echoing readers (Phase 9/11, probe) ate the peer's bytes; (2) probe_classify.py mode A/C misreported passing runs as STALL.
|
||||
TC-NET-604 is CLOSED as NOT-A-DEFECT (see docs/问题记录/CH395F_Trap_Records.md Trap 21).
|
||||
Do NOT use these recipes/verdicts for freeze detection.
|
||||
Trustworthy tools (kept one level up): ftp_freeze_probe.py (real FTP path + RETR SHA-256), ftp_abort_probe.py (abort/reconnect), bench_verdict.py (script-level STEP truth).
|
||||
@@ -0,0 +1,653 @@
|
||||
{
|
||||
"mode": "remo",
|
||||
"host": "192.168.1.100",
|
||||
"port": 7003,
|
||||
"ts_start": "2026-09-22 16:37:37",
|
||||
"ts_end": "2026-09-22 16:37:40",
|
||||
"duration_s": 2.83,
|
||||
"samples": 50,
|
||||
"cadence_ms": 100.0,
|
||||
"tcp_nodelay": false,
|
||||
"immediate_ack": null,
|
||||
"in_band_ack_probe": false,
|
||||
"stress_from": null,
|
||||
"connect_ms": 3.628,
|
||||
"reconnect_ms": null,
|
||||
"reconnect_list_ms": [],
|
||||
"reconnect_stat": {
|
||||
"n": 0
|
||||
},
|
||||
"init": {
|
||||
"rtt_ms": 20.445,
|
||||
"geom": {
|
||||
"w": 160,
|
||||
"h": 160,
|
||||
"framebytes": 3200,
|
||||
"payload_len": 8
|
||||
}
|
||||
},
|
||||
"cycle_all": {
|
||||
"n": 17,
|
||||
"min_ms": 47.914,
|
||||
"med_ms": 62.139,
|
||||
"p95_ms": 71.32,
|
||||
"max_ms": 71.32,
|
||||
"spikes500": 0
|
||||
},
|
||||
"cycle_quiet": {
|
||||
"n": 17,
|
||||
"min_ms": 47.914,
|
||||
"med_ms": 62.139,
|
||||
"p95_ms": 71.32,
|
||||
"max_ms": 71.32,
|
||||
"spikes500": 0
|
||||
},
|
||||
"cycle_stress": {
|
||||
"n": 0
|
||||
},
|
||||
"first_byte_all": {
|
||||
"n": 17,
|
||||
"min_ms": 3.359,
|
||||
"med_ms": 12.193,
|
||||
"p95_ms": 15.251,
|
||||
"max_ms": 15.251,
|
||||
"spikes500": 0
|
||||
},
|
||||
"tail_lag_all": {
|
||||
"n": 17,
|
||||
"min_ms": 44.042,
|
||||
"med_ms": 52.676,
|
||||
"p95_ms": 58.277,
|
||||
"max_ms": 58.277,
|
||||
"spikes500": 0
|
||||
},
|
||||
"crc": {
|
||||
"ok": 17,
|
||||
"fail": 0
|
||||
},
|
||||
"bitmap": {
|
||||
"nontrivial_ok": 17,
|
||||
"nontrivial_fail": 0
|
||||
},
|
||||
"frames": [
|
||||
{
|
||||
"cycle": 0,
|
||||
"stress": false,
|
||||
"first_ms": 13.517,
|
||||
"total_ms": 59.748,
|
||||
"tail_lag_ms": 46.078,
|
||||
"batches": [
|
||||
[
|
||||
13.517,
|
||||
8
|
||||
],
|
||||
[
|
||||
13.559,
|
||||
1016
|
||||
],
|
||||
[
|
||||
15.33,
|
||||
1024
|
||||
],
|
||||
[
|
||||
16.725,
|
||||
1024
|
||||
],
|
||||
[
|
||||
59.595,
|
||||
137
|
||||
]
|
||||
],
|
||||
"bytes": 3209,
|
||||
"tag": 187,
|
||||
"cmd": 3,
|
||||
"crc_ok": true,
|
||||
"nontrivial": true
|
||||
},
|
||||
{
|
||||
"cycle": 1,
|
||||
"stress": false,
|
||||
"first_ms": 13.514,
|
||||
"total_ms": 63.153,
|
||||
"tail_lag_ms": 49.579,
|
||||
"batches": [
|
||||
[
|
||||
13.514,
|
||||
8
|
||||
],
|
||||
[
|
||||
13.579,
|
||||
1016
|
||||
],
|
||||
[
|
||||
15.264,
|
||||
1024
|
||||
],
|
||||
[
|
||||
17.108,
|
||||
1024
|
||||
],
|
||||
[
|
||||
63.092,
|
||||
137
|
||||
]
|
||||
],
|
||||
"bytes": 3209,
|
||||
"tag": 187,
|
||||
"cmd": 3,
|
||||
"crc_ok": true,
|
||||
"nontrivial": true
|
||||
},
|
||||
{
|
||||
"cycle": 2,
|
||||
"stress": false,
|
||||
"first_ms": 10.477,
|
||||
"total_ms": 65.17,
|
||||
"tail_lag_ms": 54.592,
|
||||
"batches": [
|
||||
[
|
||||
10.477,
|
||||
8
|
||||
],
|
||||
[
|
||||
10.51,
|
||||
1016
|
||||
],
|
||||
[
|
||||
11.859,
|
||||
1024
|
||||
],
|
||||
[
|
||||
13.165,
|
||||
1024
|
||||
],
|
||||
[
|
||||
65.069,
|
||||
137
|
||||
]
|
||||
],
|
||||
"bytes": 3209,
|
||||
"tag": 187,
|
||||
"cmd": 3,
|
||||
"crc_ok": true,
|
||||
"nontrivial": true
|
||||
},
|
||||
{
|
||||
"cycle": 3,
|
||||
"stress": false,
|
||||
"first_ms": 15.251,
|
||||
"total_ms": 67.989,
|
||||
"tail_lag_ms": 52.676,
|
||||
"batches": [
|
||||
[
|
||||
15.251,
|
||||
8
|
||||
],
|
||||
[
|
||||
15.29,
|
||||
1016
|
||||
],
|
||||
[
|
||||
17.545,
|
||||
1024
|
||||
],
|
||||
[
|
||||
18.689,
|
||||
1024
|
||||
],
|
||||
[
|
||||
67.928,
|
||||
137
|
||||
]
|
||||
],
|
||||
"bytes": 3209,
|
||||
"tag": 187,
|
||||
"cmd": 3,
|
||||
"crc_ok": true,
|
||||
"nontrivial": true
|
||||
},
|
||||
{
|
||||
"cycle": 4,
|
||||
"stress": false,
|
||||
"first_ms": 6.239,
|
||||
"total_ms": 56.937,
|
||||
"tail_lag_ms": 50.635,
|
||||
"batches": [
|
||||
[
|
||||
6.239,
|
||||
8
|
||||
],
|
||||
[
|
||||
6.266,
|
||||
1016
|
||||
],
|
||||
[
|
||||
8.081,
|
||||
1024
|
||||
],
|
||||
[
|
||||
9.893,
|
||||
1024
|
||||
],
|
||||
[
|
||||
56.873,
|
||||
137
|
||||
]
|
||||
],
|
||||
"bytes": 3209,
|
||||
"tag": 187,
|
||||
"cmd": 3,
|
||||
"crc_ok": true,
|
||||
"nontrivial": true
|
||||
},
|
||||
{
|
||||
"cycle": 5,
|
||||
"stress": false,
|
||||
"first_ms": 8.853,
|
||||
"total_ms": 62.139,
|
||||
"tail_lag_ms": 53.201,
|
||||
"batches": [
|
||||
[
|
||||
8.853,
|
||||
8
|
||||
],
|
||||
[
|
||||
8.903,
|
||||
1016
|
||||
],
|
||||
[
|
||||
10.628,
|
||||
1024
|
||||
],
|
||||
[
|
||||
12.473,
|
||||
1024
|
||||
],
|
||||
[
|
||||
62.053,
|
||||
137
|
||||
]
|
||||
],
|
||||
"bytes": 3209,
|
||||
"tag": 187,
|
||||
"cmd": 3,
|
||||
"crc_ok": true,
|
||||
"nontrivial": true
|
||||
},
|
||||
{
|
||||
"cycle": 6,
|
||||
"stress": false,
|
||||
"first_ms": 14.097,
|
||||
"total_ms": 70.635,
|
||||
"tail_lag_ms": 56.473,
|
||||
"batches": [
|
||||
[
|
||||
14.097,
|
||||
8
|
||||
],
|
||||
[
|
||||
14.14,
|
||||
1016
|
||||
],
|
||||
[
|
||||
15.894,
|
||||
1024
|
||||
],
|
||||
[
|
||||
17.742,
|
||||
1024
|
||||
],
|
||||
[
|
||||
70.57,
|
||||
137
|
||||
]
|
||||
],
|
||||
"bytes": 3209,
|
||||
"tag": 187,
|
||||
"cmd": 3,
|
||||
"crc_ok": true,
|
||||
"nontrivial": true
|
||||
},
|
||||
{
|
||||
"cycle": 7,
|
||||
"stress": false,
|
||||
"first_ms": 14.092,
|
||||
"total_ms": 70.009,
|
||||
"tail_lag_ms": 55.854,
|
||||
"batches": [
|
||||
[
|
||||
14.092,
|
||||
8
|
||||
],
|
||||
[
|
||||
14.129,
|
||||
1016
|
||||
],
|
||||
[
|
||||
15.891,
|
||||
1024
|
||||
],
|
||||
[
|
||||
17.73,
|
||||
1024
|
||||
],
|
||||
[
|
||||
69.946,
|
||||
137
|
||||
]
|
||||
],
|
||||
"bytes": 3209,
|
||||
"tag": 187,
|
||||
"cmd": 3,
|
||||
"crc_ok": true,
|
||||
"nontrivial": true
|
||||
},
|
||||
{
|
||||
"cycle": 8,
|
||||
"stress": false,
|
||||
"first_ms": 3.805,
|
||||
"total_ms": 47.914,
|
||||
"tail_lag_ms": 44.042,
|
||||
"batches": [
|
||||
[
|
||||
3.805,
|
||||
8
|
||||
],
|
||||
[
|
||||
3.837,
|
||||
1016
|
||||
],
|
||||
[
|
||||
5.599,
|
||||
1024
|
||||
],
|
||||
[
|
||||
7.473,
|
||||
1024
|
||||
],
|
||||
[
|
||||
47.847,
|
||||
137
|
||||
]
|
||||
],
|
||||
"bytes": 3209,
|
||||
"tag": 187,
|
||||
"cmd": 3,
|
||||
"crc_ok": true,
|
||||
"nontrivial": true
|
||||
},
|
||||
{
|
||||
"cycle": 9,
|
||||
"stress": false,
|
||||
"first_ms": 4.308,
|
||||
"total_ms": 56.061,
|
||||
"tail_lag_ms": 51.644,
|
||||
"batches": [
|
||||
[
|
||||
4.308,
|
||||
8
|
||||
],
|
||||
[
|
||||
4.335,
|
||||
1016
|
||||
],
|
||||
[
|
||||
6.114,
|
||||
1024
|
||||
],
|
||||
[
|
||||
7.966,
|
||||
1024
|
||||
],
|
||||
[
|
||||
55.953,
|
||||
137
|
||||
]
|
||||
],
|
||||
"bytes": 3209,
|
||||
"tag": 187,
|
||||
"cmd": 3,
|
||||
"crc_ok": true,
|
||||
"nontrivial": true
|
||||
},
|
||||
{
|
||||
"cycle": 10,
|
||||
"stress": false,
|
||||
"first_ms": 7.908,
|
||||
"total_ms": 55.037,
|
||||
"tail_lag_ms": 47.068,
|
||||
"batches": [
|
||||
[
|
||||
7.908,
|
||||
8
|
||||
],
|
||||
[
|
||||
7.946,
|
||||
1016
|
||||
],
|
||||
[
|
||||
9.693,
|
||||
1024
|
||||
],
|
||||
[
|
||||
11.515,
|
||||
1024
|
||||
],
|
||||
[
|
||||
54.977,
|
||||
137
|
||||
]
|
||||
],
|
||||
"bytes": 3209,
|
||||
"tag": 187,
|
||||
"cmd": 3,
|
||||
"crc_ok": true,
|
||||
"nontrivial": true
|
||||
},
|
||||
{
|
||||
"cycle": 11,
|
||||
"stress": false,
|
||||
"first_ms": 12.254,
|
||||
"total_ms": 68.577,
|
||||
"tail_lag_ms": 56.259,
|
||||
"batches": [
|
||||
[
|
||||
12.254,
|
||||
8
|
||||
],
|
||||
[
|
||||
12.29,
|
||||
1016
|
||||
],
|
||||
[
|
||||
14.079,
|
||||
1024
|
||||
],
|
||||
[
|
||||
15.907,
|
||||
1024
|
||||
],
|
||||
[
|
||||
68.513,
|
||||
137
|
||||
]
|
||||
],
|
||||
"bytes": 3209,
|
||||
"tag": 187,
|
||||
"cmd": 3,
|
||||
"crc_ok": true,
|
||||
"nontrivial": true
|
||||
},
|
||||
{
|
||||
"cycle": 12,
|
||||
"stress": false,
|
||||
"first_ms": 3.359,
|
||||
"total_ms": 57.811,
|
||||
"tail_lag_ms": 54.392,
|
||||
"batches": [
|
||||
[
|
||||
3.359,
|
||||
8
|
||||
],
|
||||
[
|
||||
3.393,
|
||||
1016
|
||||
],
|
||||
[
|
||||
5.18,
|
||||
1024
|
||||
],
|
||||
[
|
||||
7.013,
|
||||
1024
|
||||
],
|
||||
[
|
||||
57.751,
|
||||
137
|
||||
]
|
||||
],
|
||||
"bytes": 3209,
|
||||
"tag": 187,
|
||||
"cmd": 3,
|
||||
"crc_ok": true,
|
||||
"nontrivial": true
|
||||
},
|
||||
{
|
||||
"cycle": 13,
|
||||
"stress": false,
|
||||
"first_ms": 13.204,
|
||||
"total_ms": 59.676,
|
||||
"tail_lag_ms": 46.401,
|
||||
"batches": [
|
||||
[
|
||||
13.204,
|
||||
8
|
||||
],
|
||||
[
|
||||
13.258,
|
||||
1016
|
||||
],
|
||||
[
|
||||
14.962,
|
||||
1024
|
||||
],
|
||||
[
|
||||
16.799,
|
||||
1024
|
||||
],
|
||||
[
|
||||
59.604,
|
||||
137
|
||||
]
|
||||
],
|
||||
"bytes": 3209,
|
||||
"tag": 187,
|
||||
"cmd": 3,
|
||||
"crc_ok": true,
|
||||
"nontrivial": true
|
||||
},
|
||||
{
|
||||
"cycle": 14,
|
||||
"stress": false,
|
||||
"first_ms": 12.941,
|
||||
"total_ms": 71.32,
|
||||
"tail_lag_ms": 58.277,
|
||||
"batches": [
|
||||
[
|
||||
12.941,
|
||||
8
|
||||
],
|
||||
[
|
||||
12.973,
|
||||
1016
|
||||
],
|
||||
[
|
||||
14.747,
|
||||
1024
|
||||
],
|
||||
[
|
||||
16.595,
|
||||
1024
|
||||
],
|
||||
[
|
||||
71.218,
|
||||
137
|
||||
]
|
||||
],
|
||||
"bytes": 3209,
|
||||
"tag": 187,
|
||||
"cmd": 3,
|
||||
"crc_ok": true,
|
||||
"nontrivial": true
|
||||
},
|
||||
{
|
||||
"cycle": 15,
|
||||
"stress": false,
|
||||
"first_ms": 12.193,
|
||||
"total_ms": 61.254,
|
||||
"tail_lag_ms": 48.998,
|
||||
"batches": [
|
||||
[
|
||||
12.193,
|
||||
8
|
||||
],
|
||||
[
|
||||
12.249,
|
||||
1016
|
||||
],
|
||||
[
|
||||
14.025,
|
||||
1024
|
||||
],
|
||||
[
|
||||
15.82,
|
||||
1024
|
||||
],
|
||||
[
|
||||
61.191,
|
||||
137
|
||||
]
|
||||
],
|
||||
"bytes": 3209,
|
||||
"tag": 187,
|
||||
"cmd": 3,
|
||||
"crc_ok": true,
|
||||
"nontrivial": true
|
||||
},
|
||||
{
|
||||
"cycle": 16,
|
||||
"stress": false,
|
||||
"first_ms": 10.55,
|
||||
"total_ms": 65.811,
|
||||
"tail_lag_ms": 55.201,
|
||||
"batches": [
|
||||
[
|
||||
10.55,
|
||||
8
|
||||
],
|
||||
[
|
||||
10.581,
|
||||
1016
|
||||
],
|
||||
[
|
||||
12.348,
|
||||
1024
|
||||
],
|
||||
[
|
||||
14.233,
|
||||
1024
|
||||
],
|
||||
[
|
||||
65.751,
|
||||
137
|
||||
]
|
||||
],
|
||||
"bytes": 3209,
|
||||
"tag": 187,
|
||||
"cmd": 3,
|
||||
"crc_ok": true,
|
||||
"nontrivial": true
|
||||
}
|
||||
],
|
||||
"error": "[WinError 10054] 远程主机强迫关闭了一个现有的连接。"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,60 @@
|
||||
# Extract the per-run chip command sequence (netcmd lines) from the serial log.
|
||||
# usage: python MDK-ARM/netcmd_extract.py <runlog> <seriallog> <run_index> [max_lines]
|
||||
# run_index = 1-based run position in the session (TS lines in the runlog delimit
|
||||
# each run's device-time window; PC local = device UTC + 8h, 4s margins).
|
||||
import re, sys
|
||||
from datetime import datetime, timezone
|
||||
|
||||
runlog, seriallog = sys.argv[1], sys.argv[2]
|
||||
base = int(sys.argv[3])
|
||||
maxl = int(sys.argv[4]) if len(sys.argv) > 4 else 300
|
||||
nextrunlog = sys.argv[5] if (len(sys.argv) > 5 and sys.argv[5] not in ("focus", "plain")) else None
|
||||
focus = any(a == "focus" for a in sys.argv[5:])
|
||||
|
||||
ts = []
|
||||
for raw in open(runlog, encoding="utf-8-sig", errors="replace"):
|
||||
m = re.match(r"TS (\d+)", raw.strip())
|
||||
if m:
|
||||
ts.append(int(m.group(1)))
|
||||
ts_next = None
|
||||
if nextrunlog:
|
||||
for raw in open(nextrunlog, encoding="utf-8-sig", errors="replace"):
|
||||
m = re.match(r"TS (\d+)", raw.strip())
|
||||
if m:
|
||||
ts_next = int(m.group(1))
|
||||
break
|
||||
if base - 1 >= len(ts):
|
||||
print("run_index out of range (%d TS lines)" % len(ts))
|
||||
sys.exit(2)
|
||||
|
||||
lo = ts[base - 1] - 4.0
|
||||
hi = (ts_next - 4.0) if ts_next is not None else (lo + 600.0)
|
||||
|
||||
CMD_RE = re.compile(r"netcmd: (\S+) fd=(-?\d+) t=(\d+) d=(\d+) x=(-?\d+)")
|
||||
DEV_RE = re.compile(r"\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\]\[")
|
||||
|
||||
n = 0
|
||||
for raw in open(seriallog, encoding="utf-8", errors="replace"):
|
||||
m = CMD_RE.search(raw)
|
||||
if not m:
|
||||
continue
|
||||
t = DEV_RE.search(raw)
|
||||
if not t:
|
||||
continue
|
||||
dt = datetime.strptime(t.group(1), "%Y-%m-%d %H:%M:%S").replace(tzinfo=timezone.utc).timestamp()
|
||||
if lo <= dt < hi:
|
||||
tag, fd, x = m.group(1), m.group(2), m.group(5)
|
||||
if focus:
|
||||
# 只留数据 socket 的 R/S 与 poll/事件行;A(各服务 accept 轮询洪水)与
|
||||
# 稳态空读(phase6 每 tick 状态读)全部降噪
|
||||
if tag == "R" and fd == "2" and x == "-1":
|
||||
continue
|
||||
if tag not in ("R", "S", "P", "p", "a", "d", "x"):
|
||||
continue
|
||||
if tag in ("R", "S") and fd != "2":
|
||||
continue
|
||||
n += 1
|
||||
if n <= maxl:
|
||||
print("%s fd=%s t=%s d=%s x=%s" % (m.group(1), m.group(2),
|
||||
m.group(3), m.group(4), m.group(5)))
|
||||
print("netcmd lines in run window: %d" % n)
|
||||
@@ -0,0 +1,27 @@
|
||||
# One-off bench probe: isolated 4096B echo test (4KB through 2KB chip buffer).
|
||||
# Connects to MCU :8080, sends 4096B increment pattern, waits for full echo.
|
||||
import socket, sys, time
|
||||
|
||||
ip, port = "192.168.1.100", 8080
|
||||
data = bytes((i & 0xFF) for i in range(4096))
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.settimeout(15)
|
||||
t0 = time.time()
|
||||
s.connect((ip, port))
|
||||
s.sendall(data)
|
||||
rx = b""
|
||||
first = None
|
||||
while len(rx) < 4096:
|
||||
chunk = s.recv(4096 - len(rx))
|
||||
if not chunk:
|
||||
break
|
||||
if first is None:
|
||||
first = time.time()
|
||||
rx += chunk
|
||||
elapsed = (time.time() - t0) * 1000
|
||||
if len(rx) == 4096 and rx == data:
|
||||
print("PASS 4096B echo full match in %.1f ms (first byte %.1f ms)" % (elapsed, (first - t0) * 1000))
|
||||
else:
|
||||
print("FAIL 4096B echo: got %d/4096 bytes after %.1f ms" % (len(rx), elapsed))
|
||||
s.close()
|
||||
sys.exit(0 if len(rx) == 4096 and rx == data else 1)
|
||||
@@ -0,0 +1,57 @@
|
||||
# Shared helpers for the 2026-09-22 primary-cause localization battery.
|
||||
# Pure-ASCII output (run logs are parsed by probe_classify.py).
|
||||
import socket, sys, time
|
||||
|
||||
IP, PORT = "192.168.1.100", 8080
|
||||
|
||||
def pattern(base, n):
|
||||
return bytes(((base + i) & 0xFF) for i in range(n))
|
||||
|
||||
def connect():
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.settimeout(3.0)
|
||||
s.connect((IP, PORT))
|
||||
print("TS %d" % int(time.time()))
|
||||
return s
|
||||
|
||||
def wait_step(s, want, label, timeout_s=3.0):
|
||||
"""recv until `want` bytes arrive or `timeout_s` of quiet; (data, stalled)"""
|
||||
rx = b""
|
||||
last = time.time()
|
||||
while len(rx) < want:
|
||||
try:
|
||||
chunk = s.recv(want - len(rx))
|
||||
except socket.timeout:
|
||||
chunk = b""
|
||||
except OSError:
|
||||
chunk = b""
|
||||
break
|
||||
if chunk:
|
||||
rx += chunk
|
||||
last = time.time()
|
||||
elif time.time() - last >= timeout_s:
|
||||
break
|
||||
print("STEP %s rx=%d/%d %s" % (label, len(rx), want,
|
||||
"QUIET" if len(rx) < want else "OK"))
|
||||
return rx, len(rx) < want
|
||||
|
||||
def send_rounds64(s, count, base, label, timeout_s=3.0):
|
||||
"""send `count` 64B rounds (global-increment pattern), verify each echo inline."""
|
||||
for i in range(count):
|
||||
data = pattern(base + i * 64, 64)
|
||||
s.sendall(data)
|
||||
rx, stalled = wait_step(s, 64, "%s r%02d" % (label, i), timeout_s)
|
||||
if stalled or rx != data:
|
||||
return False
|
||||
return True
|
||||
|
||||
def hexdump(data, prefix="HEX"):
|
||||
for i in range(0, len(data), 16):
|
||||
print("%s %05d %s" % (prefix, i, data[i:i + 16].hex()))
|
||||
|
||||
def finish(s, code):
|
||||
try:
|
||||
s.close()
|
||||
except OSError:
|
||||
pass
|
||||
sys.exit(code)
|
||||
@@ -0,0 +1,28 @@
|
||||
# Battery A (boundary scan): 64x10 + 1460 + 4096 (two 2KB fills) + 0.25s + tail 64x10.
|
||||
# The firmware probe (net_recv(64)) fires ~45+D ms after the fill1 full drain;
|
||||
# if it lands on a refilled buffer it EATS 64B at pattern offset 2048 of the 4096 block
|
||||
# (no echo for those bytes). probe_classify.py rebuilds the expected stream using the
|
||||
# actual eaten count logged on the device serial line "probe: recv64 n=%d".
|
||||
from probe_bench_lib import *
|
||||
|
||||
def main():
|
||||
s = connect()
|
||||
if not send_rounds64(s, 10, 0, "R"):
|
||||
finish(s, 1)
|
||||
b1460 = pattern(640, 1460)
|
||||
s.sendall(b1460)
|
||||
rx, stalled = wait_step(s, 1460, "B1460", 3.0)
|
||||
if stalled or rx != b1460:
|
||||
hexdump(rx)
|
||||
finish(s, 1)
|
||||
f4096 = pattern(2100, 4096)
|
||||
s.sendall(f4096)
|
||||
time.sleep(0.25)
|
||||
tail_base = 2100 + 4096
|
||||
for i in range(10):
|
||||
s.sendall(pattern(tail_base + i * 64, 64))
|
||||
rx, stalled = wait_step(s, 4736, "F4096+TAIL", 4.0)
|
||||
hexdump(rx)
|
||||
finish(s, 0)
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,199 @@
|
||||
# Offline verdict for the 2026-09-22 primary-cause localization battery (v4).
|
||||
# usage:
|
||||
# python MDK-ARM/probe_classify.py A <runlog> <seriallog> <run_index> # boundary (one-shot or sustained)
|
||||
# python MDK-ARM/probe_classify.py B <runlog> <seriallog> <run_index> # status (hold)
|
||||
# python MDK-ARM/probe_classify.py C <runlog> <seriallog> <run_index> # partial drain
|
||||
# python MDK-ARM/probe_classify.py F <runlog> <seriallog> <base_index> # fast-recon chain (10 pairs)
|
||||
# runlog = the run's stdout capture (TS/STEP lines + HEX dump, ASCII)
|
||||
# seriallog = tools/serial_tool/state/serial.log (the session's log)
|
||||
# run_index = 1-based position of this run in the session (A/B/C)
|
||||
# or 0-based first connection index (F).
|
||||
# Sustained probe (MODE 2): the per-run eaten bytes = sum of POSITIVE probe-line
|
||||
# n's in the run's device-time window (TS lines in the runlog, 8h PC->device
|
||||
# offset + 4s margins; 15s run gaps keep windows disjoint).
|
||||
import re, sys
|
||||
from datetime import datetime, timezone, timedelta
|
||||
|
||||
CST = timezone(timedelta(hours=8)) # PC local; device logs in UTC
|
||||
|
||||
def pattern(base, n):
|
||||
return bytes(((base + i) & 0xFF) for i in range(n))
|
||||
|
||||
def delete_block(data, off, n):
|
||||
if n <= 0:
|
||||
return data
|
||||
return data[:off] + data[off + n:]
|
||||
|
||||
def run_timestamps(path):
|
||||
ts = []
|
||||
for raw in open(path, encoding="utf-8-sig", errors="replace"):
|
||||
m = re.match(r"TS (\d+)", raw.strip())
|
||||
if m:
|
||||
ts.append(int(m.group(1)))
|
||||
return ts
|
||||
|
||||
def read_run(path):
|
||||
steps, streams = {}, {}
|
||||
cur = None
|
||||
for raw in open(path, encoding="utf-8-sig", errors="replace"):
|
||||
line = raw.strip()
|
||||
m = re.match(r"PAIR (\d+)", line) or re.match(r"CONN (\d+) canary=\d", line)
|
||||
if m:
|
||||
cur = int(m.group(1))
|
||||
continue
|
||||
if re.match(r"TS \d+", line):
|
||||
continue
|
||||
m = re.match(r"STEP (\S+) rx=(\d+)/(\d+) (OK|QUIET)", line)
|
||||
if m:
|
||||
steps[(cur, m.group(1))] = (int(m.group(2)), int(m.group(3)), m.group(4))
|
||||
continue
|
||||
m = re.match(r"(HEX\w+) (\d{5}) ([0-9a-f]*)", line)
|
||||
if m:
|
||||
streams.setdefault(m.group(1), []).append(
|
||||
bytes.fromhex(m.group(3)) if m.group(3) else b"")
|
||||
return steps, {k: b"".join(v) for k, v in streams.items()}
|
||||
|
||||
DEV_RE = re.compile(r"\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\]\[")
|
||||
def probe_events(seriallog):
|
||||
"""(device_utc_epoch, n) for every probe line, in log order."""
|
||||
hits = []
|
||||
for raw in open(seriallog, encoding="utf-8", errors="replace"):
|
||||
m = re.search(r"probe: recv64 n=(-?\d+)", raw)
|
||||
if not m:
|
||||
continue
|
||||
t = DEV_RE.search(raw)
|
||||
if not t:
|
||||
continue
|
||||
dt = datetime.strptime(t.group(1), "%Y-%m-%d %H:%M:%S").replace(tzinfo=timezone.utc)
|
||||
hits.append((dt.timestamp(), int(m.group(1))))
|
||||
return hits
|
||||
|
||||
def run_eaten(events, ts, ts_next):
|
||||
"""Sum of positive probe n's in this run's device-time window."""
|
||||
if not ts:
|
||||
return None
|
||||
lo = ts[0] - 4.0
|
||||
hi = (ts_next - 4.0) if ts_next is not None else (lo + 14.0)
|
||||
tot = 0
|
||||
for t, n in events:
|
||||
if lo <= t < hi and n > 0:
|
||||
tot += n
|
||||
return tot
|
||||
|
||||
def single_eaten(events, ts, ts_next):
|
||||
"""One-shot probe: the (single) n in the run window; None if absent."""
|
||||
if not ts:
|
||||
return None
|
||||
lo = ts[0] - 4.0
|
||||
hi = (ts_next - 4.0) if ts_next is not None else (lo + 14.0)
|
||||
for t, n in events:
|
||||
if lo <= t < hi:
|
||||
return n
|
||||
return None
|
||||
|
||||
def main():
|
||||
mode, runlog, seriallog = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||
base = int(sys.argv[4])
|
||||
steps, streams = read_run(runlog)
|
||||
ts_all = run_timestamps(runlog)
|
||||
events = probe_events(seriallog)
|
||||
|
||||
if mode == "F":
|
||||
for pair in range(10):
|
||||
ci, cj = 2 * pair, 2 * pair + 1
|
||||
nA = single_eaten(events, [ts_all[ci]] if ci < len(ts_all) else [],
|
||||
[ts_all[cj + 1]] if cj + 1 < len(ts_all) else None) or 0
|
||||
nB = single_eaten(events, [ts_all[cj]] if cj < len(ts_all) else [],
|
||||
[ts_all[cj + 1]] if cj + 1 < len(ts_all) else None) or 0
|
||||
gotA = streams.get("HEXc%02dF1" % ci, b"")
|
||||
gotB1 = streams.get("HEXc%02dF1" % cj, b"")
|
||||
gotG = streams.get("HEXc%02dG" % cj, b"")
|
||||
wantA = 2048 - nA
|
||||
wantB1 = 2048 - nB
|
||||
a_ok = len(gotA) == wantA and gotA == delete_block(pattern(640, 2048), 0, nA)
|
||||
b1_ok = len(gotB1) == wantB1 and gotB1 == delete_block(pattern(640, 2048), 0, nB)
|
||||
expF2, expTail = pattern(2688, 2048), pattern(4736, 640)
|
||||
if len(gotG) < 2048 or gotG[:2048] != expF2:
|
||||
v = "WEDGE@F2"
|
||||
elif len(gotG) < 2688 or gotG[2048:] != expTail:
|
||||
v = "WEDGE@TAIL"
|
||||
else:
|
||||
v = "PASS"
|
||||
if not a_ok:
|
||||
v = "WEDGE@A" if len(gotA) < wantA else "MISMATCH@A"
|
||||
elif not b1_ok:
|
||||
v = "WEDGE@B1" if len(gotB1) < wantB1 else "MISMATCH@B1"
|
||||
print("PAIR %d: %-12s A %d/%d B1 %d/%d F2 %d/2688 (nA=%s nB=%s)"
|
||||
% (pair, v, len(gotA), wantA, len(gotB1), wantB1, len(gotG), nA, nB))
|
||||
return
|
||||
|
||||
ts = [ts_all[base - 1]] if base - 1 < len(ts_all) else []
|
||||
ts_next = ts_all[base] if (base < len(ts_all) and len(ts_all) > 1) else None
|
||||
eaten = single_eaten(events, ts, ts_next)
|
||||
eaten_sum = run_eaten(events, ts, ts_next) or 0
|
||||
eaten_pos = eaten if (eaten is not None and eaten > 0) else 0
|
||||
f = streams.get("HEX", b"")
|
||||
|
||||
if mode == "A":
|
||||
# dual expectation: one-shot (first probe line) or sustained (window sum)
|
||||
cands = {eaten_pos}
|
||||
if eaten_sum != eaten_pos:
|
||||
cands.add(eaten_sum)
|
||||
verdict = None
|
||||
for e in sorted(cands):
|
||||
want_f = 4096 - e
|
||||
exp_f = delete_block(pattern(2100, 4096), 2048, e)
|
||||
got_f, got_tail = f[:want_f], f[want_f:]
|
||||
if len(got_f) == want_f and got_f == exp_f and len(got_tail) == 640 \
|
||||
and got_tail == pattern(6196, 640):
|
||||
verdict = ("PASS", e)
|
||||
break
|
||||
if verdict:
|
||||
print("VERDICT PASS | eaten=%d (probe n=%s, window sum=%d) | f %d/4096 + tail 640"
|
||||
% (verdict[1], eaten, eaten_sum, 4096 - verdict[1]))
|
||||
else:
|
||||
want = 4096
|
||||
if len(f) < 2048:
|
||||
print("VERDICT STALL@F1 | probe n=%s sum=%d | stream %d bytes (fill1 echo lost)"
|
||||
% (eaten, eaten_sum, len(f)))
|
||||
elif len(f) < 4096 - eaten_sum:
|
||||
print("VERDICT STALL@F2 | probe n=%s sum=%d | stream %d bytes (fill2 echo cut)"
|
||||
% (eaten, eaten_sum, len(f)))
|
||||
else:
|
||||
print("VERDICT MISMATCH | probe n=%s sum=%d | stream %d bytes, bytes differ"
|
||||
% (eaten, eaten_sum, len(f)))
|
||||
|
||||
elif mode == "B":
|
||||
want_f2 = 2048 - eaten_pos
|
||||
exp_f2 = delete_block(pattern(2688, 2048), 0, eaten_pos)
|
||||
exp_tail = pattern(4736, 640)
|
||||
got_f2, got_tail = f[:want_f2], f[want_f2:]
|
||||
if len(got_f2) == want_f2 and got_f2 == exp_f2 and len(got_tail) == 640 \
|
||||
and got_tail == exp_tail:
|
||||
print("VERDICT PASS | probe n=%s (eaten=%d) | canary 2048 + tail 640" % (eaten, eaten_pos))
|
||||
elif len(got_f2) < want_f2 or got_f2 != exp_f2[:len(got_f2)]:
|
||||
print("VERDICT STALL@F2 | probe n=%s (eaten=%d) | canary %d/%d" % (eaten, eaten_pos, len(got_f2), want_f2))
|
||||
else:
|
||||
print("VERDICT STALL@TAIL | probe n=%s | tail %d/640" % (eaten, len(got_tail)))
|
||||
|
||||
elif mode == "C":
|
||||
b1 = streams.get("HEX", b"")
|
||||
k = streams.get("HEX2", b"")
|
||||
want_b = 1460 - eaten_pos
|
||||
exp_b = delete_block(pattern(640, 1460), 1024, eaten_pos)
|
||||
if len(b1) < want_b or b1[:want_b] != exp_b:
|
||||
print("VERDICT STALL@B1460 | probe n=%s (eaten=%d) | fill part %d/%d" % (eaten, eaten_pos, len(b1), want_b))
|
||||
return
|
||||
exp_k, exp_tail = pattern(2100, 1460), pattern(3560, 640)
|
||||
got_k, got_tail = k[:1460], k[1460:]
|
||||
if len(got_k) < 1460 or got_k != exp_k:
|
||||
print("VERDICT STALL@K1460 | probe n=%s | canary %d/1460" % (eaten, len(got_k)))
|
||||
elif len(got_tail) < 640 or got_tail != exp_tail:
|
||||
print("VERDICT STALL@TAIL | probe n=%s | tail %d/640" % (eaten, len(got_tail)))
|
||||
else:
|
||||
print("VERDICT PASS | probe n=%s (eaten=%d) | fill 1460 + canary 1460 + tail 640" % (eaten, eaten_pos))
|
||||
else:
|
||||
print("unknown mode: %s" % mode)
|
||||
sys.exit(3)
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,47 @@
|
||||
# Battery F1 (pre-conditioning (b) / fast-reconnect residue) — chain of 20
|
||||
# connections, each 0.6s after the previous close (the Trap-22 fast window).
|
||||
# Even idx = plain connection (64x10 + 2048 fill, the residue-leaver);
|
||||
# odd idx = canary connection (64x10 + 2048 F1 + 0.4s hold + 2048 F2 canary
|
||||
# + 64x10 tail). Pair i = (conn 2i, conn 2i+1): the canary connection is
|
||||
# pre-conditioned by the immediately preceding fast plain connection.
|
||||
# Pair 0's plain connection follows a fresh boot (internal control).
|
||||
# Verdict offline: probe_classify.py F
|
||||
from probe_bench_lib import *
|
||||
|
||||
N_CONN = 20
|
||||
GAP_S = 0.6
|
||||
|
||||
def run_conn(s, idx, with_canary):
|
||||
if not send_rounds64(s, 10, 0, "R"):
|
||||
pass # keep collecting; offline classification is authoritative
|
||||
f = pattern(640, 2048)
|
||||
s.sendall(f)
|
||||
rx, _ = wait_step(s, 2048, "F1", 3.0)
|
||||
hexdump(rx, "HEXc%02dF1" % idx)
|
||||
if with_canary:
|
||||
time.sleep(0.40) # hold: in-window status reads on the device side
|
||||
f2 = pattern(2688, 2048)
|
||||
s.sendall(f2)
|
||||
time.sleep(0.30)
|
||||
for i in range(10):
|
||||
s.sendall(pattern(4736 + i * 64, 64))
|
||||
rx, _ = wait_step(s, 2688, "F2+TAIL", 4.0)
|
||||
hexdump(rx, "HEXc%02dG" % idx)
|
||||
|
||||
def main():
|
||||
for idx in range(N_CONN):
|
||||
print("CONN %d canary=%d" % (idx, 1 if idx % 2 == 1 else 0))
|
||||
try:
|
||||
s = connect()
|
||||
except OSError as e:
|
||||
print("CONN %d REFUSED: %s" % (idx, e))
|
||||
time.sleep(1.0)
|
||||
continue
|
||||
run_conn(s, idx, with_canary=(idx % 2 == 1))
|
||||
try:
|
||||
s.close()
|
||||
except OSError:
|
||||
pass
|
||||
time.sleep(GAP_S)
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,8 @@
|
||||
import socket, time
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
t0 = time.time()
|
||||
s.connect(('192.168.1.100', 8080))
|
||||
print('connected, holding silently for 95s (Phase 11 keepalive)')
|
||||
time.sleep(95)
|
||||
print('hold done, closing at %.1fs' % (time.time() - t0))
|
||||
s.close()
|
||||
@@ -0,0 +1,40 @@
|
||||
# TC-NET-604 诊断:芯片 TX 分包粒度探测(= SET_TCP_MSS 有效值)
|
||||
# 向回显端发送 N 字节,紧循环小包 recv + 计时,输出到达分块分布。
|
||||
# 芯片 TX 包 = MSS 大小 → 到达粒度 ≈ 芯片发包大小(同交换机微秒级间隔,按 1ms 窗口聚合)。
|
||||
import socket, sys, time
|
||||
|
||||
ip, port = sys.argv[1] if len(sys.argv) > 1 else "192.168.1.100", 8080
|
||||
size = int(sys.argv[2]) if len(sys.argv) > 2 else 4096
|
||||
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.settimeout(30)
|
||||
s.connect((ip, int(port)))
|
||||
payload = bytes(i & 0xFF for i in range(size))
|
||||
t0 = time.perf_counter()
|
||||
s.sendall(payload)
|
||||
|
||||
# 紧循环非阻塞式读取(recv 4096 上限,记录每次 recv 的字节数与相对时刻)
|
||||
s.setblocking(False)
|
||||
chunks = []
|
||||
got = 0
|
||||
deadline = time.time() + 10.0
|
||||
while got < size and time.time() < deadline:
|
||||
try:
|
||||
b = s.recv(4096)
|
||||
if b:
|
||||
chunks.append((round((time.perf_counter() - t0) * 1000, 1), len(b)))
|
||||
got += len(b)
|
||||
except BlockingIOError:
|
||||
time.sleep(0.001)
|
||||
|
||||
s.close()
|
||||
print(f"sent={size} got={got} in {round((time.perf_counter()-t0)*1000,1)}ms")
|
||||
if chunks:
|
||||
from collections import Counter
|
||||
c = Counter(n for _, n in chunks)
|
||||
print("arrival chunks (ms, bytes):")
|
||||
for t, n in chunks:
|
||||
print(f" +{t:7.1f}ms {n:5d}B")
|
||||
print("size histogram:", dict(sorted(c.items())))
|
||||
else:
|
||||
print("NO DATA (stall?)")
|
||||
@@ -0,0 +1,30 @@
|
||||
# Battery C (partial drain + partial read): 64x10 + 1460 fill + 1460 canary + tail 64x10.
|
||||
# Firmware variant: PHASE6_RECV_LIMIT=1024 (drains only 1024 of the 1460 fill),
|
||||
# PHASE_PROBE_ARM_MIN=1000 (probe arms on that partial drain). The probe then reads
|
||||
# 64B from the 436B REMAINDER (partial buffer, partial read, after a NON-full drain:
|
||||
# window re-announced 588 -> 1612, never zero). If the canary stalls, partial drains
|
||||
# also create the fragile window; if it passes, only the zero-window (exact-full)
|
||||
# re-arm is fragile.
|
||||
from probe_bench_lib import *
|
||||
|
||||
def main():
|
||||
s = connect()
|
||||
if not send_rounds64(s, 10, 0, "R"):
|
||||
finish(s, 1)
|
||||
b1460 = pattern(640, 1460)
|
||||
s.sendall(b1460)
|
||||
# echo of the fill = first 1024 (drained) + 436 remainder minus probe-eaten 64
|
||||
rx, _ = wait_step(s, 1460, "B1460", 2.0)
|
||||
time.sleep(0.15)
|
||||
k1460 = pattern(2100, 1460) # canary (separate pattern region)
|
||||
s.sendall(k1460)
|
||||
time.sleep(0.30)
|
||||
tail_base = 2100 + 1460
|
||||
for i in range(10):
|
||||
s.sendall(pattern(tail_base + i * 64, 64))
|
||||
rx2, _ = wait_step(s, 2100, "K1460+TAIL", 4.0)
|
||||
hexdump(rx)
|
||||
hexdump(rx2, "HEX2")
|
||||
finish(s, 0)
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,28 @@
|
||||
# Battery B (hold / pure status read): 64x10 + 2048 fill + 400ms HOLD + 2048 canary
|
||||
# + 0.3s + tail 64x10. During the hold the buffer is empty when the probe lands,
|
||||
# so the probe is a pure GET_RECV_LEN (no data movement). If a pure status read inside
|
||||
# the post-drain re-arm window still wedges the chip, the canary stalls.
|
||||
from probe_bench_lib import *
|
||||
|
||||
def main():
|
||||
s = connect()
|
||||
if not send_rounds64(s, 10, 0, "R"):
|
||||
finish(s, 1)
|
||||
f1 = pattern(640, 2048)
|
||||
s.sendall(f1)
|
||||
rx, stalled = wait_step(s, 2048, "F1", 3.0)
|
||||
if stalled or rx != f1:
|
||||
hexdump(rx)
|
||||
finish(s, 1)
|
||||
time.sleep(0.40) # hold: probe lands on the EMPTY buffer (pure status read)
|
||||
f2 = pattern(2688, 2048) # canary fill (exact window size)
|
||||
s.sendall(f2)
|
||||
time.sleep(0.30)
|
||||
tail_base = 2688 + 2048
|
||||
for i in range(10):
|
||||
s.sendall(pattern(tail_base + i * 64, 64))
|
||||
rx, stalled = wait_step(s, 2688, "F2+TAIL", 4.0)
|
||||
hexdump(rx)
|
||||
finish(s, 0)
|
||||
|
||||
main()
|
||||
Reference in New Issue
Block a user