런타임 성능 모니터링
앱인토스 Unity 게임의 실시간 성능을 모니터링하고 분석하여 최적의 사용자 경험을 제공하는 시스템을 구현해요.
1. 실시간 성능 모니터링 시스템
통합 성능 모니터
c#
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.Profiling;
public class AppsInTossPerformanceMonitor : MonoBehaviour
{
public static AppsInTossPerformanceMonitor Instance { get; private set; }
[System.Serializable]
public class PerformanceThresholds
{
[Header("FPS 임계값")]
public float criticalFPS = 15f;
public float warningFPS = 25f;
public float targetFPS = 30f;
[Header("메모리 임계값 (MB)")]
public float criticalMemoryMB = 250f;
public float warningMemoryMB = 200f;
public float targetMemoryMB = 150f;
[Header("배터리 효율성")]
public float batteryEfficiencyWarning = 1.3f; // 네이티브 앱 대비
public float batteryEfficiencyCritical = 1.5f;
[Header("네트워크 응답시간 (ms)")]
public float networkWarningMs = 2000f;
public float networkCriticalMs = 5000f;
}
[Header("모니터링 설정")]
public bool enableRealTimeMonitoring = true;
public bool enableTossAnalytics = true;
public float monitoringInterval = 1f;
public int maxDataPoints = 300; // 5분간 데이터
[Header("성능 임계값")]
public PerformanceThresholds thresholds;
[Header("UI 표시")]
public bool showDebugUI = false;
public KeyCode toggleUIKey = KeyCode.F1;
// 성능 데이터 구조
[System.Serializable]
public class PerformanceSnapshot
{
public float timestamp;
public float fps;
public float frameTime;
public long memoryUsageMB;
public int drawCalls;
public int triangles;
public float cpuTime;
public float gpuTime;
public float batteryLevel;
public string deviceTemperature;
public NetworkStatus networkStatus;
}
public enum NetworkStatus
{
Good,
Slow,
Poor,
Disconnected
}
public enum PerformanceAlert
{
FPSCritical,
FPSWarning,
MemoryCritical,
MemoryWarning,
BatteryCritical,
NetworkSlow,
DeviceOverheating
}
// 데이터 저장
private List<PerformanceSnapshot> performanceHistory = new List<PerformanceSnapshot>();
private Queue<float> fpsHistory = new Queue<float>();
private Dictionary<PerformanceAlert, float> lastAlertTime = new Dictionary<PerformanceAlert, float>();
// UI 관련
private GUIStyle debugUIStyle;
private bool showUI = false;
private Rect uiRect = new Rect(10, 10, 350, 400);
// 이벤트
public System.Action<PerformanceSnapshot> OnPerformanceUpdated;
public System.Action<PerformanceAlert, float> OnPerformanceAlert;
void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
InitializeMonitoring();
}
else
{
Destroy(gameObject);
}
}
void InitializeMonitoring()
{
Debug.Log("앱인토스 성능 모니터링 시작");
// 알림 시간 초기화
System.Enum.GetValues(typeof(PerformanceAlert)).Cast<PerformanceAlert>()
.ToList().ForEach(alert => lastAlertTime[alert] = 0f);
// 모니터링 시작
if (enableRealTimeMonitoring)
{
InvokeRepeating(nameof(CollectPerformanceData), 0f, monitoringInterval);
}
// 앱인토스 특화 이벤트 리스너
AppsInToss.OnApplicationPause += OnApplicationPause;
AppsInToss.OnApplicationFocus += OnApplicationFocus;
AppsInToss.OnLowMemoryWarning += OnLowMemoryWarning;
}
void Update()
{
// 디버그 UI 토글
if (Input.GetKeyDown(toggleUIKey))
{
showUI = !showUI;
}
}
void CollectPerformanceData()
{
var snapshot = new PerformanceSnapshot
{
timestamp = Time.realtimeSinceStartup,
fps = CalculateCurrentFPS(),
frameTime = Time.unscaledDeltaTime * 1000f,
memoryUsageMB = Profiler.GetTotalAllocatedMemory(false) / (1024 * 1024),
drawCalls = GetDrawCalls(),
triangles = GetTriangles(),
cpuTime = Time.deltaTime * 1000f,
batteryLevel = SystemInfo.batteryLevel,
deviceTemperature = GetDeviceTemperature(),
networkStatus = GetNetworkStatus()
};
// 데이터 저장
performanceHistory.Add(snapshot);
// 데이터 크기 제한
if (performanceHistory.Count > maxDataPoints)
{
performanceHistory.RemoveAt(0);
}
// FPS 히스토리 관리
fpsHistory.Enqueue(snapshot.fps);
if (fpsHistory.Count > 60) // 1분간 FPS
{
fpsHistory.Dequeue();
}
// 성능 분석 및 경고
AnalyzePerformance(snapshot);
// 이벤트 발생
OnPerformanceUpdated?.Invoke(snapshot);
// 앱인토스 분석 시스템에 전송
if (enableTossAnalytics)
{
SendPerformanceAnalytics(snapshot);
}
}
float CalculateCurrentFPS()
{
if (Time.unscaledDeltaTime > 0)
{
return 1.0f / Time.unscaledDeltaTime;
}
return 0f;
}
int GetDrawCalls()
{
// Unity 2019.3 이상에서는 다른 방법 사용 필요
return UnityEngine.Rendering.FrameDebugger.enabled ?
UnityEngine.Rendering.FrameDebugger.GetFrameEventCount() : 0;
}
int GetTriangles()
{
// 현재 프레임의 삼각형 수 추정
var renderers = FindObjectsOfType<MeshRenderer>();
int totalTriangles = 0;
foreach (var renderer in renderers)
{
if (renderer.isVisible)
{
var meshFilter = renderer.GetComponent<MeshFilter>();
if (meshFilter != null && meshFilter.mesh != null)
{
totalTriangles += meshFilter.mesh.triangles.Length / 3;
}
}
}
return totalTriangles;
}
string GetDeviceTemperature()
{
// 플랫폼별 온도 정보 (앱인토스 네이티브 API 사용)
return AppsInToss.GetDeviceTemperature();
}
NetworkStatus GetNetworkStatus()
{
// 네트워크 상태 확인 (앱인토스 네이티브 API 사용)
var pingTime = AppsInToss.GetNetworkPingTime();
if (pingTime < 0) return NetworkStatus.Disconnected;
else if (pingTime < 100) return NetworkStatus.Good;
else if (pingTime < 500) return NetworkStatus.Slow;
else return NetworkStatus.Poor;
}
void AnalyzePerformance(PerformanceSnapshot snapshot)
{
float currentTime = Time.realtimeSinceStartup;
// FPS 체크
if (snapshot.fps < thresholds.criticalFPS)
{
TriggerAlert(PerformanceAlert.FPSCritical, snapshot.fps, currentTime);
}
else if (snapshot.fps < thresholds.warningFPS)
{
TriggerAlert(PerformanceAlert.FPSWarning, snapshot.fps, currentTime);
}
// 메모리 체크
if (snapshot.memoryUsageMB > thresholds.criticalMemoryMB)
{
TriggerAlert(PerformanceAlert.MemoryCritical, snapshot.memoryUsageMB, currentTime);
}
else if (snapshot.memoryUsageMB > thresholds.warningMemoryMB)
{
TriggerAlert(PerformanceAlert.MemoryWarning, snapshot.memoryUsageMB, currentTime);
}
// 배터리 효율성 체크
float batteryDrain = CalculateBatteryDrainRate();
if (batteryDrain > thresholds.batteryEfficiencyCritical)
{
TriggerAlert(PerformanceAlert.BatteryCritical, batteryDrain, currentTime);
}
// 네트워크 상태 체크
if (snapshot.networkStatus == NetworkStatus.Poor || snapshot.networkStatus == NetworkStatus.Slow)
{
TriggerAlert(PerformanceAlert.NetworkSlow, 0f, currentTime);
}
// 기기 온도 체크
if (snapshot.deviceTemperature.Contains("Hot") || snapshot.deviceTemperature.Contains("Overheating"))
{
TriggerAlert(PerformanceAlert.DeviceOverheating, 0f, currentTime);
}
}
void TriggerAlert(PerformanceAlert alertType, float value, float currentTime)
{
// 중복 알림 방지 (30초 간격)
if (currentTime - lastAlertTime[alertType] < 30f)
{
return;
}
lastAlertTime[alertType] = currentTime;
Debug.LogWarning($"성능 알림: {alertType} - 값: {value:F2}");
// 이벤트 발생
OnPerformanceAlert?.Invoke(alertType, value);
// 앱인토스에 성능 이슈 리포트
ReportPerformanceIssue(alertType, value);
// 자동 최적화 시도
AttemptAutoOptimization(alertType, value);
}
float CalculateBatteryDrainRate()
{
// 배터리 소모율 계산 (단순화된 버전)
if (performanceHistory.Count < 60) return 0f; // 1분 미만 데이터
var recent = performanceHistory.Skip(performanceHistory.Count - 60).ToList();
var startBattery = recent.First().batteryLevel;
var endBattery = recent.Last().batteryLevel;
if (startBattery <= 0 || endBattery <= 0) return 0f;
float drainPerMinute = (startBattery - endBattery) * 60f;
return drainPerMinute / 0.5f; // 네이티브 앱 기준 대비 비율
}
void ReportPerformanceIssue(PerformanceAlert alertType, float value)
{
var issueData = new Dictionary<string, object>
{
{"alert_type", alertType.ToString()},
{"value", value},
{"device_model", SystemInfo.deviceModel},
{"memory_size", SystemInfo.systemMemorySize},
{"graphics_device", SystemInfo.graphicsDeviceName},
{"timestamp", System.DateTime.UtcNow.ToString("o")}
};
AppsInToss.ReportPerformanceIssue($"runtime_performance_{alertType.ToString().ToLower()}", issueData);
}
void AttemptAutoOptimization(PerformanceAlert alertType, float value)
{
switch (alertType)
{
case PerformanceAlert.FPSCritical:
case PerformanceAlert.FPSWarning:
OptimizeForFPS();
break;
case PerformanceAlert.MemoryCritical:
case PerformanceAlert.MemoryWarning:
OptimizeMemoryUsage();
break;
case PerformanceAlert.BatteryCritical:
OptimizeForBattery();
break;
case PerformanceAlert.DeviceOverheating:
ReducePerformanceLoad();
break;
}
}
void OptimizeForFPS()
{
// FPS 최적화
QualitySettings.masterTextureLimit = Mathf.Min(QualitySettings.masterTextureLimit + 1, 3);
QualitySettings.shadowDistance *= 0.8f;
QualitySettings.lodBias *= 0.9f;
Debug.Log("자동 FPS 최적화 적용");
}
void OptimizeMemoryUsage()
{
// 메모리 최적화
System.GC.Collect();
Resources.UnloadUnusedAssets();
// 에셋 캐시 정리
if (AppsInTossResourceManager.Instance != null)
{
AppsInTossResourceManager.Instance.ClearCache(ResourcePriority.Low);
}
Debug.Log("자동 메모리 최적화 적용");
}
void OptimizeForBattery()
{
// 배터리 최적화
Application.targetFrameRate = Mathf.Min(Application.targetFrameRate, 30);
QualitySettings.vSyncCount = 0;
Debug.Log("자동 배터리 최적화 적용");
}
void ReducePerformanceLoad()
{
// 성능 부하 감소
Application.targetFrameRate = 20;
QualitySettings.masterTextureLimit = 2;
QualitySettings.shadowDistance *= 0.5f;
Debug.Log("기기 과열로 인한 성능 부하 감소");
}
void SendPerformanceAnalytics(PerformanceSnapshot snapshot)
{
// 10초마다 한 번씩만 전송 (데이터 절약)
if (Mathf.FloorToInt(snapshot.timestamp) % 10 != 0) return;
var analyticsData = new Dictionary<string, object>
{
{"fps", snapshot.fps},
{"memory_mb", snapshot.memoryUsageMB},
{"draw_calls", snapshot.drawCalls},
{"triangles", snapshot.triangles},
{"battery_level", snapshot.batteryLevel},
{"network_status", snapshot.networkStatus.ToString()},
{"device_temperature", snapshot.deviceTemperature},
{"timestamp", System.DateTime.UtcNow.ToString("o")}
};
AppsInToss.SendAnalytics("runtime_performance", analyticsData);
}
// 이벤트 핸들러
void OnApplicationPause(bool pauseStatus)
{
if (pauseStatus)
{
// 백그라운드 진입 시 성능 리포트 전송
SendPerformanceSummary();
}
else
{
// 포그라운드 복귀 시 모니터링 재시작
performanceHistory.Clear();
}
}
void OnApplicationFocus(bool hasFocus)
{
if (hasFocus)
{
Debug.Log("앱 포커스 복귀 - 성능 모니터링 재시작");
}
}
void OnLowMemoryWarning()
{
Debug.LogWarning("시스템 메모리 부족 경고");
TriggerAlert(PerformanceAlert.MemoryCritical, GetCurrentMemoryUsage(), Time.realtimeSinceStartup);
OptimizeMemoryUsage();
}
float GetCurrentMemoryUsage()
{
return Profiler.GetTotalAllocatedMemory(false) / (1024f * 1024f);
}
void SendPerformanceSummary()
{
if (performanceHistory.Count == 0) return;
var summary = GeneratePerformanceSummary();
var summaryData = new Dictionary<string, object>
{
{"session_duration", summary.sessionDuration},
{"average_fps", summary.averageFPS},
{"min_fps", summary.minFPS},
{"max_memory_mb", summary.maxMemoryMB},
{"average_memory_mb", summary.averageMemoryMB},
{"total_alerts", summary.totalAlerts},
{"timestamp", System.DateTime.UtcNow.ToString("o")}
};
AppsInToss.SendAnalytics("performance_session_summary", summaryData);
}
public PerformanceSummary GeneratePerformanceSummary()
{
if (performanceHistory.Count == 0)
{
return new PerformanceSummary();
}
var summary = new PerformanceSummary
{
sessionDuration = performanceHistory.Last().timestamp - performanceHistory.First().timestamp,
averageFPS = performanceHistory.Average(p => p.fps),
minFPS = performanceHistory.Min(p => p.fps),
maxFPS = performanceHistory.Max(p => p.fps),
averageMemoryMB = performanceHistory.Average(p => p.memoryUsageMB),
maxMemoryMB = performanceHistory.Max(p => p.memoryUsageMB),
totalAlerts = lastAlertTime.Values.Count(t => t > 0)
};
return summary;
}
// 디버그 UI
void OnGUI()
{
if (!showUI || !showDebugUI) return;
InitializeGUIStyle();
uiRect = GUILayout.Window(0, uiRect, DrawDebugWindow, "앱인토스 성능 모니터", debugUIStyle);
}
void InitializeGUIStyle()
{
if (debugUIStyle == null)
{
debugUIStyle = new GUIStyle(GUI.skin.window);
debugUIStyle.fontSize = 12;
}
}
void DrawDebugWindow(int windowID)
{
if (performanceHistory.Count == 0)
{
GUILayout.Label("성능 데이터 수집 중...");
GUI.DragWindow();
return;
}
var latest = performanceHistory.Last();
GUILayout.BeginVertical();
// 현재 성능 정보
GUILayout.Label($"FPS: {latest.fps:F1}", GetColorStyle(latest.fps, thresholds.warningFPS, thresholds.criticalFPS, false));
GUILayout.Label($"메모리: {latest.memoryUsageMB}MB", GetColorStyle(latest.memoryUsageMB, thresholds.warningMemoryMB, thresholds.criticalMemoryMB, true));
GUILayout.Label($"Draw Calls: {latest.drawCalls}");
GUILayout.Label($"Triangles: {latest.triangles:N0}");
GUILayout.Label($"배터리: {(latest.batteryLevel * 100):F0}%");
GUILayout.Label($"네트워크: {latest.networkStatus}");
GUILayout.Label($"온도: {latest.deviceTemperature}");
GUILayout.Space(10);
// 평균값
if (performanceHistory.Count > 10)
{
float avgFPS = performanceHistory.Skip(performanceHistory.Count - 10).Average(p => p.fps);
float avgMemory = performanceHistory.Skip(performanceHistory.Count - 10).Average(p => p.memoryUsageMB);
GUILayout.Label($"10초 평균 FPS: {avgFPS:F1}");
GUILayout.Label($"10초 평균 메모리: {avgMemory:F1}MB");
}
GUILayout.Space(10);
// 제어 버튼
if (GUILayout.Button("메모리 정리"))
{
OptimizeMemoryUsage();
}
if (GUILayout.Button("성능 리포트 전송"))
{
SendPerformanceSummary();
}
GUILayout.EndVertical();
GUI.DragWindow();
}
GUIStyle GetColorStyle(float value, float warningThreshold, float criticalThreshold, bool higherIsWorse)
{
var style = new GUIStyle(GUI.skin.label);
bool critical = higherIsWorse ? value > criticalThreshold : value < criticalThreshold;
bool warning = higherIsWorse ? value > warningThreshold : value < warningThreshold;
if (critical)
{
style.normal.textColor = Color.red;
}
else if (warning)
{
style.normal.textColor = Color.yellow;
}
else
{
style.normal.textColor = Color.green;
}
return style;
}
// 공개 API
public PerformanceSnapshot GetCurrentPerformance()
{
return performanceHistory.LastOrDefault();
}
public List<PerformanceSnapshot> GetPerformanceHistory(int lastNSeconds = 60)
{
if (performanceHistory.Count == 0) return new List<PerformanceSnapshot>();
float currentTime = performanceHistory.Last().timestamp;
return performanceHistory.Where(p => currentTime - p.timestamp <= lastNSeconds).ToList();
}
public void SetMonitoringEnabled(bool enabled)
{
if (enabled && !IsInvoking(nameof(CollectPerformanceData)))
{
InvokeRepeating(nameof(CollectPerformanceData), 0f, monitoringInterval);
}
else if (!enabled && IsInvoking(nameof(CollectPerformanceData)))
{
CancelInvoke(nameof(CollectPerformanceData));
}
}
}
[System.Serializable]
public class PerformanceSummary
{
public float sessionDuration;
public float averageFPS;
public float minFPS;
public float maxFPS;
public float averageMemoryMB;
public float maxMemoryMB;
public int totalAlerts;
}실시간 성능 모니터링을 통해 사용자 경험을 지속적으로 개선하세요. 앱인토스 환경에서는 배터리 효율성과 메모리 사용량이 특히 중요해요.
