!B (Sandbox) (CE-21795) Importing models with multisubmaterials via fbx switches...
[CRYENGINE.git] / Code / Tools / Statoscope / Statoscope / FrameRecord.cs
blobbd69bf078b958a530f40903586780b22c4b0cfbe
1 // Copyright 2001-2019 Crytek GmbH / Crytek Group. All rights reserved.
3 using System;
4 using System.Collections.Generic;
5 using System.IO;
6 using System.Linq;
7 using System.Text.RegularExpressions;
8 using System.Threading;
10 namespace Statoscope
12 enum EFrameElementValueType
14 Float,
15 Int
18 enum EFrameElementType
20 None = 0,
21 Float,
22 Int,
23 String,
24 B64Texture,
25 Int64,
28 enum EEndian
30 LITTLE_ENDIAN = 0,
31 BIG_ENDIAN
34 class PerfStatFrameElement
36 public EFrameElementType m_type;
37 public string m_name;
38 public string m_units;
40 public PerfStatFrameElement(string type, string name, string units)
42 switch (type)
44 case "float":
45 m_type = EFrameElementType.Float;
46 break;
48 case "int":
49 m_type = EFrameElementType.Int;
50 break;
52 case "string":
53 m_type = EFrameElementType.String;
54 break;
56 case "b64texture":
57 m_type = EFrameElementType.B64Texture;
58 break;
61 m_name = name;
62 m_units = units;
65 public PerfStatFrameElement(EFrameElementType _type, string name, string units)
67 m_type = _type;
68 m_name = name;
69 m_units = units;
72 public static string GetRegexPatternFromFrameElementType(EFrameElementType type)
74 switch (type)
76 case EFrameElementType.Float:
77 case EFrameElementType.Int:
78 return @"-?\d+(\.\d+)?|1\.\#QNAN0";
80 case EFrameElementType.String:
81 return @"'.*?'";
83 case EFrameElementType.B64Texture:
84 return @"[A-Z|a-z|0-9|\+|\/]*";
87 return "";
90 public string GetRegexPattern()
92 return string.Format("(?<{0}>{1}) *", m_name, GetRegexPatternFromFrameElementType(m_type));
96 struct ProfileTotalData
98 public FrameRecord m_fr;
99 public List<string> m_paths;
100 public MTCounter m_profileTotalCounter;
102 public ProfileTotalData(FrameRecord fr, List<string> paths, MTCounter profileTotalCounter)
104 m_fr = fr;
105 m_paths = paths;
106 m_profileTotalCounter = profileTotalCounter;
110 public class Callstack
112 public string m_tag;
113 public List<string> m_functions;
115 public Callstack(string tag)
117 m_tag = tag;
118 m_functions = new List<string>();
122 public class FrameRecord
124 public readonly int Index; // the index into the frame records of the LogData that this belongs to
125 public readonly float FrameTimeInS; // when the frame started in seconds
126 public float FrameMidTimeInS; // half way between the start of this frame and the start of the next frame (or FrameTimeInS for the last frame)
127 public readonly MemoryStream ScreenshotImage;
129 public readonly IReadOnlyFRVs Values;
130 public readonly IEnumerable<UserMarkerLocation> UserMarkers;
131 public readonly IEnumerable<Callstack> Callstacks;
133 public FrameRecord(int index, float frameTimeInS, MemoryStream screenshotImage, IReadOnlyFRVs values, IEnumerable<UserMarker> userMarkers, IEnumerable<Callstack> callstacks)
135 Index = index;
136 FrameTimeInS = frameTimeInS;
137 FrameMidTimeInS = frameTimeInS;
138 ScreenshotImage = screenshotImage;
139 Values = values;
141 List<UserMarkerLocation> userMarkerLocs = new List<UserMarkerLocation>();
143 foreach (UserMarker userMarker in userMarkers)
145 UserMarkerLocation userMarkerLocation = new UserMarkerLocation(userMarker, this, userMarkerLocs.Count);
146 userMarkerLocs.Add(userMarkerLocation);
149 UserMarkers = userMarkerLocs;
151 Callstacks = callstacks;
154 public void CalculateFrameMidTime(FrameRecord nextFR)
156 FrameMidTimeInS = (this.FrameTimeInS + nextFR.FrameTimeInS) / 2.0f;
159 // checks if path matches pattern, delimitted by /, where a * in pattern matches any single item in path
160 // and a ** in pattern matches any number of / seperated items
161 // e.g. PathMatchesPattern("/a/b/c", "/a/b/c") == true
162 // PathMatchesPattern("/a/b/c", "/a/*/c") == true
163 // PathMatchesPattern("/a/b/c", "/*/c") == false
164 // PathMatchesPattern("/a/b/c", "/**/c") == true
165 public static bool PathMatchesPattern(string path, string pattern)
167 string[] pathLocations = path.PathLocations();
168 string[] patternLocations = pattern.PathLocations();
169 bool containsDoubleStar = false;
170 int iPattern = 0;
172 for (int iPath = 0; iPath < pathLocations.Length && iPattern < patternLocations.Length; iPath++)
174 if (patternLocations[iPattern] == "*")
176 iPattern++;
177 continue;
180 if (patternLocations[iPattern] == "**")
182 if (iPattern == patternLocations.Length - 1)
184 return true;
187 containsDoubleStar = true;
189 if (patternLocations[iPattern + 1] == pathLocations[iPath])
191 iPattern++;
194 continue;
197 if (patternLocations[iPattern] != pathLocations[iPath])
199 return false;
202 iPattern++;
205 return (containsDoubleStar && iPattern == patternLocations.Length) || (pathLocations.Length == patternLocations.Length);
208 // value paths
209 public IEnumerable<string> GetPathEnumerator(string pathToDo)
211 foreach (string path in Values.Paths)
213 if (PathMatchesPattern(path, pathToDo))
215 yield return path;
220 public float GetDisplayXValue(LogData logData, bool againstFrameTime)
222 if (againstFrameTime)
224 float startMidTime = logData.FrameRecords.First().FrameMidTimeInS;
225 return FrameMidTimeInS - startMidTime;
227 else
229 return logData.GetDisplayIndex(Index) + 0.5f;
234 public delegate float GetRecordValue(FrameRecord record);
235 delegate bool AcceptRecord(FrameRecord record);
236 public delegate IReadOnlyFRVs GetValuesFromIndex(int i);
238 class PerfStatLogLineDescriptor
240 public List<PerfStatFrameElement> m_formatElements = new List<PerfStatFrameElement>();
241 public string m_path;
242 public Regex m_regex;
244 public void CreateRegex()
246 string formatRegexPattern = @"\[";
248 foreach (PerfStatFrameElement fe in m_formatElements)
250 formatRegexPattern += fe.GetRegexPattern();
253 formatRegexPattern += @"\]";
255 m_regex = new Regex(formatRegexPattern);
259 class SocketLogData : LogData
261 List<LogView> LogViews = new List<LogView>();
262 SocketLogBinaryDataStream SocketDataStream;
263 public bool m_bTempFile = false;
264 public string m_logWriteFilename = "";
266 public SocketLogBinaryDataStream DataStream { set { SocketDataStream = value; } }
268 public SocketLogData(SocketSessionInfo session)
269 : base(session)
273 public void SaveToFile(string fileName)
275 File.Copy(m_logWriteFilename, fileName, true);
278 public override void AddFrameRecord(FrameRecord fr, List<KeyValuePair<string, EItemType>> newMetaDatas)
280 lock (this)
282 base.AddFrameRecord(fr, newMetaDatas);
283 ProcessRecords(fr.Index);
286 foreach (LogView logView in LogViews)
288 logView.m_logControl.NewRecordsAvailable();
291 //m_logControl.UpdateControls(fr.m_index); // change this to a listener setup? and also only do it for the views that have this log data
294 public void AddListeningLogView(LogView logView)
296 LogViews.Add(logView);
299 public void RemoveListeningLogView(LogView logView)
301 LogViews.Remove(logView);
303 if (LogViews.Count == 0)
305 // trigger the socket thread shutdown
306 SocketDataStream.CloseNetStream();
308 if (m_bTempFile && m_logWriteFilename != "")
310 File.Delete(m_logWriteFilename);
311 m_logWriteFilename = "";
314 for (int i = 0; (i < 10) && SocketDataStream.FileWriterNeedsClosing; i++)
316 Thread.Sleep(100);
322 public class FrameRecordRange
324 public readonly string Name; // this is only really used for the Confluence stuff now and ideally should be removed
325 public readonly int StartIdx;
326 public readonly int EndIdx;
328 public FrameRecordRange()
330 Name = "";
331 StartIdx = 0;
332 EndIdx = 0;
335 public FrameRecordRange(string name, int startIdx, int endIdx)
337 Name = name;
338 StartIdx = startIdx;
339 EndIdx = endIdx;
343 public class LogRange
345 public LogData m_logData;
346 public FrameRecordRange m_frr;
348 public LogRange(LogData logData, FrameRecordRange frr)
350 m_logData = logData;
351 m_frr = frr;
355 public class UserMarkerLocation
357 public string m_path;
358 public string m_name;
359 public FrameRecord m_fr;
360 public int m_index; // the index of this UserMarkerLocation in m_fr.UserMarkers
362 public UserMarkerLocation(UserMarker userMarker, FrameRecord fr, int index)
364 m_path = userMarker.Path;
365 m_name = userMarker.Name;
366 m_fr = fr;
367 m_index = index;
370 public string GetNameForLogView(LogView logView)
372 return logView.Index + "." + m_fr.Index + "." + m_index + " " + m_name;
375 static string oldLevelStartString = "level_start ";
376 static string newLevelStartString = "Start ";
378 bool IsOldLevelStart() { return m_name.StartsWith(oldLevelStartString); }
379 bool IsNewLevelStart() { return m_path == "/UserMarkers/Level" && m_name.StartsWith(newLevelStartString); }
380 public bool IsLevelStart() { return IsOldLevelStart() || IsNewLevelStart(); }
381 public bool IsLevelEnd() { return (m_name == "level_end") || ((m_path == "/UserMarkers/Level") && (m_name == "Start")); }
383 public string GetLevelName()
385 if (IsOldLevelStart())
387 return m_name.Substring(oldLevelStartString.Length);
389 else if (IsNewLevelStart())
391 return m_name.Substring(newLevelStartString.Length);
393 else
395 throw new Exception("Not a level user marker");
400 public class Stats
402 public float m_min;
403 public float m_max;
404 public float m_avg;
406 public Stats()
408 m_min = 9999999999999.0f;
409 m_max = 0.0f;
410 m_avg = 0.0f;
414 public class GroupItemStats
416 public GroupItemStats()
418 m_groupItemStats = new Dictionary<string, Stats>();
420 public Stats GetItemStats(string item)
422 return m_groupItemStats[item];
424 public bool ContainsItem(string item)
426 return m_groupItemStats.ContainsKey(item);
428 public Dictionary<string, Stats> m_groupItemStats;
431 public class GroupsStats
433 public GroupsStats()
435 m_groupStats = new Dictionary<string, GroupItemStats>();
437 public GroupItemStats GetGroupItemsStats(string group)
439 return m_groupStats[group];
441 public bool ContainsGroup(string group)
443 return m_groupStats.ContainsKey(group);
445 public Dictionary<string, GroupItemStats> m_groupStats;
448 public class ValueStats
450 public readonly bool m_bValueIsPerLogView;
451 public int m_numFrames;
452 public float m_min;
453 public float m_max;
454 public float m_avg;
455 double m_total;
457 public ValueStats(bool bValueIsPerLogView)
459 m_bValueIsPerLogView = bValueIsPerLogView;
460 Reset();
463 public void Reset()
465 m_numFrames = 0;
466 m_min = float.MaxValue;
467 m_max = float.MinValue;
468 m_avg = 0.0f;
469 m_total = 0.0;
472 public void Update(float value)
474 if (float.IsNaN(value))
476 return;
479 m_numFrames++;
481 if (value < m_min)
483 m_min = value;
486 if (value > m_max)
488 m_max = value;
491 m_total += value;
492 m_avg = (float)(m_total / m_numFrames);
496 public enum EItemType
498 NotSet,
499 Float,
503 public class ItemMetaData
505 public EItemType m_type;
507 public ItemMetaData(EItemType type)
509 m_type = type;
512 public override bool Equals(object obj)
514 if (obj == null || GetType() != obj.GetType())
516 return false;
519 ItemMetaData imd = (ItemMetaData)(obj);
520 return imd.m_type == m_type;
523 public override int GetHashCode()
525 return m_type.GetHashCode();
529 public partial class LogData
531 public readonly string Name;
532 public readonly List<FrameRecord> FrameRecords;
533 public readonly Dictionary<string, ItemMetaData> ItemMetaData; // things selectable in the treeview or possibly in bucket stats need meta data
534 public IEnumerable<FrameRecordRange> LevelRanges { get { return m_levelRanges; } }
535 public readonly FrameRecordRange FrameRecordRange;
536 public readonly BuildInfo BuildInfo; // only used for Confluence, which I'd like to remove; however, FFM may still use it
537 public readonly SessionInfo SessionInfo;
538 public IReadOnlyFRPC Paths { get { return m_paths; } }
540 public readonly IntervalTree IntervalTree = new IntervalTree();
542 readonly List<FrameRecordRange> m_levelRanges = new List<FrameRecordRange>();
543 readonly FrameRecordPathCollection m_paths;
545 public LogData(SessionInfo sessionInfo)
547 Name = sessionInfo.Summary;
548 FrameRecords = new List<FrameRecord>();
549 ItemMetaData = new Dictionary<string, ItemMetaData>();
550 FrameRecordRange = new FrameRecordRange();
551 BuildInfo = new BuildInfo(Name);
552 SessionInfo = sessionInfo;
553 m_paths = new FrameRecordPathCollection();
556 public LogData(LogData logData, FrameRecordRange frr)
558 int startIdx = frr.StartIdx;
559 int endIdx = frr.EndIdx;
560 int numFrames = endIdx - startIdx + 1;
562 Name = frr.Name;
563 FrameRecords = new List<FrameRecord>(logData.FrameRecords.GetRange(startIdx, numFrames));
564 ItemMetaData = new Dictionary<string, ItemMetaData>(logData.ItemMetaData); // this may not need to be copied, MT a problem though?
565 FrameRecordRange = new FrameRecordRange(Name, logData.FrameRecordRange.StartIdx + startIdx,
566 logData.FrameRecordRange.StartIdx + endIdx);
567 m_paths = logData.m_paths;
568 BuildInfo = logData.BuildInfo;
569 SessionInfo = logData.SessionInfo;
572 public FrameRecordValues CreateNewFrameRecordValues(FrameRecordValues values)
574 // make a new one based on m_paths
575 return new FrameRecordValues(m_paths, values);
578 public override string ToString()
580 return Name;
583 public virtual void AddFrameRecord(FrameRecord fr, List<KeyValuePair<string, EItemType>> newMetaDatas)
585 for (int i = 0, c = newMetaDatas.Count; i != c; ++ i)
587 SetItemMetaData(newMetaDatas[i].Key, newMetaDatas[i].Value);
590 FrameRecords.Add(fr);
593 public float LogStartInSeconds
597 if (FrameRecords.Count > 0)
599 return FrameRecords.First().FrameTimeInS;
602 return 0.0f;
606 public float LogEndInSeconds
610 if (FrameRecords.Count > 0)
612 return FrameRecords.Last().FrameTimeInS;
615 return 0.0f;
619 public int GetDisplayIndex(int idx)
621 return idx - FrameRecordRange.StartIdx;
624 public void ProcessRecords()
626 ProcessRecords(0);
627 CalculateLevelRanges();
630 public void ProcessRecords(int fromIdx)
632 ClipIncompleteFrames(fromIdx);
633 CalculateMidTimes(fromIdx);
634 CalculateBucketSets(fromIdx);
635 UpdateSessionInfo();
638 void UpdateSessionInfo()
640 float startTime = (FrameRecords.Count > 0) ? FrameRecords.First().FrameTimeInS : 0.0f;
641 float endTime = (FrameRecords.Count > 0) ? FrameRecords.Last().FrameTimeInS : 0.0f;
642 SessionInfo.Update(FrameRecords.Count, startTime, endTime);
645 protected void CalculateMidTimes(int fromIdx)
647 if (FrameRecords.Count == 0)
649 return;
652 for (int i = fromIdx; i < FrameRecords.Count - 1; i++)
654 FrameRecords[i].CalculateFrameMidTime(FrameRecords[i + 1]);
658 public float GetMovingAverage(int frame, int numFrames, string path)
660 float avgValue = 0.0f;
661 int startFrame = Math.Max(0, frame - numFrames);
662 int endFrame = Math.Min(frame + numFrames, FrameRecords.Count);
663 int numFramesPresent = 0;
665 for (int i = startFrame; i < endFrame; i++)
667 IReadOnlyFRVs frv = FrameRecords[i].Values;
669 if (frv.ContainsPath(path))
671 avgValue += frv[path];
672 numFramesPresent++;
676 return avgValue / numFramesPresent;
679 public float GetLocalMax(int frame, int numFrames, string path)
681 float maxValue = 0.0f;
682 int startFrame = Math.Max(0, frame - numFrames);
683 int endFrame = Math.Min(frame, FrameRecords.Count - 1);
685 for (int i = startFrame; i <= endFrame; i++)
687 IReadOnlyFRVs frv = FrameRecords[i].Values;
689 if (frv.ContainsPath(path))
691 maxValue = Math.Max(maxValue, frv[path]);
695 return maxValue;
698 protected void ClipIncompleteFrames(int fromIdx)
700 for (int i = fromIdx; i < FrameRecords.Count; i++)
702 FrameRecord fr = FrameRecords[i];
704 if (fr.Values.IsEmpty) //ContainsKey("/frameTimeInS"))
706 FrameRecords.RemoveRange(i, FrameRecords.Count - i);
707 break;
712 public FrameRecord FindClosestScreenshotFrameFromTime(float x)
714 return FrameRecords[FindClosestFrameIdxFromTime(x, true)];
717 public FrameRecord FindClosestScreenshotFrameFromFrameIdx(int idx)
719 int belowIdx;
721 for (belowIdx = idx; belowIdx >= 0; belowIdx--)
723 FrameRecord fr = FrameRecords[belowIdx];
725 if (fr.ScreenshotImage != null)
727 break;
731 int aboveIdx;
733 for (aboveIdx = idx; aboveIdx < FrameRecords.Count; aboveIdx++)
735 FrameRecord fr = FrameRecords[aboveIdx];
737 if (fr.ScreenshotImage != null)
739 break;
743 belowIdx = Math.Max(0, belowIdx);
744 aboveIdx = Math.Min(belowIdx, FrameRecords.Count);
746 int bestIdx = (idx - belowIdx) < (aboveIdx - idx) ? belowIdx : aboveIdx;
747 return FrameRecords[bestIdx];
750 public int FindClosestFrameIdxFromTime(float x)
752 return FindClosestFrameIdxFromTime(x, false);
755 public int FindClosestFrameIdxFromTime(float x, bool screenshotFrame)
757 int aboveIdx = 0;
758 int belowIdx = 0;
759 float startMidTime = FrameRecords.First().FrameMidTimeInS;
761 for (int i = 0; i < FrameRecords.Count - 1; i++)
763 FrameRecord fr = FrameRecords[i];
765 if (screenshotFrame && (fr.ScreenshotImage == null))
767 continue;
770 if (x < fr.FrameMidTimeInS - startMidTime)
772 aboveIdx = i;
773 break;
776 belowIdx = i;
779 aboveIdx = Math.Max(aboveIdx, belowIdx);
781 if (x > (FrameRecords[aboveIdx].FrameMidTimeInS - startMidTime + FrameRecords[belowIdx].FrameMidTimeInS - startMidTime) / 2.0f)
783 return aboveIdx;
785 else
787 return belowIdx;
791 public void GetFrameTimeData(int fromFrameIdx, int toFrameIdx, out float min, out float max, out float avg)
793 min = float.MaxValue;
794 max = 0.0f;
795 avg = 0.0f;
797 for (int i = fromFrameIdx; i <= toFrameIdx; i++)
799 FrameRecord fr = FrameRecords[i];
800 float frameLengthInMS = fr.Values["/frameLengthInMS"];
801 min = Math.Min(min, frameLengthInMS);
802 max = Math.Max(max, frameLengthInMS);
803 avg += frameLengthInMS;
806 avg /= (toFrameIdx - fromFrameIdx + 1);
809 public void GetFrameGroupData(int fromFrameIdx, int toFrameIdx, String[] groups, GroupsStats stats)
811 for (int i = fromFrameIdx; i <= toFrameIdx; i++)
813 FrameRecord fr = FrameRecords[i];
815 foreach (string key in fr.Values.Paths)
817 foreach (string group in groups)
819 if (key.Contains(group))
821 if (stats.m_groupStats.Count == 0 || !stats.ContainsGroup(group))
823 stats.m_groupStats.Add(group, new GroupItemStats());
826 GroupItemStats groupItemStats = stats.GetGroupItemsStats(group);
828 string itemKey = key.Substring(key.LastIndexOf('/') + 1);
830 if (!groupItemStats.ContainsItem(itemKey))
832 groupItemStats.m_groupItemStats.Add(itemKey, new Stats());
835 Stats itemStats = groupItemStats.GetItemStats(itemKey);
836 itemStats.m_min = Math.Min(itemStats.m_min, fr.Values[key]);
837 itemStats.m_max = Math.Max(itemStats.m_max, fr.Values[key]);
838 itemStats.m_avg += fr.Values[key];
844 foreach (string group in stats.m_groupStats.Keys)
846 foreach (string item in stats.GetGroupItemsStats(group).m_groupItemStats.Keys)
848 Stats itemStats = stats.GetGroupItemsStats(group).GetItemStats(item);
849 itemStats.m_avg /= (toFrameIdx - fromFrameIdx + 1);
854 public float GetPercentileValue(GetRecordValue grv, float percentile)
856 List<float> values = new List<float>();
858 foreach (FrameRecord fr in FrameRecords)
860 values.Add(grv(fr));
863 values.Sort();
865 return values[(int)((values.Count - 1) * percentile / 100.0f)];
868 public void CalculateLevelRanges()
870 FrameRecord startFR = null;
871 string levelName = "unknown_level_name";
873 foreach (FrameRecord fr in FrameRecords)
875 foreach (UserMarkerLocation userMarkerLoc in fr.UserMarkers)
877 if (userMarkerLoc.IsLevelStart())
879 if (startFR != null)
881 Console.WriteLine("Level start marker found (level name: '" + levelName + "') before previous end!");
884 startFR = fr;
885 levelName = userMarkerLoc.GetLevelName().Replace("/", " - "); // confluence doesn't like /
887 else if (userMarkerLoc.IsLevelEnd())
889 if (startFR != null)
891 m_levelRanges.Add(new FrameRecordRange(levelName, startFR.Index, fr.Index));
892 startFR = null;
894 else
896 Console.WriteLine("Level end marker, but no start!");
902 if (startFR != null)
904 // if there's a start with no end, use the end of the log as the range end
905 m_levelRanges.Add(new FrameRecordRange(levelName, startFR.Index, FrameRecords.Count - 1));
909 public void SetItemMetaData(string path, EItemType type)
911 if (ItemMetaData.ContainsKey(path))
913 if (ItemMetaData[path].m_type != type)
915 throw new Exception(string.Format("'{0}' type has changed: was {1}, now {2}", path, ItemMetaData[path].m_type, type));
918 else
920 ItemMetaData[path] = new ItemMetaData(type);
925 public class BuildInfo
927 public readonly string PlatformString;
928 public readonly string BuildNumberString;
930 public BuildInfo(string filename)
932 Regex regex = new Regex("perflog_(?<platform>\\w*)_(?<buildNum_3>[0-9]*)_(?<buildNum_2>[0-9]*)_(?<buildNum_1>[0-9]*)_(?<buildNum_0>[0-9]*).*.log");
933 Match match = regex.Match(filename);
935 if (match.Success)
937 PlatformString = match.Groups["platform"].Value;
938 BuildNumberString = match.Groups["buildNum_3"].Value + "." +
939 match.Groups["buildNum_2"].Value + "." +
940 match.Groups["buildNum_1"].Value + "." +
941 match.Groups["buildNum_0"].Value;
943 else
945 PlatformString = "<unknown platform>";
946 BuildNumberString = "<unknown build number>";