UE5 SDK
Framedash UE5 SDK (C++ 플러그인)를 사용하여 퍼포먼스 텔레메트리를 자동으로 수집하는 방법을 설명합니다.
요구 사항
섹션 제목: “요구 사항”- Unreal Engine 5.3 이상
- Blueprint-only 프로젝트는 일치하는 사전 빌드 패키지로 지원됩니다. 소스 빌드에는 C++ 프로젝트와 툴체인이 필요합니다.
Blueprint-only 프로젝트에는 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그런 다음 프로젝트에 추가합니다:
- 플러그인을
Plugins/Framedash디렉터리에 배치 .uproject에 추가:
{ "Plugins": [ { "Name": "Framedash", "Enabled": true } ]}- 프로젝트에 C++ 모듈이 있고 C++에서 플러그인을 호출한다면 해당 모듈의
Build.cs에 종속성을 추가합니다:
PrivateDependencyModuleNames.Add("Framedash");- 소스 플러그인, 소스 빌드 엔진 또는 사전 빌드 패키지가 지원하지 않는 대상을 사용할 때 프로젝트를 다시 빌드합니다
초기 설정
섹션 제목: “초기 설정”방법 A: 자동 초기화 (권장)
섹션 제목: “방법 A: 자동 초기화 (권장)”DefaultGame.ini에 다음을 추가하면 서브시스템이 시작 시 자동으로 초기화됩니다:
[/Script/Framedash.FramedashSettings]ApiKey=your-api-keybAutoInitialize=TrueBuildId, SamplingRate, PlayerId 등 선택 필드도 설정할 수 있습니다:
[/Script/Framedash.FramedashSettings]ApiKey=your-api-keybAutoInitialize=TrueBuildId=1.0.0SamplingRate=1.0PlayerId=player-123PlayerId는 개발자가 지정하는 플레이어 식별자입니다. 비워 두면 이벤트가 익명으로 전송되며, 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++ 초기화 코드를 작성할 필요가 없습니다.
방법 B: C++에서 수동 초기화
섹션 제목: “방법 B: 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는 선택적으로 EndpointUrl과 BuildId 매개변수도 받으며, 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) 회귀 게이트와 대시보드의 로드 시간 차트에 사용됩니다.
BeginMapLoad, EndMapLoad, ReportMapLoad는 모두 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); // 로드 시간(밀리초)ReportMapLoad는 로드 시간이 NaN, 무한대, 음수일 때 해당 샘플을 통째로 버립니다(클램프하지 않습니다).
두 경로 모두 metrics["load_time_ms"]와 attributes["map_name"]를 담은 map_load 이벤트를 발생시킵니다.
이 이벤트는 의도적으로 map_id를 비워 두므로 공간 히트맵과 활성화 게이트의 대상에서 제외됩니다.
이 호출들은 게임 스레드에서 실행되고, 예외를 던지지 않으며, 초기화 전에는 아무 동작도 하지 않습니다.
커스텀 로더나 스트리밍 로더가 워커 스레드에서 완료되는 경우, EndMapLoad나 ReportMapLoad를 호출하기 전에 게임 스레드로 되돌리세요(예: AsyncTask(ENamedThreads::GameThread, ...) 사용).
SDK는 스레드 마샬링을 대신 해 주지 않으므로, 다른 스레드에서의 호출은 이벤트를 조용히 버립니다.
디스크 I/O 메트릭
섹션 제목: “디스크 I/O 메트릭”UE5 SDK 0.1.6 이상에서 사용할 수 있습니다.
SDK는 디스크 읽기 카운터를 io.read_bytes, io.read_time_ms, io.read_ops 메트릭 키로 perf_heartbeat 이벤트에 첨부할 수 있습니다.
각 값은 이전 heartbeat 이후의 델타이며, 실제 샘플이 들어온 뒤에야 키가 붙습니다(0으로 채우지 않습니다).
다른 성능 메트릭과 마찬가지로 io.*는 perf-diff / builds-compare 회귀 게이트와 대시보드 차트에 사용됩니다.
io.* 임계값 알림은 없습니다.
자동 샘플링은 옵트인입니다.
Project Settings > Framedash에서 Track Disk IO(bTrackDiskIo, 기본값 off)를 켜면 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 Detail(bTrackMemoryDetail, 기본값 꺼짐)을 활성화하면 SDK가 heartbeat 주기로 메모리 카테고리별 사용량을 샘플링해 perf_heartbeat 이벤트에 mem.* 메트릭으로 첨부합니다:
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_heartbeat는 map_id가 비어 있어 공간 히트맵 그리드에 들어가지 않으므로, 셀 단위 메모리 히트맵이 가능하도록 SDK가 동일한 샘플을 위치가 지정된 이벤트에도 싣습니다. 위치가 지정된 이벤트에는 10초 heartbeat 주기로 갱신되는 캐시된 샘플이 실리므로 이벤트마다 샘플링이 일어나지 않습니다. TrackWithData의 metrics 맵에 직접 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
섹션 제목: “헤드리스 / CI”대화형 에디터 세션 없이 게임을 실행하려면(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 래퍼 예시:
$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() } # 120초 제한. 프로세스는 스스로 종료하지 않음경로 인자는 내장 큰따옴표('"<Project>.uproject"')로 감싸므로 공백이 포함된 프로젝트 경로도 Start-Process 인자 분할에서 깨지지 않습니다. Linux 러너에서는 대신 timeout 120 UnrealEditor-Cmd ...를 사용하세요. kill 전에 HTTP 2xx 줄이 기록될 여유를 두고 제한을 설정하세요. 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이며, 이 로그에서 SendBatch:와 Batch sent successfully (HTTP를 grep하세요. 이 줄들은 상세 로깅이 켜져 있을 때만 나타나므로, 실행 시 -LogCmds="LogFramedash Verbose"를 붙이세요(위의 상세 로깅 참고). 표준 출력을 실시간으로 파싱해야 한다면 -FORCELOGFLUSH를 추가하세요.
오프라인 큐는 정상 종료나 일시적인 전송 실패 시에만 기록되며, 강제 종료 시에는 기록되지 않습니다. 플러시가 끝나기 전에 실행이 정상적으로 종료되면 버퍼링된 이벤트는 Saved/Framedash/offline-queue.json에 기록되고 다음 초기화 시 월드가 tick한 시점에 전송됩니다. 반면 강제 kill은 버퍼에 남은 이벤트를 잃으므로, CI에서는 큐에 의존하지 말고 프로세스를 kill하기 전에 로그의 HTTP 2xx 줄을 기다리세요. 플러시가 끝날 만큼 실행을 유지하거나, 다시 실행해 큐를 비우세요. 문제 해결을 참고하세요.
에디터 내 퀵스타트 샘플
섹션 제목: “에디터 내 퀵스타트 샘플”플러그인에는 설정을 확인할 수 있는 샘플이 Plugins/Framedash/Samples/InEditorQuickstart에 포함되어 있습니다. 일치하는 사전 빌드 패키지를 사용하면 Blueprint 레시피가 Blueprint-only 프로젝트에서 작동하며 C++ 컴파일이 필요하지 않습니다. 이미 C++ 모듈이 있는 프로젝트는 포함된 C++ 액터(FramedashQuickstartActor)를 복사해 컴파일할 수도 있습니다.
이 샘플은 두 가지 전제를 가정합니다:
events:write스코프를 가진 Ingest API 키- 대시보드의 Maps > Generate demo로 등록한
map_id
두 경로 모두 플레이 시 맵에 연결된 Track 이벤트를 전송하며, 이것이 대시보드에서 프로젝트를 활성화하는 계기가 됩니다.
Blueprint 레시피
섹션 제목: “Blueprint 레시피”UFramedashSubsystem은 Framedash 카테고리에서 Blueprint로 호출할 수 있으므로, C++를 전혀 작성하지 않고도 Blueprint 그래프에서 활성화 이벤트를 보낼 수 있습니다:
- Level Blueprint(Blueprints > Open Level Blueprint)를 열고 Event BeginPlay 노드에서 시작합니다.
- Get Game Instance에서 끌어낸 뒤 Get Subsystem 노드를 추가하고 클래스를 Framedash Subsystem으로 설정합니다. 그 출력 핀이
UFramedashSubsystem인스턴스입니다. - 서브시스템 핀에서 Track(카테고리 Framedash)을 호출합니다. Event Name을
quickstart_ping등으로, Map Id를 생성한map_id로, Position을 레벨 내 임의의 위치(예: Player Start의 위치)로 설정합니다.attributes/metrics도 붙이려면 대신 Track With Data를 사용하세요. - Event BeginPlay를 Track 호출에 연결해 플레이 시 실행되도록 합니다.
Play를 누르면 맵에 연결된 Track 이벤트가 대시보드에서 프로젝트를 활성화합니다.
CI / 자동 세션
섹션 제목: “CI / 자동 세션”자동화 테스트나 프로파일링 실행에서는 세션 전체에 태그를 달아 모든 이벤트가 CI 빌드와 그 branch/commit/scenario를 담도록 하세요. 시작 시 자동 세션 API를 한 번 호출합니다(BuildId, Branch, Commit, Scenario는 모두 선택적 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_ID, FRAMEDASH_GIT_BRANCH, FRAMEDASH_GIT_COMMIT, FRAMEDASH_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_start, perf_heartbeat)는 항상 source=automated, 여러분의 Track 이벤트는 항상 source=player이며, 이는 CI에서나 일반 플레이에서나 동일합니다. 전체 파이프라인은 CI 프로파일링을 참고하세요.