| 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; |
| } |
| } |
| } |
|
|