2024-07-17 09:26:30 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using EpicGame ;
using AutomationTool ;
using Gauntlet ;
using EpicGames.Core ;
using Log = Gauntlet . Log ;
using Microsoft.Extensions.Logging ;
using UnrealBuildBase ; // for Unreal.RootDirectory
using UnrealBuildTool ; // for UnrealTargetPlatform
using static AutomationTool . CommandUtils ;
namespace AutomatedPerfTest
{
/// <summary>
/// Implementation of a Gauntlet TestNode for AutomatedPerfTest plugin
/// </summary>
/// <typeparam name="TConfigClass"></typeparam>
public abstract class AutomatedPerfTestNode < TConfigClass > : UnrealTestNode < TConfigClass >
where TConfigClass : AutomatedPerfTestConfigBase , new ( )
{
public AutomatedPerfTestNode ( UnrealTestContext InContext ) : base ( InContext )
{
2024-08-02 14:28:36 -04:00
// We need to save off the build name as if this is a preflight that suffix will be stripped
// after GetConfiguration is called. This will cause a mismatch in CreateReport.
OriginalBuildName = Globals . Params . ParseValue ( "BuildName" , InContext . BuildInfo . BuildName ) ;
Log . Info ( "Setting OriginalBuildName to {OriginalBuildName}" , OriginalBuildName ) ;
2024-08-09 10:43:50 -04:00
2024-07-17 09:26:30 -04:00
TestGuid = Guid . NewGuid ( ) ;
2024-08-02 14:28:36 -04:00
Log . Info ( "Your Test GUID is :\n" + TestGuid . ToString ( ) + '\n' ) ;
2024-07-17 09:26:30 -04:00
InitHandledErrors ( ) ;
2024-09-10 10:26:02 -04:00
LogParser = null ;
2024-07-17 09:26:30 -04:00
}
public override bool StartTest ( int Pass , int InNumPasses )
{
2024-09-10 10:26:02 -04:00
LogParser = null ;
2024-07-17 09:26:30 -04:00
return base . StartTest ( Pass , InNumPasses ) ;
}
public class HandledError
{
public string ClientErrorString ;
public string GauntletErrorString ;
/// <summary>
/// String name for the log category that should be used to filter errors. Defaults to null, i.e. no filter.
/// </summary>
public string CategoryName ;
// If error is verbose, will output debugging information such as state
public bool Verbose ;
public HandledError ( string ClientError , string GauntletError , string Category , bool VerboseIn = false )
{
ClientErrorString = ClientError ;
GauntletErrorString = GauntletError ;
CategoryName = Category ;
Verbose = VerboseIn ;
}
}
/// <summary>
/// List of errors with special-cased gauntlet messages.
/// </summary>
public List < HandledError > HandledErrors { get ; set ; }
/// <summary>
/// Guid associated with each test run for ease of differentiation between different runs on same build.
/// </summary>
public Guid TestGuid { get ; protected set ; }
/// <summary>
2024-09-10 10:26:02 -04:00
/// Track client log messages that have been written to the test logs.
2024-07-17 09:26:30 -04:00
/// </summary>
2024-09-10 10:26:02 -04:00
private UnrealLogStreamParser LogParser ;
2024-07-17 09:26:30 -04:00
/// <summary>
// Temporary directory for perf report CSVs
/// </summary>
private DirectoryInfo TempPerfCSVDir = > new DirectoryInfo ( Path . Combine ( Unreal . RootDirectory . FullName , "GauntletTemp" , "PerfReportCSVs" ) ) ;
/// <summary>
// Holds the build name as is, since if this is a preflight the suffix will be stripped after GetConfiguration is called.
/// </summary>
private string OriginalBuildName = null ;
/// <summary>
/// Set up the base list of possible expected errors, plus the messages to deliver if encountered.
/// </summary>
protected virtual void InitHandledErrors ( )
{
HandledErrors = new List < HandledError > ( ) ;
}
/// <summary>
/// Periodically called while test is running. Updates logs.
/// </summary>
public override void TickTest ( )
{
IAppInstance App = null ;
if ( TestInstance . ClientApps = = null )
{
App = TestInstance . ServerApp ;
}
else
{
if ( TestInstance . ClientApps . Length > 0 )
{
App = TestInstance . ClientApps . First ( ) ;
}
}
if ( App ! = null )
{
2024-09-10 10:26:02 -04:00
if ( LogParser = = null )
{
LogParser = new UnrealLogStreamParser ( App . GetLogBufferReader ( ) ) ;
}
LogParser . ReadStream ( ) ;
2024-08-09 10:43:50 -04:00
string LogChannelName = Context . BuildInfo . ProjectName + "Test" ;
2024-09-10 10:26:02 -04:00
List < string > TestLines = LogParser . GetLogFromChannel ( LogChannelName , false ) . ToList ( ) ;
2024-07-17 09:26:30 -04:00
string LogCategory = "Log" + LogChannelName ;
string LogCategoryError = LogCategory + ": Error:" ;
string LogCategoryWarning = LogCategory + ": Warning:" ;
2024-09-10 10:26:02 -04:00
foreach ( string Line in TestLines )
2024-07-17 09:26:30 -04:00
{
2024-09-10 10:26:02 -04:00
if ( Line . StartsWith ( LogCategoryError ) )
2024-07-17 09:26:30 -04:00
{
2024-09-10 10:26:02 -04:00
ReportError ( Line ) ;
2024-07-17 09:26:30 -04:00
}
2024-09-10 10:26:02 -04:00
else if ( Line . StartsWith ( LogCategoryWarning ) )
2024-07-17 09:26:30 -04:00
{
2024-09-10 10:26:02 -04:00
ReportWarning ( Line ) ;
2024-07-17 09:26:30 -04:00
}
else
{
2024-09-10 10:26:02 -04:00
Log . Info ( Line ) ;
2024-07-17 09:26:30 -04:00
}
}
}
base . TickTest ( ) ;
}
/// <summary>
/// This allows using a per-branch config to ignore certain issues
/// that were inherited from Main and will be addressed there
/// </summary>
/// <param name="InArtifacts"></param>
/// <returns></returns>
protected override UnrealLog CreateLogSummaryFromArtifact ( UnrealRoleArtifacts InArtifacts )
{
UnrealLog LogSummary = base . CreateLogSummaryFromArtifact ( InArtifacts ) ;
IgnoredIssueConfig IgnoredIssues = new IgnoredIssueConfig ( ) ;
string IgnoredIssuePath = GetCachedConfiguration ( ) . IgnoredIssuesConfigAbsPath ;
if ( ! File . Exists ( IgnoredIssuePath ) )
{
Log . Info ( "No IgnoredIssue Config found at {0}" , IgnoredIssuePath ) ;
}
else if ( IgnoredIssues . LoadFromFile ( IgnoredIssuePath ) )
{
Log . Info ( "Loaded IgnoredIssue config from {0}" , IgnoredIssuePath ) ;
IEnumerable < UnrealLog . CallstackMessage > IgnoredEnsures = LogSummary . Ensures . Where ( E = > IgnoredIssues . IsEnsureIgnored ( this . Name , E . Message ) ) ;
IEnumerable < UnrealLog . LogEntry > IgnoredWarnings = LogSummary . LogEntries . Where ( E = > E . Level = = UnrealLog . LogLevel . Warning & & IgnoredIssues . IsWarningIgnored ( this . Name , E . Message ) ) ;
IEnumerable < UnrealLog . LogEntry > IgnoredErrors = LogSummary . LogEntries . Where ( E = > E . Level = = UnrealLog . LogLevel . Error & & IgnoredIssues . IsErrorIgnored ( this . Name , E . Message ) ) ;
if ( IgnoredEnsures . Any ( ) )
{
Log . Info ( "Ignoring {0} ensures." , IgnoredEnsures . Count ( ) ) ;
Log . Info ( "\t{0}" , string . Join ( "\n\t" , IgnoredEnsures . Select ( E = > E . Message ) ) ) ;
LogSummary . Ensures = LogSummary . Ensures . Except ( IgnoredEnsures ) . ToArray ( ) ;
}
if ( IgnoredWarnings . Any ( ) )
{
Log . Info ( "Ignoring {0} warnings." , IgnoredWarnings . Count ( ) ) ;
Log . Info ( "\t{0}" , string . Join ( "\n\t" , IgnoredWarnings . Select ( E = > E . Message ) ) ) ;
LogSummary . LogEntries = LogSummary . LogEntries . Except ( IgnoredWarnings ) . ToArray ( ) ;
}
if ( IgnoredErrors . Any ( ) )
{
Log . Info ( "Ignoring {0} errors." , IgnoredErrors . Count ( ) ) ;
Log . Info ( "\t{0}" , string . Join ( "\n\t" , IgnoredErrors . Select ( E = > E . Message ) ) ) ;
LogSummary . LogEntries = LogSummary . LogEntries . Except ( IgnoredErrors ) . ToArray ( ) ;
}
}
return LogSummary ;
}
protected override UnrealProcessResult GetExitCodeAndReason ( StopReason InReason , UnrealLog InLogSummary , UnrealRoleArtifacts InArtifacts , out string ExitReason , out int ExitCode )
{
// Check for login failure
2024-09-10 10:26:02 -04:00
UnrealLogParser Parser = new UnrealLogParser ( InArtifacts . AppInstance . GetLogReader ( ) ) ;
2024-07-17 09:26:30 -04:00
ExitReason = "" ;
ExitCode = - 1 ;
foreach ( HandledError ErrorToCheck in HandledErrors )
{
string [ ] MatchingErrors = Parser . GetErrors ( ErrorToCheck . CategoryName ) . Where ( E = > E . Contains ( ErrorToCheck . ClientErrorString ) ) . ToArray ( ) ;
if ( MatchingErrors . Length > 0 )
{
ExitReason = string . Format ( "Test Error: {0} {1}" , ErrorToCheck . GauntletErrorString , ErrorToCheck . Verbose ? "\"" + MatchingErrors [ 0 ] + "\"" : "" ) ;
ExitCode = - 1 ;
return UnrealProcessResult . TestFailure ;
}
}
return base . GetExitCodeAndReason ( InReason , InLogSummary , InArtifacts , out ExitReason , out ExitCode ) ;
}
public override ITestReport CreateReport ( TestResult Result , UnrealTestContext Context , UnrealBuildSource Build , IEnumerable < UnrealRoleResult > Artifacts , string ArtifactPath )
{
if ( Result = = TestResult . Passed )
{
2024-08-09 10:43:50 -04:00
if ( GetCachedConfiguration ( ) . DoInsightsTrace )
{
CopyInsightsTraceToPerfCache ( ArtifactPath ) ;
}
if ( GetCurrentPass ( ) < = GetNumPasses ( ) & & GetCachedConfiguration ( ) . DoCSVProfiler )
2024-07-17 09:26:30 -04:00
{
2024-08-02 14:28:36 -04:00
// Our artifacts from each iteration such as the client log will be overwritten by subsequent iterations so we need to copy them out to a temp dir
// to preserve them until we're ready to make our report on the final iteration.
CopyPerfFilesToTempDir ( ArtifactPath ) ;
2024-07-17 09:26:30 -04:00
2024-08-02 14:28:36 -04:00
// Local report generation is an example of how to use the PerfReportTool.
if ( ! Globals . Params . ParseParam ( "NoLocalReports" ) )
2024-07-17 09:26:30 -04:00
{
2024-08-02 14:28:36 -04:00
// NOTE: This does not currently work with long paths due to the CsvTools not properly supporting them.
Log . Info ( "Generating performance reports using PerfReportTool." ) ;
GenerateLocalPerfReport ( Context . GetRoleContext ( UnrealTargetRole . Client ) . Platform , ArtifactPath ) ;
2024-07-17 09:26:30 -04:00
}
2024-08-02 14:28:36 -04:00
if ( Globals . Params . ParseParam ( "PerfReportServer" ) & &
! Globals . Params . ParseParam ( "SkipPerfReportServer" ) )
{
Dictionary < string , dynamic > CommonDataSourceFields = new Dictionary < string , dynamic >
{
{ "HordeJobUrl" , Globals . Params . ParseValue ( "JobDetails" , null ) }
} ;
2024-08-09 10:43:50 -04:00
2024-08-02 14:28:36 -04:00
Log . Info ( "Creating perf server importer with build name {BuildName}" , OriginalBuildName ) ;
string DataSourceName = GetConfiguration ( ) . DataSourceName ;
string ImportDirOverride = Globals . Params . ParseValue ( "PerfReportServerImportDir" , null ) ;
ICsvImporter Importer = ReportGenUtils . CreatePerfReportServerImporter ( DataSourceName , OriginalBuildName ,
CommandUtils . IsBuildMachine , ImportDirOverride , CommonDataSourceFields ) ;
if ( Importer ! = null )
{
// Recursively grab all the csv files we copied to the temp dir and convert them to binary.
List < FileInfo > AllBinaryCsvFiles = ReportGenUtils . CollectAndConvertCsvFilesToBinary ( TempPerfCSVDir . FullName ) ;
if ( AllBinaryCsvFiles . Count = = 0 )
{
throw new AutomationException ( $"No Csv files found in {TempPerfCSVDir}" ) ;
}
// The corresponding log for each csv sits in the same subdirectory as the csv file itself.
IEnumerable < CsvImportEntry > ImportEntries = AllBinaryCsvFiles
. Select ( CsvFile = > new CsvImportEntry ( CsvFile . FullName , Path . Combine ( CsvFile . Directory . FullName , "ClientOutput.log" ) ) ) ;
// todo update this so it associates videos with the correct CSVs
IEnumerable < CsvImportEntry > CsvImportEntries = ImportEntries as CsvImportEntry [ ] ? ? ImportEntries . ToArray ( ) ;
if ( GetConfiguration ( ) . DoInsightsTrace )
{
string InsightsFilename = Path . GetFileNameWithoutExtension ( CsvImportEntries . First ( ) . CsvFilename )
. Replace ( ".csv" , ".utrace" ) ;
2024-08-09 10:43:50 -04:00
// recursively look for trace files that match the CSV's filename in the artifact path
string [ ] MatchingTraces = FindFiles ( $"*{InsightsFilename}" , true , ArtifactPath ) ;
if ( MatchingTraces . Length > 0 )
2024-08-02 14:28:36 -04:00
{
2024-08-09 10:43:50 -04:00
if ( MatchingTraces . Length > 1 )
{
Log . Warning ( "Multiple Insights traces were found in {ArtifactPath} matching pattern *{InsightsFilename}. Only the first will be attached to the CSV import for this test." ,
ArtifactPath , InsightsFilename ) ;
}
CsvImportEntries . First ( ) . AddAdditionalFile ( "Insights" , MatchingTraces . First ( ) ) ;
2024-08-02 14:28:36 -04:00
}
else
{
2024-08-09 10:43:50 -04:00
Log . Warning ( "Insights was requested, but no matching insights traces were found matching pattern *{InsightsFilename} in {ArtifactPath}" ,
InsightsFilename , ArtifactPath ) ;
2024-08-02 14:28:36 -04:00
}
}
if ( GetConfiguration ( ) . DoVideoCapture )
{
string VideoPath = Path . Combine ( ArtifactPath , "Client" , "Videos" ) ;
string [ ] VideoFiles = Directory . GetFiles ( VideoPath , "*.mp4" ) ;
if ( VideoFiles . Length > 0 )
{
foreach ( var VideoFile in VideoFiles )
{
CsvImportEntries . First ( ) . AddAdditionalFile ( "Video" , Path . Combine ( VideoPath , VideoFile ) ) ;
}
}
else
{
Log . Warning ( "Video capture was requested, but no videos were found in path {VideoPath}" , VideoPath ) ;
}
}
// Create the import batch
Importer . Import ( CsvImportEntries ) ;
}
// Cleanup the temp dir
TempPerfCSVDir . Delete ( recursive : true ) ;
}
2024-07-17 09:26:30 -04:00
}
}
else
{
Logger . LogWarning ( "Skipping performance report generation because the perf report test failed." ) ;
}
return base . CreateReport ( Result , Context , Build , Artifacts , ArtifactPath ) ;
}
/// <summary>
/// Produces a detailed csv report using PerfReportTool.
/// Also, stores perf data in the perf cache, and generates a historic report using the data the cache contains.
/// </summary>
private void GenerateLocalPerfReport ( UnrealTargetPlatform Platform , string ArtifactPath )
{
var ReportCacheDir = GetCachedConfiguration ( ) . PerfCacheRoot ; // see if this is appropriate
var ToolPath = FileReference . Combine ( Unreal . EngineDirectory , "Binaries" , "DotNET" , "CsvTools" , "PerfreportTool.exe" ) ;
if ( ! FileReference . Exists ( ToolPath ) )
{
Logger . LogError ( "Failed to find perf report utility at this path: \"{ToolPath}\"." , ToolPath ) ;
return ;
}
2024-08-09 10:43:50 -04:00
var ReportConfigDir = Path . Combine ( Context . Options . ProjectPath . Directory . FullName , "Build" , "Scripts" , "PerfReport" ) ;
var ReportPath = Path . Combine ( ReportCacheDir , "Reports" , "Performance" ) ;
2024-07-17 09:26:30 -04:00
// Csv files may have been output in one of two places.
// Check both...
var CsvsPaths = new [ ]
{
2024-08-02 14:28:36 -04:00
Path . Combine ( ArtifactPath , "Client" , "Profiling" , "CSV" )
2024-07-17 09:26:30 -04:00
} ;
var DiscoveredCsvs = new List < string > ( ) ;
foreach ( var CsvsPath in CsvsPaths )
{
if ( Directory . Exists ( CsvsPath ) )
{
DiscoveredCsvs . AddRange (
from CsvFile in Directory . GetFiles ( CsvsPath , "*.csv" , SearchOption . AllDirectories )
select CsvFile ) ;
}
}
if ( DiscoveredCsvs . Count = = 0 )
{
Logger . LogError ( "Test completed successfully but no csv profiling results were found. Searched paths were:\r\n {Paths}" , string . Join ( "\r\n " , CsvsPaths . Select ( s = > $"\" { s } \ "" ) ) ) ;
return ;
}
// Find the newest csv file and get its directory
// (PerfReportTool will only output cached data in -csvdir mode)
var NewestFile =
( from CsvFile in DiscoveredCsvs
let Timestamp = File . GetCreationTimeUtc ( CsvFile )
orderby Timestamp descending
select CsvFile ) . First ( ) ;
var NewestDir = Path . GetDirectoryName ( NewestFile ) ;
Log . Info ( "Using perf report cache directory \"{ReportCacheDir}\"." , ReportCacheDir ) ;
Log . Info ( "Using perf report output directory \"{ReportPath}\"." , ReportPath ) ;
Log . Info ( "Using csv results directory \"{NewestDir}\". Generating historic perf report data..." , NewestDir ) ;
// Make sure the cache and output directories exist
if ( ! Directory . Exists ( ReportCacheDir ) )
{
try { Directory . CreateDirectory ( ReportCacheDir ) ; }
catch ( Exception Ex )
{
Logger . LogError ( "Failed to create perf report cache directory \"{ReportCacheDir}\". {Ex}" , ReportCacheDir , Ex ) ;
return ;
}
}
if ( ! Directory . Exists ( ReportPath ) )
{
try { Directory . CreateDirectory ( ReportPath ) ; }
catch ( Exception Ex )
{
Logger . LogError ( "Failed to create perf report output directory \"{ReportPath}\". {Ex}" , ReportPath , Ex ) ;
return ;
}
}
// Win64 is actually called "Windows" in csv profiles
var PlatformNameFilter = Platform = = UnrealTargetPlatform . Win64 ? "Windows" : $"{Platform}" ;
2024-08-09 10:43:50 -04:00
string SearchPattern = $"{Context.BuildInfo.ProjectName}*" ;
2024-07-17 09:26:30 -04:00
// Produce the detailed report, and update the perf cache
2024-08-09 10:43:50 -04:00
CommandUtils . RunAndLog ( ToolPath . FullName , $"-csvdir \" { NewestDir } \ " -o \"{ReportPath}\" -reportxmlbasedir \"{ReportConfigDir}\" -summaryTableCache \"{ReportCacheDir}\" -searchpattern {SearchPattern} -metadatafilter platform=\"{PlatformNameFilter}\"" , out int ErrorCode ) ;
2024-07-17 09:26:30 -04:00
if ( ErrorCode ! = 0 )
{
Logger . LogError ( "PerfReportTool returned error code \"{ErrorCode}\" while generating detailed report." , ErrorCode ) ;
}
// Now generate the all-time historic summary report
HistoricReport ( "HistoricReport_AllTime" , new [ ]
{
$"platform={PlatformNameFilter}"
} ) ;
// 14 days historic report
HistoricReport ( $"HistoricReport_14Days" , new [ ]
{
$"platform={PlatformNameFilter}" ,
$"starttimestamp>={DateTimeOffset.Now.ToUnixTimeSeconds() - (14 * 60L * 60L * 24L)}"
} ) ;
// 7 days historic report
HistoricReport ( $"HistoricReport_7Days" , new [ ]
{
$"platform={PlatformNameFilter}" ,
$"starttimestamp>={DateTimeOffset.Now.ToUnixTimeSeconds() - (7 * 60L * 60L * 24L)}"
} ) ;
void HistoricReport ( string Name , IEnumerable < string > Filter )
{
var Args = new [ ]
{
$"-summarytablecachein \" { ReportCacheDir } \ "" ,
$"-summaryTableFilename \" { Name } . html \ "" ,
$"-reportxmlbasedir \" { ReportConfigDir } \ "" ,
$"-o \" { ReportPath } \ "" ,
$"-metadatafilter \" { string . Join ( " and " , Filter ) } \ "" ,
"-summaryTable autoPerfReportStandard" ,
"-condensedSummaryTable autoPerfReportStandard" ,
"-emailtable" ,
"-recurse"
} ;
var ArgStr = string . Join ( " " , Args ) ;
CommandUtils . RunAndLog ( ToolPath . FullName , ArgStr , out ErrorCode ) ;
if ( ErrorCode ! = 0 )
{
Logger . LogError ( "PerfReportTool returned error code \"{ErrorCode}\" while generating historic report." , ErrorCode ) ;
}
}
}
private void CopyPerfFilesToTempDir ( string ArtifactPath )
{
if ( ! TempPerfCSVDir . Exists )
{
Log . Info ( "Creating temp perf csv dir: {TempPerfCSVDir}" , TempPerfCSVDir ) ;
TempPerfCSVDir . Create ( ) ;
}
string ClientArtifactDir = Path . Combine ( ArtifactPath , "Client" ) ;
string ClientLogPath = Path . Combine ( ClientArtifactDir , "ClientOutput.log" ) ;
2024-08-02 14:28:36 -04:00
string CSVPath = PathUtils . FindRelevantPath ( ClientArtifactDir , "Profiling" , "CSV" ) ;
if ( string . IsNullOrEmpty ( CSVPath ) )
2024-07-17 09:26:30 -04:00
{
2024-08-02 14:28:36 -04:00
Log . Warning ( "Failed to find CSV folder folder in {ClientArtifactDir}" , ClientArtifactDir ) ;
2024-07-17 09:26:30 -04:00
return ;
}
// Grab all the csv files that have valid metadata.
// We don't want to convert to binary in place as the legacy reports require the raw csv.
2024-08-02 14:28:36 -04:00
List < FileInfo > CsvFiles = ReportGenUtils . CollectValidCsvFiles ( CSVPath ) ;
2024-07-17 09:26:30 -04:00
if ( CsvFiles . Count > 0 )
{
// We only want to copy the latest file as the other will have already been copied when this was run for those iterations.
CsvFiles . SortBy ( Info = > Info . LastWriteTimeUtc ) ;
FileInfo LatestCsvFile = CsvFiles . Last ( ) ;
// Create a subdir for each pass as we want to store the csv and log together in the same dir to make it easier to find them later.
string PassDir = Path . Combine ( TempPerfCSVDir . FullName , $"PerfCsv_Pass_{GetCurrentPass()}" ) ;
Directory . CreateDirectory ( PassDir ) ;
FileInfo LogFileInfo = new FileInfo ( ClientLogPath ) ;
if ( LogFileInfo . Exists )
{
string LogDestPath = Path . Combine ( PassDir , LogFileInfo . Name ) ;
Log . Info ( "Copying Log {ClientLogPath} To {LogDest}" , ClientLogPath , LogDestPath ) ;
2024-08-02 14:28:36 -04:00
LogFileInfo . CopyTo ( LogDestPath , true ) ;
2024-07-17 09:26:30 -04:00
}
else
{
2024-08-02 14:28:36 -04:00
Log . Warning ( "No log file was found at {ClientLogPath}" , ClientLogPath ) ;
2024-07-17 09:26:30 -04:00
}
string CsvDestPath = Path . Combine ( PassDir , LatestCsvFile . Name ) ;
Log . Info ( "Copying Csv {CsvPath} To {CsvDestPath}" , LatestCsvFile . FullName , CsvDestPath ) ;
2024-08-02 14:28:36 -04:00
LatestCsvFile . CopyTo ( CsvDestPath , true ) ;
2024-07-17 09:26:30 -04:00
}
else
{
2024-08-02 14:28:36 -04:00
Log . Warning ( "No valid csv files found in {CSVPath}" , CSVPath ) ;
2024-07-17 09:26:30 -04:00
}
}
2024-08-09 10:43:50 -04:00
2024-08-02 14:28:36 -04:00
protected virtual string GetSubtestName ( )
{
return "Performance" ;
}
public override TConfigClass GetConfiguration ( )
{
TConfigClass Config = base . GetConfiguration ( ) ;
Config . MaxDuration = Context . TestParams . ParseValue ( "MaxDuration" , 60 * 60 ) ; // 1 hour max
UnrealTestRole ClientRole = Config . RequireRole ( UnrealTargetRole . Client ) ;
// the controller will be added by the subclasses
2024-08-09 10:43:50 -04:00
ClientRole . CommandLineParams . AddOrAppendParamValue ( "logcmds" , "LogHttp Verbose, LogAutomatedPerfTest Verbose" ) ;
2024-08-02 14:28:36 -04:00
ClientRole . CommandLineParams . Add ( "-deterministic" ) ;
2024-08-09 10:43:50 -04:00
Log . Info ( "AutomatedPerfTestNode<>.GetConfiguration(): Config.DoFPSChart={0}, Config.DoCSVProfiler={1}, Config.DoVideoCapture={2}, Config.DoInsightsTrace={3}" , Config . DoFPSChart , Config . DoCSVProfiler , Config . DoVideoCapture , Config . DoInsightsTrace ) ;
ClientRole . CommandLineParams . AddOrAppendParamValue ( "AutomatedPerfTest.TestName" , Config . TestName ) ;
2024-08-02 14:28:36 -04:00
2024-08-09 10:43:50 -04:00
if ( Config . DeviceProfileOverride ! = String . Empty )
{
ClientRole . CommandLineParams . AddOrAppendParamValue ( "AutomatedPerfTest.DeviceProfileOverride" , Config . DeviceProfileOverride ) ;
}
if ( Config . DoInsightsTrace )
{
ClientRole . CommandLineParams . Add ( "AutomatedPerfTest.DoInsightsTrace" ) ;
if ( Config . TraceChannels ! = String . Empty )
{
ClientRole . CommandLineParams . AddOrAppendParamValue ( "AutomatedPerfTest.TraceChannels" , Config . TraceChannels ) ;
}
}
2024-08-02 14:28:36 -04:00
if ( Config . DoFPSChart )
{
ClientRole . CommandLineParams . Add ( "AutomatedPerfTest.DoFPSChart" ) ;
}
if ( Config . DoCSVProfiler )
{
ClientRole . CommandLineParams . Add ( "AutomatedPerfTest.DoCSVProfiler" ) ;
ClientRole . CommandLineParams . Add ( "csvGpuStats" ) ;
// Add CSV metadata
List < string > CsvMetadata = new List < string >
{
2024-08-09 10:43:50 -04:00
string . Format ( "testname={0}" , Context . BuildInfo . ProjectName ) ,
2024-08-02 14:28:36 -04:00
"gauntletTestType=AutomatedPerfTest" ,
string . Format ( "gauntletSubTest={0}" , GetSubtestName ( ) ) ,
"testBuildIsPreflight=" + ( ReportGenUtils . IsTestingPreflightBuild ( OriginalBuildName ) ? "1" : "0" ) ,
"testBuildVersion=" + OriginalBuildName
} ;
if ( ! string . IsNullOrEmpty ( Context . BuildInfo . Branch ) & & Context . BuildInfo . Changelist ! = 0 )
{
CsvMetadata . Add ( "branch=" + Context . BuildInfo . Branch ) ;
CsvMetadata . Add ( "changelist=" + Context . BuildInfo . Changelist ) ;
}
ClientRole . CommandLineParams . Add ( "csvMetadata" , "\"" + String . Join ( "," , CsvMetadata ) + "\"" ) ;
}
if ( Config . DoVideoCapture )
{
ClientRole . CommandLineParams . Add ( "AutomatedPerfTest.DoVideoCapture" ) ;
}
return Config ;
}
2024-08-09 10:43:50 -04:00
public void CopyInsightsTraceToPerfCache ( string ArtifactPath )
{
Logger . LogInformation ( "Copying test insights trace from artifact path to report cache" ) ;
// find all the available trace paths
var DiscoveredTraces = new List < string > ( ) ;
if ( Directory . Exists ( ArtifactPath ) )
{
DiscoveredTraces . AddRange (
from TraceFile in Directory . GetFiles ( ArtifactPath , "*.utrace" , SearchOption . AllDirectories )
select TraceFile ) ;
}
// if we couldn't find any traces, report that and bail out
if ( DiscoveredTraces . Count = = 0 )
{
Logger . LogError ( "Test completed successfully but no trace results were found. Searched path was {ArtifactPath}" , ArtifactPath ) ;
return ;
}
// iterate over each of the discovered traces (there should be one for each test case that was run)
// first, sort the cases by timestamp
string [ ] SortedTraces =
( from TraceFile in DiscoveredTraces
let Timestamp = File . GetCreationTimeUtc ( TraceFile )
orderby Timestamp descending
select TraceFile ) . ToArray ( ) ;
var ReportPath = GetCachedConfiguration ( ) . PerfCacheRoot ;
if ( SortedTraces . Length > 0 )
{
string Filename = Path . GetFileNameWithoutExtension ( SortedTraces [ 0 ] ) ;
string PerfCachePath = Path . Combine ( ReportPath , Filename + ".utrace" ) ;
Logger . LogInformation ( "Copying latest utrace file from {ArtifactPath} to perf cache: {PerfCachePath}" , ArtifactPath ,
PerfCachePath ) ;
// just try the copy over, and log a failure, but don't bail out of the test.
try
{
2024-08-30 10:30:48 -04:00
InternalUtils . SafeCreateDirectory ( Path . GetDirectoryName ( PerfCachePath ) , true ) ;
2024-08-09 10:43:50 -04:00
File . Copy ( SortedTraces [ 0 ] , PerfCachePath ) ;
}
catch ( Exception e )
{
Logger . LogWarning ( "Failed to copy local trace file: {Text}" , e ) ;
}
}
}
2024-08-02 14:28:36 -04:00
2024-07-17 09:26:30 -04:00
/// <summary>
/// Returns the cached version of our config. Avoids repeatedly calling GetConfiguration() on derived nodes
/// </summary>
/// <returns></returns>
private TConfigClass GetCachedConfiguration ( )
{
if ( CachedConfig = = null )
{
return GetConfiguration ( ) ;
}
return CachedConfig ;
}
}
2024-08-02 14:28:36 -04:00
/// <summary>
/// Implementation of a Gauntlet TestNode for AutomatedPerfTest plugin
/// </summary>
/// <typeparam name="TConfigClass"></typeparam>
public abstract class AutomatedSequencePerfTestNode < TConfigClass > : AutomatedPerfTestNode < TConfigClass >
2024-08-09 10:43:50 -04:00
where TConfigClass : AutomatedSequencePerfTestConfig , new ( )
2024-08-02 14:28:36 -04:00
{
public AutomatedSequencePerfTestNode ( UnrealTestContext InContext ) : base ( InContext )
{
}
public override TConfigClass GetConfiguration ( )
{
TConfigClass Config = base . GetConfiguration ( ) ;
2024-08-30 10:30:48 -04:00
Config . DataSourceName = Config . GetDataSourceName ( Context . BuildInfo . ProjectName , "Sequence" ) ;
2024-08-09 10:43:50 -04:00
// extend the role(s) that we initialized in the base class
if ( Config . GetRequiredRoles ( UnrealTargetRole . Client ) . Any ( ) )
{
foreach ( UnrealTestRole ClientRole in Config . GetRequiredRoles ( UnrealTargetRole . Client ) )
{
ClientRole . Controllers . Add ( "AutomatedSequencePerfTest" ) ;
// if a specific MapSequenceComboName was defined in the commandline to UAT, then add that to the commandline for the role
if ( ! string . IsNullOrEmpty ( Config . MapSequenceComboName ) )
{
// use add Unique, since there should only ever be one of these specified
ClientRole . CommandLineParams . AddUnique ( $"AutomatedPerfTest.SequencePerfTest.MapSequenceName" ,
Config . MapSequenceComboName ) ;
}
}
}
2024-08-02 14:28:36 -04:00
return Config ;
}
}
2024-08-30 10:30:48 -04:00
/// <summary>
/// Implementation of a Gauntlet TestNode for AutomatedPerfTest plugin
/// </summary>
/// <typeparam name="TConfigClass"></typeparam>
public abstract class AutomatedStaticCameraPerfTestNode < TConfigClass > : AutomatedPerfTestNode < TConfigClass >
where TConfigClass : AutomatedStaticCameraPerfTestConfig , new ( )
{
public AutomatedStaticCameraPerfTestNode ( UnrealTestContext InContext ) : base ( InContext )
{
}
public override TConfigClass GetConfiguration ( )
{
TConfigClass Config = base . GetConfiguration ( ) ;
Config . DataSourceName = Config . GetDataSourceName ( Context . BuildInfo . ProjectName , "StaticCamera" ) ;
// extend the role(s) that we initialized in the base class
if ( Config . GetRequiredRoles ( UnrealTargetRole . Client ) . Any ( ) )
{
foreach ( UnrealTestRole ClientRole in Config . GetRequiredRoles ( UnrealTargetRole . Client ) )
{
ClientRole . Controllers . Add ( "AutomatedPlacedStaticCameraPerfTest" ) ;
// if a specific MapName was defined in the commandline to UAT, then add that to the commandline for the role
if ( ! string . IsNullOrEmpty ( Config . MapName ) )
{
// use add Unique, since there should only ever be one of these specified
ClientRole . CommandLineParams . AddUnique ( $"AutomatedPerfTest.StaticCameraPerfTest.MapName" ,
Config . MapName ) ;
}
}
}
return Config ;
}
}
/// <summary>
/// Implementation of a Gauntlet TestNode for AutomatedPerfTest plugin
/// </summary>
/// <typeparam name="TConfigClass"></typeparam>
public abstract class AutomatedMaterialPerfTestNode < TConfigClass > : AutomatedPerfTestNode < TConfigClass >
where TConfigClass : AutomatedMaterialPerfTestConfig , new ( )
{
public AutomatedMaterialPerfTestNode ( UnrealTestContext InContext ) : base ( InContext )
{
}
public override TConfigClass GetConfiguration ( )
{
TConfigClass Config = base . GetConfiguration ( ) ;
Config . DataSourceName = Config . GetDataSourceName ( Context . BuildInfo . ProjectName , "Material" ) ;
// extend the role(s) that we initialized in the base class
if ( Config . GetRequiredRoles ( UnrealTargetRole . Client ) . Any ( ) )
{
foreach ( UnrealTestRole ClientRole in Config . GetRequiredRoles ( UnrealTargetRole . Client ) )
{
ClientRole . Controllers . Add ( "AutomatedMaterialPerfTest" ) ;
}
}
return Config ;
}
}
2024-07-17 09:26:30 -04:00
}