406 lines
15 KiB
C#
406 lines
15 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
namespace UnityEditor.Performance.ProfileAnalyzer
|
|
{
|
|
[Serializable]
|
|
internal class ProfileAnalysis
|
|
{
|
|
FrameSummary m_FrameSummary = new FrameSummary();
|
|
List<MarkerData> m_Markers = new List<MarkerData>();
|
|
List<ThreadData> m_Threads = new List<ThreadData>();
|
|
|
|
public ProfileAnalysis()
|
|
{
|
|
m_FrameSummary.first = 0;
|
|
m_FrameSummary.last = 0;
|
|
m_FrameSummary.count = 0;
|
|
m_FrameSummary.msTotal = 0.0;
|
|
m_FrameSummary.msMin = float.MaxValue;
|
|
m_FrameSummary.msMax = 0.0f;
|
|
m_FrameSummary.minFrameIndex = 0;
|
|
m_FrameSummary.maxFrameIndex = 0;
|
|
m_FrameSummary.maxMarkerDepth = 0;
|
|
m_FrameSummary.totalMarkers = 0;
|
|
m_FrameSummary.markerCountMax = 0;
|
|
m_FrameSummary.markerCountMaxMean = 0.0f;
|
|
for (int b = 0; b < m_FrameSummary.buckets.Length; b++)
|
|
m_FrameSummary.buckets[b] = 0;
|
|
|
|
m_Markers.Clear();
|
|
m_Threads.Clear();
|
|
}
|
|
|
|
public void SetRange(int firstFrameIndex, int lastFrameIndex)
|
|
{
|
|
m_FrameSummary.first = firstFrameIndex;
|
|
m_FrameSummary.last = lastFrameIndex;
|
|
|
|
// Ensure these are initialized to frame indices within the range
|
|
m_FrameSummary.minFrameIndex = firstFrameIndex;
|
|
// if this wasn't initialized, and all frames had 0 length, it wouldn't be set in the UpdateSummary step of the analysis and point out of range
|
|
m_FrameSummary.maxFrameIndex = firstFrameIndex;
|
|
}
|
|
|
|
public void AddMarker(MarkerData marker)
|
|
{
|
|
m_Markers.Add(marker);
|
|
}
|
|
|
|
public void AddThread(ThreadData thread)
|
|
{
|
|
m_Threads.Add(thread);
|
|
}
|
|
|
|
public void UpdateSummary(int frameIndex, float msFrame)
|
|
{
|
|
m_FrameSummary.msTotal += msFrame;
|
|
m_FrameSummary.count += 1;
|
|
if (msFrame < m_FrameSummary.msMin)
|
|
{
|
|
m_FrameSummary.msMin = msFrame;
|
|
m_FrameSummary.minFrameIndex = frameIndex;
|
|
}
|
|
if (msFrame > m_FrameSummary.msMax)
|
|
{
|
|
m_FrameSummary.msMax = msFrame;
|
|
m_FrameSummary.maxFrameIndex = frameIndex;
|
|
}
|
|
|
|
m_FrameSummary.frames.Add(new FrameTime(frameIndex, msFrame, 1));
|
|
}
|
|
|
|
FrameTime GetPercentageOffset(List<FrameTime> frames, float percent, out int outputFrameIndex)
|
|
{
|
|
int index = (int)((frames.Count - 1) * percent / 100);
|
|
outputFrameIndex = frames[index].frameIndex;
|
|
|
|
// True median is half of the sum of the middle 2 frames for an even count. However this would be a value never recorded so we avoid that.
|
|
return frames[index];
|
|
}
|
|
|
|
float GetThreadPercentageOffset(List<ThreadFrameTime> frames, float percent, out int outputFrameIndex)
|
|
{
|
|
int index = (int)((frames.Count - 1) * percent / 100);
|
|
outputFrameIndex = frames[index].frameIndex;
|
|
|
|
// True median is half of the sum of the middle 2 frames for an even count. However this would be a value never recorded so we avoid that.
|
|
return frames[index].ms;
|
|
}
|
|
|
|
void CalculateStandardDeviations(MarkerData marker)
|
|
{
|
|
if (marker.frames.Count <= 1)
|
|
{
|
|
marker.msStandardDeviation = 0;
|
|
marker.countStandardDeviation = 0;
|
|
return;
|
|
}
|
|
|
|
int frameCount = marker.frames.Count;
|
|
float msMean = marker.msMean;
|
|
float countMean = marker.countMean;
|
|
|
|
double msSum = 0.0;
|
|
double countSum = 0.0;
|
|
for (int i = 0; i < frameCount; ++i)
|
|
{
|
|
float delta = (marker.frames[i].ms - msMean);
|
|
msSum += (delta * delta);
|
|
|
|
delta = (marker.frames[i].count - countMean);
|
|
countSum += (delta * delta);
|
|
}
|
|
|
|
double variance = msSum / (frameCount - 1);
|
|
marker.msStandardDeviation = (float)Math.Sqrt(variance);
|
|
variance = countSum / (frameCount - 1);
|
|
marker.countStandardDeviation = (float)Math.Sqrt(variance);
|
|
}
|
|
|
|
public void SetupMarkers()
|
|
{
|
|
int countMax = 0;
|
|
float countMaxMean = 0.0f;
|
|
|
|
foreach (MarkerData marker in m_Markers)
|
|
{
|
|
marker.msAtMedian = 0.0f;
|
|
marker.msMin = float.MaxValue;
|
|
marker.msMax = float.MinValue;
|
|
marker.minFrameIndex = 0;
|
|
marker.maxFrameIndex = 0;
|
|
marker.countMin = int.MaxValue;
|
|
marker.countMax = int.MinValue;
|
|
|
|
foreach (FrameTime frameTime in marker.frames)
|
|
{
|
|
var ms = frameTime.ms;
|
|
int frameIndex = frameTime.frameIndex;
|
|
|
|
// Total time for marker over frame
|
|
if (ms < marker.msMin)
|
|
{
|
|
marker.msMin = ms;
|
|
marker.minFrameIndex = frameIndex;
|
|
}
|
|
if (ms > marker.msMax)
|
|
{
|
|
marker.msMax = ms;
|
|
marker.maxFrameIndex = frameIndex;
|
|
}
|
|
|
|
if (frameIndex == m_FrameSummary.medianFrameIndex)
|
|
marker.msAtMedian = ms;
|
|
|
|
var count = frameTime.count;
|
|
|
|
// count for marker over frame
|
|
if (count < marker.countMin)
|
|
{
|
|
marker.countMin = count;
|
|
}
|
|
if (count > marker.countMax)
|
|
{
|
|
marker.countMax = count;
|
|
}
|
|
}
|
|
|
|
int unusedIndex;
|
|
|
|
marker.msMean = marker.presentOnFrameCount > 0 ? (float)(marker.msTotal / marker.presentOnFrameCount) : 0f;
|
|
marker.frames.Sort(FrameTime.CompareCount);
|
|
marker.countMedian = GetPercentageOffset(marker.frames, 50, out marker.medianFrameIndex).count;
|
|
marker.countLowerQuartile = GetPercentageOffset(marker.frames, 25, out unusedIndex).count;
|
|
marker.countUpperQuartile = GetPercentageOffset(marker.frames, 75, out unusedIndex).count;
|
|
|
|
marker.countMean = marker.presentOnFrameCount > 0 ? (float)marker.count / marker.presentOnFrameCount : 0f;
|
|
marker.frames.Sort(FrameTime.CompareMs);
|
|
marker.msMedian = GetPercentageOffset(marker.frames, 50, out marker.medianFrameIndex).ms;
|
|
marker.msLowerQuartile = GetPercentageOffset(marker.frames, 25, out unusedIndex).ms;
|
|
marker.msUpperQuartile = GetPercentageOffset(marker.frames, 75, out unusedIndex).ms;
|
|
|
|
CalculateStandardDeviations(marker);
|
|
|
|
if (marker.countMax > countMax)
|
|
countMax = marker.countMax;
|
|
if (marker.countMean > countMaxMean)
|
|
countMaxMean = marker.countMean;
|
|
}
|
|
|
|
m_FrameSummary.markerCountMax = countMax;
|
|
m_FrameSummary.markerCountMaxMean = countMaxMean;
|
|
}
|
|
|
|
public void SetupMarkerBuckets()
|
|
{
|
|
// using a for loop instead of foreach is surprisingly faster on Mono
|
|
for (int i = 0, n = m_Markers.Count; i < n; i++)
|
|
{
|
|
var marker = m_Markers[i];
|
|
marker.ComputeBuckets(marker.msMin, marker.msMax);
|
|
marker.ComputeCountBuckets(marker.countMin, marker.countMax);
|
|
}
|
|
}
|
|
|
|
public void SetupFrameBuckets(float timeScaleMax)
|
|
{
|
|
float first = 0;
|
|
float last = timeScaleMax;
|
|
float range = last - first;
|
|
|
|
int maxBucketIndex = m_FrameSummary.buckets.Length - 1;
|
|
|
|
for (int bucketIndex = 0; bucketIndex < m_FrameSummary.buckets.Length; bucketIndex++)
|
|
{
|
|
m_FrameSummary.buckets[bucketIndex] = 0;
|
|
}
|
|
|
|
float scale = range > 0 ? m_FrameSummary.buckets.Length / range : 0;
|
|
// using a for loop instead of foreach is surprisingly faster on Mono
|
|
for (int i = 0, n = m_FrameSummary.frames.Count; i < n; i++)
|
|
{
|
|
var frameData = m_FrameSummary.frames[i];
|
|
var msFrame = frameData.ms;
|
|
//var frameIndex = frameData.frameIndex;
|
|
|
|
int bucketIndex = (int)((msFrame - first) * scale);
|
|
if (bucketIndex < 0 || bucketIndex > maxBucketIndex)
|
|
{
|
|
// It can occur for the highest entry in the range (max-min/range) = 1
|
|
// if (ms > max) // Check for the spilling case
|
|
// Debug.Log(string.Format("Frame {0}ms exceeds range {1}-{2} on frame {3}", msFrame, first, last, frameIndex));
|
|
if (bucketIndex > maxBucketIndex)
|
|
bucketIndex = maxBucketIndex;
|
|
else
|
|
bucketIndex = 0;
|
|
}
|
|
m_FrameSummary.buckets[bucketIndex] += 1;
|
|
}
|
|
|
|
if (range == 0)
|
|
{
|
|
// All buckets will be the same
|
|
for (int bucketIndex = 1; bucketIndex < m_FrameSummary.buckets.Length; bucketIndex++)
|
|
{
|
|
m_FrameSummary.buckets[bucketIndex] = m_FrameSummary.buckets[0];
|
|
}
|
|
}
|
|
}
|
|
|
|
void CalculateThreadMedians()
|
|
{
|
|
foreach (var thread in m_Threads)
|
|
{
|
|
if (thread.frames.Count > 0)
|
|
{
|
|
thread.frames.Sort();
|
|
int unusedIndex;
|
|
|
|
thread.msMin = GetThreadPercentageOffset(thread.frames, 0, out thread.minFrameIndex);
|
|
thread.msLowerQuartile = GetThreadPercentageOffset(thread.frames, 25, out unusedIndex);
|
|
thread.msMedian = GetThreadPercentageOffset(thread.frames, 50, out thread.medianFrameIndex);
|
|
thread.msUpperQuartile = GetThreadPercentageOffset(thread.frames, 75, out unusedIndex);
|
|
thread.msMax = GetThreadPercentageOffset(thread.frames, 100, out thread.maxFrameIndex);
|
|
|
|
// Put back in order of frames
|
|
thread.frames.Sort((a, b) => a.frameIndex.CompareTo(b.frameIndex));
|
|
}
|
|
else
|
|
{
|
|
thread.msMin = 0f;
|
|
thread.msLowerQuartile = 0f;
|
|
thread.msMedian = 0f;
|
|
thread.msUpperQuartile = 0f;
|
|
thread.msMax = 0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Finalise(float timeScaleMax, int maxMarkerDepth)
|
|
{
|
|
if (m_FrameSummary.frames.Count > 0)
|
|
{
|
|
m_FrameSummary.frames.Sort();
|
|
m_FrameSummary.msMean = (float)(m_FrameSummary.msTotal / m_FrameSummary.count);
|
|
m_FrameSummary.msMedian = GetPercentageOffset(m_FrameSummary.frames, 50, out m_FrameSummary.medianFrameIndex).ms;
|
|
int unusedIndex;
|
|
m_FrameSummary.msLowerQuartile = GetPercentageOffset(m_FrameSummary.frames, 25, out unusedIndex).ms;
|
|
m_FrameSummary.msUpperQuartile = GetPercentageOffset(m_FrameSummary.frames, 75, out unusedIndex).ms;
|
|
}
|
|
else
|
|
{
|
|
m_FrameSummary.msMean = 0f;
|
|
m_FrameSummary.msMedian = 0f;
|
|
m_FrameSummary.msLowerQuartile = 0f;
|
|
m_FrameSummary.msUpperQuartile = 0f;
|
|
|
|
// This started as float.MaxValue and won't have been updated
|
|
m_FrameSummary.msMin = 0f;
|
|
}
|
|
// No longer need the frame time list ?
|
|
//m_frameSummary.msFrame.Clear();
|
|
m_FrameSummary.maxMarkerDepth = maxMarkerDepth;
|
|
|
|
if (timeScaleMax <= 0.0f)
|
|
{
|
|
// If max frame time range not specified then use the max frame value found.
|
|
timeScaleMax = m_FrameSummary.msMax;
|
|
}
|
|
else if (timeScaleMax < m_FrameSummary.msMax)
|
|
{
|
|
Debug.Log(string.Format("Expanding timeScaleMax {0} to match max value found {1}", timeScaleMax, m_FrameSummary.msMax));
|
|
|
|
// If max frame time range too small we must expand it.
|
|
timeScaleMax = m_FrameSummary.msMax;
|
|
}
|
|
|
|
SetupMarkers();
|
|
SetupMarkerBuckets();
|
|
SetupFrameBuckets(timeScaleMax);
|
|
|
|
// Sort in median order (highest first)
|
|
m_Markers.Sort(SortByAtMedian);
|
|
|
|
CalculateThreadMedians();
|
|
}
|
|
|
|
int SortByAtMedian(MarkerData a, MarkerData b)
|
|
{
|
|
if (a.msAtMedian == b.msAtMedian)
|
|
return -a.medianFrameIndex.CompareTo(b.medianFrameIndex);
|
|
|
|
return -a.msAtMedian.CompareTo(b.msAtMedian);
|
|
}
|
|
|
|
public List<MarkerData> GetMarkers()
|
|
{
|
|
return m_Markers;
|
|
}
|
|
|
|
public List<ThreadData> GetThreads()
|
|
{
|
|
return m_Threads;
|
|
}
|
|
|
|
public ThreadData GetThreadByName(string threadNameWithIndex)
|
|
{
|
|
foreach (var thread in m_Threads)
|
|
{
|
|
if (thread.threadNameWithIndex == threadNameWithIndex)
|
|
return thread;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public FrameSummary GetFrameSummary()
|
|
{
|
|
return m_FrameSummary;
|
|
}
|
|
|
|
public MarkerData GetMarker(int index)
|
|
{
|
|
if (index < 0 || index >= m_Markers.Count)
|
|
return null;
|
|
|
|
return m_Markers[index];
|
|
}
|
|
|
|
public int GetMarkerIndexByName(string markerName)
|
|
{
|
|
if (markerName == null)
|
|
return -1;
|
|
|
|
for (int index = 0; index < m_Markers.Count; index++)
|
|
{
|
|
var marker = m_Markers[index];
|
|
if (marker.name == markerName)
|
|
{
|
|
return index;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
public MarkerData GetMarkerByName(string markerName)
|
|
{
|
|
if (markerName == null)
|
|
return null;
|
|
|
|
for (int index = 0; index < m_Markers.Count; index++)
|
|
{
|
|
var marker = m_Markers[index];
|
|
if (marker.name == markerName)
|
|
{
|
|
return marker;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
}
|