refactor(streamer): remove gstreamer and legacy rtmp paths
This commit is contained in:
@@ -12,10 +12,7 @@ from pathlib import Path
|
||||
from typing import cast
|
||||
|
||||
|
||||
MetricValue = int | float | str | bool
|
||||
|
||||
|
||||
KV_PATTERN = re.compile(r"([a-zA-Z_]+)=([^\s]+)")
|
||||
KV_PATTERN = re.compile(r"([a-zA-Z0-9_]+)=([^\s]+)")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -32,31 +29,20 @@ def parse_args() -> CliArgs:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Build JSON summary for standalone acceptance matrix"
|
||||
)
|
||||
|
||||
_ = parser.add_argument(
|
||||
"--manifest", required=True, help="TSV manifest produced by acceptance runner"
|
||||
)
|
||||
_ = parser.add_argument("--output", required=True, help="Output JSON summary path")
|
||||
_ = parser.add_argument("--manifest", required=True)
|
||||
_ = parser.add_argument("--output", required=True)
|
||||
_ = parser.add_argument("--run-id", required=True)
|
||||
_ = parser.add_argument("--run-dir", required=True)
|
||||
_ = parser.add_argument("--started-at", required=True)
|
||||
_ = parser.add_argument("--finished-at", required=True)
|
||||
|
||||
parsed = parser.parse_args(sys.argv[1:])
|
||||
manifest = cast(str, parsed.manifest)
|
||||
output = cast(str, parsed.output)
|
||||
run_id = cast(str, parsed.run_id)
|
||||
run_dir = cast(str, parsed.run_dir)
|
||||
started_at = cast(str, parsed.started_at)
|
||||
finished_at = cast(str, parsed.finished_at)
|
||||
|
||||
return CliArgs(
|
||||
manifest=manifest,
|
||||
output=output,
|
||||
run_id=run_id,
|
||||
run_dir=run_dir,
|
||||
started_at=started_at,
|
||||
finished_at=finished_at,
|
||||
manifest=cast(str, parsed.manifest),
|
||||
output=cast(str, parsed.output),
|
||||
run_id=cast(str, parsed.run_id),
|
||||
run_dir=cast(str, parsed.run_dir),
|
||||
started_at=cast(str, parsed.started_at),
|
||||
finished_at=cast(str, parsed.finished_at),
|
||||
)
|
||||
|
||||
|
||||
@@ -70,7 +56,7 @@ def read_text(path: str) -> str:
|
||||
return ""
|
||||
|
||||
|
||||
def to_number(value: str) -> MetricValue:
|
||||
def to_number(value: str) -> int | float | str:
|
||||
if re.fullmatch(r"-?\d+", value):
|
||||
try:
|
||||
return int(value)
|
||||
@@ -84,25 +70,20 @@ def to_number(value: str) -> MetricValue:
|
||||
return value
|
||||
|
||||
|
||||
def parse_key_value_metrics(line: str) -> dict[str, MetricValue]:
|
||||
metrics: dict[str, MetricValue] = {}
|
||||
for match in KV_PATTERN.finditer(line):
|
||||
key = match.group(1)
|
||||
raw = match.group(2)
|
||||
metrics[key] = to_number(raw)
|
||||
return metrics
|
||||
def parse_key_values(line: str) -> dict[str, int | float | str]:
|
||||
return {match.group(1): to_number(match.group(2)) for match in KV_PATTERN.finditer(line)}
|
||||
|
||||
|
||||
def extract_last_matching_line(text: str, token: str) -> str:
|
||||
match = ""
|
||||
def last_line_with_token(text: str, token: str) -> str:
|
||||
found = ""
|
||||
for line in text.splitlines():
|
||||
if token in line:
|
||||
match = line
|
||||
return match
|
||||
found = line
|
||||
return found
|
||||
|
||||
|
||||
def parse_rtp_tester_metrics(text: str) -> dict[str, MetricValue]:
|
||||
metrics: dict[str, MetricValue] = {}
|
||||
def parse_rtp_receiver_metrics(text: str) -> dict[str, int]:
|
||||
metrics: dict[str, int] = {}
|
||||
patterns = {
|
||||
"packets_received": r"Packets received:\s*(\d+)",
|
||||
"sequence_gaps": r"Sequence gaps:\s*(\d+)",
|
||||
@@ -110,15 +91,14 @@ def parse_rtp_tester_metrics(text: str) -> dict[str, MetricValue]:
|
||||
"detected_payload_type": r"Detected payload type:\s*(\d+)",
|
||||
}
|
||||
for key, pattern in patterns.items():
|
||||
m = re.search(pattern, text)
|
||||
if m:
|
||||
metrics[key] = int(m.group(1))
|
||||
match = re.search(pattern, text)
|
||||
if match:
|
||||
metrics[key] = int(match.group(1))
|
||||
return metrics
|
||||
|
||||
|
||||
def parse_rtmp_tester_metrics(text: str) -> dict[str, MetricValue]:
|
||||
metrics: dict[str, MetricValue] = {}
|
||||
|
||||
def parse_rtmp_stub_metrics(text: str) -> dict[str, int]:
|
||||
metrics: dict[str, int] = {}
|
||||
messages = re.search(
|
||||
r"Messages:\s*total=(\d+),\s*audio=(\d+),\s*video=(\d+),\s*data=(\d+),\s*chunk-size-updates=(\d+)",
|
||||
text,
|
||||
@@ -135,7 +115,7 @@ def parse_rtmp_tester_metrics(text: str) -> dict[str, MetricValue]:
|
||||
)
|
||||
|
||||
counts = re.search(
|
||||
r"Video signaling counts:\s*h264=(\d+),\s*h265-enhanced=(\d+),\s*h265-domestic=(\d+),\s*unknown=(\d+)",
|
||||
r"Video signaling counts:\s*h264=(\d+),\s*h265-enhanced=(\d+),\s*unknown=(\d+)",
|
||||
text,
|
||||
)
|
||||
if counts:
|
||||
@@ -143,8 +123,7 @@ def parse_rtmp_tester_metrics(text: str) -> dict[str, MetricValue]:
|
||||
{
|
||||
"h264_video_messages": int(counts.group(1)),
|
||||
"h265_enhanced_video_messages": int(counts.group(2)),
|
||||
"h265_domestic_video_messages": int(counts.group(3)),
|
||||
"unknown_video_messages": int(counts.group(4)),
|
||||
"unknown_video_messages": int(counts.group(3)),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -158,43 +137,29 @@ def parse_rtmp_tester_metrics(text: str) -> dict[str, MetricValue]:
|
||||
"matching_threshold": int(matching.group(2)),
|
||||
}
|
||||
)
|
||||
|
||||
return metrics
|
||||
|
||||
|
||||
def parse_streamer_metrics(text: str) -> dict[str, dict[str, MetricValue]]:
|
||||
result: dict[str, dict[str, MetricValue]] = {}
|
||||
for token, key in (
|
||||
("PIPELINE_METRICS", "pipeline"),
|
||||
("RTP_METRICS", "rtp"),
|
||||
("RTMP_METRICS", "rtmp"),
|
||||
):
|
||||
line = extract_last_matching_line(text, token)
|
||||
if line:
|
||||
result[key] = parse_key_value_metrics(line)
|
||||
return result
|
||||
|
||||
|
||||
def parse_sdp_metrics(path: str) -> dict[str, MetricValue]:
|
||||
p = Path(path)
|
||||
def parse_sdp_metrics(path: str) -> dict[str, object]:
|
||||
if not path:
|
||||
return {}
|
||||
p = Path(path)
|
||||
if not p.exists():
|
||||
return {"exists": False}
|
||||
text = read_text(path)
|
||||
metrics: dict[str, MetricValue] = {
|
||||
metrics: dict[str, object] = {
|
||||
"exists": True,
|
||||
"bytes": p.stat().st_size,
|
||||
"has_h264": "H264/90000" in text,
|
||||
"has_h265": ("H265/90000" in text) or ("HEVC/90000" in text),
|
||||
}
|
||||
m = re.search(r"m=video\s+\d+\s+RTP/AVP\s+(\d+)", text)
|
||||
if m:
|
||||
metrics["payload_type"] = int(m.group(1))
|
||||
match = re.search(r"m=video\s+\d+\s+RTP/AVP\s+(\d+)", text)
|
||||
if match:
|
||||
metrics["payload_type"] = int(match.group(1))
|
||||
return metrics
|
||||
|
||||
|
||||
def parse_exit_code(value: str) -> int:
|
||||
def parse_exit(value: str) -> int:
|
||||
try:
|
||||
return int(value)
|
||||
except (TypeError, ValueError):
|
||||
@@ -208,35 +173,12 @@ def parse_duration_ms(value: str) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
MANIFEST_FIELDS = (
|
||||
"order",
|
||||
"row_id",
|
||||
"name",
|
||||
"protocol",
|
||||
"codec",
|
||||
"rtmp_mode",
|
||||
"status",
|
||||
"reason",
|
||||
"duration_ms",
|
||||
"sim_rc",
|
||||
"streamer_rc",
|
||||
"tester_rc",
|
||||
"sim_log",
|
||||
"streamer_log",
|
||||
"tester_log",
|
||||
"sdp_path",
|
||||
)
|
||||
|
||||
|
||||
def parse_manifest(path: str) -> list[dict[str, str]]:
|
||||
rows: list[dict[str, str]] = []
|
||||
with open(path, "r", encoding="utf-8", newline="") as handle:
|
||||
reader = csv.DictReader(handle, delimiter="\t")
|
||||
for raw_row in reader:
|
||||
row: dict[str, str] = {}
|
||||
for field in MANIFEST_FIELDS:
|
||||
value = raw_row.get(field, "")
|
||||
row[field] = "" if value is None else str(value)
|
||||
for raw in reader:
|
||||
row = {key: "" if value is None else str(value) for key, value in raw.items()}
|
||||
rows.append(row)
|
||||
return rows
|
||||
|
||||
@@ -246,26 +188,22 @@ def build_summary(args: CliArgs) -> dict[str, object]:
|
||||
rows: list[dict[str, object]] = []
|
||||
|
||||
for row in sorted(manifest_rows, key=lambda item: int(item["order"])):
|
||||
streamer_log = row["streamer_log"]
|
||||
tester_log = row["tester_log"]
|
||||
sim_log = row["sim_log"]
|
||||
sdp_path = row.get("sdp_path", "")
|
||||
emitter_text = read_text(row["emitter_log"])
|
||||
receiver_text = read_text(row["receiver_log"])
|
||||
emitter_metrics: dict[str, dict[str, int | float | str]] = {}
|
||||
for token, key in (
|
||||
("RTP_METRICS", "rtp"),
|
||||
("RTMP_OUTPUT_METRICS", "rtmp"),
|
||||
):
|
||||
line = last_line_with_token(emitter_text, token)
|
||||
if line:
|
||||
emitter_metrics[key] = parse_key_values(line)
|
||||
|
||||
streamer_text = read_text(streamer_log)
|
||||
tester_text = read_text(tester_log)
|
||||
|
||||
tester_metrics: dict[str, MetricValue]
|
||||
receiver_metrics: dict[str, object]
|
||||
if row["protocol"] == "rtp":
|
||||
tester_metrics = parse_rtp_tester_metrics(tester_text)
|
||||
receiver_metrics = parse_rtp_receiver_metrics(receiver_text)
|
||||
else:
|
||||
tester_metrics = parse_rtmp_tester_metrics(tester_text)
|
||||
|
||||
metrics: dict[str, object] = {
|
||||
"tester": tester_metrics,
|
||||
"streamer": parse_streamer_metrics(streamer_text),
|
||||
}
|
||||
if row["protocol"] == "rtp":
|
||||
metrics["sdp"] = parse_sdp_metrics(sdp_path)
|
||||
receiver_metrics = parse_rtmp_stub_metrics(receiver_text)
|
||||
|
||||
rows.append(
|
||||
{
|
||||
@@ -274,21 +212,23 @@ def build_summary(args: CliArgs) -> dict[str, object]:
|
||||
"name": row["name"],
|
||||
"protocol": row["protocol"],
|
||||
"codec": row["codec"],
|
||||
"rtmp_mode": row["rtmp_mode"] if row["rtmp_mode"] else None,
|
||||
"transport": row["transport"],
|
||||
"status": row["status"],
|
||||
"reason": row["reason"],
|
||||
"duration_ms": parse_duration_ms(row["duration_ms"]),
|
||||
"exit_codes": {
|
||||
"sim": parse_exit_code(row["sim_rc"]),
|
||||
"streamer": parse_exit_code(row["streamer_rc"]),
|
||||
"tester": parse_exit_code(row["tester_rc"]),
|
||||
"emitter": parse_exit(row["emitter_rc"]),
|
||||
"receiver": parse_exit(row["receiver_rc"]),
|
||||
},
|
||||
"metrics": {
|
||||
"emitter": emitter_metrics,
|
||||
"receiver": receiver_metrics,
|
||||
"sdp": parse_sdp_metrics(row.get("sdp_path", "")),
|
||||
},
|
||||
"metrics": metrics,
|
||||
"evidence": {
|
||||
"sim_log": sim_log,
|
||||
"streamer_log": streamer_log,
|
||||
"tester_log": tester_log,
|
||||
"sdp": sdp_path if sdp_path else None,
|
||||
"emitter_log": row["emitter_log"],
|
||||
"receiver_log": row["receiver_log"],
|
||||
"sdp_path": row.get("sdp_path") or None,
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -296,10 +236,7 @@ def build_summary(args: CliArgs) -> dict[str, object]:
|
||||
pass_count = sum(1 for row in rows if row["status"] == "PASS")
|
||||
fail_count = sum(1 for row in rows if row["status"] == "FAIL")
|
||||
skip_count = sum(1 for row in rows if row["status"] == "SKIP")
|
||||
|
||||
all_pass = (
|
||||
len(rows) == 5 and pass_count == 5 and fail_count == 0 and skip_count == 0
|
||||
)
|
||||
all_pass = len(rows) == 6 and pass_count == 6 and fail_count == 0 and skip_count == 0
|
||||
|
||||
return {
|
||||
"run_id": args.run_id,
|
||||
@@ -322,11 +259,8 @@ def main() -> int:
|
||||
args = parse_args()
|
||||
output_path = Path(args.output)
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
summary = build_summary(args)
|
||||
_ = output_path.write_text(
|
||||
json.dumps(summary, indent=2, sort_keys=False) + "\n", encoding="utf-8"
|
||||
)
|
||||
output_path.write_text(json.dumps(summary, indent=2) + "\n", encoding="utf-8")
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user