122 lines
4.4 KiB
Python
122 lines
4.4 KiB
Python
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",
|
|
]
|