1 // Copyright 2001-2019 Crytek GmbH / Crytek Group. All rights reserved.
4 using System
.Collections
.Generic
;
7 using System
.Text
.RegularExpressions
;
8 using System
.Threading
;
12 enum EFrameElementValueType
18 enum EFrameElementType
34 class PerfStatFrameElement
36 public EFrameElementType m_type
;
38 public string m_units
;
40 public PerfStatFrameElement(string type
, string name
, string units
)
45 m_type
= EFrameElementType
.Float
;
49 m_type
= EFrameElementType
.Int
;
53 m_type
= EFrameElementType
.String
;
57 m_type
= EFrameElementType
.B64Texture
;
65 public PerfStatFrameElement(EFrameElementType _type
, string name
, string units
)
72 public static string GetRegexPatternFromFrameElementType(EFrameElementType type
)
76 case EFrameElementType
.Float
:
77 case EFrameElementType
.Int
:
78 return @"-?\d+(\.\d+)?|1\.\#QNAN0";
80 case EFrameElementType
.String
:
83 case EFrameElementType
.B64Texture
:
84 return @"[A-Z|a-z|0-9|\+|\/]*";
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
)
106 m_profileTotalCounter
= profileTotalCounter
;
110 public class Callstack
113 public List
<string> m_functions
;
115 public Callstack(string 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
)
136 FrameTimeInS
= frameTimeInS
;
137 FrameMidTimeInS
= frameTimeInS
;
138 ScreenshotImage
= screenshotImage
;
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;
172 for (int iPath
= 0; iPath
< pathLocations
.Length
&& iPattern
< patternLocations
.Length
; iPath
++)
174 if (patternLocations
[iPattern
] == "*")
180 if (patternLocations
[iPattern
] == "**")
182 if (iPattern
== patternLocations
.Length
- 1)
187 containsDoubleStar
= true;
189 if (patternLocations
[iPattern
+ 1] == pathLocations
[iPath
])
197 if (patternLocations
[iPattern
] != pathLocations
[iPath
])
205 return (containsDoubleStar
&& iPattern
== patternLocations
.Length
) || (pathLocations
.Length
== patternLocations
.Length
);
209 public IEnumerable
<string> GetPathEnumerator(string pathToDo
)
211 foreach (string path
in Values
.Paths
)
213 if (PathMatchesPattern(path
, pathToDo
))
220 public float GetDisplayXValue(LogData logData
, bool againstFrameTime
)
222 if (againstFrameTime
)
224 float startMidTime
= logData
.FrameRecords
.First().FrameMidTimeInS
;
225 return FrameMidTimeInS
- startMidTime
;
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
)
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
)
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
++)
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()
335 public FrameRecordRange(string name
, int startIdx
, int endIdx
)
343 public class LogRange
345 public LogData m_logData
;
346 public FrameRecordRange m_frr
;
348 public LogRange(LogData logData
, FrameRecordRange 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
;
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
);
395 throw new Exception("Not a level user marker");
408 m_min
= 9999999999999.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
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
;
457 public ValueStats(bool bValueIsPerLogView
)
459 m_bValueIsPerLogView
= bValueIsPerLogView
;
466 m_min
= float.MaxValue
;
467 m_max
= float.MinValue
;
472 public void Update(float value)
474 if (float.IsNaN(value))
492 m_avg
= (float)(m_total
/ m_numFrames
);
496 public enum EItemType
503 public class ItemMetaData
505 public EItemType m_type
;
507 public ItemMetaData(EItemType type
)
512 public override bool Equals(object obj
)
514 if (obj
== null || GetType() != obj
.GetType())
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;
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()
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
;
606 public float LogEndInSeconds
610 if (FrameRecords
.Count
> 0)
612 return FrameRecords
.Last().FrameTimeInS
;
619 public int GetDisplayIndex(int idx
)
621 return idx
- FrameRecordRange
.StartIdx
;
624 public void ProcessRecords()
627 CalculateLevelRanges();
630 public void ProcessRecords(int fromIdx
)
632 ClipIncompleteFrames(fromIdx
);
633 CalculateMidTimes(fromIdx
);
634 CalculateBucketSets(fromIdx
);
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)
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
];
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
]);
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
);
712 public FrameRecord
FindClosestScreenshotFrameFromTime(float x
)
714 return FrameRecords
[FindClosestFrameIdxFromTime(x
, true)];
717 public FrameRecord
FindClosestScreenshotFrameFromFrameIdx(int idx
)
721 for (belowIdx
= idx
; belowIdx
>= 0; belowIdx
--)
723 FrameRecord fr
= FrameRecords
[belowIdx
];
725 if (fr
.ScreenshotImage
!= null)
733 for (aboveIdx
= idx
; aboveIdx
< FrameRecords
.Count
; aboveIdx
++)
735 FrameRecord fr
= FrameRecords
[aboveIdx
];
737 if (fr
.ScreenshotImage
!= null)
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
)
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))
770 if (x
< fr
.FrameMidTimeInS
- startMidTime
)
779 aboveIdx
= Math
.Max(aboveIdx
, belowIdx
);
781 if (x
> (FrameRecords
[aboveIdx
].FrameMidTimeInS
- startMidTime
+ FrameRecords
[belowIdx
].FrameMidTimeInS
- startMidTime
) / 2.0f
)
791 public void GetFrameTimeData(int fromFrameIdx
, int toFrameIdx
, out float min
, out float max
, out float avg
)
793 min
= float.MaxValue
;
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
)
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())
881 Console
.WriteLine("Level start marker found (level name: '" + levelName
+ "') before previous end!");
885 levelName
= userMarkerLoc
.GetLevelName().Replace("/", " - "); // confluence doesn't like /
887 else if (userMarkerLoc
.IsLevelEnd())
891 m_levelRanges
.Add(new FrameRecordRange(levelName
, startFR
.Index
, fr
.Index
));
896 Console
.WriteLine("Level end marker, but no start!");
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
));
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
);
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
;
945 PlatformString
= "<unknown platform>";
946 BuildNumberString
= "<unknown build number>";