A WinUI application can feel slow even when the code looks reasonable. A button click may take noticeable time to respond, startup may pause for several seconds, scrolling may become uneven, or a screen may occasionally stop responding while the application is doing work in the background.
The difficult part is finding out where that time actually goes.
The Visual Studio debugger is useful for inspecting code and finding obvious problems, but it does not always show the complete picture of what Windows is doing while your application runs. A WinUI app can spend time on the UI thread, wait for another thread, perform file or network I/O, trigger garbage collection, wait for disk activity, or spend time in rendering and composition.
Event Tracing for Windows, usually called ETW, gives you another way to investigate these problems. ETW is a Windows tracing infrastructure that can collect events from both the operating system and applications while they are running. Windows Performance Recorder collects those events, and Windows Performance Analyzer opens the resulting trace and turns it into graphs and tables that can be investigated. Microsoft specifically documents WPR and WPA as tools for analyzing WinUI performance.
This makes ETW particularly useful before shipping a WinUI application because you can reproduce a performance problem locally, record what Windows and your application were doing, and then inspect the trace without adding permanent diagnostic code to the application.
What ETW Actually Captures
ETW is an event tracing system built into Windows. Applications and Windows components can publish events through ETW providers. A provider is simply a component that knows how to describe and emit tracing events.
For example, Windows can publish events related to CPU scheduling, disk I/O, networking, process activity, thread activity, memory behavior, and other system operations. WinUI and related Windows components can also emit events that help explain UI behavior.
The basic flow looks like this:
WinUI Application
|
| ETW events
v
Windows Performance Recorder
|
| ETL trace file
v
Windows Performance Analyzer
|
v
CPU / UI / Disk / Memory / Thread / Rendering analysis
The ETL file is the recorded trace. It contains timestamped events collected during the recording session.
WPR controls the recording session. WPA consumes the resulting ETL file and provides the analysis interface. Microsoft describes WPR as the recording component and WPA as the analysis component of the Windows Performance Toolkit.
The important point is that ETW is not simply another application log.
A normal application log might tell you:
Loading customer data...
Customer data loaded.
An ETW trace can help you understand what happened between those two points.
You may discover that the application spent part of that time waiting for a file operation, another thread, CPU scheduling, a network request, garbage collection, or UI processing.
That difference is important when investigating performance.
Why Profile a WinUI App Before Shipping
Performance problems are often difficult to reproduce after an application reaches production.
A developer may have a fast development machine, a small test database, a limited amount of application data, and a debugger attached. The final customer may have a different CPU, slower storage, multiple monitors, a large data set, different graphics hardware, or a machine already running several other applications.
Profiling locally before release gives you a controlled baseline.
You can measure:
- Application startup
- Time spent on the UI thread
- CPU-intensive operations
- Thread scheduling
- File and disk activity
- Network activity
- Memory and heap behavior
- Page faults
- Long-running UI frames
- Background work
- Rendering-related activity
WinUI uses a retained-mode UI system where the application describes a UI tree and WinUI performs layout and rendering. The UI thread processes work in frames, and long-running frames can delay both visual updates and input processing. Microsoft provides a XAML Frame Analysis table for WPA to make slow frame analysis easier.
That gives you a much better question than:
“Why does this screen feel slow?”
You can instead ask:
“Which thread was executing during the slow frame, what was it doing, and what was it waiting for?”
WPR and WPA: The Two Tools You Need
Windows Performance Recorder and Windows Performance Analyzer are part of the Windows Performance Toolkit.
WPR records the workload.
WPA analyzes the recording.
The Windows Performance Toolkit is included with the Windows Assessment and Deployment Kit. The Windows operating system also includes the WPR command-line tool on supported Windows versions, while the graphical WPR experience and WPA are provided through the Windows Performance Toolkit.
For most WinUI development work, you can think about the tools this way:
| Tool | Purpose |
|---|
| Visual Studio debugger | Inspect code and debug application logic |
| Visual Studio profiler | Investigate application-level performance |
| WPR | Record system and application ETW activity |
| WPA | Analyze ETW recordings |
| dotnet-counters | Watch selected .NET runtime counters |
| dotnet-trace | Capture .NET runtime tracing |
| PerfView | Detailed .NET and ETW investigation |
Microsoft also lists .NET-specific diagnostic tools alongside the Windows Performance Toolkit, so these tools are complementary rather than mutually exclusive.
Installing the Required Tools
Install the Windows Assessment and Deployment Kit and include the Windows Performance Toolkit components.
After installation, verify that WPR is available.
wpr -help
You can also inspect the available recording profiles:
wpr -profiles
The WPR command-line interface supports built-in profiles and custom WPR profiles. Microsoft documents commands for starting, stopping, checking status, and inspecting available providers and profiles.
You should also be able to start Windows Performance Analyzer from the Windows Performance Toolkit installation.
For normal application profiling, it is better to begin with built-in profiles instead of immediately creating a custom ETW profile.
Custom profiles are useful when you already understand the exact provider or event you need. They are unnecessary for the first investigation of most WinUI responsiveness problems.
Create a Release-Like Build First
Do not make your primary performance decision from a Debug build.
Debugging changes application behavior. Debug symbols, diagnostic features, debugger interaction, optimization differences, and development configuration can change timing.
For a realistic local test, create a build that is close to what you intend to ship.
For example:
dotnet publish -c Release
The exact publish configuration depends on how your WinUI application is packaged and distributed, so the important part is that the profiling workload should use the same configuration that you expect customers to run.
Also keep the following consistent when comparing traces:
- Same application version
- Same Windows version
- Same test data
- Same machine configuration
- Same startup state
- Same application settings
- Same workload
- Same number of repeated operations
Otherwise, you can easily compare two traces that are not actually comparable.
Start a Simple WPR Recording
The easiest approach is to use the WPR graphical interface.
Open Windows Performance Recorder and select the performance profiles appropriate for the problem you are investigating.
For a general WinUI performance investigation, CPU usage and XAML-related activity are useful starting points. Microsoft specifically recommends WPR for creating ETW recordings and provides WinUI-oriented guidance for CPU and XAML activity.
For a command-line workflow, WPR can start a built-in CPU profile:
wpr -start CPU
Run the application and reproduce the problem.
When the problem has been reproduced, stop the recording and save the ETL file:
wpr -stop C:\Traces\WinUI-Performance.etl
The command-line interface supports multiple profiles, light and verbose profiles, file-mode recording, and other options.
For larger traces, choose the recording mode and destination carefully. A trace that runs for a long time can consume substantial disk space, particularly when broad or verbose profiles are enabled.
Reproduce One Problem at a Time
This is one of the most important profiling rules.
Do not start a trace and then spend ten minutes randomly clicking around the application.
Instead, define a specific scenario.
For example:
1. Start the application.
2. Wait until the main window is ready.
3. Open the Orders page.
4. Click Refresh.
5. Wait for the page to finish loading.
6. Stop the trace.
If the problem is startup:
1. Close the application.
2. Start WPR.
3. Launch the application.
4. Wait until the main window becomes usable.
5. Stop the trace.
If the problem is scrolling:
1. Open the affected page.
2. Start WPR.
3. Scroll through the same section several times.
4. Stop WPR.
Short, repeatable scenarios make traces much easier to analyze.
Finding CPU Problems in WPA
Open the ETL file in Windows Performance Analyzer.
Start with CPU-related views rather than opening every available graph.
The first question is:
“Which process and thread consumed the CPU?”
If your application process is using a large percentage of CPU, drill into the process and then the individual threads.
You may find something similar to this:
MyApp.exe
UI Thread
Layout calculation
Collection processing
JSON parsing
Worker Thread
Data transformation
ThreadPool Thread
Database result processing
The next question is whether the CPU work is actually on the UI thread.
If a worker thread is busy but the UI remains responsive, that may be acceptable.
If the UI thread is continuously executing expensive code, the same CPU usage becomes a responsiveness problem.
This distinction is critical in WinUI applications.
CPU Time Is Not the Same as Waiting Time
A common profiling mistake is assuming that a slow operation must have high CPU usage.
Consider this code:
private async void RefreshButton_Click(object sender, RoutedEventArgs e)
{
var response = await httpClient.GetStringAsync(apiUrl);
Orders = ParseOrders(response);
OrdersList.ItemsSource = Orders;
}
Suppose the operation takes five seconds.
That does not mean the CPU was busy for five seconds.
The application may have spent most of the time waiting for the network.
Another example:
private void LoadButton_Click(object sender, RoutedEventArgs e)
{
var data = File.ReadAllText(filePath);
ProcessLargeDocument(data);
}
The application might spend part of the operation waiting for storage and another part using the CPU to process the resulting data.
WPA helps separate these types of behavior by showing scheduling and resource activity rather than giving you only an application-level elapsed time.
That is one reason ETW is useful when ordinary stopwatch measurements are not enough.
Investigating the WinUI UI Thread
The UI thread deserves special attention because too much work there directly affects interaction.
Suppose a button click starts a large operation:
private void LoadButton_Click(object sender, RoutedEventArgs e)
{
var records = LoadThousandsOfRecords();
BuildViewModels(records);
OrdersList.ItemsSource = records;
}
If LoadThousandsOfRecords and BuildViewModels take significant CPU time on the UI thread, the application may stop processing input while that work runs.
A better design might move appropriate CPU-bound work away from the UI thread:
private async void LoadButton_Click(object sender, RoutedEventArgs e)
{
var records = await Task.Run(() =>
{
var data = LoadThousandsOfRecords();
return BuildViewModels(data);
});
OrdersList.ItemsSource = records;
}
This is not a universal fix.
You should not move UI operations into Task.Run. WinUI objects that require UI-thread access must still be used from the appropriate thread.
The real goal is to separate CPU-bound application work from UI operations when doing so is safe.
ETW helps verify whether the UI thread is actually the bottleneck instead of guessing.
Analyze Slow XAML Frames
WinUI applications can have another class of performance problem: individual UI frames taking too long.
A frame is the unit of UI work that needs to progress through processing and rendering before the updated interface can appear.
If frames consistently take too long, scrolling, animations, and input handling can become noticeably less responsive.
Microsoft's current WinUI performance guidance includes XAML-related WPR collection and a XAML Frame Analysis table in WPA. The plugin calculates frame duration and exposes other time-consuming operations that would otherwise require manual interpretation of ETW events.
For current tooling, the XAML Frame Analysis table requires WPA from Windows ADK 10.1.26100.1 or later. The plugin is enabled through the WPA perfcore.ini configuration.
This is useful when the application does not show an obvious CPU spike but still feels visually slow.
Typical areas to investigate include:
- Excessive UI element creation
- Large visual trees
- Expensive layout work
- Repeated updates to bound collections
- Large changes to the UI tree
- Frequent invalidation
- Expensive rendering-related operations
The important point is to measure the frame rather than assuming that XAML itself is the problem.
Investigating Disk and File I/O
Suppose the application has a slow startup.
You might discover that the application loads several configuration files, local databases, images, cached API responses, or other resources before showing the main screen.
Select the appropriate WPR disk or file I/O profiles when investigating this type of problem.
Microsoft provides built-in profiles for CPU, disk I/O, file I/O, networking, heap usage, registry I/O, and other resource categories.
A useful investigation looks like this:
Application startup
|
+-- CPU activity
|
+-- File reads
|
+-- Database access
|
+-- Network activity
|
+-- UI initialization
If the startup delay is caused by storage, optimizing CPU code will not solve the problem.
You need to identify the actual resource that is consuming the time.
Investigating Memory Problems
Memory analysis is slightly different from CPU profiling.
A process can have high memory usage without continuously consuming CPU.
Look at working set, allocations, heap-related activity, and page faults when the problem involves memory pressure or unexpected growth.
For example, an application that loads thousands of images into memory may appear to have a rendering problem when the underlying issue is memory pressure caused by keeping too many resources alive.
The trace can help answer questions such as:
- Does memory continually increase?
- Does the increase happen during one particular operation?
- Are page faults increasing?
- Does the application release memory after closing a page?
- Is a large allocation associated with a specific workload?
For managed applications, ETW-based analysis can also complement .NET-specific diagnostic tools when garbage collection or managed allocations need deeper investigation.
Do Not Trace Everything
One of the easiest ways to make ETW analysis difficult is to collect too much data.
A huge trace containing every available provider can make analysis slower and produce enormous amounts of unrelated information.
Start with the smallest useful recording.
For example:
Problem: slow button response
Start with: CPU + relevant UI/XAML activity
Problem: slow startup
Start with: CPU + disk/file I/O
Problem: network-related delay
Start with: CPU + networking activity
Problem: memory growth
Start with: memory/heap-related profiles
Add more providers only when the initial trace does not explain the behavior.
This is the same principle used in production observability: collect enough information to answer the question, not every possible event.
Use Trace Markers for Repeatable Tests
When you perform repeated tests, you need a way to identify where the important scenario occurred in the trace.
WPR supports event markers that can help identify points in a recording. The command-line interface includes a marker option for this purpose.
For example:
wpr -marker "Orders Refresh Started"
Then reproduce the operation and place another marker when it finishes:
wpr -marker "Orders Refresh Finished"
This can make long traces easier to navigate because you can identify the section corresponding to the operation you care about.
For automated performance testing, markers can also make repeated trace analysis more systematic.
Symbols Matter
A trace can contain excellent timing information but still be difficult to understand if the relevant symbols are unavailable.
Symbols allow WPA to associate low-level addresses with meaningful functions.
For a .NET application, WPR also handles managed symbols as part of its trace workflow. Microsoft notes that managed symbol generation can require significant resources and that WPR maintains a symbol cache to improve subsequent analysis.
When preparing a profiling machine, make sure you have a consistent build and the symbols required to understand that build.
Do not compare a trace from one binary version with symbols from another version.
The result can be misleading because the addresses and compiled code may no longer correspond.
A Practical WinUI Performance Example
Imagine a WinUI application with an Orders page.
Users report that clicking Refresh causes the interface to freeze for approximately two seconds.
The first attempt might be to add logging:
Refresh started
Loading orders
Processing orders
Refresh completed
That tells you that two seconds passed, but not why.
You run a WPR trace while reproducing the problem.
WPA shows:
UI Thread
1.4 seconds CPU time
0.3 seconds waiting
0.3 seconds rendering/layout
You inspect the CPU activity and find that the UI thread is processing thousands of records and creating view models.
The network request itself is not the main problem.
That changes the fix.
Instead of trying to make the HTTP request faster, you can investigate:
Network request
|
v
Raw data
|
v
Background processing
|
v
Prepared UI data
|
v
UI thread updates visible collection
The performance problem becomes an architectural problem rather than a network problem.
This is exactly where ETW provides value: it gives you evidence about where the elapsed time is being spent.
Common Mistakes
Profiling only with the debugger attached
A debugger can change application timing and thread behavior.
Use a normal Release-style build for your primary performance measurements.
Recording for too long
Long recordings create large ETL files and make analysis harder.
Keep the scenario short and repeatable.
Collecting every provider
More events do not automatically mean better diagnostics.
Start with a focused profile and expand only when necessary.
Looking only at CPU usage
A process can be slow while spending much of its time waiting.
Always consider scheduling, I/O, network activity, and UI frame behavior.
Ignoring the UI thread
Background CPU usage may look alarming but may not affect responsiveness.
Always identify which thread is blocking interaction.
Using different workloads for each trace
If one trace processes 1,000 records and another processes 100,000 records, the comparison is not meaningful.
Keep the workload controlled.
Optimizing before measuring
Moving code to another thread, changing collection types, reducing logging, or rewriting XAML without evidence can create unnecessary complexity.
First establish where the time is going.
Handling Sensitive Trace Data
ETL traces can contain detailed information about system and application activity.
Treat performance traces as diagnostic artifacts rather than harmless temporary files.
Consider the following:
- Store traces in controlled locations.
- Do not upload traces to public repositories.
- Review traces before sharing them outside your team.
- Avoid capturing unnecessary application activity.
- Delete traces when they are no longer needed.
- Be careful when traces are collected from machines containing sensitive workloads.
Local profiling is particularly useful because you can investigate the problem without sending application execution data to an external service.
When ETW Is the Right Tool
ETW is a strong choice when the problem crosses application and operating-system boundaries.
Use it when you need to understand:
- Why startup is slow
- Why the UI thread is blocked
- Why scrolling is inconsistent
- Why CPU usage spikes
- Why disk activity delays an operation
- Why a background task affects responsiveness
- Why an operation spends time waiting
- Why a particular UI frame takes too long
For simple application-level CPU problems, Visual Studio profiling may be faster.
For .NET runtime-specific investigation, dotnet-trace or dotnet-counters may provide more focused information.
For system-level questions involving Windows scheduling, I/O, CPU, memory, and WinUI behavior, WPR and WPA are often the better fit.
Advantages and Disadvantages
Advantages:
- Uses Windows' built-in ETW infrastructure.
- Can correlate application and operating-system activity.
- Provides detailed CPU and scheduling information.
- Can investigate disk, file, network, memory, and UI activity.
- Works well for short, repeatable local performance tests.
- Does not require permanent diagnostic code in the application.
- WPA provides detailed graphs and tables for analysis.
Disadvantages:
- WPA has a learning curve.
- ETL files can become large.
- Broad traces can be difficult to analyze.
- Symbol configuration can affect how useful the trace is.
- Understanding scheduling data requires some Windows performance knowledge.
- Some advanced analysis requires custom WPR profiles or deeper ETW knowledge.
The main limitation is not the amount of data ETW can collect. It is knowing which data matters for the problem you are investigating.
A Practical Pre-Release Profiling Workflow
Before shipping a WinUI application, use a repeatable workflow.
1. Build the application in a release-like configuration.
2. Define one specific performance scenario.
3. Start WPR with the smallest useful profile.
4. Launch or operate the application.
5. Reproduce the problem consistently.
6. Add markers when useful.
7. Stop the recording.
8. Open the ETL file in WPA.
9. Identify the process and relevant thread.
10. Check CPU, waits, I/O, memory, and UI frame behavior.
11. Identify the actual bottleneck.
12. Make one focused change.
13. Repeat the same workload.
14. Compare the new trace with the baseline.
15. Keep the change only if the measured result improves.
This turns performance work into an engineering process instead of a collection of guesses.
Production Checklist
Before shipping a WinUI application, check the following:
- Profile important startup paths.
- Test common navigation paths.
- Test large data sets.
- Test scrolling and frequently updated screens.
- Profile the UI thread.
- Investigate long-running frames.
- Check CPU-heavy operations.
- Check file and disk activity during startup.
- Check network-related delays.
- Test with realistic hardware.
- Use a release-like build.
- Keep profiling scenarios repeatable.
- Save baseline traces for important performance tests.
- Avoid collecting unnecessarily broad ETW data.
- Verify symbols for the exact build being analyzed.
- Treat ETL files as potentially sensitive diagnostic data.
- Re-profile after major UI or architectural changes.
Summary
Profiling a WinUI application does not have to start with complicated instrumentation or a large observability system. Windows already provides ETW as a tracing infrastructure, and the Windows Performance Toolkit provides WPR and WPA for recording and analyzing that information.
The important part is using the tools with a focused question.
If a screen is slow, find out whether the UI thread is doing too much work. If startup is slow, check CPU, file, disk, and network activity. If scrolling is poor, investigate UI frame duration and rendering-related work. If memory keeps growing, look at allocation and memory behavior instead of assuming the problem is CPU-related.
The most useful ETW workflow is simple:
Reproduce
↓
Record
↓
Analyze
↓
Find the bottleneck
↓
Change the code
↓
Record again
↓
Compare
That approach gives WinUI performance work a measurable baseline before the application reaches users. Instead of deciding that an application “feels slow,” you can identify the thread, operation, resource, or UI frame responsible for the delay and make a targeted change based on evidence.
Join the conversation! Your thoughts help the community grow.