Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Reviews, improves, and writes SwiftUI code following state management, view composition, performance, and iOS 26+ Liquid Glass best practices.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
scripts/instruments_parser/swiftui.py
1"""SwiftUI lane parser (Xcode 26+).23Primary schema is `swiftui-updates` with columns: start, duration, id,4update-type, allocations, description, category, view-hierarchy, module,5view-name, process, thread, root-causes, severity, cause-graph-node,6full-cause-graph-node.78We aggregate by view-name across all SwiftUI schemas (future-proofing against9schema renames) and break severity out separately so the agent can focus on10the high-severity rows.11"""12from __future__ import annotations1314from collections import Counter, defaultdict15from pathlib import Path16from typing import Any1718from . import xctrace, xml_utils1920START_KEYS = ("start", "time", "sample-time", "timestamp")21DURATION_KEYS = ("duration", "body-duration", "update-duration")22VIEW_KEYS = ("view-name", "view", "view-type", "name", "type")23MODULE_KEYS = ("module",)24CATEGORY_KEYS = ("category",)25UPDATE_TYPE_KEYS = ("update-type",)26SEVERITY_KEYS = ("severity",)27DESCRIPTION_KEYS = ("description",)2829HIGH_SEVERITIES = {"High", "Very High", "Severe", "Critical"}3031# Ongoing / unterminated updates carry a sentinel duration (≈ UINT64_MAX-ish).32# Any duration longer than an hour is almost certainly that sentinel and would33# break aggregates + the correlation overlap check.34_SENTINEL_DURATION_NS = 60 * 60 * 1_000_000_000 # 1 hour353637def analyze(38trace_path: Path,39toc_schemas: frozenset[str],40top_n: int = 10,41window: tuple[int, int] | None = None,42run: int = 1,43) -> dict[str, Any]:44schemas = sorted(45s for s in toc_schemas46if s.startswith("swiftui") and not s.endswith("-causes")47)48if not schemas:49return {50"lane": "swiftui",51"available": False,52"notes": ["SwiftUI lane not in trace (Xcode 26+ SwiftUI template required)."],53}5455events: list[dict] = []56per_view_total_ns: dict[str, int] = defaultdict(int)57per_view_count: dict[str, int] = defaultdict(int)58severity_counts: Counter[str] = Counter()59update_type_counts: Counter[str] = Counter()60category_counts: Counter[str] = Counter()6162for schema in schemas:63xml_bytes = xctrace.export_schema(trace_path, schema, run=run)64stream = xml_utils.RowStream(xml_bytes)65for row in stream:66start_ns = _first_int(row, stream, START_KEYS)67dur_ns = _first_int(row, stream, DURATION_KEYS)68if start_ns is None or dur_ns is None:69continue70if dur_ns < 0 or dur_ns > _SENTINEL_DURATION_NS:71# Unterminated / ongoing update; skip so it doesn't poison72# totals and the correlation overlap check.73continue74if not xml_utils.event_overlaps_window(start_ns, start_ns + dur_ns, window):75continue7677view = _first_str(row, stream, VIEW_KEYS)78module = _first_str(row, stream, MODULE_KEYS)79category = _first_str(row, stream, CATEGORY_KEYS)80update_type = _first_str(row, stream, UPDATE_TYPE_KEYS)81severity = _first_str(row, stream, SEVERITY_KEYS)82description = _first_str(row, stream, DESCRIPTION_KEYS)83# Fall back through description → category → update-type so the84# agent sees "EnvironmentWriter: RootEnvironment" instead of85# "<unknown>" when SwiftUI doesn't record a view type.86if not view:87view = description or category or update_type or "<unknown>"8889per_view_total_ns[view] += dur_ns90per_view_count[view] += 191if severity:92severity_counts[severity] += 193if update_type:94update_type_counts[update_type] += 195if category:96category_counts[category] += 19798events.append({99"schema": schema,100"start_ns": start_ns,101"end_ns": start_ns + dur_ns,102"duration_ns": dur_ns,103"duration_ms": round(dur_ns / 1_000_000, 2),104"start_ms": round(start_ns / 1_000_000, 2),105"view": view,106"module": module,107"category": category,108"update_type": update_type,109"severity": severity,110"description": description,111})112113events.sort(key=lambda e: e["duration_ns"], reverse=True)114115top_by_total = sorted(116per_view_total_ns.items(), key=lambda kv: kv[1], reverse=True117)[:top_n]118top_offenders = [119{120"view": view,121"total_ms": round(total_ns / 1_000_000, 2),122"count": per_view_count[view],123"avg_ms": round(total_ns / per_view_count[view] / 1_000_000, 2),124}125for view, total_ns in top_by_total126]127128high_severity = [129{130"view": e["view"],131"severity": e["severity"],132"duration_ms": e["duration_ms"],133"start_ms": e["start_ms"],134"category": e["category"],135"update_type": e["update_type"],136"description": e["description"],137}138for e in events if e["severity"] in HIGH_SEVERITIES139][:top_n]140141longest = [142{143"view": e["view"],144"duration_ms": e["duration_ms"],145"start_ms": e["start_ms"],146"category": e["category"],147"update_type": e["update_type"],148"severity": e["severity"],149}150for e in events[:top_n]151]152153return {154"lane": "swiftui",155"available": True,156"schemas_used": schemas,157"metrics": {158"total_events": len(events),159"unique_views": len(per_view_total_ns),160"total_duration_ms": round(161sum(per_view_total_ns.values()) / 1_000_000, 2162),163"severity_breakdown": dict(severity_counts.most_common()),164"update_type_breakdown": dict(update_type_counts.most_common()),165"category_breakdown": dict(category_counts.most_common()),166},167"top_offenders": top_offenders,168"longest_single_events": longest,169"high_severity_events": high_severity,170"notes": [],171"_events": events,172}173174175def _first_int(row, stream, keys):176for key in keys:177el = row.get(key)178if el is None:179continue180val = xml_utils.int_text(stream.resolve(el))181if val is not None:182return val183return None184185186def _first_str(row, stream, keys):187for key in keys:188el = row.get(key)189if el is None:190continue191resolved = stream.resolve(el)192txt = xml_utils.str_text(resolved) or resolved.get("fmt")193if txt:194return txt195return None196