ProfileData.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760
  1. using System;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEditorInternal;
  5. using System.IO;
  6. using System.Runtime.Serialization.Formatters.Binary;
  7. using System.Text.RegularExpressions;
  8. namespace UnityEditor.Performance.ProfileAnalyzer
  9. {
  10. [Serializable]
  11. internal class ProfileData
  12. {
  13. static int latestVersion = 7;
  14. /*
  15. Version 1 - Initial version. Thread names index:threadName (Some invalid thread names count:threadName index)
  16. Version 2 - Added frame start time.
  17. Version 3 - Saved out marker children times in the data (Never needed so rapidly skipped)
  18. Version 4 - Removed the child times again (at this point data was saved with 1 less frame at start and end)
  19. 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)
  20. Version 6 - fixed msStartTime (previously was 'seconds')
  21. Version 7 - Data now only skips the frame at the end
  22. */
  23. static Regex trailingDigit = new Regex(@"^(.*[^\s])[\s]+([\d]+)$", RegexOptions.Compiled);
  24. public int Version { get; private set; }
  25. public int FrameIndexOffset { get; private set; }
  26. public bool FirstFrameIncomplete;
  27. public bool LastFrameIncomplete;
  28. List<ProfileFrame> frames = new List<ProfileFrame>();
  29. List<string> markerNames = new List<string>();
  30. List<string> threadNames = new List<string>();
  31. Dictionary<string, int> markerNamesDict = new Dictionary<string, int>();
  32. Dictionary<string, int> threadNameDict = new Dictionary<string, int>();
  33. public string FilePath { get; private set; }
  34. static float s_Progress = 0;
  35. public ProfileData()
  36. {
  37. FrameIndexOffset = 0;
  38. FilePath = string.Empty;
  39. Version = latestVersion;
  40. }
  41. public ProfileData(string filename)
  42. {
  43. FrameIndexOffset = 0;
  44. FilePath = filename;
  45. Version = latestVersion;
  46. }
  47. void Read()
  48. {
  49. if (string.IsNullOrEmpty(FilePath))
  50. throw new Exception("File path is invalid");
  51. using (var reader = new BinaryReader(File.Open(FilePath, FileMode.Open)))
  52. {
  53. s_Progress = 0;
  54. Version = reader.ReadInt32();
  55. if (Version < 0 || Version > latestVersion)
  56. {
  57. throw new Exception(String.Format("File version unsupported: {0} != {1} expected, at path: {2}", Version, latestVersion, FilePath));
  58. }
  59. FrameIndexOffset = reader.ReadInt32();
  60. int frameCount = reader.ReadInt32();
  61. frames.Clear();
  62. for (int frame = 0; frame < frameCount; frame++)
  63. {
  64. frames.Add(new ProfileFrame(reader, Version));
  65. s_Progress = (float)frame / frameCount;
  66. }
  67. int markerCount = reader.ReadInt32();
  68. markerNames.Clear();
  69. for (int marker = 0; marker < markerCount; marker++)
  70. {
  71. markerNames.Add(reader.ReadString());
  72. s_Progress = (float)marker / markerCount;
  73. }
  74. int threadCount = reader.ReadInt32();
  75. threadNames.Clear();
  76. for (int thread = 0; thread < threadCount; thread++)
  77. {
  78. var threadNameWithIndex = reader.ReadString();
  79. threadNameWithIndex = CorrectThreadName(threadNameWithIndex);
  80. threadNames.Add(threadNameWithIndex);
  81. s_Progress = (float)thread / threadCount;
  82. }
  83. }
  84. }
  85. internal void DeleteTmpFiles()
  86. {
  87. if (ProfileAnalyzerWindow.FileInTempDir(FilePath))
  88. File.Delete(FilePath);
  89. }
  90. bool IsFrameSame(int frameIndex, ProfileData other)
  91. {
  92. ProfileFrame thisFrame = GetFrame(frameIndex);
  93. ProfileFrame otherFrame = other.GetFrame(frameIndex);
  94. return thisFrame.IsSame(otherFrame);
  95. }
  96. public bool IsSame(ProfileData other)
  97. {
  98. if (other == null)
  99. return false;
  100. int frameCount = GetFrameCount();
  101. if (frameCount != other.GetFrameCount())
  102. {
  103. // Frame counts differ
  104. return false;
  105. }
  106. if (frameCount == 0)
  107. {
  108. // Both empty
  109. return true;
  110. }
  111. if (!IsFrameSame(0, other))
  112. return false;
  113. if (!IsFrameSame(frameCount - 1, other))
  114. return false;
  115. // Close enough if same number of frames and first/last have exactly the same frame time and time offset.
  116. // If we see false matches we could add a full has of the data on load/pull
  117. return true;
  118. }
  119. static public string ThreadNameWithIndex(int index, string threadName)
  120. {
  121. return string.Format("{0}:{1}", index, threadName);
  122. }
  123. public void SetFrameIndexOffset(int offset)
  124. {
  125. FrameIndexOffset = offset;
  126. }
  127. public int GetFrameCount()
  128. {
  129. return frames.Count;
  130. }
  131. public ProfileFrame GetFrame(int offset)
  132. {
  133. if (offset < 0 || offset >= frames.Count)
  134. return null;
  135. return frames[offset];
  136. }
  137. public List<string> GetMarkerNames()
  138. {
  139. return markerNames;
  140. }
  141. public List<string> GetThreadNames()
  142. {
  143. return threadNames;
  144. }
  145. public int GetThreadCount()
  146. {
  147. return threadNames.Count;
  148. }
  149. public int OffsetToDisplayFrame(int offset)
  150. {
  151. return offset + (1 + FrameIndexOffset);
  152. }
  153. public int DisplayFrameToOffset(int displayFrame)
  154. {
  155. return displayFrame - (1 + FrameIndexOffset);
  156. }
  157. public void AddThreadName(string threadName, ProfileThread thread)
  158. {
  159. threadName = CorrectThreadName(threadName);
  160. int index = -1;
  161. if (!threadNameDict.TryGetValue(threadName, out index))
  162. {
  163. threadNames.Add(threadName);
  164. index = threadNames.Count - 1;
  165. threadNameDict.Add(threadName, index);
  166. }
  167. thread.threadIndex = index;
  168. }
  169. public void AddMarkerName(string markerName, ProfileMarker marker)
  170. {
  171. int index = -1;
  172. if (!markerNamesDict.TryGetValue(markerName, out index))
  173. {
  174. markerNames.Add(markerName);
  175. index = markerNames.Count - 1;
  176. markerNamesDict.Add(markerName, index);
  177. }
  178. marker.nameIndex = index;
  179. }
  180. public string GetThreadName(ProfileThread thread)
  181. {
  182. return threadNames[thread.threadIndex];
  183. }
  184. public string GetMarkerName(ProfileMarker marker)
  185. {
  186. return markerNames[marker.nameIndex];
  187. }
  188. public int GetMarkerIndex(string markerName)
  189. {
  190. for (int nameIndex = 0; nameIndex < markerNames.Count; ++nameIndex)
  191. {
  192. if (markerName == markerNames[nameIndex])
  193. return nameIndex;
  194. }
  195. return -1;
  196. }
  197. public void Add(ProfileFrame frame)
  198. {
  199. frames.Add(frame);
  200. }
  201. void WriteInternal(string filepath)
  202. {
  203. using (var writer = new BinaryWriter(File.Open(filepath, FileMode.OpenOrCreate)))
  204. {
  205. Version = latestVersion;
  206. writer.Write(Version);
  207. writer.Write(FrameIndexOffset);
  208. writer.Write(frames.Count);
  209. foreach (var frame in frames)
  210. {
  211. frame.Write(writer);
  212. }
  213. writer.Write(markerNames.Count);
  214. foreach (var markerName in markerNames)
  215. {
  216. writer.Write(markerName);
  217. }
  218. writer.Write(threadNames.Count);
  219. foreach (var threadName in threadNames)
  220. {
  221. writer.Write(threadName);
  222. }
  223. }
  224. }
  225. internal void Write()
  226. {
  227. //ensure that we can always write to the temp location at least
  228. if (string.IsNullOrEmpty(FilePath))
  229. FilePath = ProfileAnalyzerWindow.TmpPath;
  230. WriteInternal(FilePath);
  231. }
  232. internal void WriteTo(string path)
  233. {
  234. //no point in trying to save on top of ourselves
  235. if (path == FilePath)
  236. return;
  237. if (!string.IsNullOrEmpty(FilePath) && File.Exists(FilePath))
  238. {
  239. if (File.Exists(path))
  240. File.Delete(path);
  241. File.Copy(FilePath, path);
  242. }
  243. else
  244. {
  245. WriteInternal(path);
  246. }
  247. FilePath = path;
  248. }
  249. public static string CorrectThreadName(string threadNameWithIndex)
  250. {
  251. var info = threadNameWithIndex.Split(':');
  252. if (info.Length >= 2)
  253. {
  254. string threadGroupIndexString = info[0];
  255. string threadName = info[1];
  256. if (threadName.Trim() == "")
  257. {
  258. // Scan seen with no thread name
  259. threadNameWithIndex = string.Format("{0}:[Unknown]", threadGroupIndexString);
  260. }
  261. else
  262. {
  263. // Some scans have thread names such as
  264. // "1:Worker Thread 0"
  265. // "1:Worker Thread 1"
  266. // rather than
  267. // "1:Worker Thread"
  268. // "2:Worker Thread"
  269. // Update to the second format so the 'All' case is correctly determined
  270. Match m = trailingDigit.Match(threadName);
  271. if (m.Success)
  272. {
  273. string threadNamePrefix = m.Groups[1].Value;
  274. int threadGroupIndex = 1 + int.Parse(m.Groups[2].Value);
  275. threadNameWithIndex = string.Format("{0}:{1}", threadGroupIndex, threadNamePrefix);
  276. }
  277. }
  278. }
  279. threadNameWithIndex = threadNameWithIndex.Trim();
  280. return threadNameWithIndex;
  281. }
  282. public static string GetThreadNameWithGroup(string threadName, string groupName)
  283. {
  284. if (string.IsNullOrEmpty(groupName))
  285. return threadName;
  286. return string.Format("{0}.{1}", groupName, threadName);
  287. }
  288. public static string GetThreadNameWithoutGroup(string threadNameWithGroup, out string groupName)
  289. {
  290. string[] tokens = threadNameWithGroup.Split('.');
  291. if (tokens.Length <= 1)
  292. {
  293. groupName = "";
  294. return tokens[0];
  295. }
  296. groupName = tokens[0];
  297. return tokens[1].TrimStart();
  298. }
  299. internal bool HasFrames
  300. {
  301. get
  302. {
  303. return frames != null && frames.Count > 0;
  304. }
  305. }
  306. internal bool HasThreads
  307. {
  308. get
  309. {
  310. return frames[0].threads != null && frames[0].threads.Count > 0;
  311. }
  312. }
  313. internal bool NeedsMarkerRebuild
  314. {
  315. get
  316. {
  317. if (frames.Count > 0 && frames[0].threads.Count > 0)
  318. return frames[0].threads[0].markers.Count != frames[0].threads[0].markerCount;
  319. return false;
  320. }
  321. }
  322. public static bool Save(string filename, ProfileData data)
  323. {
  324. if (data == null)
  325. return false;
  326. if (string.IsNullOrEmpty(filename))
  327. return false;
  328. if (filename.EndsWith(".json"))
  329. {
  330. var json = JsonUtility.ToJson(data);
  331. File.WriteAllText(filename, json);
  332. }
  333. else if (filename.EndsWith(".padata"))
  334. {
  335. FileStream stream = File.Create(filename);
  336. var formatter = new BinaryFormatter();
  337. formatter.Serialize(stream, data);
  338. stream.Close();
  339. }
  340. else if (filename.EndsWith(".pdata"))
  341. {
  342. data.WriteTo(filename);
  343. }
  344. return true;
  345. }
  346. public static bool Load(string filename, out ProfileData data)
  347. {
  348. if (filename.EndsWith(".json"))
  349. {
  350. string json = File.ReadAllText(filename);
  351. data = JsonUtility.FromJson<ProfileData>(json);
  352. }
  353. else if (filename.EndsWith(".padata"))
  354. {
  355. FileStream stream = File.OpenRead(filename);
  356. var formatter = new BinaryFormatter();
  357. data = (ProfileData)formatter.Deserialize(stream);
  358. stream.Close();
  359. if (data.Version != latestVersion)
  360. {
  361. Debug.Log(String.Format("Unable to load file. Incorrect file version in {0} : (file {1} != {2} expected", filename, data.Version, latestVersion));
  362. data = null;
  363. return false;
  364. }
  365. }
  366. else if (filename.EndsWith(".pdata"))
  367. {
  368. if (!File.Exists(filename))
  369. {
  370. data = null;
  371. return false;
  372. }
  373. try
  374. {
  375. data = new ProfileData(filename);
  376. data.Read();
  377. }
  378. catch (Exception e)
  379. {
  380. var message = e.Message;
  381. if (!string.IsNullOrEmpty(message))
  382. Debug.Log(e.Message);
  383. data = null;
  384. return false;
  385. }
  386. }
  387. else
  388. {
  389. string errorMessage;
  390. if (filename.EndsWith(".data"))
  391. {
  392. 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.";
  393. }
  394. else
  395. {
  396. errorMessage = string.Format("Unable to load file. Unsupported file format: '{0}'.", Path.GetExtension(filename));
  397. }
  398. Debug.Log(errorMessage);
  399. data = null;
  400. return false;
  401. }
  402. data.Finalise();
  403. return true;
  404. }
  405. void PushMarker(Stack<ProfileMarker> markerStack, ProfileMarker markerData)
  406. {
  407. Debug.Assert(markerData.depth == markerStack.Count + 1);
  408. markerStack.Push(markerData);
  409. }
  410. ProfileMarker PopMarkerAndRecordTimeInParent(Stack<ProfileMarker> markerStack)
  411. {
  412. ProfileMarker child = markerStack.Pop();
  413. ProfileMarker parentMarker = (markerStack.Count > 0) ? markerStack.Peek() : null;
  414. // Record the last markers time in its parent
  415. if (parentMarker != null)
  416. parentMarker.msChildren += child.msMarkerTotal;
  417. return parentMarker;
  418. }
  419. public void Finalise()
  420. {
  421. CalculateMarkerChildTimes();
  422. markerNamesDict.Clear();
  423. }
  424. void CalculateMarkerChildTimes()
  425. {
  426. var markerStack = new Stack<ProfileMarker>();
  427. for (int frameOffset = 0; frameOffset <= frames.Count; ++frameOffset)
  428. {
  429. var frameData = GetFrame(frameOffset);
  430. if (frameData == null)
  431. continue;
  432. for (int threadIndex = 0; threadIndex < frameData.threads.Count; threadIndex++)
  433. {
  434. var threadData = frameData.threads[threadIndex];
  435. // The markers are in depth first order and the depth is known
  436. // So we can infer a parent child relationship
  437. // Zero them first
  438. foreach (ProfileMarker markerData in threadData.markers)
  439. {
  440. markerData.msChildren = 0.0f;
  441. }
  442. // Update the child times
  443. markerStack.Clear();
  444. foreach (ProfileMarker markerData in threadData.markers)
  445. {
  446. int depth = markerData.depth;
  447. // Update depth stack and record child times in the parent
  448. if (depth >= markerStack.Count)
  449. {
  450. // If at same level then remove the last item at this level
  451. if (depth == markerStack.Count)
  452. {
  453. PopMarkerAndRecordTimeInParent(markerStack);
  454. }
  455. // Assume we can't move down depth without markers between levels.
  456. }
  457. else if (depth < markerStack.Count)
  458. {
  459. // We can move up depth several layers so need to pop off all those markers
  460. while (markerStack.Count >= depth)
  461. {
  462. PopMarkerAndRecordTimeInParent(markerStack);
  463. }
  464. }
  465. PushMarker(markerStack, markerData);
  466. }
  467. }
  468. }
  469. }
  470. public static float GetLoadingProgress()
  471. {
  472. return s_Progress;
  473. }
  474. }
  475. [Serializable]
  476. internal class ProfileFrame
  477. {
  478. public List<ProfileThread> threads = new List<ProfileThread>();
  479. public double msStartTime;
  480. public float msFrame;
  481. public ProfileFrame()
  482. {
  483. msStartTime = 0.0;
  484. msFrame = 0f;
  485. }
  486. public bool IsSame(ProfileFrame otherFrame)
  487. {
  488. if (msStartTime != otherFrame.msStartTime)
  489. return false;
  490. if (msFrame != otherFrame.msFrame)
  491. return false;
  492. if (threads.Count != otherFrame.threads.Count)
  493. return false;
  494. // Close enough.
  495. return true;
  496. }
  497. public void Add(ProfileThread thread)
  498. {
  499. threads.Add(thread);
  500. }
  501. public void Write(BinaryWriter writer)
  502. {
  503. writer.Write(msStartTime);
  504. writer.Write(msFrame);
  505. writer.Write(threads.Count);
  506. foreach (var thread in threads)
  507. {
  508. thread.Write(writer);
  509. }
  510. ;
  511. }
  512. public ProfileFrame(BinaryReader reader, int fileVersion)
  513. {
  514. if (fileVersion > 1)
  515. {
  516. if (fileVersion >= 6)
  517. {
  518. msStartTime = reader.ReadDouble();
  519. }
  520. else
  521. {
  522. double sStartTime = reader.ReadDouble();
  523. msStartTime = sStartTime * 1000.0;
  524. }
  525. }
  526. msFrame = reader.ReadSingle();
  527. int threadCount = reader.ReadInt32();
  528. threads.Clear();
  529. for (int thread = 0; thread < threadCount; thread++)
  530. {
  531. threads.Add(new ProfileThread(reader, fileVersion));
  532. }
  533. }
  534. }
  535. [Serializable]
  536. internal class ProfileThread
  537. {
  538. [NonSerialized]
  539. public List<ProfileMarker> markers = new List<ProfileMarker>();
  540. public int threadIndex;
  541. public long streamPos;
  542. public int markerCount = 0;
  543. public int fileVersion;
  544. public ProfileThread()
  545. {
  546. }
  547. public void Write(BinaryWriter writer)
  548. {
  549. writer.Write(threadIndex);
  550. writer.Write(markers.Count);
  551. foreach (var marker in markers)
  552. {
  553. marker.Write(writer);
  554. }
  555. ;
  556. }
  557. public ProfileThread(BinaryReader reader, int fileversion)
  558. {
  559. streamPos = reader.BaseStream.Position;
  560. fileVersion = fileversion;
  561. threadIndex = reader.ReadInt32();
  562. markerCount = reader.ReadInt32();
  563. markers.Clear();
  564. for (int marker = 0; marker < markerCount; marker++)
  565. {
  566. markers.Add(new ProfileMarker(reader, fileVersion));
  567. }
  568. }
  569. public bool ReadMarkers(string path)
  570. {
  571. if (streamPos == 0)
  572. return false; // the stream positions havent been written yet.
  573. var stream = File.OpenRead(path);
  574. BinaryReader br = new BinaryReader(stream);
  575. br.BaseStream.Position = streamPos;
  576. threadIndex = br.ReadInt32();
  577. markerCount = br.ReadInt32();
  578. markers.Clear();
  579. for (int marker = 0; marker < markerCount; marker++)
  580. {
  581. markers.Add(new ProfileMarker(br, fileVersion));
  582. }
  583. br.Close();
  584. return true;
  585. }
  586. public void AddMarker(ProfileMarker markerData)
  587. {
  588. markers.Add(markerData);
  589. markerCount++;
  590. }
  591. public void RebuildMarkers(string path)
  592. {
  593. if (!File.Exists(path)) return;
  594. FileStream stream = File.OpenRead(path);
  595. using (var reader = new BinaryReader(stream))
  596. {
  597. reader.BaseStream.Position = streamPos;
  598. threadIndex = reader.ReadInt32();
  599. markerCount = reader.ReadInt32();
  600. markers.Clear();
  601. for (int marker = 0; marker < markerCount; marker++)
  602. {
  603. markers.Add(new ProfileMarker(reader, fileVersion));
  604. }
  605. }
  606. }
  607. }
  608. [Serializable]
  609. internal class ProfileMarker
  610. {
  611. public int nameIndex;
  612. public float msMarkerTotal;
  613. public int depth;
  614. [NonSerialized]
  615. public float msChildren; // Recalculated on load so not saved in file
  616. public ProfileMarker()
  617. {
  618. }
  619. public static ProfileMarker Create(float durationMS, int depth)
  620. {
  621. var item = new ProfileMarker
  622. {
  623. msMarkerTotal = durationMS,
  624. depth = depth,
  625. msChildren = 0.0f
  626. };
  627. return item;
  628. }
  629. public static ProfileMarker Create(ProfilerFrameDataIterator frameData)
  630. {
  631. return Create(frameData.durationMS, frameData.depth);
  632. }
  633. public void Write(BinaryWriter writer)
  634. {
  635. writer.Write(nameIndex);
  636. writer.Write(msMarkerTotal);
  637. writer.Write(depth);
  638. }
  639. public ProfileMarker(BinaryReader reader, int fileVersion)
  640. {
  641. nameIndex = reader.ReadInt32();
  642. msMarkerTotal = reader.ReadSingle();
  643. depth = reader.ReadInt32();
  644. if (fileVersion == 3) // In this version we saved the msChildren value but we don't need to as we now recalculate on load
  645. msChildren = reader.ReadSingle();
  646. else
  647. msChildren = 0.0f;
  648. }
  649. }
  650. }