123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760 |
- 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;
- /*
- Version 1 - Initial version. Thread names index:threadName (Some invalid thread names count:threadName index)
- Version 2 - Added frame start time.
- Version 3 - Saved out marker children times in the data (Never needed so rapidly skipped)
- Version 4 - Removed the child times again (at this point data was saved with 1 less frame at start and end)
- Version 5 - Updated the thread names to include the thread group as a prefix (index:threadGroup.threadName, index is 1 based, original is 0 based)
- Version 6 - fixed msStartTime (previously was 'seconds')
- Version 7 - Data now only skips the frame at the end
- */
- 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; }
- 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())
- {
- // Frame counts differ
- return false;
- }
- if (frameCount == 0)
- {
- // Both empty
- return true;
- }
- if (!IsFrameSame(0, other))
- return false;
- if (!IsFrameSame(frameCount - 1, other))
- return false;
- // Close enough if same number of frames and first/last have exactly the same frame time and time offset.
- // If we see false matches we could add a full has of the data on load/pull
- 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()
- {
- //ensure that we can always write to the temp location at least
- if (string.IsNullOrEmpty(FilePath))
- FilePath = ProfileAnalyzerWindow.TmpPath;
- WriteInternal(FilePath);
- }
- internal void WriteTo(string path)
- {
- //no point in trying to save on top of ourselves
- 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() == "")
- {
- // Scan seen with no thread name
- threadNameWithIndex = string.Format("{0}:[Unknown]", threadGroupIndexString);
- }
- else
- {
- // Some scans have thread names such as
- // "1:Worker Thread 0"
- // "1:Worker Thread 1"
- // rather than
- // "1:Worker Thread"
- // "2:Worker Thread"
- // Update to the second format so the 'All' case is correctly determined
- 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;
- // Record the last markers time in its parent
- 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];
- // The markers are in depth first order and the depth is known
- // So we can infer a parent child relationship
- // Zero them first
- foreach (ProfileMarker markerData in threadData.markers)
- {
- markerData.msChildren = 0.0f;
- }
- // Update the child times
- markerStack.Clear();
- foreach (ProfileMarker markerData in threadData.markers)
- {
- int depth = markerData.depth;
- // Update depth stack and record child times in the parent
- if (depth >= markerStack.Count)
- {
- // If at same level then remove the last item at this level
- if (depth == markerStack.Count)
- {
- PopMarkerAndRecordTimeInParent(markerStack);
- }
- // Assume we can't move down depth without markers between levels.
- }
- else if (depth < markerStack.Count)
- {
- // We can move up depth several layers so need to pop off all those markers
- 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;
- // Close enough.
- 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; // the stream positions havent been written yet.
- 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; // Recalculated on load so not saved in file
- 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) // In this version we saved the msChildren value but we don't need to as we now recalculate on load
- msChildren = reader.ReadSingle();
- else
- msChildren = 0.0f;
- }
- }
- }
|