Preserve formatted NAND boot and Wii Remote pairing, add DMA/SRAM conformance tests and a Wii comparison probe. Keep P1 profiling tools and native update A/B evidence; reject the unhelpful targeted SRAM shortcut.
101 lines
4.5 KiB
Python
101 lines
4.5 KiB
Python
"""Summarize CPU leaf samples per thread from an already-aligned P1 capture.
|
|
|
|
These are sample counts, not exact CPU times or a critical-path attribution.
|
|
"""
|
|
|
|
import argparse
|
|
from collections import Counter, defaultdict
|
|
import json
|
|
from pathlib import Path
|
|
|
|
from analyze_p1 import percentile
|
|
|
|
|
|
def summarize(path, pid):
|
|
by_thread = defaultdict(Counter)
|
|
processors = defaultdict(Counter)
|
|
sample_begin = None
|
|
sample_end = None
|
|
with path.open(encoding="utf-8") as source:
|
|
for line in source:
|
|
event = json.loads(line)
|
|
if event["native"] or event["pid"] != pid or event["event"] != "SampledProfile":
|
|
continue
|
|
data = event["data"]
|
|
tid = int(data["ThreadId"])
|
|
weight = int(data.get("Count", 1))
|
|
by_thread[tid][data.get("Symbol", "") or "<unresolved>"] += weight
|
|
processors[tid][int(data["Processor"])] += weight
|
|
sample_begin = event["qpc"] if sample_begin is None else min(sample_begin, event["qpc"])
|
|
sample_end = event["qpc"] if sample_end is None else max(sample_end, event["qpc"])
|
|
if sample_begin is None:
|
|
raise ValueError("No target CPU samples in the selected window")
|
|
frequency = None
|
|
previous_frame = {}
|
|
frame_deltas = defaultdict(list)
|
|
snapshots = {}
|
|
with path.open(encoding="utf-8") as source:
|
|
for line in source:
|
|
event = json.loads(line)
|
|
if not event["native"] or event["pid"] != pid:
|
|
continue
|
|
qpc = event["qpc"]
|
|
if not sample_begin <= qpc <= sample_end:
|
|
continue
|
|
data = event["data"]
|
|
if event["event"] == "Marker" and data["Name"] == "PerformanceMetricsReset":
|
|
previous_frame.clear()
|
|
elif event["event"] == "Frame":
|
|
if frequency is None:
|
|
frequency = int(data["QPCFrequency"])
|
|
elif frequency != int(data["QPCFrequency"]):
|
|
raise ValueError("Inconsistent frame QPC frequency")
|
|
kind = data["Kind"]
|
|
if kind in previous_frame:
|
|
frame_deltas[kind].append(1000 * (qpc - previous_frame[kind]) / frequency)
|
|
previous_frame[kind] = qpc
|
|
elif event["event"] == "Snapshot":
|
|
core = int(data["CoreId"])
|
|
if core not in snapshots:
|
|
snapshots[core] = [data, data]
|
|
else:
|
|
snapshots[core][1] = data
|
|
frames = {kind: {"intervals": len(deltas), "p50_ms": percentile(deltas, .5),
|
|
"p95_ms": percentile(deltas, .95), "p99_ms": percentile(deltas, .99)}
|
|
for kind, deltas in frame_deltas.items()}
|
|
return {
|
|
"pid": pid,
|
|
"sample_count": sum(sum(symbols.values()) for symbols in by_thread.values()),
|
|
"sample_window_qpc": {"begin": sample_begin, "end": sample_end,
|
|
"duration_seconds": (sample_end - sample_begin) / frequency
|
|
if frequency else None},
|
|
"frame_intervals_in_sample_window": frames,
|
|
"snapshot_deltas_in_sample_window": {
|
|
str(core): {field: last[field] - first[field]
|
|
for field in ("Instructions", "NativeInstructions", "SlowReads",
|
|
"SlowWrites", "Compiles", "Fallbacks")}
|
|
for core, (first, last) in snapshots.items()
|
|
},
|
|
"note": "Leaf sample counts and host frame intervals within the same selected window; no exact CPU duration or critical-path claim.",
|
|
"threads": [
|
|
{"tid": tid, "samples": sum(symbols.values()),
|
|
"processors": dict(sorted(processors[tid].items())),
|
|
"top_symbols": [{"symbol": symbol, "samples": count}
|
|
for symbol, count in symbols.most_common(12)]}
|
|
for tid, symbols in sorted(by_thread.items(),
|
|
key=lambda item: -sum(item[1].values()))
|
|
],
|
|
}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("events", type=Path)
|
|
parser.add_argument("--pid", required=True, type=int)
|
|
parser.add_argument("--output", required=True, type=Path)
|
|
args = parser.parse_args()
|
|
result = summarize(args.events, args.pid)
|
|
with args.output.open("x", encoding="utf-8") as target:
|
|
json.dump(result, target, indent=2)
|
|
print(f"{result['sample_count']} CPU samples from {len(result['threads'])} threads")
|