📦 deps(thirdparty): update snapshots
This commit is contained in:
+108
@@ -0,0 +1,108 @@
|
||||
"""Hangs lane parser (schema `potential-hangs`).
|
||||
|
||||
The schema lacks inline backtraces — stacks come from Time Profiler samples
|
||||
that overlap each hang's window. Correlation is done later in correlate.py.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from . import xctrace, xml_utils
|
||||
|
||||
PREFERRED_SCHEMAS = ("potential-hangs",)
|
||||
FALLBACK_SCHEMAS = ("main-thread-hang", "hang", "hangs")
|
||||
|
||||
|
||||
def analyze(
|
||||
trace_path: Path,
|
||||
toc_schemas: frozenset[str],
|
||||
top_n: int = 10,
|
||||
window: tuple[int, int] | None = None,
|
||||
run: int = 1,
|
||||
) -> dict[str, Any]:
|
||||
schema = _pick_schema(toc_schemas)
|
||||
if schema is None:
|
||||
return {
|
||||
"lane": "hangs",
|
||||
"available": False,
|
||||
"notes": ["Hangs data not present in trace."],
|
||||
}
|
||||
|
||||
xml_bytes = xctrace.export_schema(trace_path, schema, run=run)
|
||||
stream = xml_utils.RowStream(xml_bytes)
|
||||
|
||||
hangs: list[dict] = []
|
||||
for row in stream:
|
||||
start_el = row.get("start")
|
||||
dur_el = row.get("duration")
|
||||
type_el = row.get("hang-type")
|
||||
thread_el = row.get("thread")
|
||||
if start_el is None or dur_el is None:
|
||||
continue
|
||||
start_ns = xml_utils.int_text(stream.resolve(start_el))
|
||||
duration_ns = xml_utils.int_text(stream.resolve(dur_el))
|
||||
if start_ns is None or duration_ns is None:
|
||||
continue
|
||||
if not xml_utils.event_overlaps_window(start_ns, start_ns + duration_ns, window):
|
||||
continue
|
||||
hang_type = xml_utils.str_text(stream.resolve(type_el)) if type_el is not None else None
|
||||
thread = xml_utils.extract_thread(thread_el, stream) if thread_el is not None else None
|
||||
|
||||
hangs.append({
|
||||
"start_ns": start_ns,
|
||||
"duration_ns": duration_ns,
|
||||
"end_ns": start_ns + duration_ns,
|
||||
"duration_ms": round(duration_ns / 1_000_000, 2),
|
||||
"start_ms": round(start_ns / 1_000_000, 2),
|
||||
"hang_type": hang_type or "Hang",
|
||||
"thread": thread,
|
||||
})
|
||||
|
||||
hangs.sort(key=lambda h: h["duration_ns"], reverse=True)
|
||||
|
||||
total_ms = sum(h["duration_ms"] for h in hangs)
|
||||
worst = hangs[0] if hangs else None
|
||||
|
||||
# Severity buckets per Apple docs (Microhang: 250ms–500ms, Hang: ≥500ms).
|
||||
# We bucket by raw duration so the agent can reason about it.
|
||||
buckets = {"lt_250ms": 0, "250ms_1s": 0, "gt_1s": 0}
|
||||
for h in hangs:
|
||||
if h["duration_ms"] < 250:
|
||||
buckets["lt_250ms"] += 1
|
||||
elif h["duration_ms"] < 1000:
|
||||
buckets["250ms_1s"] += 1
|
||||
else:
|
||||
buckets["gt_1s"] += 1
|
||||
|
||||
top_offenders = [
|
||||
{
|
||||
"start_ms": h["start_ms"],
|
||||
"duration_ms": h["duration_ms"],
|
||||
"hang_type": h["hang_type"],
|
||||
"thread": (h["thread"] or {}).get("name", ""),
|
||||
}
|
||||
for h in hangs[:top_n]
|
||||
]
|
||||
|
||||
return {
|
||||
"lane": "hangs",
|
||||
"available": True,
|
||||
"schema_used": schema,
|
||||
"metrics": {
|
||||
"count": len(hangs),
|
||||
"total_duration_ms": round(total_ms, 2),
|
||||
"worst_duration_ms": worst["duration_ms"] if worst else 0,
|
||||
"severity_buckets": buckets,
|
||||
},
|
||||
"top_offenders": top_offenders,
|
||||
"notes": [],
|
||||
"_events": hangs, # retained for correlation
|
||||
}
|
||||
|
||||
|
||||
def _pick_schema(available: frozenset[str]) -> str | None:
|
||||
for s in PREFERRED_SCHEMAS + FALLBACK_SCHEMAS:
|
||||
if s in available:
|
||||
return s
|
||||
return None
|
||||
Reference in New Issue
Block a user