ProfileAnalyzer.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. using System.Collections.Generic;
  2. using UnityEngine;
  3. using UnityEditorInternal;
  4. using System.Text.RegularExpressions;
  5. using System;
  6. namespace UnityEditor.Performance.ProfileAnalyzer
  7. {
  8. internal class ProfileAnalyzer
  9. {
  10. public const int kDepthAll = -1;
  11. int m_Progress = 0;
  12. ProfilerFrameDataIterator m_frameData;
  13. List<string> m_threadNames = new List<string>();
  14. ProfileAnalysis m_analysis;
  15. public ProfileAnalyzer()
  16. {
  17. }
  18. public void QuickScan()
  19. {
  20. var frameData = new ProfilerFrameDataIterator();
  21. m_threadNames.Clear();
  22. int frameIndex = 0;
  23. int threadCount = frameData.GetThreadCount(0);
  24. frameData.SetRoot(frameIndex, 0);
  25. Dictionary<string, int> threadNameCount = new Dictionary<string, int>();
  26. for (int threadIndex = 0; threadIndex < threadCount; ++threadIndex)
  27. {
  28. frameData.SetRoot(frameIndex, threadIndex);
  29. var threadName = frameData.GetThreadName();
  30. var groupName = frameData.GetGroupName();
  31. threadName = ProfileData.GetThreadNameWithGroup(threadName, groupName);
  32. if (!threadNameCount.ContainsKey(threadName))
  33. threadNameCount.Add(threadName, 1);
  34. else
  35. threadNameCount[threadName] += 1;
  36. string threadNameWithIndex = ProfileData.ThreadNameWithIndex(threadNameCount[threadName], threadName);
  37. threadNameWithIndex = ProfileData.CorrectThreadName(threadNameWithIndex);
  38. m_threadNames.Add(threadNameWithIndex);
  39. }
  40. frameData.Dispose();
  41. }
  42. public List<string> GetThreadNames()
  43. {
  44. return m_threadNames;
  45. }
  46. void CalculateFrameTimeStats(ProfileData data, out float median, out float mean, out float standardDeviation)
  47. {
  48. List<float> frameTimes = new List<float>();
  49. for (int frameIndex = 0; frameIndex < data.GetFrameCount(); frameIndex++)
  50. {
  51. var frame = data.GetFrame(frameIndex);
  52. float msFrame = frame.msFrame;
  53. frameTimes.Add(msFrame);
  54. }
  55. frameTimes.Sort();
  56. median = frameTimes[frameTimes.Count / 2];
  57. double total = 0.0f;
  58. foreach (float msFrame in frameTimes)
  59. {
  60. total += msFrame;
  61. }
  62. mean = (float)(total / (double)frameTimes.Count);
  63. if (frameTimes.Count <= 1)
  64. {
  65. standardDeviation = 0f;
  66. }
  67. else
  68. {
  69. total = 0.0f;
  70. foreach (float msFrame in frameTimes)
  71. {
  72. float d = msFrame - mean;
  73. total += (d * d);
  74. }
  75. total /= (frameTimes.Count - 1);
  76. standardDeviation = (float)Math.Sqrt(total);
  77. }
  78. }
  79. int GetClampedOffsetToFrame(ProfileData profileData, int frameIndex)
  80. {
  81. int frameOffset = profileData.DisplayFrameToOffset(frameIndex);
  82. if (frameOffset < 0)
  83. {
  84. Debug.Log(string.Format("Frame index {0} offset {1} < 0, clamping", frameIndex, frameOffset));
  85. frameOffset = 0;
  86. }
  87. if (frameOffset >= profileData.GetFrameCount())
  88. {
  89. Debug.Log(string.Format("Frame index {0} offset {1} >= frame count {2}, clamping", frameIndex, frameOffset, profileData.GetFrameCount()));
  90. frameOffset = profileData.GetFrameCount() - 1;
  91. }
  92. return frameOffset;
  93. }
  94. public static bool MatchThreadFilter(string threadNameWithIndex, List<string> threadFilters)
  95. {
  96. if (threadFilters == null || threadFilters.Count == 0)
  97. return false;
  98. if (threadFilters.Contains(threadNameWithIndex))
  99. return true;
  100. return false;
  101. }
  102. public bool IsNullOrWhiteSpace(string s)
  103. {
  104. // return string.IsNullOrWhiteSpace(parentMarker);
  105. if (s == null || Regex.IsMatch(s, @"^[\s]*$"))
  106. return true;
  107. return false;
  108. }
  109. public ProfileAnalysis Analyze(ProfileData profileData, List<int> selectionIndices, List<string> threadFilters, int depthFilter, bool selfTimes = false, string parentMarker = null, float timeScaleMax = 0)
  110. {
  111. m_Progress = 0;
  112. if (profileData == null)
  113. {
  114. return null;
  115. }
  116. if (profileData.GetFrameCount() <= 0)
  117. {
  118. return null;
  119. }
  120. int frameCount = selectionIndices.Count;
  121. if (frameCount < 0)
  122. {
  123. return null;
  124. }
  125. if (profileData.HasFrames && !profileData.HasThreads)
  126. {
  127. if (!ProfileData.Load(profileData.FilePath, out profileData))
  128. {
  129. return null;
  130. }
  131. }
  132. bool processMarkers = (threadFilters != null);
  133. ProfileAnalysis analysis = new ProfileAnalysis();
  134. if (selectionIndices.Count > 0)
  135. analysis.SetRange(selectionIndices[0], selectionIndices[selectionIndices.Count - 1]);
  136. else
  137. analysis.SetRange(0, 0);
  138. m_threadNames.Clear();
  139. int maxMarkerDepthFound = 0;
  140. var threads = new Dictionary<string, ThreadData>();
  141. var markers = new Dictionary<string, MarkerData>();
  142. var allMarkers = new Dictionary<string, int>();
  143. bool filteringByParentMarker = false;
  144. int parentMarkerIndex = -1;
  145. if (!IsNullOrWhiteSpace(parentMarker))
  146. {
  147. // Returns -1 if this marker doesn't exist in the data set
  148. parentMarkerIndex = profileData.GetMarkerIndex(parentMarker);
  149. filteringByParentMarker = true;
  150. }
  151. int at = 0;
  152. foreach (int frameIndex in selectionIndices)
  153. {
  154. int frameOffset = profileData.DisplayFrameToOffset(frameIndex);
  155. var frameData = profileData.GetFrame(frameOffset);
  156. if (frameData == null)
  157. continue;
  158. var msFrame = frameData.msFrame;
  159. analysis.UpdateSummary(frameIndex, msFrame);
  160. if (processMarkers)
  161. {
  162. // get the file reader in case we need to rebuild the markers rather than opening
  163. // the file for every marker
  164. for (int threadIndex = 0; threadIndex < frameData.threads.Count; threadIndex++)
  165. {
  166. float msTimeOfMinDepthMarkers = 0.0f;
  167. float msIdleTimeOfMinDepthMarkers = 0.0f;
  168. var threadData = frameData.threads[threadIndex];
  169. var threadNameWithIndex = profileData.GetThreadName(threadData);
  170. ThreadData thread;
  171. if (!threads.ContainsKey(threadNameWithIndex))
  172. {
  173. m_threadNames.Add(threadNameWithIndex);
  174. thread = new ThreadData(threadNameWithIndex);
  175. analysis.AddThread(thread);
  176. threads[threadNameWithIndex] = thread;
  177. // Update threadsInGroup for all thread records of the same group name
  178. foreach (var threadAt in threads.Values)
  179. {
  180. if (threadAt == thread)
  181. continue;
  182. if (thread.threadGroupName == threadAt.threadGroupName)
  183. {
  184. threadAt.threadsInGroup += 1;
  185. thread.threadsInGroup += 1;
  186. }
  187. }
  188. }
  189. else
  190. {
  191. thread = threads[threadNameWithIndex];
  192. }
  193. bool include = MatchThreadFilter(threadNameWithIndex, threadFilters);
  194. int parentMarkerDepth = -1;
  195. if (threadData.markers.Count != threadData.markerCount)
  196. {
  197. if (!threadData.ReadMarkers(profileData.FilePath))
  198. {
  199. Debug.LogError("failed to read markers");
  200. }
  201. }
  202. foreach (ProfileMarker markerData in threadData.markers)
  203. {
  204. string markerName = profileData.GetMarkerName(markerData);
  205. if (!allMarkers.ContainsKey(markerName))
  206. allMarkers.Add(markerName, 1);
  207. // No longer counting how many times we see the marker (this saves 1/3 of the analysis time).
  208. float ms = markerData.msMarkerTotal - (selfTimes ? markerData.msChildren : 0);
  209. var markerDepth = markerData.depth;
  210. if (markerDepth > maxMarkerDepthFound)
  211. maxMarkerDepthFound = markerDepth;
  212. if (markerDepth == 1)
  213. {
  214. if (markerName == "Idle")
  215. msIdleTimeOfMinDepthMarkers += ms;
  216. else
  217. msTimeOfMinDepthMarkers += ms;
  218. }
  219. if (!include)
  220. continue;
  221. if (depthFilter != kDepthAll && markerDepth != depthFilter)
  222. continue;
  223. // If only looking for markers below the parent
  224. if (filteringByParentMarker)
  225. {
  226. // If found the parent marker
  227. if (markerData.nameIndex == parentMarkerIndex)
  228. {
  229. // And we are not already below the parent higher in the depth tree
  230. if (parentMarkerDepth < 0)
  231. {
  232. // record the parent marker depth
  233. parentMarkerDepth = markerData.depth;
  234. }
  235. }
  236. else
  237. {
  238. // If we are now above or beside the parent marker then we are done for this level
  239. if (markerData.depth <= parentMarkerDepth)
  240. {
  241. parentMarkerDepth = -1;
  242. }
  243. }
  244. if (parentMarkerDepth < 0)
  245. continue;
  246. }
  247. MarkerData marker;
  248. if (markers.ContainsKey(markerName))
  249. {
  250. marker = markers[markerName];
  251. if (!marker.threads.Contains(threadNameWithIndex))
  252. marker.threads.Add(threadNameWithIndex);
  253. }
  254. else
  255. {
  256. marker = new MarkerData(markerName);
  257. marker.firstFrameIndex = frameIndex;
  258. marker.minDepth = markerDepth;
  259. marker.maxDepth = markerDepth;
  260. marker.threads.Add(threadNameWithIndex);
  261. analysis.AddMarker(marker);
  262. markers.Add(markerName, marker);
  263. }
  264. marker.count += 1;
  265. marker.msTotal += ms;
  266. // Individual marker time (not total over frame)
  267. if (ms < marker.msMinIndividual)
  268. {
  269. marker.msMinIndividual = ms;
  270. marker.minIndividualFrameIndex = frameIndex;
  271. }
  272. if (ms > marker.msMaxIndividual)
  273. {
  274. marker.msMaxIndividual = ms;
  275. marker.maxIndividualFrameIndex = frameIndex;
  276. }
  277. // Record highest depth foun
  278. if (markerDepth < marker.minDepth)
  279. marker.minDepth = markerDepth;
  280. if (markerDepth > marker.maxDepth)
  281. marker.maxDepth = markerDepth;
  282. FrameTime frameTime;
  283. if (frameIndex != marker.lastFrame)
  284. {
  285. marker.presentOnFrameCount += 1;
  286. frameTime = new FrameTime(frameIndex, ms, 1);
  287. marker.frames.Add(frameTime);
  288. marker.lastFrame = frameIndex;
  289. }
  290. else
  291. {
  292. frameTime = marker.frames[marker.frames.Count - 1];
  293. frameTime = new FrameTime(frameTime.frameIndex, frameTime.ms + ms, frameTime.count + 1);
  294. marker.frames[marker.frames.Count - 1] = frameTime;
  295. }
  296. }
  297. if (include)
  298. thread.frames.Add(new ThreadFrameTime(frameIndex, msTimeOfMinDepthMarkers, msIdleTimeOfMinDepthMarkers));
  299. }
  300. }
  301. at++;
  302. m_Progress = (100 * at) / frameCount;
  303. }
  304. analysis.GetFrameSummary().totalMarkers = allMarkers.Count;
  305. analysis.Finalise(timeScaleMax, maxMarkerDepthFound);
  306. /*
  307. foreach (int frameIndex in selectionIndices)
  308. {
  309. int frameOffset = profileData.DisplayFrameToOffset(frameIndex);
  310. var frameData = profileData.GetFrame(frameOffset);
  311. foreach (var threadData in frameData.threads)
  312. {
  313. var threadNameWithIndex = profileData.GetThreadName(threadData);
  314. if (filterThreads && threadFilter != threadNameWithIndex)
  315. continue;
  316. const bool enterChildren = true;
  317. foreach (var markerData in threadData.markers)
  318. {
  319. var markerName = markerData.name;
  320. var ms = markerData.msFrame;
  321. var markerDepth = markerData.depth;
  322. if (depthFilter != kDepthAll && markerDepth != depthFilter)
  323. continue;
  324. MarkerData marker = markers[markerName];
  325. bucketIndex = (range > 0) ? (int)(((marker.buckets.Length-1) * (ms - first)) / range) : 0;
  326. if (bucketIndex<0 || bucketIndex > (marker.buckets.Length - 1))
  327. {
  328. // This can happen if a single marker range is longer than the frame start end (which could occur if running on a separate thread)
  329. // Debug.Log(string.Format("Marker {0} : {1}ms exceeds range {2}-{3} on frame {4}", marker.name, ms, first, last, frameIndex));
  330. if (bucketIndex > (marker.buckets.Length - 1))
  331. bucketIndex = (marker.buckets.Length - 1);
  332. else
  333. bucketIndex = 0;
  334. }
  335. marker.individualBuckets[bucketIndex] += 1;
  336. }
  337. }
  338. }
  339. */
  340. m_Progress = 100;
  341. return analysis;
  342. }
  343. public int GetProgress()
  344. {
  345. return m_Progress;
  346. }
  347. }
  348. }