Files
UnrealEngine/Engine/Source/Programs/AutomationTool/Gauntlet/Unreal/Utils/Gauntlet.UnrealSnapshotParser.cs
2025-05-18 13:04:45 +08:00

480 lines
12 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Globalization;
namespace Gauntlet
{
public class UnrealHealthSnapshot
{
public string Name;
public float ProfileLength;
public float? CpuUsedMemory;
public float? CpuPeakMemory;
public float? PhysicalUsedMemory;
public float? PhysicalPeakMemory;
public float? StreamingUsedMemory;
public float? StreamingPeakMemory;
public float? MVP;
public float? AvgFps;
public float? Hitches;
public float? AvgHitches;
public float? DynamicRes;
public float? GTTime;
public float? RTTime;
public float? GPUTime;
public float? FTTime;
public float? RHIT;
public int? DrawCalls;
public int? DrawnPrims;
public int? UnbuiltHLODs;
public UnrealHealthSnapshot()
{
Name = "Unknown";
ProfileLength = 0;
}
public override string ToString()
{
StringBuilder SB = new StringBuilder();
SB.AppendFormat("Snapshot {0}\n", Name);
SB.AppendFormat("Duration\t{0}\n", ProfileLength);
if (CpuUsedMemory.HasValue)
{
SB.AppendFormat("CpuUsedMemory:\t\t{0} MB\n", CpuUsedMemory.Value);
}
if (CpuPeakMemory.HasValue)
{
SB.AppendFormat("CpuPeakMemory:\t\t{0} MB\n", CpuPeakMemory.Value);
}
if (PhysicalUsedMemory.HasValue)
{
SB.AppendFormat("PhysicalUsedMemory:\t\t{0} MB\n", PhysicalUsedMemory.Value);
}
if (PhysicalPeakMemory.HasValue)
{
SB.AppendFormat("PhysicalPeakMemory:\t\t{0} MB\n", PhysicalPeakMemory.Value);
}
if (StreamingUsedMemory.HasValue)
{
SB.AppendFormat("StreamingUsedMemory:\t\t{0} MB\n", StreamingUsedMemory.Value);
}
if (StreamingPeakMemory.HasValue)
{
SB.AppendFormat("StreamingPeakMemory:\t\t{0} MB\n", StreamingPeakMemory.Value);
}
if (ProfileLength > 0)
{
if (MVP.HasValue)
{
SB.AppendFormat("MVP:\t\t{0:0.00}\n", MVP.Value);
}
if (AvgFps.HasValue)
{
SB.AppendFormat("AvgFps:\t\t{0:0.00}\n", AvgFps.Value);
}
if (Hitches.HasValue)
{
SB.AppendFormat("HPM:\t\t{0:0.00}\n", Hitches.Value);
}
if (AvgHitches.HasValue)
{
SB.AppendFormat("AvgH:\t\t{0:0.00}ms\n", AvgHitches.Value);
}
if (DynamicRes.HasValue)
{
SB.AppendFormat("DynRes:\t\t{0:0.00}\n", DynamicRes.Value);
}
if (GTTime.HasValue)
{
SB.AppendFormat("GT:\t\t{0:0.00}ms\n", GTTime.Value);
}
if (RTTime.HasValue)
{
SB.AppendFormat("RT:\t\t{0:0.00}ms\n", RTTime.Value);
}
if (GPUTime.HasValue)
{
SB.AppendFormat("GPU:\t\t{0:0.00}ms\n", GPUTime.Value);
}
if (FTTime.HasValue)
{
SB.AppendFormat("FT:\t\t{0:0.00}ms\n", FTTime.Value);
}
if (RHIT.HasValue)
{
SB.AppendFormat("RHIT:\t\t{0:0.00}ms\n", RHIT.Value);
}
if (DrawCalls.HasValue)
{
SB.AppendFormat("DrawCalls:\t{0}\n", DrawCalls.Value);
}
if (DrawnPrims.HasValue)
{
SB.AppendFormat("DrawnPrims:\t{0}\n", DrawnPrims.Value);
}
if (UnbuiltHLODs.HasValue)
{
SB.AppendFormat("UnbuiltHLODs:\t{0}\n", UnbuiltHLODs.Value);
}
}
else
{
SB.Append("No info\n");
}
return SB.ToString();
}
public virtual void CreateFromString(string InContent)
{
RegexUtil.MatchAndApplyGroups(InContent, @".+Snapshot:\s+(.+?)\s+=", (Groups) =>
{
Name = Groups[1];
});
RegexUtil.MatchAndApplyGroups(InContent, @"MeasuredPerfTime:\s(\d.+) S", (Groups) =>
{
ProfileLength = ParseSingleInvariant(Groups[1]);
});
RegexUtil.MatchAndApplyGroups(InContent, @"CPU Memory:[\s\w]+?([\d\.]+)MB,[\s\w]+?([\d\.]+)MB", (Groups) =>
{
CpuUsedMemory = ParseSingleInvariant(Groups[1]);
CpuPeakMemory = ParseSingleInvariant(Groups[2]);
});
RegexUtil.MatchAndApplyGroups(InContent, @"Physical Memory:[\s\w]+?([\d\.]+)MB,[\s\w:]+?([\d\.]+)MB", (Groups) =>
{
PhysicalUsedMemory = ParseSingleInvariant(Groups[1]);
PhysicalPeakMemory = ParseSingleInvariant(Groups[2]);
});
RegexUtil.MatchAndApplyGroups(InContent, @"Streaming Memory:[\s\w]+?([\d\.]+)MB,[\s\w:]+?([\d\.]+)MB", (Groups) =>
{
StreamingUsedMemory = ParseSingleInvariant(Groups[1]);
StreamingPeakMemory = ParseSingleInvariant(Groups[2]);
});
RegexUtil.MatchAndApplyGroups(InContent, @"MVP:\s(\d.+)%,\s.*AvgFPS:(\d.+),\s.*HitchesPerMinute:\s(\d.+),\sAvg\sHitch\s(\d.+)ms", (Groups) =>
{
MVP = ParseSingleInvariant(Groups[1]);
AvgFps = ParseSingleInvariant(Groups[2]);
Hitches = ParseSingleInvariant(Groups[3]);
AvgHitches = ParseSingleInvariant(Groups[4]);
});
RegexUtil.MatchAndApplyGroups(InContent, @"GT:.* Avg\s(\d.+)ms,", (Groups) =>
{
GTTime = ParseSingleInvariant(Groups[1]);
});
RegexUtil.MatchAndApplyGroups(InContent, @"RT:.* Avg\s(\d.+)ms,", (Groups) =>
{
RTTime = ParseSingleInvariant(Groups[1]);
});
RegexUtil.MatchAndApplyGroups(InContent, @"GPU:.* Avg\s(\d.+)ms,", (Groups) =>
{
GPUTime = ParseSingleInvariant(Groups[1]);
});
RegexUtil.MatchAndApplyGroups(InContent, @"FT:.* Avg:\s([\d\.]+)ms,", (Groups) =>
{
FTTime = ParseSingleInvariant(Groups[1]);
});
RegexUtil.MatchAndApplyGroups(InContent, @"RHIT:.* Avg\s([\d\.]+)ms,", (Groups) =>
{
RHIT = ParseSingleInvariant(Groups[1]);
});
RegexUtil.MatchAndApplyGroups(InContent, @"DynRes:.* Avg:\s([\d\.]+)%,", (Groups) =>
{
DynamicRes = ParseSingleInvariant(Groups[1]);
});
RegexUtil.MatchAndApplyGroups(InContent, @"DrawCalls:[\w\s:]+?([\d\.]+),[\w\s:]+?([\d\.]+),[\w\s:]+?([\d\.]+)", (Groups) =>
{
DrawCalls = ParseIntInvariant(Groups[1]);
});
RegexUtil.MatchAndApplyGroups(InContent, @"DrawnPrims:[\w\s:]+?([\d\.]+),[\w\s:]+?([\d\.]+),[\w\s:]+?([\d\.]+)", (Groups) =>
{
DrawnPrims = ParseIntInvariant(Groups[1]);
});
RegexUtil.MatchAndApplyGroups(InContent, @"UnbuiltHLODs:\s(\d.+)", (Groups) =>
{
UnbuiltHLODs = ParseIntInvariant(Groups[1]);
});
}
private static float ParseSingleInvariant(string Text)
{
return Single.Parse(Text, CultureInfo.InvariantCulture);
}
private static int ParseIntInvariant(string Text)
{
return int.Parse(Text, CultureInfo.InvariantCulture);
}
}
public class UnrealSnapshotSummary<TSnapshotClass>
where TSnapshotClass : UnrealHealthSnapshot, new()
{
public int SampleCount;
public float SessionTime;
public float PeakMemory;
public List<float> MVP;
public List<float> AvgFps;
public List<float> Hitches;
public List<float> AvgHitches;
public List<float> DynamicRes;
public List<float> GTTime;
public List<float> RTTime;
public List<float> GPUTime;
public List<float> FTTime;
public List<float> RHIT;
public List<int> DrawCalls;
public List<int> DrawnPrims;
public List<int> UnbuiltHLODs;
public IEnumerable<TSnapshotClass> Snapshots;
public UnrealSnapshotSummary(ILogStreamReader LogReader, string SnapshotTitles="")
{
SampleCount = 0;
SessionTime = 0.0f;
PeakMemory = 0.0f;
MVP = new List<float>();
AvgFps = new List<float>();
Hitches = new List<float>();
AvgHitches = new List<float>();
DynamicRes = new List<float>();
GTTime = new List<float>();
RTTime = new List<float>();
GPUTime = new List<float>();
FTTime = new List<float>();
RHIT = new List<float>();
DrawCalls = new List<int>();
DrawnPrims = new List<int>();
UnbuiltHLODs = new List<int>();
CreateFromLog(LogReader, SnapshotTitles);
}
public UnrealSnapshotSummary(string InContent, string SnapshotTitles = "")
: this(new DynamicStringReader(() => InContent), SnapshotTitles)
{ }
protected virtual void CreateFromLog(ILogStreamReader LogReader, string InTitle)
{
UnrealLogParser Parser = new UnrealLogParser(LogReader);
if (string.IsNullOrEmpty(InTitle))
{
InTitle = "=== Snapshot: ";
}
try
{
// Find all end of match reports
string[] SessionSnapshots = Parser.GetGroupsOfLinesBetween(InTitle, "==============");
SampleCount = SessionSnapshots.Length;
SessionTime = 0;
// convert these blocks into snapshot info
Snapshots = SessionSnapshots.Select(S =>
{
var Snapshot = new TSnapshotClass();
Snapshot.CreateFromString(S);
return Snapshot;
}).ToList();
// get average MVP, GT, RT, GPU times
foreach (UnrealHealthSnapshot Snap in Snapshots)
{
SessionTime += Snap.ProfileLength;
if (Snap.MVP.HasValue)
{
MVP.Add(Snap.MVP.Value);
}
if (Snap.AvgFps.HasValue)
{
AvgFps.Add(Snap.AvgFps.Value);
}
if (Snap.Hitches.HasValue)
{
Hitches.Add(Snap.Hitches.Value);
}
if (Snap.AvgHitches.HasValue)
{
AvgHitches.Add(Snap.AvgHitches.Value);
}
if (Snap.DynamicRes.HasValue)
{
DynamicRes.Add(Snap.DynamicRes.Value);
}
if (Snap.GTTime.HasValue)
{
GTTime.Add(Snap.GTTime.Value);
}
if (Snap.RTTime.HasValue)
{
RTTime.Add(Snap.RTTime.Value);
}
if (Snap.GPUTime.HasValue)
{
GPUTime.Add(Snap.GPUTime.Value);
}
if (Snap.FTTime.HasValue)
{
FTTime.Add(Snap.FTTime.Value);
}
if (Snap.RHIT.HasValue)
{
RHIT.Add(Snap.RHIT.Value);
}
if (Snap.DrawCalls.HasValue)
{
DrawCalls.Add(Snap.DrawCalls.Value);
}
if (Snap.DrawnPrims.HasValue)
{
DrawnPrims.Add(Snap.DrawnPrims.Value);
}
if (Snap.UnbuiltHLODs.HasValue)
{
UnbuiltHLODs.Add(Snap.UnbuiltHLODs.Value);
}
}
// Now get peak memory from the last health report
if (Snapshots.Count() > 0)
{
var LastSnapshot = Snapshots.Last();
float? SnapshotPeakMemory = LastSnapshot.PhysicalPeakMemory;
//if PeakMemory is reporting as 0, use Memory if it's higher than our last
if (SnapshotPeakMemory.HasValue)
{
PeakMemory = SnapshotPeakMemory.Value;
}
else
{
Log.Info("PeakMemory reported as 0mb");
}
}
}
catch (Exception Ex)
{
Log.Info("Failed parsing PerformanceSummary: " + Ex.ToString());
}
}
public override string ToString()
{
StringBuilder SB = new StringBuilder();
if (SampleCount > 0)
{
SB.AppendFormat("Performance report from {0} samples and {1} seconds\n", SampleCount, SessionTime);
SB.AppendFormat("Peak Memory: {0} MB\n", PeakMemory);
if (MVP.Count > 0)
{
SB.AppendFormat("MVP:\t{0:0.00} (Min: {1:0.00}, Max: {2:0.00})\n", MVP.Average(), MVP.Min(), MVP.Max());
}
if (AvgFps.Count > 0)
{
SB.AppendFormat("AvgFPS:\t{0:0.00} (Min: {1:0.00}, Max: {2:0.00})\n", AvgFps.Average(), AvgFps.Min(), AvgFps.Max());
}
if (Hitches.Count > 0)
{
SB.AppendFormat("HPM:\t{0:0.00} (Min: {1:0.00}, Max: {2:0.00})\n", Hitches.Average(), Hitches.Min(), Hitches.Max());
}
if (AvgHitches.Count > 0)
{
SB.AppendFormat("AvgH:\t{0:0.00}ms (Min: {1:0.00}ms, Max: {2:0.00}ms)\n", AvgHitches.Average(), AvgHitches.Min(), AvgHitches.Max());
}
if (DynamicRes.Count > 0)
{
SB.AppendFormat("DynRes:\t{0:0.00} (Min: {1:0.00}, Max: {2:0.00})\n", DynamicRes.Average(), DynamicRes.Min(), DynamicRes.Max());
}
if (GTTime.Count > 0)
{
SB.AppendFormat("GT:\t{0:0.00}ms (Min: {1:0.00}ms, Max: {2:0.00}ms)\n", GTTime.Average(), GTTime.Min(), GTTime.Max());
}
if (RTTime.Count > 0)
{
SB.AppendFormat("RT:\t{0:0.00}ms (Min: {1:0.00}ms, Max: {2:0.00}ms)\n", RTTime.Average(), RTTime.Min(), RTTime.Max());
}
if (GPUTime.Count > 0)
{
SB.AppendFormat("GPU:\t{0:0.00}ms (Min: {1:0.00}ms, Max: {2:0.00}ms)\n", GPUTime.Average(), GPUTime.Min(), GPUTime.Max());
}
if (FTTime.Count > 0)
{
SB.AppendFormat("FT:\t{0:0.00}ms (Min: {1:0.00}ms, Max: {2:0.00}ms)\n", FTTime.Average(), FTTime.Min(), FTTime.Max());
}
if (RHIT.Count > 0)
{
SB.AppendFormat("RHIT:\t{0:0.00}ms (Min: {1:0.00}ms, Max: {2:0.00}ms)\n", RHIT.Average(), RHIT.Min(), RHIT.Max());
}
if (DrawCalls.Count > 0)
{
SB.AppendFormat("DrawCalls:\t{0:0} (Min: {1}, Max: {2})\n", DrawCalls.Average(), DrawCalls.Min(), DrawCalls.Max());
}
if (DrawnPrims.Count > 0)
{
SB.AppendFormat("DrawnPrims:\t{0:0} (Min: {1}, Max: {2})\n", DrawnPrims.Average(), DrawnPrims.Min(), DrawnPrims.Max());
}
}
else
{
SB.Append("No session info");
}
return SB.ToString();
}
}
}