ProfilerWindowInterface.cs 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903
  1. using UnityEditorInternal;
  2. using System.Reflection;
  3. using System;
  4. using System.Collections;
  5. using UnityEngine;
  6. using System.Collections.Generic;
  7. using System.IO;
  8. using UnityEngine.Profiling;
  9. #if UNITY_2020_1_OR_NEWER
  10. using UnityEditor.Profiling;
  11. #endif
  12. #if UNITY_2021_2_OR_NEWER
  13. using Unity.Profiling.Editor;
  14. // stub so that ProfilerWindow can be moved to this namespace in trunk without a need to change PA
  15. namespace Unity.Profiling.Editor {}
  16. #endif
  17. namespace UnityEditor.Performance.ProfileAnalyzer
  18. {
  19. internal class ProfilerWindowInterface
  20. {
  21. bool m_ProfilerWindowInitialized = false;
  22. const float k_NsToMs = 1000000;
  23. #if UNITY_2020_1_OR_NEWER
  24. bool s_UseRawIterator = true;
  25. #endif
  26. ProgressBarDisplay m_progressBar;
  27. [NonSerialized] bool m_SendingSelectionEventToProfilerWindowInProgress = false;
  28. [NonSerialized] int m_LastSelectedFrameInProfilerWindow = 0;
  29. #if UNITY_2021_1_OR_NEWER
  30. [NonSerialized] ProfilerWindow m_ProfilerWindow;
  31. [NonSerialized] IProfilerFrameTimeViewSampleSelectionController m_CpuProfilerModule;
  32. #else
  33. Type m_ProfilerWindowType;
  34. EditorWindow m_ProfilerWindow;
  35. FieldInfo m_CurrentFrameFieldInfo;
  36. FieldInfo m_TimeLineGUIFieldInfo;
  37. FieldInfo m_SelectedEntryFieldInfo;
  38. FieldInfo m_SelectedNameFieldInfo;
  39. FieldInfo m_SelectedTimeFieldInfo;
  40. FieldInfo m_SelectedDurationFieldInfo;
  41. FieldInfo m_SelectedInstanceIdFieldInfo;
  42. FieldInfo m_SelectedFrameIdFieldInfo;
  43. FieldInfo m_SelectedThreadIndexFieldInfo;
  44. FieldInfo m_SelectedNativeIndexFieldInfo;
  45. FieldInfo m_SelectedInstanceCountFieldInfo;
  46. FieldInfo m_SelectedInstanceCountForThreadFieldInfo;
  47. FieldInfo m_SelectedInstanceCountForFrameFieldInfo;
  48. FieldInfo m_SelectedMetaDataFieldInfo;
  49. FieldInfo m_SelectedThreadCountFieldInfo;
  50. FieldInfo m_SelectedCallstackInfoFieldInfo;
  51. MethodInfo m_GetProfilerModuleInfo;
  52. Type m_CPUProfilerModuleType;
  53. #endif
  54. public ProfilerWindowInterface(ProgressBarDisplay progressBar)
  55. {
  56. m_progressBar = progressBar;
  57. #if !UNITY_2021_1_OR_NEWER
  58. Assembly assem = typeof(Editor).Assembly;
  59. m_ProfilerWindowType = assem.GetType("UnityEditor.ProfilerWindow");
  60. m_CurrentFrameFieldInfo = m_ProfilerWindowType.GetField("m_CurrentFrame", BindingFlags.NonPublic | BindingFlags.Instance);
  61. m_TimeLineGUIFieldInfo = m_ProfilerWindowType.GetField("m_CPUTimelineGUI", BindingFlags.NonPublic | BindingFlags.Instance);
  62. if (m_TimeLineGUIFieldInfo == null)
  63. {
  64. // m_CPUTimelineGUI isn't present in 2019.3.0a8 onward
  65. m_GetProfilerModuleInfo = m_ProfilerWindowType.GetMethod("GetProfilerModule", BindingFlags.NonPublic | BindingFlags.Instance);
  66. if (m_GetProfilerModuleInfo == null)
  67. {
  68. Debug.Log("Unable to initialise link to Profiler Timeline, no GetProfilerModule found");
  69. }
  70. m_CPUProfilerModuleType = assem.GetType("UnityEditorInternal.Profiling.CPUProfilerModule");
  71. m_TimeLineGUIFieldInfo = m_CPUProfilerModuleType.GetField("m_TimelineGUI", BindingFlags.NonPublic | BindingFlags.Instance);
  72. if (m_TimeLineGUIFieldInfo == null)
  73. {
  74. Debug.Log("Unable to initialise link to Profiler Timeline");
  75. }
  76. }
  77. if (m_TimeLineGUIFieldInfo != null)
  78. m_SelectedEntryFieldInfo = m_TimeLineGUIFieldInfo.FieldType.GetField("m_SelectedEntry", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  79. if (m_SelectedEntryFieldInfo != null)
  80. {
  81. m_SelectedNameFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("name", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  82. m_SelectedTimeFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("time", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  83. m_SelectedDurationFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("duration", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  84. m_SelectedInstanceIdFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("instanceId", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  85. m_SelectedFrameIdFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("frameId", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  86. // confusingly this is called threadId but is the thread _index_
  87. m_SelectedThreadIndexFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("threadId", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  88. m_SelectedNativeIndexFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("nativeIndex", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  89. m_SelectedInstanceCountFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("instanceCount", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  90. m_SelectedInstanceCountForThreadFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("instanceCountForThread", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  91. m_SelectedInstanceCountForFrameFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("instanceCountForFrame", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  92. m_SelectedThreadCountFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("threadCount", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  93. m_SelectedMetaDataFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("metaData", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  94. m_SelectedCallstackInfoFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("callstackInfo", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  95. }
  96. #endif
  97. }
  98. public bool IsReady()
  99. {
  100. return m_ProfilerWindow != null && m_ProfilerWindowInitialized;
  101. }
  102. public void GetProfilerWindowHandle()
  103. {
  104. Profiler.BeginSample("GetProfilerWindowHandle");
  105. #if UNITY_2021_1_OR_NEWER
  106. if (m_CpuProfilerModule != null)
  107. {
  108. m_CpuProfilerModule.selectionChanged -= OnSelectionChangedInCpuProfilerModule;
  109. m_CpuProfilerModule = null;
  110. }
  111. var windows = Resources.FindObjectsOfTypeAll<ProfilerWindow>();
  112. if (windows != null && windows.Length > 0)
  113. m_ProfilerWindow = windows[0];
  114. if (m_ProfilerWindow != null)
  115. {
  116. #if UNITY_2021_2_OR_NEWER
  117. var cpuModuleIdentifier = ProfilerWindow.cpuModuleIdentifier;
  118. #else
  119. var cpuModuleIdentifier = ProfilerWindow.cpuModuleName;
  120. #endif
  121. m_CpuProfilerModule =
  122. m_ProfilerWindow.GetFrameTimeViewSampleSelectionController(cpuModuleIdentifier);
  123. m_CpuProfilerModule.selectionChanged -= OnSelectionChangedInCpuProfilerModule;
  124. m_CpuProfilerModule.selectionChanged += OnSelectionChangedInCpuProfilerModule;
  125. m_ProfilerWindow.Repaint();
  126. m_ProfilerWindowInitialized = false;
  127. // wait a frame for the Profiler to get Repainted
  128. EditorApplication.delayCall += () => m_ProfilerWindowInitialized = true;
  129. }
  130. #else
  131. UnityEngine.Object[] windows = Resources.FindObjectsOfTypeAll(m_ProfilerWindowType);
  132. if (windows != null && windows.Length > 0)
  133. m_ProfilerWindow = (EditorWindow)windows[0];
  134. m_ProfilerWindowInitialized = true;
  135. #endif
  136. Profiler.EndSample();
  137. }
  138. public void OpenProfilerOrUseExisting()
  139. {
  140. // Note we use existing if possible to fix a bug after domain reload
  141. // Where calling EditorWindow.GetWindow directly causes a second window to open
  142. if (m_ProfilerWindow == null)
  143. {
  144. #if UNITY_2021_1_OR_NEWER
  145. m_ProfilerWindow = EditorWindow.GetWindow<ProfilerWindow>();
  146. #if UNITY_2021_2_OR_NEWER
  147. var cpuModuleIdentifier = ProfilerWindow.cpuModuleIdentifier;
  148. #else
  149. var cpuModuleIdentifier = ProfilerWindow.cpuModuleName;
  150. #endif
  151. m_CpuProfilerModule = m_ProfilerWindow.GetFrameTimeViewSampleSelectionController(cpuModuleIdentifier);
  152. m_CpuProfilerModule.selectionChanged -= OnSelectionChangedInCpuProfilerModule;
  153. m_CpuProfilerModule.selectionChanged += OnSelectionChangedInCpuProfilerModule;
  154. #else
  155. // Create new
  156. m_ProfilerWindow = EditorWindow.GetWindow(m_ProfilerWindowType);
  157. #endif
  158. }
  159. }
  160. public bool GetFrameRangeFromProfiler(out int first, out int last)
  161. {
  162. if (m_ProfilerWindow != null)
  163. {
  164. first = 1 + ProfilerDriver.firstFrameIndex;
  165. last = 1 + ProfilerDriver.lastFrameIndex;
  166. #if !UNITY_2018_4_OR_NEWER
  167. //Prior to 18.4 we need to clip to the visible frames in the profile which indents 1 in from end
  168. //as the last frame is not visible and sometimes still being processed
  169. if (first < last)
  170. last--;
  171. #endif
  172. return true;
  173. }
  174. first = 1;
  175. last = 1;
  176. return false;
  177. }
  178. public void CloseProfiler()
  179. {
  180. if (m_ProfilerWindow != null)
  181. m_ProfilerWindow.Close();
  182. }
  183. #if !UNITY_2021_1_OR_NEWER
  184. object GetTimeLineGUI()
  185. {
  186. object timeLineGUI = null;
  187. if (m_CPUProfilerModuleType != null)
  188. {
  189. object[] parametersArray = new object[] { ProfilerArea.CPU };
  190. var getCPUProfilerModuleInfo = m_GetProfilerModuleInfo.MakeGenericMethod(m_CPUProfilerModuleType);
  191. var cpuModule = getCPUProfilerModuleInfo.Invoke(m_ProfilerWindow, parametersArray);
  192. timeLineGUI = m_TimeLineGUIFieldInfo.GetValue(cpuModule);
  193. }
  194. else if (m_TimeLineGUIFieldInfo != null)
  195. {
  196. timeLineGUI = m_TimeLineGUIFieldInfo.GetValue(m_ProfilerWindow);
  197. }
  198. return timeLineGUI;
  199. }
  200. #endif
  201. #if UNITY_2021_1_OR_NEWER
  202. private void OnSelectionChangedInCpuProfilerModule(IProfilerFrameTimeViewSampleSelectionController controller, ProfilerTimeSampleSelection selection)
  203. {
  204. if (controller == m_CpuProfilerModule && !m_SendingSelectionEventToProfilerWindowInProgress)
  205. {
  206. if (selection != null && selection.markerNamePath != null && selection.markerNamePath.Count > 0)
  207. {
  208. selectedMarkerChanged(selection.markerNamePath[selection.markerNamePath.Count - 1], selection.threadGroupName, selection.threadName);
  209. }
  210. }
  211. }
  212. #endif
  213. public event Action<string, string, string> selectedMarkerChanged = delegate {};
  214. public void PollProfilerWindowMarkerName()
  215. {
  216. #if !UNITY_2021_1_OR_NEWER
  217. if (m_ProfilerWindow != null)
  218. {
  219. var timeLineGUI = GetTimeLineGUI();
  220. if (timeLineGUI != null && m_SelectedEntryFieldInfo != null)
  221. {
  222. var selectedEntry = m_SelectedEntryFieldInfo.GetValue(timeLineGUI);
  223. if (selectedEntry != null && m_SelectedNameFieldInfo != null)
  224. {
  225. string threadGroupName = null;
  226. string threadName = null;
  227. #if UNITY_2020_1_OR_NEWER
  228. if (m_SelectedFrameIdFieldInfo != null && m_SelectedThreadIndexFieldInfo != null)
  229. {
  230. using (RawFrameDataView frameData = ProfilerDriver.GetRawFrameDataView((int)m_SelectedFrameIdFieldInfo.GetValue(selectedEntry), (int)m_SelectedThreadIndexFieldInfo.GetValue(selectedEntry)))
  231. {
  232. if (frameData != null && frameData.valid)
  233. {
  234. threadGroupName = frameData.threadGroupName;
  235. threadName = frameData.threadName;
  236. }
  237. }
  238. }
  239. #endif
  240. selectedMarkerChanged(m_SelectedNameFieldInfo.GetValue(selectedEntry).ToString(), threadGroupName, threadName);
  241. }
  242. }
  243. }
  244. #endif
  245. }
  246. public ProfileData PullFromProfiler(int firstFrameDisplayIndex, int lastFrameDisplayIndex)
  247. {
  248. Profiler.BeginSample("ProfilerWindowInterface.PullFromProfiler");
  249. bool recording = IsRecording();
  250. if (recording)
  251. StopRecording();
  252. int firstFrameIndex = Mathf.Max(firstFrameDisplayIndex - 1, 0);
  253. int lastFrameIndex = lastFrameDisplayIndex - 1;
  254. ProfileData profileData = GetData(firstFrameIndex, lastFrameIndex);
  255. if (recording)
  256. StartRecording();
  257. Profiler.EndSample();
  258. return profileData;
  259. }
  260. public int GetThreadCountForFrame(int frameIndex)
  261. {
  262. if (!IsReady())
  263. return 0;
  264. ProfilerFrameDataIterator frameData = new ProfilerFrameDataIterator();
  265. frameData.SetRoot(frameIndex, 0);
  266. return frameData.GetThreadCount(frameIndex);
  267. }
  268. public ProfileFrame GetProfileFrameForThread(int frameIndex, int threadIndex)
  269. {
  270. if (!IsReady())
  271. return null;
  272. var frame = new ProfileFrame();
  273. #if UNITY_2020_1_OR_NEWER
  274. using (RawFrameDataView frameData = ProfilerDriver.GetRawFrameDataView(frameIndex, threadIndex))
  275. {
  276. frame.msStartTime = frameData.frameStartTimeMs;
  277. frame.msFrame = frameData.frameTimeMs;
  278. }
  279. #else
  280. ProfilerFrameDataIterator frameData = new ProfilerFrameDataIterator();
  281. frameData.SetRoot(frameIndex, threadIndex);
  282. frame.msStartTime = 1000.0 * frameData.GetFrameStartS(frameIndex);
  283. frame.msFrame = frameData.frameTimeMS;
  284. #endif
  285. return frame;
  286. }
  287. #if UNITY_2020_1_OR_NEWER
  288. ProfileData GetDataRaw(ProfileData data, int firstFrameIndex, int lastFrameIndex)
  289. {
  290. bool firstError = true;
  291. data.SetFrameIndexOffset(firstFrameIndex);
  292. var depthStack = new Stack<int>();
  293. var threadNameCount = new Dictionary<string, int>();
  294. var markerIdToNameIndex = new Dictionary<int, int>();
  295. for (int frameIndex = firstFrameIndex; frameIndex <= lastFrameIndex; ++frameIndex)
  296. {
  297. m_progressBar.AdvanceProgressBar();
  298. int threadIndex = 0;
  299. threadNameCount.Clear();
  300. ProfileFrame frame = null;
  301. while (true)
  302. {
  303. using (RawFrameDataView frameData = ProfilerDriver.GetRawFrameDataView(frameIndex, threadIndex))
  304. {
  305. if (threadIndex == 0)
  306. {
  307. if ((frameIndex == firstFrameIndex || frameIndex == lastFrameIndex)
  308. && firstFrameIndex != lastFrameIndex && (!frameData.valid || frameData.frameTimeNs == 0))
  309. {
  310. // skip incomplete frames when they are at the beginning or end of the capture
  311. if (++frameIndex <= lastFrameIndex)
  312. {
  313. data.FirstFrameIncomplete = true;
  314. data.SetFrameIndexOffset(frameIndex);
  315. continue;
  316. }
  317. else
  318. {
  319. // break out entirely if this is the last frame
  320. data.LastFrameIncomplete = true;
  321. break;
  322. }
  323. }
  324. frame = new ProfileFrame();
  325. if (frameData.valid)
  326. {
  327. frame.msStartTime = frameData.frameStartTimeMs;
  328. frame.msFrame = frameData.frameTimeMs;
  329. }
  330. data.Add(frame);
  331. }
  332. if (!frameData.valid)
  333. break;
  334. string threadNameWithIndex = null;
  335. string threadName = frameData.threadName;
  336. if (threadName.Trim() == "")
  337. {
  338. Debug.Log(string.Format("Warning: Unnamed thread found on frame {0}. Corrupted data suspected, ignoring frame", frameIndex));
  339. threadIndex++;
  340. continue;
  341. }
  342. var groupName = frameData.threadGroupName;
  343. threadName = ProfileData.GetThreadNameWithGroup(threadName, groupName);
  344. int nameCount = 0;
  345. threadNameCount.TryGetValue(threadName, out nameCount);
  346. threadNameCount[threadName] = nameCount + 1;
  347. threadNameWithIndex = ProfileData.ThreadNameWithIndex(threadNameCount[threadName], threadName);
  348. var thread = new ProfileThread();
  349. data.AddThreadName(threadNameWithIndex, thread);
  350. frame.Add(thread);
  351. // The markers are in depth first order
  352. depthStack.Clear();
  353. // first sample is the thread name
  354. for (int i = 1; i < frameData.sampleCount; i++)
  355. {
  356. float durationMS = frameData.GetSampleTimeMs(i);
  357. int markerId = frameData.GetSampleMarkerId(i);
  358. if (durationMS < 0)
  359. {
  360. if (firstError)
  361. {
  362. int displayIndex = data.OffsetToDisplayFrame(frameIndex);
  363. string name = frameData.GetSampleName(i);
  364. Debug.LogFormat("Ignoring Invalid marker time found for {0} on frame {1} on thread {2} ({3} < 0)",
  365. name, displayIndex, threadName, durationMS);
  366. firstError = false;
  367. }
  368. }
  369. else
  370. {
  371. int depth = 1 + depthStack.Count;
  372. var markerData = ProfileMarker.Create(durationMS, depth);
  373. // Use name index directly if we have already stored this named marker before
  374. int nameIndex;
  375. if (markerIdToNameIndex.TryGetValue(markerId, out nameIndex))
  376. {
  377. markerData.nameIndex = nameIndex;
  378. }
  379. else
  380. {
  381. string name = frameData.GetSampleName(i);
  382. data.AddMarkerName(name, markerData);
  383. markerIdToNameIndex[markerId] = markerData.nameIndex;
  384. }
  385. thread.AddMarker(markerData);
  386. }
  387. int childrenCount = frameData.GetSampleChildrenCount(i);
  388. if (childrenCount > 0)
  389. {
  390. depthStack.Push(childrenCount);
  391. }
  392. else
  393. {
  394. while (depthStack.Count > 0)
  395. {
  396. int remainingChildren = depthStack.Pop();
  397. if (remainingChildren > 1)
  398. {
  399. depthStack.Push(remainingChildren - 1);
  400. break;
  401. }
  402. }
  403. }
  404. }
  405. }
  406. threadIndex++;
  407. }
  408. }
  409. data.Finalise();
  410. return data;
  411. }
  412. #endif
  413. ProfileData GetDataOriginal(ProfileData data, int firstFrameIndex, int lastFrameIndex)
  414. {
  415. ProfilerFrameDataIterator frameData = new ProfilerFrameDataIterator();
  416. bool firstError = true;
  417. data.SetFrameIndexOffset(firstFrameIndex);
  418. Dictionary<string, int> threadNameCount = new Dictionary<string, int>();
  419. for (int frameIndex = firstFrameIndex; frameIndex <= lastFrameIndex; ++frameIndex)
  420. {
  421. m_progressBar.AdvanceProgressBar();
  422. int threadCount = frameData.GetThreadCount(frameIndex);
  423. frameData.SetRoot(frameIndex, 0);
  424. var msFrame = frameData.frameTimeMS;
  425. if ((frameIndex == firstFrameIndex || frameIndex == lastFrameIndex)
  426. && firstFrameIndex != lastFrameIndex && msFrame == 0)
  427. {
  428. var nextFrame = frameIndex + 1;
  429. // skip incomplete frames when they are at the beginning or end of the capture
  430. if (nextFrame <= lastFrameIndex)
  431. {
  432. data.FirstFrameIncomplete = true;
  433. data.SetFrameIndexOffset(nextFrame);
  434. continue;
  435. }
  436. else
  437. {
  438. // break out entirely if this is the last frame
  439. data.LastFrameIncomplete = true;
  440. break;
  441. }
  442. }
  443. /*
  444. if (frameIndex == lastFrameIndex)
  445. {
  446. // Check if last frame appears to be invalid data
  447. float median;
  448. float mean;
  449. float standardDeviation;
  450. CalculateFrameTimeStats(data, out median, out mean, out standardDeviation);
  451. float execessiveDeviation = (3f * standardDeviation);
  452. if (msFrame > (median + execessiveDeviation))
  453. {
  454. Debug.LogFormat("Dropping last frame as it is significantly larger than the median of the rest of the data set {0} > {1} (median {2} + 3 * standard deviation {3})", msFrame, median + execessiveDeviation, median, standardDeviation);
  455. break;
  456. }
  457. if (msFrame < (median - execessiveDeviation))
  458. {
  459. Debug.LogFormat("Dropping last frame as it is significantly smaller than the median of the rest of the data set {0} < {1} (median {2} - 3 * standard deviation {3})", msFrame, median - execessiveDeviation, median, standardDeviation);
  460. break;
  461. }
  462. }
  463. */
  464. ProfileFrame frame = new ProfileFrame();
  465. frame.msStartTime = 1000.0 * frameData.GetFrameStartS(frameIndex);
  466. frame.msFrame = msFrame;
  467. data.Add(frame);
  468. threadNameCount.Clear();
  469. for (int threadIndex = 0; threadIndex < threadCount; ++threadIndex)
  470. {
  471. frameData.SetRoot(frameIndex, threadIndex);
  472. var threadName = frameData.GetThreadName();
  473. if (threadName.Trim() == "")
  474. {
  475. Debug.Log(string.Format("Warning: Unnamed thread found on frame {0}. Corrupted data suspected, ignoring frame", frameIndex));
  476. continue;
  477. }
  478. var groupName = frameData.GetGroupName();
  479. threadName = ProfileData.GetThreadNameWithGroup(threadName, groupName);
  480. ProfileThread thread = new ProfileThread();
  481. frame.Add(thread);
  482. int nameCount = 0;
  483. threadNameCount.TryGetValue(threadName, out nameCount);
  484. threadNameCount[threadName] = nameCount + 1;
  485. data.AddThreadName(ProfileData.ThreadNameWithIndex(threadNameCount[threadName], threadName), thread);
  486. const bool enterChildren = true;
  487. // The markers are in depth first order and the depth is known
  488. // So we can infer a parent child relationship
  489. while (frameData.Next(enterChildren))
  490. {
  491. if (frameData.durationMS < 0)
  492. {
  493. if (firstError)
  494. {
  495. int displayIndex = data.OffsetToDisplayFrame(frameIndex);
  496. Debug.LogFormat("Ignoring Invalid marker time found for {0} on frame {1} on thread {2} ({3} < 0) : Instance id : {4}",
  497. frameData.name, displayIndex, threadName, frameData.durationMS, frameData.instanceId);
  498. firstError = false;
  499. }
  500. continue;
  501. }
  502. var markerData = ProfileMarker.Create(frameData);
  503. data.AddMarkerName(frameData.name, markerData);
  504. thread.AddMarker(markerData);
  505. }
  506. }
  507. }
  508. data.Finalise();
  509. frameData.Dispose();
  510. return data;
  511. }
  512. ProfileData GetData(int firstFrameIndex, int lastFrameIndex)
  513. {
  514. ProfileData data = new ProfileData(ProfileAnalyzerWindow.TmpPath);
  515. #if UNITY_2020_1_OR_NEWER
  516. GetDataRaw(data, firstFrameIndex, lastFrameIndex);
  517. #else
  518. GetDataOriginal(data, firstFrameIndex, lastFrameIndex);
  519. #endif
  520. data.Write();
  521. return data;
  522. }
  523. #if UNITY_2020_1_OR_NEWER
  524. public float GetFrameTimeRaw(int frameIndex)
  525. {
  526. using (RawFrameDataView frameData = ProfilerDriver.GetRawFrameDataView(frameIndex, 0))
  527. {
  528. if (!frameData.valid)
  529. return 0f;
  530. return frameData.frameTimeMs;
  531. }
  532. }
  533. #endif
  534. public float GetFrameTime(int frameIndex)
  535. {
  536. #if UNITY_2020_1_OR_NEWER
  537. if (s_UseRawIterator)
  538. return GetFrameTimeRaw(frameIndex);
  539. #endif
  540. ProfilerFrameDataIterator frameData = new ProfilerFrameDataIterator();
  541. frameData.SetRoot(frameIndex, 0);
  542. float ms = frameData.frameTimeMS;
  543. frameData.Dispose();
  544. return ms;
  545. }
  546. struct ThreadIndexIterator
  547. {
  548. public ProfilerFrameDataIterator frameData;
  549. public int threadIndex;
  550. }
  551. IEnumerator<ThreadIndexIterator> GetNextThreadIndexFittingThreadFilters(int frameIndex, List<string> threadFilters)
  552. {
  553. ProfilerFrameDataIterator frameData = new ProfilerFrameDataIterator();
  554. int threadCount = frameData.GetThreadCount(frameIndex);
  555. Dictionary<string, int> threadNameCount = new Dictionary<string, int>();
  556. for (int threadIndex = 0; threadIndex < threadCount; ++threadIndex)
  557. {
  558. frameData.SetRoot(frameIndex, threadIndex);
  559. var threadName = frameData.GetThreadName();
  560. // Name here could be "Worker Thread 1"
  561. var groupName = frameData.GetGroupName();
  562. threadName = ProfileData.GetThreadNameWithGroup(threadName, groupName);
  563. int nameCount = 0;
  564. threadNameCount.TryGetValue(threadName, out nameCount);
  565. threadNameCount[threadName] = nameCount + 1;
  566. var threadNameWithIndex = ProfileData.ThreadNameWithIndex(threadNameCount[threadName], threadName);
  567. // To compare on the filter we need to remove the postfix on the thread name
  568. // "3:Worker Thread 0" -> "1:Worker Thread"
  569. // The index of the thread (0) is used +1 as a prefix
  570. // The preceding number (3) is the count of number of threads with this name
  571. // Unfortunately multiple threads can have the same name
  572. threadNameWithIndex = ProfileData.CorrectThreadName(threadNameWithIndex);
  573. if (threadFilters.Contains(threadNameWithIndex))
  574. {
  575. yield return new ThreadIndexIterator {frameData = frameData, threadIndex = threadIndex};
  576. }
  577. }
  578. frameData.Dispose();
  579. }
  580. bool GetMarkerInfo(string markerName, int frameIndex, List<string> threadFilters, out int outThreadIndex, out int outNativeIndex, out float time, out float duration, out int instanceId)
  581. {
  582. outThreadIndex = 0;
  583. outNativeIndex = 0;
  584. time = 0.0f;
  585. duration = 0.0f;
  586. instanceId = 0;
  587. bool found = false;
  588. var iterator = GetNextThreadIndexFittingThreadFilters(frameIndex, threadFilters);
  589. while (iterator.MoveNext())
  590. {
  591. const bool enterChildren = true;
  592. while (iterator.Current.frameData.Next(enterChildren))
  593. {
  594. if (iterator.Current.frameData.name == markerName)
  595. {
  596. time = iterator.Current.frameData.startTimeMS;
  597. duration = iterator.Current.frameData.durationMS;
  598. instanceId = iterator.Current.frameData.instanceId;
  599. outNativeIndex = iterator.Current.frameData.sampleId;
  600. outThreadIndex = iterator.Current.threadIndex;
  601. found = true;
  602. break;
  603. }
  604. }
  605. if (found)
  606. break;
  607. }
  608. return found;
  609. }
  610. public bool SetProfilerWindowMarkerName(string markerName, List<string> threadFilters)
  611. {
  612. m_SendingSelectionEventToProfilerWindowInProgress = true;
  613. if (m_ProfilerWindow == null)
  614. return false;
  615. #if UNITY_2021_1_OR_NEWER
  616. #if UNITY_2021_2_OR_NEWER
  617. var cpuModuleIdentifier = ProfilerWindow.cpuModuleIdentifier;
  618. var selectedModuleIdentifier = m_ProfilerWindow.selectedModuleIdentifier;
  619. #else
  620. var cpuModuleIdentifier = ProfilerWindow.cpuModuleName;
  621. var selectedModuleIdentifier = m_ProfilerWindow.selectedModuleName;
  622. #endif
  623. m_CpuProfilerModule = m_ProfilerWindow.GetFrameTimeViewSampleSelectionController(cpuModuleIdentifier);
  624. if (m_CpuProfilerModule != null && selectedModuleIdentifier == cpuModuleIdentifier
  625. && m_ProfilerWindow.firstAvailableFrameIndex >= 0)
  626. {
  627. // Read profiler data direct from profile to find time/duration
  628. int currentFrameIndex = (int)m_ProfilerWindow.selectedFrameIndex;
  629. var iterator = GetNextThreadIndexFittingThreadFilters(currentFrameIndex, threadFilters);
  630. while (iterator.MoveNext())
  631. {
  632. using (var rawFrameDataView = ProfilerDriver.GetRawFrameDataView(currentFrameIndex, iterator.Current.threadIndex))
  633. {
  634. if (m_CpuProfilerModule.SetSelection(currentFrameIndex,
  635. rawFrameDataView.threadGroupName, rawFrameDataView.threadName, markerName,
  636. threadId: rawFrameDataView.threadId))
  637. {
  638. m_ProfilerWindow.Repaint();
  639. m_SendingSelectionEventToProfilerWindowInProgress = false;
  640. return true; // setting the selection was successful, nothing more to do here.
  641. }
  642. }
  643. }
  644. // selection couldn't be found, so clear the current one to avoid confusion
  645. m_CpuProfilerModule.ClearSelection();
  646. m_ProfilerWindow.Repaint();
  647. }
  648. #else
  649. var timeLineGUI = GetTimeLineGUI();
  650. if (timeLineGUI == null)
  651. {
  652. m_SendingSelectionEventToProfilerWindowInProgress = false;
  653. return false;
  654. }
  655. if (m_SelectedEntryFieldInfo != null)
  656. {
  657. var selectedEntry = m_SelectedEntryFieldInfo.GetValue(timeLineGUI);
  658. if (selectedEntry != null)
  659. {
  660. // Read profiler data direct from profile to find time/duration
  661. int currentFrameIndex = (int)m_CurrentFrameFieldInfo.GetValue(m_ProfilerWindow);
  662. float time;
  663. float duration;
  664. int instanceId;
  665. int nativeIndex;
  666. int threadIndex;
  667. if (GetMarkerInfo(markerName, currentFrameIndex, threadFilters, out threadIndex, out nativeIndex, out time, out duration, out instanceId))
  668. {
  669. /*
  670. Debug.Log(string.Format("Setting profiler to {0} on {1} at frame {2} at {3}ms for {4}ms ({5})",
  671. markerName, currentFrameIndex, threadFilter, time, duration, instanceId));
  672. */
  673. if (m_SelectedNameFieldInfo != null)
  674. m_SelectedNameFieldInfo.SetValue(selectedEntry, markerName);
  675. if (m_SelectedTimeFieldInfo != null)
  676. m_SelectedTimeFieldInfo.SetValue(selectedEntry, time);
  677. if (m_SelectedDurationFieldInfo != null)
  678. m_SelectedDurationFieldInfo.SetValue(selectedEntry, duration);
  679. if (m_SelectedInstanceIdFieldInfo != null)
  680. m_SelectedInstanceIdFieldInfo.SetValue(selectedEntry, instanceId);
  681. if (m_SelectedFrameIdFieldInfo != null)
  682. m_SelectedFrameIdFieldInfo.SetValue(selectedEntry, currentFrameIndex);
  683. if (m_SelectedNativeIndexFieldInfo != null)
  684. m_SelectedNativeIndexFieldInfo.SetValue(selectedEntry, nativeIndex);
  685. if (m_SelectedThreadIndexFieldInfo != null)
  686. m_SelectedThreadIndexFieldInfo.SetValue(selectedEntry, threadIndex);
  687. // TODO : Update to fill in the total and number of instances.
  688. // For now we force Instance count to 1 to avoid the incorrect info showing.
  689. if (m_SelectedInstanceCountFieldInfo != null)
  690. m_SelectedInstanceCountFieldInfo.SetValue(selectedEntry, 1);
  691. if (m_SelectedInstanceCountForThreadFieldInfo != null)
  692. m_SelectedInstanceCountForThreadFieldInfo.SetValue(selectedEntry, 1);
  693. if (m_SelectedInstanceCountForFrameFieldInfo != null)
  694. m_SelectedInstanceCountForFrameFieldInfo.SetValue(selectedEntry, 1);
  695. if (m_SelectedThreadCountFieldInfo != null)
  696. m_SelectedThreadCountFieldInfo.SetValue(selectedEntry, 1);
  697. if (m_SelectedMetaDataFieldInfo != null)
  698. m_SelectedMetaDataFieldInfo.SetValue(selectedEntry, "");
  699. if (m_SelectedCallstackInfoFieldInfo != null)
  700. m_SelectedCallstackInfoFieldInfo.SetValue(selectedEntry, "");
  701. m_ProfilerWindow.Repaint();
  702. m_SendingSelectionEventToProfilerWindowInProgress = false;
  703. return true;
  704. }
  705. }
  706. }
  707. #endif
  708. m_SendingSelectionEventToProfilerWindowInProgress = false;
  709. return false;
  710. }
  711. public bool JumpToFrame(int index)
  712. {
  713. if (m_ProfilerWindow == null)
  714. return false;
  715. if (index - 1 < ProfilerDriver.firstFrameIndex)
  716. return false;
  717. if (index - 1 > ProfilerDriver.lastFrameIndex)
  718. return false;
  719. #if UNITY_2021_1_OR_NEWER
  720. m_ProfilerWindow.selectedFrameIndex = index - 1;
  721. #else
  722. m_CurrentFrameFieldInfo.SetValue(m_ProfilerWindow, index - 1);
  723. #endif
  724. m_ProfilerWindow.Repaint();
  725. return true;
  726. }
  727. public int selectedFrame
  728. {
  729. get
  730. {
  731. if (m_ProfilerWindow == null)
  732. return 0;
  733. #if UNITY_2021_1_OR_NEWER
  734. return (int)m_ProfilerWindow.selectedFrameIndex + 1;
  735. #else
  736. return (int)m_CurrentFrameFieldInfo.GetValue(m_ProfilerWindow) + 1;
  737. #endif
  738. }
  739. }
  740. public event Action<int> selectedFrameChanged = delegate {};
  741. public void PollSelectedFrameChanges()
  742. {
  743. var currentlySelectedFrame = selectedFrame;
  744. if (m_LastSelectedFrameInProfilerWindow != currentlySelectedFrame && !m_SendingSelectionEventToProfilerWindowInProgress)
  745. {
  746. m_LastSelectedFrameInProfilerWindow = currentlySelectedFrame;
  747. selectedFrameChanged(currentlySelectedFrame);
  748. }
  749. }
  750. public bool IsRecording()
  751. {
  752. #if UNITY_2017_4_OR_NEWER
  753. return ProfilerDriver.enabled;
  754. #else
  755. return false;
  756. #endif
  757. }
  758. public void StopRecording()
  759. {
  760. #if UNITY_2017_4_OR_NEWER
  761. // Stop recording first
  762. ProfilerDriver.enabled = false;
  763. #endif
  764. }
  765. public void StartRecording()
  766. {
  767. #if UNITY_2017_4_OR_NEWER
  768. // Stop recording first
  769. ProfilerDriver.enabled = true;
  770. #endif
  771. }
  772. public void OnDisable()
  773. {
  774. if (m_ProfilerWindow != null)
  775. {
  776. m_ProfilerWindow = null;
  777. }
  778. #if UNITY_2021_1_OR_NEWER
  779. if (m_CpuProfilerModule != null)
  780. {
  781. m_CpuProfilerModule.selectionChanged -= OnSelectionChangedInCpuProfilerModule;
  782. m_CpuProfilerModule = null;
  783. }
  784. #endif
  785. }
  786. }
  787. }