--- name: winapp-performance description: Investigate slow WinUI 3 layout, scrolling and virtualization using native ETW captures, timed operation trees and managed GC suspension context. Use bounded queries for runtime performance questions, not crash analysis or UI correctness. --- ## Workflow 1. Read the [WinUI performance guide](https://github.com/microsoft/WinAppCli/blob/main/docs/guides/winui-performance.md) for capture limits, query options, partial-result handling, privacy and interpretation. 2. Use the existing app PID when possible. Start with `winapp perf start --app --output --json`. `--app` also accepts a process name or window title using the same matching rules as `winapp ui`. Use the returned `target.pid` for subsequent UI actions. For a new launch, use `winapp run --profile --detach --json`. This records after launch, which is enough for navigation, scrolling and other post-launch scenarios. For startup investigations only, add `--profile-mode elevated` (one UAC prompt for the recorder, which the user must approve) and check `Profile.StartupCoverage`; see the guide's [startup workflow](https://github.com/microsoft/WinAppCli/blob/main/docs/guides/winui-performance.md#record-startup-layout). Do not add startup delays or replace a generated `Main`. 3. Wait for successful recording readiness. Save the returned capture ID. 4. Mark the beginning with `winapp perf mark --name scenario-start --json`, drive a short repeatable scenario with verified UI selectors, wait for its visible outcome, then run `winapp perf mark --name scenario-end --json`. 5. Run `winapp perf stop --json` before closing the app. 6. Start with `winapp perf analyze --from-marker scenario-start --to-marker scenario-end --json` to compare mutually exclusive primary-UI-thread activity, the hottest parsed XAML resources, and the detailed operation ranking. Use `--view parsing` over the same range when the parsing preview needs full paging. Then query `winapp perf analyze --view hotspots --from-marker scenario-start --to-marker scenario-end --json`; its default threshold is 16.67 ms and each row includes dominant direct child operations. Expand a returned operation with `--view call --id --depth 2 --from-marker scenario-start --to-marker scenario-end --json`. Use `--view calls --family layout` with the same marker range when the hotspot evidence points to layout. Query `--view gc --sort duration` over the same marker range to find the longest collection and suspension intervals, then inspect referenced interval IDs. Use element and event views for source and evidence drill-down. Follow `nextOffset`; do not dump NDJSON or ETL into context. For overall resource cost, use `--view resources` over the same marker range; see the guide's [process counters](https://github.com/microsoft/WinAppCli/blob/main/docs/guides/winui-performance.md#compare-process-resource-cost). For app-side composition, query `--view calls --family composition` and `--view events --event DCompDeviceCommit` over the same marker range; see [composition submission](https://github.com/microsoft/WinAppCli/blob/main/docs/guides/winui-performance.md#separate-xaml-work-from-composition-submission). 7. Report observed cost, supporting event IDs, coverage gaps, and one concrete next experiment. Preserve stdout when partial results accompany a nonzero exit code. ## Guardrails - Use `--profile-mode elevated` only for startup, and tell the user to expect a UAC prompt. If they decline it, do not retry; fall back to the default attach mode and say that early startup may be missing. Otherwise never elevate, change tracing ACLs, or modify app code to make capture work. - Recording is local only. Do not combine `run --profile` with `--on sandbox`; see the guide for the supported launch and recording commands. - Readiness is not decoded coverage. Missing events are inconclusive. - For WinUI operations, use elapsed-time terminology; do not claim CPU/GPU attribution, displayed FPS, proven input-to-display latency, or complete startup/visual-tree coverage. - `Unclassified` includes idle, waits, and uninstrumented work. Do not describe it as app code or CPU use. Off-thread image decode is not UI-thread occupancy. - Process CPU counters include all target threads but do not attribute functions, modules, or waits. Sampled memory/thread maxima can miss brief spikes; missing samples are not zero resource use. - Trace-local element IDs are not UI Automation selectors. - Composition calls are elapsed API scopes, not GPU work or display latency. DComp device commits are notifications, not timed or displayed frames; check `directCompositionProvider` before interpreting missing events. - Call trees are instrumented operation scopes, not CPU stacks or visual trees. Keep exclusive scope time distinct from element self time. - GC is requested by default but may be unavailable. Check `gcCoverage` separately; missing events are not zero pauses. Background GC duration is not suspension time, and overlapping runtime suspension is not proof that GC blocked the native UI. - Keep raw captures local unless the user has reviewed and authorized sharing them. - Use the UI automation skill to interact with the app and troubleshooting/debug output workflows for crashes; neither replaces performance evidence.