Unreal Insights From Zero — A Practical Guide for UE5 Developers
Insights is the profiler that replaced the legacy UE4 Profiler — a frame-accurate, multithreaded performance analyser that can capture CPU, GPU, memory, networking, animation, and Slate data in a single trace. This guide walks you from "I have never opened it" to "I know which thread, which frame, and which function is costing me the budget."
What Unreal Insights Actually Is
Unreal Insights is two programs working together. UnrealTraceServer is a small background daemon that listens for trace data — either from a running game on the same machine or from a remote build over TCP. UnrealInsights is the GUI front-end you actually look at: it owns the trace store, the session browser, and every analysis view (Timing, Memory, Networking, Animation, Slate, Loading).
Trace data itself is a stream of compact binary records emitted by the engine via the Trace system. The engine groups records into channels — cpu, gpu, frame, bookmark, memory, net, and so on. You enable the channels you care about, run the game, and the resulting .utrace file gets parsed and visualised by Insights.
Three things to remember up front:
- Insights is offline-friendly. A trace is a self-contained file. You can record on a console, copy the
.utraceback to your workstation, and open it days later. - Channels are opt-in. The defaults are conservative. If you want CPU samples, GPU timings, render commands, or memory allocations, you must turn them on either via command line or via the editor's Trace menu.
- Tracing is not free. Some channels are nearly free; others (like
cpuwith-statnamedevents, ormemorywith callstacks) measurably affect frame time. We'll cover what's safe to leave on and what to enable only for targeted captures.
Find & Launch the Insights Executable
Insights ships with the engine. You don't install it separately — you just run the binary that's already on disk.
# Windows <UE_ROOT>\Engine\Binaries\Win64\UnrealInsights.exe # macOS <UE_ROOT>/Engine/Binaries/Mac/UnrealInsights.app # Linux <UE_ROOT>/Engine/Binaries/Linux/UnrealInsights
For a Launcher install on Windows, <UE_ROOT> is something like C:\Program Files\Epic Games\UE_5.5. For a from-source build it's wherever you cloned and ran Setup.bat. You will be running this binary often — make a desktop shortcut or pin it.
Launching it for the first time gives you the Session Browser: an empty list of trace sessions and a bar at the top showing Trace Store Directory. That directory is where every .utrace recording lands by default. Make a mental note of it; you'll be opening files from there constantly.
UnrealTraceServer in the background. You don't normally need to touch it. If something gets wedged, kill the UnrealTraceServer process in Task Manager / Activity Monitor / pkill, relaunch Insights, and you'll get a fresh server.
Capture Your First Trace from the Editor
The fastest way to get a trace is to start Insights, then start your game. The editor will detect Insights is listening on the local trace server and connect automatically.
- Launch
UnrealInsights.exe— leave the Session Browser open. - Open your project in Unreal Editor.
- In the bottom-right corner of the editor (the status bar) you'll see a small Trace Status widget. Click it to open the Trace Store menu.
- Pick Channels and tick the data you want to record. Sensible defaults:
Frame,CPU,GPU,Bookmark,Log,Counters. - Click Trace → Start Trace (or use the Trace Control tab). The status widget turns green.
- Press Play in the editor — ideally Standalone Game mode (next step explains why).
- Do the work you want to measure. Twenty to forty seconds is plenty for a first pass.
- Stop the game, then click Trace → Stop Trace.
- Switch back to the Session Browser. Your new trace appears at the top of the list. Double-click it to open the analysis viewer.
That's it — you have a trace. Don't worry about reading it yet; the rest of this tutorial is dedicated to that.
Standalone Game vs PIE — Why It Matters
This is the single most-skipped step, and it's the one that quietly invalidates everyone's first profile.
Play in Editor (PIE) runs your game inside the editor's process. The editor world is still alive: viewport rendering, asset tooling, the editor UI, content browser thumbnail generation, hot reload watchers. Every one of those costs frame time, and none of them exist in a shipped build. Profiling PIE measures "your game plus the editor" — not your game.
Standalone Game launches your project in a separate process with no editor world attached. It's still a Development build (with full debug symbols and stat commands available) but it's a much closer analog to what your players will run.
Set this once and forget it:
- Open Editor Preferences → Level Editor → Play.
- Find Default Player Start Location and Standalone Game sections.
- Add any per-launch flags you want to Additional Launch Parameters (e.g.
-trace=cpu,gpu,frame,bookmark). - From the Play dropdown in the toolbar, pick Standalone Game instead of Selected Viewport.
For an even closer-to-shipping signal, profile a packaged Development build. PIE and Standalone both have editor-only systems (like the Asset Registry being fully loaded) that a cooked build does not. Use packaged builds when chasing the last single-digit-percent of a frame budget.
Capture from the Command Line
For CI, automated runs, or remote builds you want trace launched from the command line. There are three useful flags.
# Stream live to a running Insights instance on this machine -tracehost=127.0.0.1 -trace=cpu,gpu,frame,bookmark,log,counters # Stream live to Insights on a different machine (devkit / build farm) -tracehost=10.0.0.42 -trace=cpu,gpu,frame,bookmark,log,counters # Record to a file with no Insights connection — analyse later -tracefile=D:\traces\session_2026_04_25.utrace -trace=cpu,gpu,frame,bookmark,log,counters # Add finer-grained CPU events from the legacy stat system -statnamedevents
Use -tracehost when you want a live session and Insights is already running. Use -tracefile when there's no GUI to connect to (CI runners, headless devkit captures, the PerfGuard Gauntlet controller). You can supply either — not both — per launch.
A typical, "give me everything useful" capture looks like:
YourProject.exe ^
-game ^
-trace=cpu,gpu,frame,bookmark,log,counters,rhicommands,rendercommands,object,loadtime,file ^
-statnamedevents ^
-tracefile=D:\traces\full_capture.utrace
Console commands work too. From a running game, the in-game console accepts:
trace.start cpu,gpu,frame,bookmark # Begin recording with the listed channels trace.stop # Finish and flush to the trace store trace.snapshotfile mySnapshot.utrace # Dump a one-shot snapshot to disk trace.sendto 192.168.1.50 # Switch the live target host on the fly trace.bookmark "Combat begins" # Insert a manual marker into the trace
The Trace Channel Cheat Sheet
Channels decide what shows up in your trace and what doesn't. Enable everything and your traces become huge and tracing itself starts moving the numbers; enable nothing and the views are empty. Pick the smallest set that answers your question.
| Channel | What it captures | Cost |
|---|---|---|
| frame | Frame boundaries. The minimum required to get the Frames timeline. | Trivial |
| cpu | CPU timing events on every traced thread (game, render, RHI, task workers). | Low–moderate |
| gpu | GPU work durations from the RHI's timing queries. | Trivial |
| bookmark | Named time markers, both engine-emitted and user-defined via TRACE_BOOKMARK. | Trivial |
| log | Routes UE_LOG output into the Log panel of the trace. | Trivial |
| counters | Custom and engine-emitted scalar counters; required for graphable values. | Trivial |
| rhicommands | Per-RHI-command timings (DrawIndexedPrimitive, SetPipelineState, etc.). | Moderate |
| rendercommands | Render-thread command list submission events. | Moderate |
| object | UObject lifecycle events — useful for "who spawned this?" questions. | Moderate |
| loadtime | Asset/package loading timings; required for the Asset Loading Insights view. | Low |
| file | File I/O activity — opens, reads, seeks, with sizes and durations. | Low |
| memory | Allocation events feeding Memory Insights. Pairs with LLM tags. | High |
| callstack | Captures a callstack on every allocation. Use only for leak hunts. | Very high |
| net | Replicated property & RPC traffic for Networking Insights. | Moderate |
| animation | Anim graph evaluations, state machine transitions, blend nodes. | Moderate |
| slate | Widget paints, invalidations, layout passes for Slate Insights. | Moderate |
| task | TaskGraph and Tasks system dispatches — required to see job dependencies. | Low |
Suggested presets:
- "What's eating my frame?" —
frame,cpu,gpu,bookmark,log,counters. - "GPU is the bottleneck" — add
rhicommands,rendercommands. - "Hitches at level streaming" — add
loadtime,file,object. - "Suspected memory leak" —
frame,bookmark,memory,callstack(and accept the perf hit). - "Replication churn in multiplayer" — add
net.
trace.status in the in-game console for the live list of channels and which are enabled. New channels show up between point releases — UE 5.5 and 5.6 both added new ones, and 5.7 surfaced more memory-tracking channels.
Open a Trace — The Trace Store & Browser
Every captured trace lands as a .utrace file in the Trace Store Directory. The default is under your engine install (Engine/Programs/UnrealInsights/Saved/TraceSessions) but you can change it from Session Browser → Settings → Trace Store Directory — point it at a fast SSD on your data drive and you'll thank yourself later.
The Browser shows three categories:
- LIVE — sessions currently being recorded. Double-click to attach the analyser to a still-running game.
- OFFLINE — finished traces in the store directory.
- EXTERNAL — traces opened from anywhere via File → Open Trace File; useful for sharing a
.utracereceived from a teammate or a console.
Right-clicking a trace lets you rename, mark as favourite, copy the path, or delete it. Traces add up fast — an hour of cpu,gpu,frame,bookmark from a complex level can be hundreds of megabytes. Sweep stale traces regularly.
.utrace is fully self-contained. Zip it, drop it in Slack or attach it to a Jira ticket, and the recipient opens it in their Insights with no setup. Make this a habit when reporting performance bugs — "I see a hitch" is much harder to debug than "open this .utrace and look at frame 1480."
Reading the Timing View
The Timing view is the heart of Insights. It's also where most newcomers get stuck because the controls aren't obvious. Spend ten minutes learning the keys here and the rest of the tool clicks into place.
Layout: time runs left-to-right along the X axis. Each horizontal lane is a track — one per CPU thread, plus a GPU track, plus the Frames bar at the top. Inside each track, each block is an event (a TRACE_CPUPROFILER_EVENT_SCOPE, a stat scope, a render-pass marker). Nested blocks are children of their parent event.
Mouse:
- Left-drag on the timeline — pan time left/right.
- Mouse wheel — zoom in or out around the cursor.
- Shift + wheel — pan horizontally without zoom.
- Double-click an event — highlight all instances of the same event name; everything else fades.
- Ctrl + double-click an event — set the visible time range to the event's bounds. Other panels (Callees, Counters) update to that selection.
Keyboard:
- F — frame the current selection. Closest equivalent to "fit to view."
- C — compress tracks vertically so more fit on screen.
- U / Y — toggle CPU / GPU track visibility.
- G — toggle the Frames bar.
- Home / End — jump to the start or end of the trace.
Track management: right-click a track header to pin it to the top, pin to the bottom, or hide it. Pin Game Thread + Render Thread + RHI + GPU at the top and you've made yourself a permanent "frame summary" you can scroll past every other thread for.
Frames, Threads, and the GPU Track
The Frames bar at the top shows one bar per frame, scaled to that frame's duration. Anything above 16.67 ms (60 fps) or 33.3 ms (30 fps) is your bar over your target. Click any bar and the Timing view scrolls to that frame; right-click and you can set selection to this frame, which constrains every other panel to that single frame.
Find the worst frame fast: click the Frames track header and pick Sort by → Duration (descending). Now the largest spikes float to the top and you can step through them one by one.
Once you're looking at a frame, four threads matter:
- GameThread — gameplay logic, ticking, AI, physics dispatch, UI update. Most regressions on most projects are here.
- RenderThread — visibility culling, draw call building, scene proxy management. Spikes here usually mean too many primitives, too many materials, or a bad shader on a hot pass.
- RHIThread — submits work to the GPU driver. Large gaps here usually mean the driver is back-pressuring you (CPU-bound at the API level).
- GPU — the actual GPU work duration as reported by the RHI. Compare its length to the GameThread bar: if GPU is bigger, you're GPU-bound; if GameThread is bigger, you're CPU-bound. (See the CPU regressions and GPU regressions tutorials for what to do next.)
The GPU track is special. Events on it are RHI passes — "BasePass", "ShadowDepths", "Lumen.SceneLighting", "TSR Update History", "Translucency". Click into one of those and the Insights GPU track lets you drill into sub-passes. This is your "where on the GPU is my time going" view; far better than stat gpu for seeing structure.
Callers & Callees — Inclusive vs Exclusive Time
The Timing view shows you where time is being spent. The Timers / Callers / Callees panels tell you which functions are spending it. They are how you go from "frame 1840 is 45 ms" to "and 12 of those milliseconds are inside USkeletalMeshComponent::TickComponent."
Open the panel from Window → Timers (or it's docked by default in the Timing layout). The columns you live in are:
- Count — how many times this event fired in the current selection.
- Incl(usive) Time — total time inside this event including all its children. This tells you "how much of the frame did this subsystem own?"
- Excl(usive) Time — total time inside this event not counting its children. This tells you "how much of the frame is this function itself, not the things it called?"
- Avg / Min / Max / Median — per-call timings.
How to read it:
- High Inclusive, low Exclusive — this function is a router. The cost is in what it calls. Drill into Callees.
- High Inclusive, high Exclusive — this function is itself slow. Look at the body.
- Low Inclusive, called thousands of times — "death by a thousand cuts." Often a tick on a populous actor or a per-pixel material doing too much.
Constrain the view to what matters. Tracking the worst frame? Right-click in the Frames track and choose Set Selection to Frame. The Timers panel now shows just that frame. Tracking a region across many frames? Ctrl + drag in the Timing view to set a custom range. Every aggregate column updates accordingly.
Callers / Callees: select a row in the Timers panel and the Callers panel shows which functions invoked it (and how often), while Callees shows what it invoked (and where its inclusive time went). This is the exact same "build a flame graph in your head" workflow as Sampling Profiler / VTune / Tracy — just with cleaner names because Insights uses your engine's own scope labels.
Bookmarks & the Log Panel
Bookmarks are named timestamps. They appear as vertical lines on every track and as searchable rows in the Bookmarks panel. The engine emits some itself — EnginePreInit, EngineInit, FEngineLoop::Tick, CollectGarbage, level streaming events — and you can add your own.
They are absurdly useful. Found a hitch but can't tell what triggered it? Look for a bookmark inside the spike. Profiling a level transition? Bookmarks frame "before stream-in / during stream-in / after stream-in" so the Timers panel can be set to each region individually.
Add one from a console at any time:
trace.bookmark "Player entered Boss Arena" trace.bookmark "Wave 3 begins"
Or from C++ (covered in step 13).
The Log panel mirrors anything that went through UE_LOG when the log channel was enabled. It's filterable by category and severity. Pair it with bookmarks: when chasing a specific event, log a unique string when it happens, and the Log panel becomes a perfectly time-aligned breadcrumb trail through the trace.
Instrument Your Own Code in C++
The default trace is rich, but anonymous game code looks like a single block. To see your systems on the timeline you need to add scoped events.
Three macros do most of the work:
// Static, literal name — cheapest, preferred for hot paths. void AMyActor::DoExpensiveWork() { TRACE_CPUPROFILER_EVENT_SCOPE(AMyActor_DoExpensiveWork); // ... } // Same but with a string literal — useful when names contain spaces. { TRACE_CPUPROFILER_EVENT_SCOPE_STR("Compute world chunk LODs"); // ... } // Dynamic name (FString built at runtime) — a little more expensive, // only use when the name itself carries information. { TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(*FString::Printf(TEXT("Player_%d_Tick"), PlayerId)); // ... }
All three open a scope; the timer stops automatically when the scope ends (RAII). The scope shows up in the Timing view as a coloured block on whatever thread it ran on, with the name you gave it.
For richer named events (the kind -statnamedevents activates in the legacy stat system), the equivalent is:
SCOPED_NAMED_EVENT(MyHeavyWork, FColor::Red); // or with a string SCOPED_NAMED_EVENT_TEXT("AsyncCompile", FColor::Cyan);
SCOPED_NAMED_EVENT appears in both Insights and the legacy stat profiler — useful if your team still has hand-rolled stat tooling around. TRACE_CPUPROFILER_EVENT_SCOPE is Insights-only and slightly cheaper.
Custom Counters and Bookmarks from Code
Beyond timing scopes, you can stream your own scalar values and named markers into the trace. Both show up as graphable / searchable rows in Insights.
Bookmarks from C++:
TRACE_BOOKMARK(TEXT("Player respawned")); TRACE_BOOKMARK(TEXT("Boss phase 2 (HP=%d)"), CurrentHP);
Bookmarks are cheap (just a name + timestamp); use them liberally for state transitions, level streaming events, network connection events, anything you'd want to find again in the timeline.
Custom counters need a one-time declaration plus per-frame writes:
// At file scope, once. TRACE_DECLARE_INT_COUNTER(ActiveAIAgents, TEXT("AI/ActiveAgents")); TRACE_DECLARE_FLOAT_COUNTER(NetBandwidthKBps, TEXT("Net/BandwidthKBps")); // Anywhere — typically once per frame. TRACE_COUNTER_SET(ActiveAIAgents, AIPool.NumActive()); TRACE_COUNTER_ADD(NetBandwidthKBps, -DeltaKB); // Or one-shot increments: TRACE_COUNTER_INCREMENT(ProjectilesFired); TRACE_COUNTER_DECREMENT(LiveDecals);
Open Window → Counters in Insights and your declared counters appear in the list. Drag any onto the Timing view and you get a graph track time-aligned with the rest of the trace — "spawned more agents at t=12s" lines up perfectly with "GameThread spiked at t=12s," and you've found your suspect in seconds.
Memory Insights — Tracking Allocations & Leaks
Memory Insights is its own analyser within the Insights app, fed by the memory trace channel and the Low-Level Memory Tracker (LLM). When enabled, every allocation and free is recorded along with the LLM tag it belongs to (UObject, RenderTargets, Audio, Networking, etc).
Capture command line:
YourProject.exe -game ^ -trace=cpu,frame,bookmark,memory ^ -llm ^ -tracefile=mem_capture.utrace
Open the trace and switch to the Memory Insights view (top-right tab next to Timing). You'll see four key panels:
- LLM Tags — live and peak memory by tag, drillable by subtag.
- Modules — allocations attributed by source module (CoreUObject, Engine, your game module).
- Investigation — the leak-hunting tool. Pick two timestamps. Insights shows blocks allocated in that window and never freed. Pair this with bookmarks for "before action / after action" and a leak announces itself as a row in this panel.
- Timing — live memory over time, graphable like any counter.
For real leak hunts, also enable callstack:
-trace=cpu,frame,bookmark,memory,callstack -llm
This records a callstack for every allocation. Each leaked block in the Investigation view becomes a clickable trace back to the exact line of code that allocated it. The cost is real — expect double-digit-percent frame-time slowdown and large traces. Use it for targeted runs, not all-day captures.
memory channel will produce empty data. Pass -llm on the command line, or check that LowMemoryTracker is enabled in DefaultEngine.ini. stat memory in the in-game console verifies LLM is active.
Networking, Animation, and Slate Insights
Three specialist analysers ride on the same trace pipeline. Each is invaluable when the relevant channel is enabled and pretty much invisible when it isn't.
Networking Insights — channel net. Shows replicated actor and component traffic, which properties replicated each frame, RPC dispatches, and per-connection bandwidth. The killer feature is the NetProfiler view: pick a frame, see exactly which property on which actor cost how many bits to send. If your multiplayer game's bandwidth is leaking, this is where you find the actor that's replicating on every tick.
Animation Insights — channel animation and the Animation Insights editor plugin. Visualises anim graph evaluation per skeletal mesh: which states fired, which blend nodes evaluated, which Anim Notifies hit, how long each took. Makes "why is anim eating my game thread?" tractable in a way stat anim never was.
Slate Insights — channel slate and the Slate Insights editor plugin. Shows per-frame widget paints, invalidations, and layout passes. The list of widgets repainting every single frame is usually the answer to "why is my UI hot." Common offenders: a binding that allocates an FString every frame, a widget animation playing on an off-screen panel, an invalidation propagating up to a root canvas.
Five Gotchas That Bite Every First-Timer
The mistakes below come up on every Insights forum thread and every team's first profiling session. None are obvious from the docs.
1. Profiling in PIE. Already covered, worth repeating. PIE includes editor world cost, asset thumbnail rendering, hot-reload watchers, and the editor UI. The numbers will not match your shipping build. Always profile Standalone Game at minimum, and packaged Development for anything you'll act on.
2. Trace overhead skewing the result. The act of recording costs frame time. -statnamedevents alone can add several percent. callstack tracing adds more. Memory tracing adds more again. If you're chasing a 0.5 ms regression, run two captures — one with the channel set you want, one with only frame,bookmark — and check that the baseline numbers are similar. If they aren't, your channel set is moving the measurement.
3. Naming events with dynamic strings in hot paths. TRACE_CPUPROFILER_EVENT_SCOPE_TEXT with a runtime-built FString allocates a string every call. Inside a per-frame loop over thousands of items, that's a measurable allocation tax. Use the literal-name macro TRACE_CPUPROFILER_EVENT_SCOPE wherever the name is constant, and the _TEXT variant only for outer scopes.
4. The trace store filling up. Each capture is a full file. A few weeks of profiling and you have tens of GB sitting in the trace store directory. Check Session Browser → Settings → Trace Store Directory occasionally and prune. If it sits on your engine's install drive (default), this can quietly fill your system disk.
5. The editor not auto-tracing in newer engine versions. Recent engines stopped auto-connecting the editor process to a running Insights instance. If you launch Insights, then launch the editor, and don't see a LIVE session — you need to explicitly start a trace via the editor's status-bar Trace widget, or pass -tracehost=127.0.0.1 on the editor's launch command. Also confirm your project hasn't disabled the Trace plugin.
stat unit first, then Insights
Insights is a microscope, not a thermometer. Before opening a trace, run stat unit in the live game and look at Frame / Game / Draw / GPU. That tells you which thread is the bottleneck and roughly by how much. Then open Insights with the right channel set to dig into that specific thread, instead of fishing through every panel cold.
Insights + PerfGuard — Profiling on Top of Regression Catches
Insights is the right tool when you already know something changed and you want to know what. PerfGuard is the right tool for catching the change in the first place. They are complementary, and using them together is the workflow most shipping UE5 teams converge on.
The PerfGuard Gauntlet capture command already passes a full trace channel set:
-trace=cpu,gpu,frame,counters,rhicommands,rendercommands
That means every PerfGuard run produces a .utrace file alongside the CSV. When PerfGuard's report flags a regression on a specific scenario, you can:
- Open the PerfGuard HTML report and read the per-stat regression summary — you now know which thread and which subsystem.
- Find the matching
.utracefor that run inSaved/PerfGuard/Traces/. - Open it in Insights, jump to the worst frame the report calls out, and use the Timers panel to attribute the time.
- Compare against the baseline run's
.utrace: switching between two traces in Insights makes it obvious what changed, function by function.
This is the loop — PerfGuard catches the regression in CI before it merges, Insights tells you exactly which call grew, and the fix lands before the slow code spreads further. The CPU regressions, GPU regressions, and Common UE Performance Gotchas tutorials walk through that loop on the kinds of regressions you'll actually see in practice.
References & Further Reading
- Epic Games — Unreal Insights in Unreal Engine (official UE 5.7 documentation hub).
- Epic Games — Trace Quick Start Guide in Unreal Engine.
- Epic Games — Unreal Insights Reference in Unreal Engine 5 (channels, command-line options).
- Epic Games — Developer Guide to Tracing in Unreal Engine (instrumenting custom code).
- Epic Games — Memory Insights in Unreal Engine and Using the Low-Level Memory Tracker.
- Epic Games — Networking Insights, Animation Insights, and Slate Insights documentation pages.
- Tom Looman — Adding Counters & Traces to Unreal Insights & Stats System.
- Unreal Community Wiki — Profiling with Unreal Insights.
- Unrealcode.net — Optimization Part 3: Using Unreal Insights and Optimization Part 4: Adding Task Data to Traces.
- Rick Davidson — C++ Profiling in Unreal Engine 5 (TRACE_CPUPROFILER_EVENT_SCOPE patterns).
- Pavel Harbunou — Unreal Insights notes.
- ibbles — LearningUnrealEngine: Unreal Insights notes (GitHub).
- Meta — Get Started with Unreal Insights on Meta Quest for UE5 (remote tracing on Quest).