traces
This commit is contained in:
121
h8536/serial_scenario_compare.py
Normal file
121
h8536/serial_scenario_compare.py
Normal file
@@ -0,0 +1,121 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
from typing import Any, TextIO
|
||||
|
||||
from .bench_connect_lcd import format_frame, label_frame, parse_frame
|
||||
|
||||
|
||||
def build_arg_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Compare two serial_scenario result JSON files and highlight extra button/report traffic."
|
||||
)
|
||||
parser.add_argument("baseline", type=Path, help="baseline result JSON, usually a no-button run")
|
||||
parser.add_argument("candidate", type=Path, help="candidate result JSON, usually a one-button run")
|
||||
parser.add_argument("--show-labels", action="store_true", help="also show label count deltas")
|
||||
return parser
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None, *, stdout: TextIO = sys.stdout) -> int:
|
||||
args = build_arg_parser().parse_args(argv)
|
||||
baseline = _load_result(args.baseline)
|
||||
candidate = _load_result(args.candidate)
|
||||
print(format_comparison(baseline, candidate, show_labels=args.show_labels), file=stdout)
|
||||
return 0
|
||||
|
||||
|
||||
def _load_result(path: Path) -> dict[str, Any]:
|
||||
with path.open("r", encoding="utf-8") as handle:
|
||||
result = json.load(handle)
|
||||
if not isinstance(result, dict):
|
||||
raise SystemExit(f"{path} is not a result JSON object")
|
||||
result["_path"] = str(path)
|
||||
return result
|
||||
|
||||
|
||||
def format_comparison(
|
||||
baseline: dict[str, Any],
|
||||
candidate: dict[str, Any],
|
||||
*,
|
||||
show_labels: bool = False,
|
||||
) -> str:
|
||||
base_targets = Counter(_string_int_mapping(baseline.get("ack_targets", {})))
|
||||
candidate_targets = Counter(_string_int_mapping(candidate.get("ack_targets", {})))
|
||||
target_delta = candidate_targets - base_targets
|
||||
|
||||
lines = [
|
||||
"Serial scenario comparison",
|
||||
f"baseline={baseline.get('_path', '')}",
|
||||
f"candidate={candidate.get('_path', '')}",
|
||||
f"baseline_log={baseline.get('log', '')}",
|
||||
f"candidate_log={candidate.get('log', '')}",
|
||||
f"baseline_rx_frames={baseline.get('rx_frames', 0)} candidate_rx_frames={candidate.get('rx_frames', 0)}",
|
||||
f"baseline_ack_sent={baseline.get('ack_sent', 0)} candidate_ack_sent={candidate.get('ack_sent', 0)}",
|
||||
]
|
||||
|
||||
if target_delta:
|
||||
lines.append("extra ACK-target frames in candidate:")
|
||||
for frame_text, count in sorted(target_delta.items(), key=_frame_sort_key):
|
||||
lines.append(f" +{count:3d} {frame_text} {_describe_frame(frame_text)}")
|
||||
else:
|
||||
lines.append("no extra ACK-target frames found in candidate")
|
||||
|
||||
missing = base_targets - candidate_targets
|
||||
if missing:
|
||||
lines.append("baseline ACK-target frames missing/decreased in candidate:")
|
||||
for frame_text, count in sorted(missing.items(), key=_frame_sort_key):
|
||||
lines.append(f" -{count:3d} {frame_text} {_describe_frame(frame_text)}")
|
||||
|
||||
if show_labels:
|
||||
base_labels = Counter(_string_int_mapping(baseline.get("labels", {})))
|
||||
candidate_labels = Counter(_string_int_mapping(candidate.get("labels", {})))
|
||||
label_delta = candidate_labels - base_labels
|
||||
lines.append("label count increases:")
|
||||
if label_delta:
|
||||
for label, count in sorted(label_delta.items()):
|
||||
lines.append(f" +{count:3d} {label}")
|
||||
else:
|
||||
lines.append(" none")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _string_int_mapping(raw: Any) -> dict[str, int]:
|
||||
if not isinstance(raw, dict):
|
||||
return {}
|
||||
output: dict[str, int] = {}
|
||||
for key, value in raw.items():
|
||||
try:
|
||||
output[str(key)] = int(value)
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
return output
|
||||
|
||||
|
||||
def _describe_frame(frame_text: str) -> str:
|
||||
frame = parse_frame(frame_text)
|
||||
selector = ((frame[1] & 0x7F) << 7) | frame[2]
|
||||
value = (frame[3] << 8) | frame[4]
|
||||
label = label_frame(frame) or "checksum_ok_unlabeled"
|
||||
return f"cmd=0x{frame[0]:02X} selector=0x{selector:04X} value=0x{value:04X} label={label}"
|
||||
|
||||
|
||||
def _frame_sort_key(item: tuple[str, int]) -> tuple[int, int, int, str]:
|
||||
frame_text, _count = item
|
||||
try:
|
||||
frame = parse_frame(frame_text)
|
||||
except argparse.ArgumentTypeError:
|
||||
return (0xFFFF, 0xFFFF, 0xFFFF, frame_text)
|
||||
selector = ((frame[1] & 0x7F) << 7) | frame[2]
|
||||
value = (frame[3] << 8) | frame[4]
|
||||
return (selector, frame[0], value, frame_text)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"build_arg_parser",
|
||||
"format_comparison",
|
||||
"main",
|
||||
]
|
||||
Reference in New Issue
Block a user