Skip to content

Instantly share code, notes, and snippets.

@jbvrtx
Last active June 1, 2023 09:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jbvrtx/8a659f100fe16c3d012856f6cd06f726 to your computer and use it in GitHub Desktop.
Save jbvrtx/8a659f100fe16c3d012856f6cd06f726 to your computer and use it in GitHub Desktop.
Profiling Tool for Unity that Finds Peaks in Frame Time Usage (Lags)
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class FrameTimeBudget : MonoBehaviour
{
// Serialized Fields
[SerializeField]
[Tooltip("The target frame rate. This is used to calculate the available frame time.\n\n" +
"30 fps = 33.333 ms per frame\n60 fps = 16.666 ms per frame\n90 fps = 11.111 ms per frame")]
private float _fpsGoal = 30f;
[SerializeField]
[Tooltip("The interval in seconds at which to log frame time data. Set to 0 to disable.")]
private float _logIntervalSeconds = 5f;
[SerializeField]
[Tooltip("Activate this to issue a warning each time a frame exceeds the maximum.")]
private bool _warnIfFrameTimeExceeded = true;
// Private Fields
private float[] _frameTimes;
private bool _listLocked = false;
private int _index;
// Properties
public int FrameTimeDataSize => (int)(100f * _logIntervalSeconds);
private float MaxFrameTimeMilliseconds => 1000 / _fpsGoal;
public float AverageFrameTime
{
get
{
if (_frameTimes.Length == 0) return 0;
// Lock the list to prevent it from being modified while we're reading it
_listLocked = true;
// Count all values that have been filled and are not -1
int count = 0;
float averageFrameTime = 0;
for (int i = 0; i < _frameTimes.Length; i++)
{
if (_frameTimes[i] == -1) break;
averageFrameTime += _frameTimes[i];
count++;
}
_listLocked = false;
return averageFrameTime / count;
}
}
public float MaximumFrameTime => _frameTimes.Max();
public float MinimumFrameTime => _frameTimes.Where(time => time >= 0).Min();
#if UNITY_EDITOR || DEVELOPMENT_BUILD
private void Start()
{
// Initialize the array and fill it with -1
_frameTimes = new float[FrameTimeDataSize];
ClearFrameTimeData();
// Start logging frame time data
if (_logIntervalSeconds > 0)
{
InvokeRepeating(nameof(LogFrameTimeData), _logIntervalSeconds, _logIntervalSeconds);
}
}
private void Update()
{
if (_logIntervalSeconds == 0 && !_warnIfFrameTimeExceeded) return;
float currentFrameTime = Time.deltaTime;
if (_warnIfFrameTimeExceeded)
{
if (currentFrameTime > MaxFrameTimeMilliseconds / 1000)
{
Debug.LogFormat(LogType.Warning, LogOption.NoStacktrace, null,
$"Frame time exceeded the maximum of {MaxFrameTimeMilliseconds,0:N3}. Frame ms: {currentFrameTime * 1000,0:N3} ms");
}
}
if (_listLocked) return;
// Add new value, overwriting the oldest value
_frameTimes[_index] = currentFrameTime;
// Increment the index (wrapping back to 0)
_index = (_index + 1) % FrameTimeDataSize;
}
private void LogFrameTimeData()
{
// Log average, max, and min frame times
Debug.LogFormat(LogType.Log, LogOption.NoStacktrace, null,
$"Frame time data (should not exceed {MaxFrameTimeMilliseconds,0:N2} ms). Average: {AverageFrameTime * 1000,0:N2} ms, " +
$"Peak: {MaximumFrameTime * 1000,0:N2} ms, " +
$"Minimum: {MinimumFrameTime * 1000,0:N2} ms");
ClearFrameTimeData();
}
private void ClearFrameTimeData()
{
if (_listLocked) return;
_listLocked = true;
for (int i = 0; i < _frameTimes.Length; i++)
{
_frameTimes[i] = -1;
}
_index = 0;
_listLocked = false;
}
#endif
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment