"""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 ""] += 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")