"게임이 끊겨요" 티켓 진단하기 ③ — 발생 순간의 성능 데이터를 자동으로 남기기
Profiler 없는 QA 머신에서 성능 문제의 증거를 남기는 법. ProfilerRecorder로 상시 경량 수집하고, 스파이크 순간을 영상과 같은 시간축에 놓습니다.
- unity
- performance
- qa
1편에서 분류를, 2편에서 계측을 다뤘습니다. 남은 문제는 하나입니다. 성능 문제는 Profiler가 없는 곳에서 발생합니다. QA 머신, 사내 테스트 빌드, 퍼블리셔 검수 환경. "끊겼어요"라는 보고를 받고 계측을 붙이러 가면 이미 늦었습니다.
답의 방향은 버그 리포트 3부작과 같습니다 — 항상 기록하고, 필요할 때만 남긴다. 이번엔 로그 대신 성능 지표입니다.
상시 수집의 조건: 수집기가 병목이 되지 않을 것
성능을 재는 코드가 성능을 잡아먹으면 본말전도입니다. Profiler 전체를 붙이는 건 그래서 안 되고, 조건은 셋입니다.
- 프레임당 비용이 무시 가능할 것 (수 마이크로초 수준)
- GC 할당이 없을 것 — 수집기가 GC 스파이크를 만들면 최악입니다
- 배포·QA 빌드에서 동작할 것
ProfilerRecorder API가 이 조건에 맞습니다. Profiler 창 없이, 특정 카운터만 골라 런타임에서 읽습니다.
최소 구현 — 프레임 타임·GC 링 버퍼
using Unity.Profiling;
using UnityEngine;
public class PerfSampler : MonoBehaviour
{
// 프레임 타임은 Time API 로, GC 는 ProfilerRecorder 로 — 1편의 4분류 중
// CPU/GC 를 커버한다. GPU 시간은 FrameTimingManager 로 별도 수집(2편 참조),
// 드로우콜 등 다른 카운터도 _gcMemory 와 같은 방식으로 추가하면 된다.
ProfilerRecorder _gcMemory;
const int Capacity = 1800; // 60fps × 30초
readonly float[] _frameTimes = new float[Capacity];
readonly long[] _gcDeltas = new long[Capacity];
int _head;
long _lastGc;
void OnEnable()
{
_gcMemory = ProfilerRecorder.StartNew(ProfilerCategory.Memory, "GC Used Memory");
// ⚠️ release 빌드에서는 일부 카운터가 지원되지 않는다 — Valid 가 false 면
// 그 값을 리포트에 싣지 말 것 (미지원의 0 을 실측 0 으로 오독하게 된다)
if (!_gcMemory.Valid) Debug.LogWarning("[Perf] GC counter unavailable in this build");
}
void OnDisable()
{
_gcMemory.Dispose();
}
void Update()
{
// 고정 배열 + 순환 인덱스 — 몇 시간을 돌아도 할당이 없다
_frameTimes[_head] = Time.unscaledDeltaTime * 1000f;
long gc = _gcMemory.LastValue;
_gcDeltas[_head] = gc - _lastGc; // 음수로 떨어지는 프레임 = GC 가 돈 순간
_lastGc = gc;
_head = (_head + 1) % Capacity;
}
}
구조는 로그 링 버퍼와 동일합니다. 최근 30초를 메모리에서 덮어쓰며 유지하다가, 문제를 목격한 순간 디스크로 확정합니다.
스파이크에 마커 남기기
숫자 배열만으로는 "언제가 문제 순간인지"를 나중에 찾기 어렵습니다. 프레임 타임이 임계값을 넘는 순간을 로그에 남겨두면, 로그 링 버퍼와 성능 버퍼가 같은 사건을 가리키게 됩니다.
// 직전 프레임 대비 급등을 감지 — 절대값 임계는 기기별로 오탐이 많다
if (_frameTimes[_head] > _recentAverage * 3f && _frameTimes[_head] > 33f)
Debug.LogWarning($"[Perf] frame spike: {_frameTimes[_head]:F1}ms (avg {_recentAverage:F1}ms)");
핵심은 이번에도 같은 시간축
성능 그래프 단독으로는 "17번째 초에 스파이크가 있었다"는 사실만 남습니다. 그 순간 화면에서 무슨 일이 있었는지 — 보스가 등장했는지, 인벤토리를 열었는지 — 와 붙어야 원인 가설이 나옵니다.
그래서 도착점은 버그 리포트 3부작과 합류합니다. 영상·로그·성능 지표가 같은 타임라인에 정렬되어 있으면, 프레임 그래프의 스파이크를 클릭했을 때 그 순간의 화면과 로그가 따라옵니다. "끊겨요"라는 체감 보고가 "이 장면에서 GC가 돌며 47ms 스파이크"라는 진단 가능한 티켓이 됩니다.
저희는 이 파이프라인을 Rekon의 성능 타임라인으로 만들고 있습니다 — 플레이 영상과 FPS·메모리 그래프를 같은 축에서 스크럽하는 형태입니다. 직접 구현하시든 도구를 쓰시든, 시리즈의 결론은 하나입니다. 성능 티켓도 결국 증거의 문제이고, 증거는 발생 순간에만 수집할 수 있습니다.