UE5 SDK
This guide explains how to automatically collect performance telemetry using the Framedash UE5 SDK (C++ plugin).
Requirements
Section titled “Requirements”- Unreal Engine 5.3 or later
- Blueprint-only projects are supported with a matching prebuilt package; source builds require a C++ project and toolchain
Installation
Section titled “Installation”For a Blueprint-only project, install a prebuilt package from the Fab listing or the GitHub Releases page. Choose the package that matches your engine and target platform. GitHub ships framedash-ue5-v<version>-ue<engine>.zip for UE 5.3-5.8 (for example -ue5.6 for UE 5.6), with compiled Win64 Binaries that Launcher-engine projects can use without rebuilding.
To build from source, or to target a platform not covered by your prebuilt package, clone the public mirror instead. This path requires a C++ project and toolchain. The source Framedash.uplugin pins no engine version, so it builds on every platform Framedash.Build.cs supports (Win64 / Mac / iOS / Android / Unix):
git clone https://github.com/crane-valley/framedash-ue5-sdk.gitThen add it to your project:
- Place the plugin in the
Plugins/Framedashdirectory - Add to your
.uproject:
{ "Plugins": [ { "Name": "Framedash", "Enabled": true } ]}- If your project has a C++ module and calls the plugin from C++, add the module dependency to its
Build.cs:
PrivateDependencyModuleNames.Add("Framedash");- Rebuild when using the source plugin, a source-built engine, or a target not covered by the prebuilt package
Initial Setup
Section titled “Initial Setup”Option A: Auto Initialize (Recommended)
Section titled “Option A: Auto Initialize (Recommended)”Add the following to DefaultGame.ini and the subsystem will initialize automatically on startup:
[/Script/Framedash.FramedashSettings]ApiKey=your-api-keybAutoInitialize=TrueYou can also set optional fields like BuildId, SamplingRate, and PlayerId:
[/Script/Framedash.FramedashSettings]ApiKey=your-api-keybAutoInitialize=TrueBuildId=1.0.0SamplingRate=1.0PlayerId=player-123PlayerId is a developer-supplied player identifier. If left empty, events are sent as anonymous and the SDK logs No player_id set. Events will be sent as anonymous.
The compiled default EndpointUrl already points at https://ingest.framedash.dev/v1/events, so you only need to set it when targeting a local or self-hosted ingest endpoint. If you do, wrap the value in double quotes:
EndpointUrl="https://ingest.framedash.dev/v1/events"These settings are also available in Project Settings > Plugins > Framedash.
No initialization code in C++ is required.
Option B: Manual Initialization from C++
Section titled “Option B: Manual Initialization from C++”You can initialize the SDK directly from code without using config files:
#include "FramedashSubsystem.h"
void AMyGameMode::BeginPlay(){ Super::BeginPlay();
if (auto* Subsystem = GetGameInstance()->GetSubsystem<UFramedashSubsystem>()) { FString ApiKey = FPlatformMisc::GetEnvironmentVariable(TEXT("FRAMEDASH_API_KEY")); Subsystem->InitializeTelemetry(ApiKey); }}InitializeTelemetry also accepts optional EndpointUrl and BuildId parameters, useful in CI environments:
if (auto* Subsystem = GetGameInstance()->GetSubsystem<UFramedashSubsystem>()){ FString ApiKey = FPlatformMisc::GetEnvironmentVariable(TEXT("FRAMEDASH_API_KEY")); FString BuildId = FPlatformMisc::GetEnvironmentVariable(TEXT("FRAMEDASH_BUILD_ID")); // Pass an empty string for EndpointUrl to use the default value. Subsystem->InitializeTelemetry(ApiKey, TEXT(""), BuildId);}Automatically Collected Data
Section titled “Automatically Collected Data”Once initialized, the following data is collected automatically:
- FPS / Frame Time: Equivalent to
stat unitdata - GPU Time: GPU frame time reported by RHI when available
- Memory: Used physical memory reported by the platform
Custom Events
Section titled “Custom Events”Basic Tracking
Section titled “Basic Tracking”if (auto* Framedash = GetGameInstance()->GetSubsystem<UFramedashSubsystem>()){ const FVector PlayerLocation(1000.0f, 2000.0f, 50.0f); Framedash->Track(TEXT("player_death"), TEXT("Map01"), PlayerLocation);}With Custom Attributes and Metrics
Section titled “With Custom Attributes and Metrics”Use TrackWithData when you need to attach additional metadata:
if (auto* Framedash = GetGameInstance()->GetSubsystem<UFramedashSubsystem>()){ const FVector PlayerLocation(1000.0f, 2000.0f, 50.0f);
TMap<FString, FString> Attributes; Attributes.Add(TEXT("cause"), TEXT("fall_damage"));
TMap<FString, double> Metrics; Metrics.Add(TEXT("health"), 0.0);
Framedash->TrackWithData( TEXT("player_death"), TEXT("Map01"), PlayerLocation, Attributes, Metrics);}Runtime Sampling Override
Section titled “Runtime Sampling Override”The global SamplingRate (project settings) applies to all Player-source events; automatic events bypass sampling. High-frequency events can opt into a lower per-event-name rate that overrides the global rate:
if (auto* Framedash = GetGameInstance()->GetSubsystem<UFramedashSubsystem>()){ Framedash->SetEventSamplingRate(TEXT("ai_pathfind_step"), 0.05f); // ~5% Framedash->RemoveEventSamplingRate(TEXT("ai_pathfind_step")); // back to the global rate}SetEventSamplingRate(const FString& EventName, float Rate) sets the rate, clamped to [0, 1], and RemoveEventSamplingRate(const FString& EventName) drops the override. Both are also Blueprint-callable.
Map Load-Time Capture
Section titled “Map Load-Time Capture”Available in UE5 SDK 0.1.6 and later. The subsystem can time how long a level takes to load and report it as a map_load event, which feeds the build-comparison / perf-diff regression gate and the dashboard load-time charts. BeginMapLoad, EndMapLoad, and ReportMapLoad are all Blueprint-callable.
Wrap a load you control:
if (auto* Framedash = GetGameInstance()->GetSubsystem<UFramedashSubsystem>()){ Framedash->BeginMapLoad(TEXT("Level_01")); // ... open the level ... Framedash->EndMapLoad();}BeginMapLoad starts a timer on a monotonic clock that pause and time dilation do not affect, and EndMapLoad stops it and emits the event. Calling BeginMapLoad again before EndMapLoad replaces the pending measurement.
If a custom or streaming loader already measures the time itself, report it directly:
Framedash->ReportMapLoad(TEXT("Level_01"), 1234.0); // load time in millisecondsReportMapLoad drops the sample entirely when the load time is NaN, infinite, or negative; the value is not clamped.
Both paths emit a map_load event carrying metrics["load_time_ms"] and attributes["map_name"]. The event leaves map_id empty on purpose, so it stays out of spatial heatmaps and the activation gate. These calls run on the game thread, never throw, and are a no-op before initialization. When a custom or streaming loader finishes on a worker thread, dispatch back to the game thread (for example AsyncTask(ENamedThreads::GameThread, ...)) before calling EndMapLoad or ReportMapLoad. The SDK does not marshal for you, and a call from another thread silently drops the event.
Disk I/O Metrics
Section titled “Disk I/O Metrics”Available in UE5 SDK 0.1.6 and later. The SDK can attach disk-read counters to the perf_heartbeat event under the io.read_bytes, io.read_time_ms, and io.read_ops metric keys. Each value is a delta since the previous heartbeat, and the keys appear only after a real sample has landed (there is no zero-stuffing). Like the other performance metrics, io.* feeds the perf-diff / builds-compare regression gate and the dashboard charts. There are no io.* threshold alerts.
Automatic sampling is opt-in. Enable Track Disk IO (bTrackDiskIo) in Project Settings > Framedash (default off) and the SDK chains an IPlatformFile wrapper that counts synchronous disk reads. Reads served by the IoDispatcher / IoStore path (the zen loader, Nanite streaming) bypass that wrapper, so the counters undercount Nanite-heavy I/O.
ReportIoSample is Blueprint-callable and feeds a sample regardless of the setting:
if (auto* Framedash = GetGameInstance()->GetSubsystem<UFramedashSubsystem>()){ Framedash->ReportIoSample(/*Bytes=*/1048576, /*ReadTimeMs=*/3.2, /*Ops=*/12);}Memory-Category Metrics
Section titled “Memory-Category Metrics”Available in UE5 SDK 0.1.7 and later. When you enable Track Memory Detail (bTrackMemoryDetail, off by default), the SDK samples memory-category usage at the heartbeat cadence and attaches it as mem.* metrics on perf_heartbeat events:
mem.vram: RHI texture memory in use, in bytes (the sum of streaming and non-streaming texture allocations). Read fromRHIGetTextureMemoryStats, it works on any RHI without a special launch flag. Omitted on headless /-nullrhibuilds.mem.textures/mem.meshes/mem.audio: per-tag bytes from the Low-Level Memory tracker (LLM). Attached only when LLM is compiled into the build AND enabled at runtime (-llm). When LLM is disabled, onlymem.vramis emitted.
An untracked category leaves its key absent (which means “not collected”, distinct from a collected 0).
Besides perf_heartbeat, these keys attach to position-qualified events (any event with a non-empty map_id). Because perf_heartbeat carries an empty map_id and never enters the spatial heatmap grid, the SDK attaches the same sample to position-qualified events so per-cell memory heatmaps work. Position-qualified events carry a cached sample refreshed at the 10s heartbeat cadence, so no per-event sampling happens. If you pass your own mem.* key in the TrackWithData metrics map, your value wins.
Enable it under Project Settings > Plugins > Framedash > Track Memory Detail, or set bTrackMemoryDetail=True in Config/DefaultGame.ini. It is off by default, so default sessions keep the zero-allocation event path. mem.vram is also a perf-diff (build-comparison) metric.
In-Editor Cloud Heatmap
Section titled “In-Editor Cloud Heatmap”Available in UE5 SDK 0.1.7 and later. The plugin’s FramedashEditor module adds a Framedash Heatmap tab that fetches and displays cloud-aggregated heatmaps inside the editor. Open it from Window menu > Framedash > Framedash Heatmap.
First, set the read API key (Read API Key, analytics:read scope) and Project ID under Project Settings > Plugins > Framedash Heatmap. The API Base URL defaults to https://app.framedash.dev.
In UE5 SDK 0.1.11 and later, you can leave Read API Key empty and set FRAMEDASH_ANALYTICS_API_KEY before launching Unreal Editor. The environment value is never saved to editor settings. A Read API Key entered in Project Settings takes precedence over the environment variable.
The panel lets you:
- Fetch and select the map list
- Set the time window (days), cell size, and an event-name filter
- Fetch cloud-aggregated heatmap cells
- Frame the level viewport around the fetched heatmap
In UE5 SDK 0.1.13 and later, fetch the data outside Play-in-Editor (PIE), then enable Show > Framedash Heatmap in each level viewport where you want to inspect it. The Show flag is off by default and independent per viewport. The heatmap is suspended during PIE so it does not obstruct playtesting, then the previous viewport choices are restored when PIE ends.
Cells with measured Z data render as cloud voxels at their recorded height; 2D responses remain flat. Because the heatmap is rendered in the viewport’s main scene pass, it is included in both standard F9 and high-resolution viewport screenshots.
Verbose Logging
Section titled “Verbose Logging”The SDK logs under the LogFramedash category. To confirm delivery during integration, run the game with verbose logging:
-LogCmds="LogFramedash Verbose"You can also enable it at runtime from the console with Log LogFramedash Verbose. A send logs SendBatch: N events -> https://ingest.framedash.dev/v1/events followed by the HTTP result. If events do not arrive, see Troubleshooting.
Headless / CI
Section titled “Headless / CI”To run the game without an interactive editor session (for CI or verification), first build the editor target, then launch the game with a null RHI.
After adding the plugin, build the editor target from the command line so no editor UI is needed:
"<EngineRoot>\Engine\Build\BatchFiles\Build.bat" <ProjectName>Editor Win64 Development -Project="<full path>\<ProjectName>.uproject"Then launch the commandlet-style game mode with a null RHI. Replace <MapPath> with a full map path. The Third Person template ships /Game/ThirdPerson/Maps/ThirdPersonMap on UE 5.3-5.5 and /Game/ThirdPerson/Lvl_ThirdPerson on 5.6 and later:
UnrealEditor-Cmd.exe <Project>.uproject <MapPath> -game -nullrhi -nosound -unattended -nosplash -stdout-game starts an endless game loop and never exits on its own. In CI, limit the run with an external timeout or kill, and treat the HTTP 2xx line in the log (not process exit) as the success signal. -unattended and -nosplash keep the run non-interactive.
Because the game never exits on its own, wrap the launch in a timeout that kills the process after the run has had time to flush. A minimal PowerShell wrapper:
$fdArgs = '"<Project>.uproject"','"<MapPath>"',"-game","-nullrhi","-nosound","-unattended","-nosplash","-stdout"$p = Start-Process -FilePath "UnrealEditor-Cmd.exe" -ArgumentList $fdArgs -PassThruif (-not $p.WaitForExit(120000)) { $p.Kill() } # 120s bound; the process never terminates by itselfThe path arguments are wrapped in embedded double quotes ('"<Project>.uproject"') so a project path containing spaces survives Start-Process argument splitting. On Linux runners use timeout 120 UnrealEditor-Cmd ... instead. Give the bound enough headroom for the HTTP 2xx line to land before the kill. GNU timeout exits with status 124 when it stops the run, so gate success on the HTTP 2xx log line, not the exit code, and treat 124 as expected once that log check passes. macOS ships no GNU timeout by default: install coreutils (brew install coreutils) and use gtimeout, or wrap the launch in the PowerShell-style kill-after-delay shown above.
When you pipe or redirect -stdout, the stream is block-buffered, so a run that has already succeeded can look stalled (for example, frozen at Waiting on static mesh...) even though the HTTP 202 already happened. The authoritative record is Saved/Logs/<Project>.log; grep it for SendBatch: and Batch sent successfully (HTTP. Those lines appear only when verbose logging is on, so launch with -LogCmds="LogFramedash Verbose" (see Verbose Logging above). Add -FORCELOGFLUSH if you must parse stdout in real time.
The offline queue is written only on a graceful shutdown or a transient send failure, not on a hard kill. If a run shuts down gracefully before the transport finishes flushing, the buffered events are written to Saved/Framedash/offline-queue.json and sent on the next initialization once a world ticks. A hard kill loses whatever is still buffered, so in CI wait for the HTTP 2xx line in the log before killing the process rather than relying on the queue. Keep the run alive long enough for a flush, or run again to drain the queue. See Troubleshooting.
In-editor Quickstart Sample
Section titled “In-editor Quickstart Sample”The plugin bundles a sample at Plugins/Framedash/Samples/InEditorQuickstart for verifying your setup. With a matching prebuilt package, the Blueprint recipe works in a Blueprint-only project and requires no C++ compilation. Projects that already have a C++ module can instead copy and compile the included C++ actor (FramedashQuickstartActor).
The sample assumes two prerequisites:
- An Ingest API key with the
events:writescope - A
map_idregistered in the dashboard via Maps > Generate demo
Either path sends a map-qualified Track event when you play, which is what activates the project in the dashboard.
Blueprint recipe
Section titled “Blueprint recipe”UFramedashSubsystem is Blueprint-callable under the Framedash category, so you can send the activating event from a Blueprint graph without writing any C++:
- Open the Level Blueprint (Blueprints > Open Level Blueprint) and start from the Event BeginPlay node.
- Drag off Get Game Instance, then add a Get Subsystem node and set its class to Framedash Subsystem. That output pin is the
UFramedashSubsysteminstance. - From the subsystem pin, call Track (category Framedash). Set Event Name to something like
quickstart_ping, Map Id to themap_idyou generated, and Position to any in-level location (for example a Player Start’s location). Use Track With Data instead if you also want to attachattributes/metrics. - Connect Event BeginPlay into the Track call so it runs on play.
Press Play, and the map-qualified Track event activates the project in the dashboard.
CI / Automated Sessions
Section titled “CI / Automated Sessions”In an automated test or profiling run, tag the whole session so every event carries the CI build and its branch/commit/scenario. Call the automated-session API once at startup (BuildId, Branch, Commit, and Scenario are all optional FString values, and the methods are Blueprint-callable):
if (auto* Framedash = GetGameInstance()->GetSubsystem<UFramedashSubsystem>()){ Framedash->BeginAutomatedSession( /*BuildId=*/TEXT("build-123"), /*Branch=*/TEXT("main"), /*Commit=*/TEXT("abc1234"), /*Scenario=*/TEXT("nightly")); // ... run the automated scenario ... Framedash->EndAutomatedSession();}In CI, prefer BeginAutomatedSessionFromEnvironment(), which reads FRAMEDASH_BUILD_ID, FRAMEDASH_GIT_BRANCH, FRAMEDASH_GIT_COMMIT, and FRAMEDASH_TEST_SCENARIO (the variables framedash run-profile-test exports):
if (auto* Framedash = GetGameInstance()->GetSubsystem<UFramedashSubsystem>()){ Framedash->BeginAutomatedSessionFromEnvironment(); // ... run the automated scenario ... Framedash->EndAutomatedSession();}An automated session tags every event in the session with a build_id override and ci.branch / ci.commit / ci.scenario attributes, which is what feeds the build-comparison / perf-diff CI gate. It does not change any event’s source: the SDK’s automatic events (session_start, perf_heartbeat) stay source=automated and your Track events stay source=player, in CI as in normal play. See CI Profiling for the full pipeline.
Next Steps
Section titled “Next Steps”- Data Model: Telemetry data structure
- Troubleshooting: When events do not show up
- CI Profiling: Automated build testing