📦 deps(thirdparty): update snapshots
This commit is contained in:
+135
@@ -0,0 +1,135 @@
|
||||
"""Time Profiler lane parser (schema `time-profile`).
|
||||
|
||||
Aggregates CPU samples by leaf symbol, keeps per-sample rows so that other
|
||||
lanes can correlate by timestamp window.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from . import xctrace, xml_utils
|
||||
|
||||
PREFERRED_SCHEMAS = ("time-profile",)
|
||||
FALLBACK_SCHEMAS = ("time-sample",) # no symbolication; used only if nothing else
|
||||
|
||||
|
||||
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": "time-profiler",
|
||||
"available": False,
|
||||
"notes": ["Time Profiler data not present in trace."],
|
||||
}
|
||||
|
||||
xml_bytes = xctrace.export_schema(trace_path, schema, run=run)
|
||||
stream = xml_utils.RowStream(xml_bytes)
|
||||
|
||||
samples: list[dict] = []
|
||||
symbol_weight: dict[str, int] = defaultdict(int)
|
||||
symbol_samples: dict[str, int] = defaultdict(int)
|
||||
symbol_thread: dict[str, str] = {}
|
||||
processes: set[str] = set()
|
||||
total_weight = 0
|
||||
min_time: int | None = None
|
||||
max_time: int | None = None
|
||||
|
||||
for row in stream:
|
||||
time_el = row.get("time")
|
||||
weight_el = row.get("weight")
|
||||
thread_el = row.get("thread")
|
||||
stack_el = row.get("stack")
|
||||
if stack_el is None or time_el is None or thread_el is None:
|
||||
continue
|
||||
|
||||
sample_time_ns = xml_utils.int_text(stream.resolve(time_el))
|
||||
if not xml_utils.in_window(sample_time_ns, window):
|
||||
continue
|
||||
weight_ns = xml_utils.int_text(stream.resolve(weight_el)) or 0
|
||||
frames = xml_utils.extract_backtrace(stack_el, stream, max_frames=20)
|
||||
if not frames:
|
||||
continue
|
||||
|
||||
thread = xml_utils.extract_thread(thread_el, stream)
|
||||
process_name = (thread.get("process") or {}).get("name")
|
||||
if process_name:
|
||||
processes.add(process_name)
|
||||
|
||||
leaf = xml_utils.top_symbol(frames)
|
||||
symbol_weight[leaf] += weight_ns
|
||||
symbol_samples[leaf] += 1
|
||||
symbol_thread.setdefault(
|
||||
leaf, "main" if thread["is_main"] else thread.get("name", "")
|
||||
)
|
||||
total_weight += weight_ns
|
||||
|
||||
if sample_time_ns is not None:
|
||||
min_time = sample_time_ns if min_time is None else min(min_time, sample_time_ns)
|
||||
max_time = sample_time_ns if max_time is None else max(max_time, sample_time_ns)
|
||||
|
||||
samples.append({
|
||||
"time_ns": sample_time_ns,
|
||||
"weight_ns": weight_ns,
|
||||
"thread_name": thread["name"],
|
||||
"is_main": thread["is_main"],
|
||||
"process": process_name,
|
||||
"leaf_symbol": leaf,
|
||||
"frames": frames[:5],
|
||||
})
|
||||
|
||||
samples.sort(key=lambda s: s["time_ns"])
|
||||
|
||||
top = sorted(
|
||||
symbol_weight.items(), key=lambda kv: kv[1], reverse=True
|
||||
)[:top_n]
|
||||
top_offenders = [
|
||||
{
|
||||
"symbol": sym,
|
||||
"weight_ns": w,
|
||||
"weight_ms": round(w / 1_000_000, 2),
|
||||
"samples": symbol_samples[sym],
|
||||
"percent": round(100.0 * w / total_weight, 2) if total_weight else 0.0,
|
||||
"thread": symbol_thread.get(sym, ""),
|
||||
}
|
||||
for sym, w in top
|
||||
]
|
||||
|
||||
notes: list[str] = []
|
||||
if schema in FALLBACK_SCHEMAS:
|
||||
notes.append(
|
||||
f"Using fallback schema `{schema}`; backtraces may be unsymbolicated."
|
||||
)
|
||||
|
||||
return {
|
||||
"lane": "time-profiler",
|
||||
"available": True,
|
||||
"schema_used": schema,
|
||||
"metrics": {
|
||||
"total_samples": len(samples),
|
||||
"total_weight_ns": total_weight,
|
||||
"total_weight_ms": round(total_weight / 1_000_000, 2),
|
||||
"window_start_ns": min_time,
|
||||
"window_end_ns": max_time,
|
||||
"processes": sorted(processes),
|
||||
},
|
||||
"top_offenders": top_offenders,
|
||||
"notes": notes,
|
||||
# Internal: retained for correlation. Stripped before JSON emission
|
||||
# if --slim is requested by the orchestrator.
|
||||
"_samples": samples,
|
||||
}
|
||||
|
||||
|
||||
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