跳转到内容

UE5 SDK

本文介绍如何使用 Framedash UE5 SDK(C++ 插件)自动收集性能遥测数据。

  • Unreal Engine 5.3 及以上
  • 纯 Blueprint 项目可使用匹配的预编译包;从源码构建需要 C++ 项目和工具链

对于纯 Blueprint 项目,请使用 Fab 页面GitHub Releases 页面上的预编译包。请选择与你的引擎和目标平台匹配的包。GitHub 为 UE 5.3-5.8 提供 framedash-ue5-v<version>-ue<engine>.zip(例如 UE 5.6 使用 -ue5.6),其中包含已编译的 Win64 Binaries,因此使用 Launcher 引擎的项目无需重新构建即可使用。

若要从源码构建,或面向预编译包未覆盖的平台,请改为克隆公开镜像。此路径需要 C++ 项目和工具链。源码中的 Framedash.uplugin 未固定引擎版本,因此可在 Framedash.Build.cs 支持的所有平台(Win64 / Mac / iOS / Android / Unix)上构建:

git clone https://github.com/crane-valley/framedash-ue5-sdk.git

然后将其添加到项目中:

  1. 将插件放置到 Plugins/Framedash 目录
  2. .uproject 中添加:
{
"Plugins": [
{
"Name": "Framedash",
"Enabled": true
}
]
}
  1. 如果项目包含 C++ 模块并从 C++ 调用插件,请在该模块的 Build.cs 中添加依赖:
PrivateDependencyModuleNames.Add("Framedash");
  1. 使用源码插件、源码构建的引擎或预编译包未覆盖的目标时,重新构建项目

DefaultGame.ini 中添加以下内容,子系统将在启动时自动初始化:

[/Script/Framedash.FramedashSettings]
ApiKey=your-api-key
bAutoInitialize=True

也可以设置可选字段,如 BuildIdSamplingRatePlayerId

[/Script/Framedash.FramedashSettings]
ApiKey=your-api-key
bAutoInitialize=True
BuildId=1.0.0
SamplingRate=1.0
PlayerId=player-123

PlayerId 是开发者提供的玩家标识符。若留空,事件将以匿名方式发送,且 SDK 会记录 No player_id set. Events will be sent as anonymous.

编译后的默认 EndpointUrl 已指向 https://ingest.framedash.dev/v1/events,因此仅在使用本地或自托管的 ingest 端点时才需要设置。若要设置,请用双引号包裹该值:

EndpointUrl="https://ingest.framedash.dev/v1/events"

这些设置也可以在 Project Settings > Plugins > Framedash 中编辑。

无需编写 C++ 初始化代码。

您也可以不使用配置文件,直接通过代码初始化:

#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 还接受可选的 EndpointUrlBuildId 参数,在 CI 环境中很有用:

if (auto* Subsystem = GetGameInstance()->GetSubsystem<UFramedashSubsystem>())
{
FString ApiKey = FPlatformMisc::GetEnvironmentVariable(TEXT("FRAMEDASH_API_KEY"));
FString BuildId = FPlatformMisc::GetEnvironmentVariable(TEXT("FRAMEDASH_BUILD_ID"));
// 传递空字符串作为 EndpointUrl 以使用默认值
Subsystem->InitializeTelemetry(ApiKey, TEXT(""), BuildId);
}

初始化完成后,以下数据将自动收集:

  • FPS / Frame Time: 等同于 stat unit 的数据
  • GPU Time: RHI 可用时报告的 GPU 帧时间
  • Memory: 平台报告的已用物理内存
if (auto* Framedash = GetGameInstance()->GetSubsystem<UFramedashSubsystem>())
{
const FVector PlayerLocation(1000.0f, 2000.0f, 50.0f);
Framedash->Track(TEXT("player_death"), TEXT("Map01"), PlayerLocation);
}

需要附加额外元数据时,使用 TrackWithData:

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);
}

全局 SamplingRate(项目设置)应用于所有 Player 来源的事件;自动事件不受采样影响。高频事件可以选用按事件名的采样率,覆盖全局采样率:

if (auto* Framedash = GetGameInstance()->GetSubsystem<UFramedashSubsystem>())
{
Framedash->SetEventSamplingRate(TEXT("ai_pathfind_step"), 0.05f); // 约 5%
Framedash->RemoveEventSamplingRate(TEXT("ai_pathfind_step")); // 回到全局采样率
}

SetEventSamplingRate(const FString& EventName, float Rate) 设置采样率并钳制到 [0, 1],RemoveEventSamplingRate(const FString& EventName) 移除该覆盖。两者都可从 Blueprint 调用。

自 UE5 SDK 0.1.6 起可用。子系统可以测量关卡加载所需的时间,并将其作为 map_load 事件上报,进入构建比对(perf-diff)回归门禁以及仪表盘的加载耗时图表。BeginMapLoadEndMapLoadReportMapLoad 都可从 Blueprint 调用。

包裹一次你自行控制的加载:

if (auto* Framedash = GetGameInstance()->GetSubsystem<UFramedashSubsystem>())
{
Framedash->BeginMapLoad(TEXT("Level_01"));
// ... 打开关卡 ...
Framedash->EndMapLoad();
}

BeginMapLoad 会在不受暂停和时间膨胀(time dilation)影响的单调时钟上启动计时,EndMapLoad 则停止计时并发出事件。在 EndMapLoad 之前再次调用 BeginMapLoad,会替换掉当前挂起的测量。

如果自定义加载器或流式加载器已经自行测量了耗时,可以直接上报:

Framedash->ReportMapLoad(TEXT("Level_01"), 1234.0); // 加载耗时(毫秒)

当加载耗时为 NaN、无穷大或负值时,ReportMapLoad 会整个丢弃该样本(不会钳制)。

两条路径都会发出携带 metrics["load_time_ms"]attributes["map_name"]map_load 事件。该事件有意将 map_id 留空,因此不会进入空间热力图和激活门禁。这些调用在游戏线程上执行,不会抛出异常,并且在初始化之前为空操作。当自定义加载器或流式加载器在工作线程上完成时,请在调用 EndMapLoadReportMapLoad 之前先切回游戏线程(例如借助 AsyncTask(ENamedThreads::GameThread, ...))。SDK 不会替你做线程编组,从其他线程发起的调用会静默丢弃该事件。

自 UE5 SDK 0.1.6 起可用。SDK 可以把磁盘读取计数器以 io.read_bytesio.read_time_msio.read_ops 这几个指标键附加到 perf_heartbeat 事件上。每个值都是相对上一次 heartbeat 的增量,并且只有在真正采集到样本之后这些键才会出现(不会填零)。与其他性能指标一样,io.* 会进入 perf-diff / builds-compare 回归门禁以及仪表盘图表。io.* 没有阈值告警。

自动采样是选择性开启的。在 Project Settings > Framedash 中启用 Track Disk IObTrackDiskIo,默认关闭),SDK 就会串接一个统计同步磁盘读取的 IPlatformFile 包装器。由 IoDispatcher / IoStore 路径(zen loader、Nanite 流式加载)处理的读取会绕过该包装器,因此在 Nanite 密集的 I/O 场景下计数会偏低。

ReportIoSample 可从 Blueprint 调用,无论该设置如何都会供给一个样本:

if (auto* Framedash = GetGameInstance()->GetSubsystem<UFramedashSubsystem>())
{
Framedash->ReportIoSample(/*Bytes=*/1048576, /*ReadTimeMs=*/3.2, /*Ops=*/12);
}

UE5 SDK 0.1.7 及更高版本可用。启用 Track Memory DetailbTrackMemoryDetail,默认关闭)后,SDK 会按 heartbeat 周期采样各内存分类的用量,并作为 mem.* 指标附加到 perf_heartbeat 事件上:

  • mem.vram:RHI 报告的使用中纹理显存(字节,即流式与非流式纹理分配之和)。从 RHIGetTextureMemoryStats 读取,无需特殊启动参数即可在任何 RHI 上使用。在无头 / -nullrhi 构建中不会附加。
  • mem.textures / mem.meshes / mem.audio:来自 Low-Level Memory 追踪器(LLM)各标签的字节数。仅当 LLM 编译进构建且在运行时启用(-llm)时才会附加。LLM 未启用时只会附加 mem.vram

未追踪的分类会让该键缺失(表示“未收集”,与已收集的 0 不同)。

perf_heartbeat 外,这些键还会附加到带位置(map_id 非空)的事件上。由于 perf_heartbeatmap_id 为空、不会进入空间热力图网格,SDK 会把同一采样也放到带位置的事件上,从而支持逐单元格的内存热力图。带位置的事件携带的是按 10 秒 heartbeat 周期刷新的缓存采样,不会发生逐事件采样。若你在 TrackWithDatametrics 映射中传入自己的 mem.* 键,则以你的值为准。

Project Settings > Plugins > Framedash > Track Memory Detail 启用,或在 Config/DefaultGame.ini 中设置 bTrackMemoryDetail=True。默认关闭,因此默认会话保持零分配的事件路径。mem.vram 同时也是 perf-diff(构建比对)的比较指标。

UE5 SDK 0.1.7 及更高版本可用。插件的 FramedashEditor 模块新增了 Framedash Heatmap 标签页,可在编辑器内获取并显示云端聚合的热力图。从 Window 菜单 > Framedash > Framedash Heatmap 打开。

首先在 Project Settings > Plugins > Framedash Heatmap 设置读取 API 密钥(Read API Key,analytics:read 作用域)和项目 ID(Project ID)。API Base URL 默认为 https://app.framedash.dev

在 UE5 SDK 0.1.11 及更高版本中,可以将 Read API Key 留空,并在启动 Unreal Editor 前设置 FRAMEDASH_ANALYTICS_API_KEY。环境变量的值不会保存到编辑器设置。如果在 Project Settings 中输入 Read API Key,该值优先于环境变量。

面板支持以下操作:

  • 获取并选择地图列表
  • 设置时间窗口(天数)、单元格大小和事件名筛选
  • 获取云端聚合的热力图单元格
  • 将关卡视口框定到已获取的完整热力图

在 UE5 SDK 0.1.13 及更高版本中,请在非 Play-in-Editor(PIE)状态下获取数据,然后在需要显示热力图的每个关卡视口中启用 Show > Framedash Heatmap。Show 标志默认关闭,并且每个视口相互独立。为避免干扰游戏测试,PIE 期间热力图会暂停显示,并在 PIE 结束后恢复各视口之前的选择。

具有实测 Z 坐标的单元格会在相应高度显示为云端体素;2D 响应仍保持平面显示。热力图在视口的主场景通道中渲染,因此会同时出现在标准 F9 和高分辨率视口截图中。

SDK 在 LogFramedash 类别下记录日志。集成期间要确认发送,请以详细日志运行游戏:

-LogCmds="LogFramedash Verbose"

你也可以在控制台用 Log LogFramedash Verbose 在运行时启用。一次发送会记录 SendBatch: N events -> https://ingest.framedash.dev/v1/events,随后是 HTTP 结果。若事件未到达,见故障排查

要在没有交互式编辑器会话的情况下运行游戏(用于 CI 或验证),请先构建编辑器目标,再以 null RHI 启动游戏。

添加插件后,在命令行中构建编辑器目标,无需编辑器 UI:

"<EngineRoot>\Engine\Build\BatchFiles\Build.bat" <ProjectName>Editor Win64 Development -Project="<full path>\<ProjectName>.uproject"

然后以 null RHI 启动游戏模式。请把 <MapPath> 替换为完整的地图路径。Third Person 模板在 UE 5.3-5.5 上自带 /Game/ThirdPerson/Maps/ThirdPersonMap,在 5.6 及更高版本上自带 /Game/ThirdPerson/Lvl_ThirdPerson

UnrealEditor-Cmd.exe <Project>.uproject <MapPath> -game -nullrhi -nosound -unattended -nosplash -stdout

-game 会启动一个无限的游戏循环,永远不会自行退出。在 CI 中请用外部超时或 kill 来限定运行时长,并把日志中的 HTTP 2xx 行(而非进程退出)作为成功信号。加上 -unattended-nosplash 可让运行保持非交互。

由于游戏不会自行退出,请用一个在运行有足够时间刷新后 kill 进程的超时来包裹启动。最小的 PowerShell 包装示例:

Terminal window
$fdArgs = '"<Project>.uproject"','"<MapPath>"',"-game","-nullrhi","-nosound","-unattended","-nosplash","-stdout"
$p = Start-Process -FilePath "UnrealEditor-Cmd.exe" -ArgumentList $fdArgs -PassThru
if (-not $p.WaitForExit(120000)) { $p.Kill() } # 120 秒上限;进程不会自行终止

路径参数用内嵌双引号('"<Project>.uproject"')包裹,因此包含空格的项目路径也不会在 Start-Process 参数拆分时损坏。在 Linux runner 上改用 timeout 120 UnrealEditor-Cmd ...。请为上限留出足够余量,让 HTTP 2xx 行在 kill 之前落地。GNU timeout 在终止运行时会以状态码 124 退出,因此请以日志中的 HTTP 2xx 行而非退出码作为成功判据,日志检查通过后将 124 视为预期结果。macOS 默认没有 GNU timeout:安装 coreutils(brew install coreutils)并使用 gtimeout,或用上面 PowerShell 那样的延迟后 kill 的方式包裹启动。

当你对 -stdout 做管道或重定向时,该流是块缓冲的,因此一次已经成功的运行可能看起来卡住了(例如停在 Waiting on static mesh...,但 HTTP 202 其实已经发生)。权威记录是 Saved/Logs/<Project>.log;在其中 grep SendBatch:Batch sent successfully (HTTP。这些行只有在开启详细日志时才会出现,因此启动时请加上 -LogCmds="LogFramedash Verbose"(见上文详细日志)。如果必须实时解析 stdout,请加上 -FORCELOGFLUSH

离线队列只在优雅关闭或临时发送失败时写入,硬性 kill 时不会写入。如果运行在传输完成刷新之前优雅关闭,缓冲的事件会写入 Saved/Framedash/offline-queue.json,并在下次初始化、世界开始 tick 后发送。相反,硬性 kill 会丢失仍在缓冲中的事件,因此在 CI 中不要依赖队列,请在 kill 进程之前等待日志中的 HTTP 2xx 行。请让运行保持足够长以完成刷新,或再运行一次以排空队列。见故障排查

插件在 Plugins/Framedash/Samples/InEditorQuickstart 附带了一个用于验证配置的示例。使用匹配的预编译包时,Blueprint 方案可在纯 Blueprint 项目中运行,无需编译 C++。已有 C++ 模块的项目也可以复制并编译随附的 C++ Actor(FramedashQuickstartActor)。

该示例假定两个前提:

  • 具有 events:write 范围的 Ingest API 密钥
  • 通过仪表盘 Maps > Generate demo 注册的 map_id

两条路径在运行时都会发送一个绑定地图的 Track 事件,这正是在仪表盘中激活项目的触发条件。

UFramedashSubsystemFramedash 类别下可从 Blueprint 调用,因此你无需编写任何 C++,即可从 Blueprint 图表发送激活事件:

  1. 打开 Level BlueprintBlueprints > Open Level Blueprint),从 Event BeginPlay 节点开始。
  2. Get Game Instance 拖出,再添加一个 Get Subsystem 节点并将其类设置为 Framedash Subsystem。该输出引脚就是 UFramedashSubsystem 实例。
  3. 从子系统引脚调用 Track(类别 Framedash)。将 Event Name 设为类似 quickstart_ping,将 Map Id 设为你生成的 map_id,并将 Position 设为关卡内任意位置(例如 Player Start 的位置)。若还想附加 attributes / metrics,请改用 Track With Data
  4. Event BeginPlay 连到 Track 调用,使其在运行时执行。

按下 Play,绑定地图的 Track 事件即会在仪表盘中激活项目。

在自动化测试或性能剖析运行中,请为整个会话打标签,使每个事件都携带 CI 构建及其 branch/commit/scenario。在启动时调用一次自动会话 API(BuildIdBranchCommitScenario 均为可选的 FString,且这些方法都可从 Blueprint 调用):

if (auto* Framedash = GetGameInstance()->GetSubsystem<UFramedashSubsystem>())
{
Framedash->BeginAutomatedSession(
/*BuildId=*/TEXT("build-123"),
/*Branch=*/TEXT("main"),
/*Commit=*/TEXT("abc1234"),
/*Scenario=*/TEXT("nightly"));
// ... 运行自动化场景 ...
Framedash->EndAutomatedSession();
}

在 CI 中,推荐使用 BeginAutomatedSessionFromEnvironment(),它会读取 FRAMEDASH_BUILD_IDFRAMEDASH_GIT_BRANCHFRAMEDASH_GIT_COMMITFRAMEDASH_TEST_SCENARIO(这些正是 framedash run-profile-test 导出的变量):

if (auto* Framedash = GetGameInstance()->GetSubsystem<UFramedashSubsystem>())
{
Framedash->BeginAutomatedSessionFromEnvironment();
// ... 运行自动化场景 ...
Framedash->EndAutomatedSession();
}

自动会话会为会话内的每个事件打上 build_id 覆盖和 ci.branch / ci.commit / ci.scenario 属性,这正是进入构建比对(perf-diff)CI 门禁的依据。它不会改变任何事件的 source:SDK 自身的自动事件(session_startperf_heartbeat)始终是 source=automated,而你的 Track 事件始终是 source=player,在 CI 与正常游玩中都一样。完整流水线见 CI 剖析