|
using System; |
|
using System.Collections.Generic; |
|
using UnityEngine; |
|
using UnityEditorInternal; |
|
using System.IO; |
|
using System.Runtime.Serialization.Formatters.Binary; |
|
using System.Text.RegularExpressions; |
|
|
|
namespace UnityEditor.Performance.ProfileAnalyzer |
|
{ |
|
[Serializable] |
|
internal class ProfileData |
|
{ |
|
static int latestVersion = 7; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static Regex trailingDigit = new Regex(@"^(.*[^\s])[\s]+([\d]+)$", RegexOptions.Compiled); |
|
public int Version { get; private set; } |
|
public int FrameIndexOffset { get; private set; } |
|
public bool FirstFrameIncomplete; |
|
public bool LastFrameIncomplete; |
|
List<ProfileFrame> frames = new List<ProfileFrame>(); |
|
List<string> markerNames = new List<string>(); |
|
List<string> threadNames = new List<string>(); |
|
Dictionary<string, int> markerNamesDict = new Dictionary<string, int>(); |
|
Dictionary<string, int> threadNameDict = new Dictionary<string, int>(); |
|
public string FilePath { get; private set; } |
|
public int MarkerNameCount => markerNames.Count; |
|
static float s_Progress = 0; |
|
|
|
public ProfileData() |
|
{ |
|
FrameIndexOffset = 0; |
|
FilePath = string.Empty; |
|
Version = latestVersion; |
|
} |
|
|
|
public ProfileData(string filename) |
|
{ |
|
FrameIndexOffset = 0; |
|
FilePath = filename; |
|
Version = latestVersion; |
|
} |
|
|
|
void Read() |
|
{ |
|
if (string.IsNullOrEmpty(FilePath)) |
|
throw new Exception("File path is invalid"); |
|
|
|
using (var reader = new BinaryReader(File.Open(FilePath, FileMode.Open))) |
|
{ |
|
s_Progress = 0; |
|
Version = reader.ReadInt32(); |
|
if (Version < 0 || Version > latestVersion) |
|
{ |
|
throw new Exception(String.Format("File version unsupported: {0} != {1} expected, at path: {2}", Version, latestVersion, FilePath)); |
|
} |
|
|
|
FrameIndexOffset = reader.ReadInt32(); |
|
int frameCount = reader.ReadInt32(); |
|
frames.Clear(); |
|
for (int frame = 0; frame < frameCount; frame++) |
|
{ |
|
frames.Add(new ProfileFrame(reader, Version)); |
|
s_Progress = (float)frame / frameCount; |
|
} |
|
|
|
int markerCount = reader.ReadInt32(); |
|
markerNames.Clear(); |
|
for (int marker = 0; marker < markerCount; marker++) |
|
{ |
|
markerNames.Add(reader.ReadString()); |
|
s_Progress = (float)marker / markerCount; |
|
} |
|
|
|
int threadCount = reader.ReadInt32(); |
|
threadNames.Clear(); |
|
for (int thread = 0; thread < threadCount; thread++) |
|
{ |
|
var threadNameWithIndex = reader.ReadString(); |
|
|
|
threadNameWithIndex = CorrectThreadName(threadNameWithIndex); |
|
|
|
threadNames.Add(threadNameWithIndex); |
|
s_Progress = (float)thread / threadCount; |
|
} |
|
} |
|
} |
|
|
|
internal void DeleteTmpFiles() |
|
{ |
|
if (ProfileAnalyzerWindow.FileInTempDir(FilePath)) |
|
File.Delete(FilePath); |
|
} |
|
|
|
bool IsFrameSame(int frameIndex, ProfileData other) |
|
{ |
|
ProfileFrame thisFrame = GetFrame(frameIndex); |
|
ProfileFrame otherFrame = other.GetFrame(frameIndex); |
|
return thisFrame.IsSame(otherFrame); |
|
} |
|
|
|
public bool IsSame(ProfileData other) |
|
{ |
|
if (other == null) |
|
return false; |
|
|
|
int frameCount = GetFrameCount(); |
|
if (frameCount != other.GetFrameCount()) |
|
{ |
|
|
|
return false; |
|
} |
|
|
|
if (frameCount == 0) |
|
{ |
|
|
|
return true; |
|
} |
|
|
|
if (!IsFrameSame(0, other)) |
|
return false; |
|
if (!IsFrameSame(frameCount - 1, other)) |
|
return false; |
|
|
|
|
|
|
|
return true; |
|
} |
|
|
|
static public string ThreadNameWithIndex(int index, string threadName) |
|
{ |
|
return string.Format("{0}:{1}", index, threadName); |
|
} |
|
|
|
public void SetFrameIndexOffset(int offset) |
|
{ |
|
FrameIndexOffset = offset; |
|
} |
|
|
|
public int GetFrameCount() |
|
{ |
|
return frames.Count; |
|
} |
|
|
|
public ProfileFrame GetFrame(int offset) |
|
{ |
|
if (offset < 0 || offset >= frames.Count) |
|
return null; |
|
|
|
return frames[offset]; |
|
} |
|
|
|
public List<string> GetMarkerNames() |
|
{ |
|
return markerNames; |
|
} |
|
|
|
public List<string> GetThreadNames() |
|
{ |
|
return threadNames; |
|
} |
|
|
|
public int GetThreadCount() |
|
{ |
|
return threadNames.Count; |
|
} |
|
|
|
public int OffsetToDisplayFrame(int offset) |
|
{ |
|
return offset + (1 + FrameIndexOffset); |
|
} |
|
|
|
public int DisplayFrameToOffset(int displayFrame) |
|
{ |
|
return displayFrame - (1 + FrameIndexOffset); |
|
} |
|
|
|
public void AddThreadName(string threadName, ProfileThread thread) |
|
{ |
|
threadName = CorrectThreadName(threadName); |
|
|
|
int index = -1; |
|
|
|
if (!threadNameDict.TryGetValue(threadName, out index)) |
|
{ |
|
threadNames.Add(threadName); |
|
index = threadNames.Count - 1; |
|
|
|
threadNameDict.Add(threadName, index); |
|
} |
|
|
|
thread.threadIndex = index; |
|
} |
|
|
|
public void AddMarkerName(string markerName, ProfileMarker marker) |
|
{ |
|
int index = -1; |
|
if (!markerNamesDict.TryGetValue(markerName, out index)) |
|
{ |
|
markerNames.Add(markerName); |
|
index = markerNames.Count - 1; |
|
|
|
markerNamesDict.Add(markerName, index); |
|
} |
|
|
|
marker.nameIndex = index; |
|
} |
|
|
|
public string GetThreadName(ProfileThread thread) |
|
{ |
|
return threadNames[thread.threadIndex]; |
|
} |
|
|
|
public string GetMarkerName(ProfileMarker marker) |
|
{ |
|
return markerNames[marker.nameIndex]; |
|
} |
|
|
|
public int GetMarkerIndex(string markerName) |
|
{ |
|
for (int nameIndex = 0; nameIndex < markerNames.Count; ++nameIndex) |
|
{ |
|
if (markerName == markerNames[nameIndex]) |
|
return nameIndex; |
|
} |
|
return -1; |
|
} |
|
|
|
public void Add(ProfileFrame frame) |
|
{ |
|
frames.Add(frame); |
|
} |
|
|
|
void WriteInternal(string filepath) |
|
{ |
|
using (var writer = new BinaryWriter(File.Open(filepath, FileMode.OpenOrCreate))) |
|
{ |
|
Version = latestVersion; |
|
|
|
writer.Write(Version); |
|
writer.Write(FrameIndexOffset); |
|
|
|
writer.Write(frames.Count); |
|
foreach (var frame in frames) |
|
{ |
|
frame.Write(writer); |
|
} |
|
|
|
writer.Write(markerNames.Count); |
|
foreach (var markerName in markerNames) |
|
{ |
|
writer.Write(markerName); |
|
} |
|
|
|
writer.Write(threadNames.Count); |
|
foreach (var threadName in threadNames) |
|
{ |
|
writer.Write(threadName); |
|
} |
|
} |
|
} |
|
|
|
internal void Write() |
|
{ |
|
|
|
if (string.IsNullOrEmpty(FilePath)) |
|
FilePath = ProfileAnalyzerWindow.TmpPath; |
|
|
|
WriteInternal(FilePath); |
|
} |
|
|
|
internal void WriteTo(string path) |
|
{ |
|
|
|
if (path == FilePath) |
|
return; |
|
|
|
if (!string.IsNullOrEmpty(FilePath) && File.Exists(FilePath)) |
|
{ |
|
if (File.Exists(path)) |
|
File.Delete(path); |
|
|
|
File.Copy(FilePath, path); |
|
} |
|
else |
|
{ |
|
WriteInternal(path); |
|
} |
|
FilePath = path; |
|
} |
|
|
|
public static string CorrectThreadName(string threadNameWithIndex) |
|
{ |
|
var info = threadNameWithIndex.Split(':'); |
|
if (info.Length >= 2) |
|
{ |
|
string threadGroupIndexString = info[0]; |
|
string threadName = info[1]; |
|
if (threadName.Trim() == "") |
|
{ |
|
|
|
threadNameWithIndex = string.Format("{0}:[Unknown]", threadGroupIndexString); |
|
} |
|
else |
|
{ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Match m = trailingDigit.Match(threadName); |
|
if (m.Success) |
|
{ |
|
string threadNamePrefix = m.Groups[1].Value; |
|
int threadGroupIndex = 1 + int.Parse(m.Groups[2].Value); |
|
|
|
threadNameWithIndex = string.Format("{0}:{1}", threadGroupIndex, threadNamePrefix); |
|
} |
|
} |
|
} |
|
|
|
threadNameWithIndex = threadNameWithIndex.Trim(); |
|
|
|
return threadNameWithIndex; |
|
} |
|
|
|
public static string GetThreadNameWithGroup(string threadName, string groupName) |
|
{ |
|
if (string.IsNullOrEmpty(groupName)) |
|
return threadName; |
|
|
|
return string.Format("{0}.{1}", groupName, threadName); |
|
} |
|
|
|
public static string GetThreadNameWithoutGroup(string threadNameWithGroup, out string groupName) |
|
{ |
|
string[] tokens = threadNameWithGroup.Split('.'); |
|
if (tokens.Length <= 1) |
|
{ |
|
groupName = ""; |
|
return tokens[0]; |
|
} |
|
|
|
groupName = tokens[0]; |
|
return tokens[1].TrimStart(); |
|
} |
|
|
|
internal bool HasFrames |
|
{ |
|
get |
|
{ |
|
return frames != null && frames.Count > 0; |
|
} |
|
} |
|
|
|
internal bool HasThreads |
|
{ |
|
get |
|
{ |
|
return frames[0].threads != null && frames[0].threads.Count > 0; |
|
} |
|
} |
|
|
|
internal bool NeedsMarkerRebuild |
|
{ |
|
get |
|
{ |
|
if (frames.Count > 0 && frames[0].threads.Count > 0) |
|
return frames[0].threads[0].markers.Count != frames[0].threads[0].markerCount; |
|
|
|
return false; |
|
} |
|
} |
|
|
|
public static bool Save(string filename, ProfileData data) |
|
{ |
|
if (data == null) |
|
return false; |
|
|
|
if (string.IsNullOrEmpty(filename)) |
|
return false; |
|
|
|
if (filename.EndsWith(".json")) |
|
{ |
|
var json = JsonUtility.ToJson(data); |
|
File.WriteAllText(filename, json); |
|
} |
|
else if (filename.EndsWith(".padata")) |
|
{ |
|
FileStream stream = File.Create(filename); |
|
var formatter = new BinaryFormatter(); |
|
formatter.Serialize(stream, data); |
|
stream.Close(); |
|
} |
|
else if (filename.EndsWith(".pdata")) |
|
{ |
|
data.WriteTo(filename); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
public static bool Load(string filename, out ProfileData data) |
|
{ |
|
if (filename.EndsWith(".json")) |
|
{ |
|
string json = File.ReadAllText(filename); |
|
data = JsonUtility.FromJson<ProfileData>(json); |
|
} |
|
else if (filename.EndsWith(".padata")) |
|
{ |
|
FileStream stream = File.OpenRead(filename); |
|
var formatter = new BinaryFormatter(); |
|
data = (ProfileData)formatter.Deserialize(stream); |
|
stream.Close(); |
|
|
|
if (data.Version != latestVersion) |
|
{ |
|
Debug.Log(String.Format("Unable to load file. Incorrect file version in {0} : (file {1} != {2} expected", filename, data.Version, latestVersion)); |
|
data = null; |
|
return false; |
|
} |
|
} |
|
else if (filename.EndsWith(".pdata")) |
|
{ |
|
if (!File.Exists(filename)) |
|
{ |
|
data = null; |
|
return false; |
|
} |
|
|
|
try |
|
{ |
|
data = new ProfileData(filename); |
|
data.Read(); |
|
} |
|
catch (Exception e) |
|
{ |
|
var message = e.Message; |
|
if (!string.IsNullOrEmpty(message)) |
|
Debug.Log(e.Message); |
|
data = null; |
|
return false; |
|
} |
|
} |
|
else |
|
{ |
|
string errorMessage; |
|
if (filename.EndsWith(".data")) |
|
{ |
|
errorMessage = "Unable to load file. Profiler captures (.data) should be loaded in the Profiler Window and then pulled into the Analyzer via its Pull Data button."; |
|
} |
|
else |
|
{ |
|
errorMessage = string.Format("Unable to load file. Unsupported file format: '{0}'.", Path.GetExtension(filename)); |
|
} |
|
|
|
Debug.Log(errorMessage); |
|
data = null; |
|
return false; |
|
} |
|
|
|
data.Finalise(); |
|
return true; |
|
} |
|
|
|
void PushMarker(Stack<ProfileMarker> markerStack, ProfileMarker markerData) |
|
{ |
|
Debug.Assert(markerData.depth == markerStack.Count + 1); |
|
markerStack.Push(markerData); |
|
} |
|
|
|
ProfileMarker PopMarkerAndRecordTimeInParent(Stack<ProfileMarker> markerStack) |
|
{ |
|
ProfileMarker child = markerStack.Pop(); |
|
|
|
ProfileMarker parentMarker = (markerStack.Count > 0) ? markerStack.Peek() : null; |
|
|
|
|
|
if (parentMarker != null) |
|
parentMarker.msChildren += child.msMarkerTotal; |
|
|
|
return parentMarker; |
|
} |
|
|
|
public void Finalise() |
|
{ |
|
CalculateMarkerChildTimes(); |
|
markerNamesDict.Clear(); |
|
} |
|
|
|
void CalculateMarkerChildTimes() |
|
{ |
|
var markerStack = new Stack<ProfileMarker>(); |
|
|
|
for (int frameOffset = 0; frameOffset <= frames.Count; ++frameOffset) |
|
{ |
|
var frameData = GetFrame(frameOffset); |
|
if (frameData == null) |
|
continue; |
|
|
|
for (int threadIndex = 0; threadIndex < frameData.threads.Count; threadIndex++) |
|
{ |
|
var threadData = frameData.threads[threadIndex]; |
|
|
|
|
|
|
|
|
|
foreach (ProfileMarker markerData in threadData.markers) |
|
{ |
|
markerData.msChildren = 0.0f; |
|
} |
|
|
|
|
|
markerStack.Clear(); |
|
foreach (ProfileMarker markerData in threadData.markers) |
|
{ |
|
int depth = markerData.depth; |
|
|
|
|
|
if (depth >= markerStack.Count) |
|
{ |
|
|
|
if (depth == markerStack.Count) |
|
{ |
|
PopMarkerAndRecordTimeInParent(markerStack); |
|
} |
|
|
|
|
|
} |
|
else if (depth < markerStack.Count) |
|
{ |
|
|
|
while (markerStack.Count >= depth) |
|
{ |
|
PopMarkerAndRecordTimeInParent(markerStack); |
|
} |
|
} |
|
|
|
PushMarker(markerStack, markerData); |
|
} |
|
} |
|
} |
|
} |
|
|
|
public static float GetLoadingProgress() |
|
{ |
|
return s_Progress; |
|
} |
|
} |
|
|
|
[Serializable] |
|
internal class ProfileFrame |
|
{ |
|
public List<ProfileThread> threads = new List<ProfileThread>(); |
|
public double msStartTime; |
|
public float msFrame; |
|
|
|
public ProfileFrame() |
|
{ |
|
msStartTime = 0.0; |
|
msFrame = 0f; |
|
} |
|
|
|
public bool IsSame(ProfileFrame otherFrame) |
|
{ |
|
if (msStartTime != otherFrame.msStartTime) |
|
return false; |
|
if (msFrame != otherFrame.msFrame) |
|
return false; |
|
if (threads.Count != otherFrame.threads.Count) |
|
return false; |
|
|
|
|
|
return true; |
|
} |
|
|
|
public void Add(ProfileThread thread) |
|
{ |
|
threads.Add(thread); |
|
} |
|
|
|
public void Write(BinaryWriter writer) |
|
{ |
|
writer.Write(msStartTime); |
|
writer.Write(msFrame); |
|
writer.Write(threads.Count); |
|
foreach (var thread in threads) |
|
{ |
|
thread.Write(writer); |
|
} |
|
; |
|
} |
|
|
|
public ProfileFrame(BinaryReader reader, int fileVersion) |
|
{ |
|
if (fileVersion > 1) |
|
{ |
|
if (fileVersion >= 6) |
|
{ |
|
msStartTime = reader.ReadDouble(); |
|
} |
|
else |
|
{ |
|
double sStartTime = reader.ReadDouble(); |
|
msStartTime = sStartTime * 1000.0; |
|
} |
|
} |
|
|
|
msFrame = reader.ReadSingle(); |
|
int threadCount = reader.ReadInt32(); |
|
threads.Clear(); |
|
for (int thread = 0; thread < threadCount; thread++) |
|
{ |
|
threads.Add(new ProfileThread(reader, fileVersion)); |
|
} |
|
} |
|
} |
|
|
|
[Serializable] |
|
internal class ProfileThread |
|
{ |
|
[NonSerialized] |
|
public List<ProfileMarker> markers = new List<ProfileMarker>(); |
|
public int threadIndex; |
|
public long streamPos; |
|
public int markerCount = 0; |
|
public int fileVersion; |
|
|
|
public ProfileThread() |
|
{ |
|
} |
|
|
|
public void Write(BinaryWriter writer) |
|
{ |
|
writer.Write(threadIndex); |
|
writer.Write(markers.Count); |
|
foreach (var marker in markers) |
|
{ |
|
marker.Write(writer); |
|
} |
|
; |
|
} |
|
|
|
public ProfileThread(BinaryReader reader, int fileversion) |
|
{ |
|
streamPos = reader.BaseStream.Position; |
|
fileVersion = fileversion; |
|
threadIndex = reader.ReadInt32(); |
|
markerCount = reader.ReadInt32(); |
|
markers.Clear(); |
|
for (int marker = 0; marker < markerCount; marker++) |
|
{ |
|
markers.Add(new ProfileMarker(reader, fileVersion)); |
|
} |
|
} |
|
|
|
public bool ReadMarkers(string path) |
|
{ |
|
if (streamPos == 0) |
|
return false; |
|
var stream = File.OpenRead(path); |
|
BinaryReader br = new BinaryReader(stream); |
|
|
|
br.BaseStream.Position = streamPos; |
|
threadIndex = br.ReadInt32(); |
|
markerCount = br.ReadInt32(); |
|
|
|
markers.Clear(); |
|
for (int marker = 0; marker < markerCount; marker++) |
|
{ |
|
markers.Add(new ProfileMarker(br, fileVersion)); |
|
} |
|
|
|
br.Close(); |
|
return true; |
|
} |
|
|
|
public void AddMarker(ProfileMarker markerData) |
|
{ |
|
markers.Add(markerData); |
|
markerCount++; |
|
} |
|
|
|
public void RebuildMarkers(string path) |
|
{ |
|
if (!File.Exists(path)) return; |
|
FileStream stream = File.OpenRead(path); |
|
using (var reader = new BinaryReader(stream)) |
|
{ |
|
reader.BaseStream.Position = streamPos; |
|
threadIndex = reader.ReadInt32(); |
|
markerCount = reader.ReadInt32(); |
|
markers.Clear(); |
|
for (int marker = 0; marker < markerCount; marker++) |
|
{ |
|
markers.Add(new ProfileMarker(reader, fileVersion)); |
|
} |
|
} |
|
} |
|
} |
|
|
|
[Serializable] |
|
internal class ProfileMarker |
|
{ |
|
public int nameIndex; |
|
public float msMarkerTotal; |
|
public int depth; |
|
[NonSerialized] |
|
public float msChildren; |
|
|
|
public ProfileMarker() |
|
{ |
|
} |
|
|
|
public static ProfileMarker Create(float durationMS, int depth) |
|
{ |
|
var item = new ProfileMarker |
|
{ |
|
msMarkerTotal = durationMS, |
|
depth = depth, |
|
msChildren = 0.0f |
|
}; |
|
|
|
return item; |
|
} |
|
|
|
public static ProfileMarker Create(ProfilerFrameDataIterator frameData) |
|
{ |
|
return Create(frameData.durationMS, frameData.depth); |
|
} |
|
|
|
public void Write(BinaryWriter writer) |
|
{ |
|
writer.Write(nameIndex); |
|
writer.Write(msMarkerTotal); |
|
writer.Write(depth); |
|
} |
|
|
|
public ProfileMarker(BinaryReader reader, int fileVersion) |
|
{ |
|
nameIndex = reader.ReadInt32(); |
|
msMarkerTotal = reader.ReadSingle(); |
|
depth = reader.ReadInt32(); |
|
if (fileVersion == 3) |
|
msChildren = reader.ReadSingle(); |
|
else |
|
msChildren = 0.0f; |
|
} |
|
} |
|
} |
|
|