2020-02-18 17:19:54 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
using AutomationTool ;
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using System.Text ;
using System.Threading ;
using System.Threading.Tasks ;
2020-12-21 23:07:37 -04:00
using EpicGames.Core ;
2020-02-18 17:19:54 -05:00
using UnrealBuildTool ;
2021-06-11 18:21:35 -04:00
using UnrealBuildBase ;
2023-03-08 12:43:35 -05:00
using Microsoft.Extensions.Logging ;
2020-02-18 17:19:54 -05:00
namespace AutomationTool.Benchmark
{
[Help("Runs benchmarks and reports overall results")]
2022-01-06 14:16:53 -05:00
[Help("Example1: RunUAT BenchmarkBuild -all -project=Unreal")]
[Help("Example2: RunUAT BenchmarkBuild -allcompile -project=Unreal+EngineTest -platform=PS4")]
2022-07-22 00:56:39 -04:00
[Help("Example3: RunUAT BenchmarkBuild -editor -client -cook -cooknoshaderddc -cooknoddc -xge -noxge -singlecompile -nopcompile -project=Unreal+QAGame+EngineTest -platform=WIn64+PS4+XboxOne+Switch -iterations=3")]
2020-02-18 17:19:54 -05:00
[Help("preview", "List everything that will run but don't do it")]
2022-07-22 00:56:39 -04:00
[Help("project=<name>", "Do tests on the specified project(s). E.g. -project=Unreal+FortniteGame+QAGame")]
[Help("editor", "Time building the editor")]
[Help("client", "Time building the for the specified platform(s)")]
2022-09-03 11:10:17 -04:00
[Help("compile", "Time compiling the target")]
[Help("singlecompile", "Do a single-file compile")]
[Help("nopcompile", "Do a nothing-needs-compiled compile")]
[Help("AllCompile", "Shorthand for -compile -singlecompile -nopcompile")]
2020-02-21 13:09:10 -05:00
[Help("platform=<p1+p2>", "Specify the platform(s) to use for client compilation/cooking, if empty the local platform be used if -client or -cook is specified")]
2022-07-22 00:56:39 -04:00
[Help("xge", "Do a pass with XGE / FASTBuild (default)")]
[Help("noxge", "Do a pass without XGE / FASTBuild")]
2022-09-03 11:10:17 -04:00
[Help("cores=X+Y+Z", "Do noxge builds with these processor counts (default is Environment.ProcessorCount)")]
[Help("editor-startup", "Time launching the editor. Specify maps with -editor-startup=map1+map2")]
[Help("editor-pie", "Time pie'ing for a project (only valid when -project is specified). Specify maps with -editor-pie=map1+map2")]
[Help("editor-game", "Time launching the editor as -game (only valid when -project is specified). Specify maps with -editor-game=map1+map2")]
[Help("AllEditor", "Shorthand for -editor-startup -editor-pie -editor-game")]
[Help("editor-maps", "Map to Launch/PIE with (only valid when using a single project. Same as setting editor-pie=m1+m2, editor-startup=m1+m2 individually ")]
[Help("cook", "Time cooking the project for the specified platform(s). Specify maps with -editor-cook=map1+map2")]
[Help("cook-iterative", "Time an iterative cook for the specified platform(s) (will run a cook first if -cook is not specified). Specify maps with -editor-cook-iterative=map1+map2")]
[Help("AllCook", "Shorthand for -cook -cook-iterative")]
2020-03-09 17:03:55 -04:00
[Help("warmddc", "Cook / PIE with a warm DDC")]
[Help("hotddc", "Cook / PIE with a hot local DDC (an untimed pre-run is performed)")]
[Help("coldddc", "Cook / PIE with a cold local DDC (a temporary folder is used)")]
2022-07-22 00:56:39 -04:00
[Help("coldddc-noshared", "Cook / PIE with a cold local DDC and no shared ddc ")]
2020-03-09 17:03:55 -04:00
[Help("noshaderddc", "Cook / PIE with no shaders in the DDC")]
2022-09-03 11:10:17 -04:00
[Help("AllDDC", "Shorthand for -coldddc -coldddc-noshared -noshaderddc -hotddc")]
[Help("All", "Shorthand for -editor -client -AllCompile -AllEditor -AllCook -AllDDC")]
2022-07-22 00:56:39 -04:00
[Help("editorxge", "Do a pass with XGE for editor DDC (default)")]
[Help("noeditorxge", "Do a pass without XGE for editor DDC")]
2023-01-09 13:22:13 -05:00
[Help("UBTArgs=", "Extra args to use when compiling. -UBTArgs=\"-foo\" -UBT2Args=\"-bar\" will run two compile passes with -foo and -bar")]
[Help("CookArgs=", "Extra args to use when cooking. -CookArgs=\"-foo\" -Cook2Args=\"-bar\" will run two cook passes with -foo and -bar")]
[Help("LaunchArgs=", "Extra args to use for launching. -LaunchArgs=\"-foo\" -Launch2Args=\"-bar\" will run two launch passes with -foo and -bar")]
[Help("PIEArgs=", "Extra args to use for PIE. -PIEArgs=\"-foo\" -PIE2Args=\"-bar\" will run two PIE passes with -foo and -bar")]
2020-02-21 13:09:10 -05:00
[Help("iterations=<n>", "How many times to perform each test)")]
[Help("wait=<n>", "How many seconds to wait between each test)")]
2022-07-22 00:56:39 -04:00
[Help("csv", "Name/path of file to write CSV results to. If empty the local machine name will be used")]
2020-03-05 13:50:48 -05:00
[Help("noclean", "Don't build from clean. (Mostly just to speed things up when testing)")]
2022-07-22 00:56:39 -04:00
[Help("nopostclean", "Don't clean artifacts after a task when building a lot of platforms/projects")]
2020-02-18 17:19:54 -05:00
class BenchmarkBuild : BuildCommand
{
2020-03-05 13:50:48 -05:00
class BenchmarkOptions : BuildCommand
{
public bool Preview = false ;
2020-02-18 17:19:54 -05:00
2022-01-06 14:16:53 -05:00
public bool DoUETests = false ;
2020-03-05 13:50:48 -05:00
public IEnumerable < string > ProjectsToTest = Enumerable . Empty < string > ( ) ;
public IEnumerable < UnrealTargetPlatform > PlatformsToTest = Enumerable . Empty < UnrealTargetPlatform > ( ) ;
// building
public bool DoBuildEditorTests = false ;
public bool DoBuildClientTests = false ;
2022-09-03 11:10:17 -04:00
public bool DoCompileTests = false ;
2020-03-05 13:50:48 -05:00
public bool DoNoCompileTests = false ;
public bool DoSingleCompileTests = false ;
2022-07-22 00:56:39 -04:00
public IEnumerable < int > CoresForLocalJobs = Enumerable . Empty < int > ( ) ;
2020-03-05 13:50:48 -05:00
// cooking
public bool DoCookTests = false ;
2022-07-22 00:56:39 -04:00
public bool DoIterativeCookTests = false ;
2020-03-05 13:50:48 -05:00
2022-07-22 00:56:39 -04:00
// editor PIE tests
2022-07-22 00:13:12 -04:00
public bool DoPIETests = false ;
2022-07-22 00:12:59 -04:00
2022-07-22 00:56:39 -04:00
// editor startup tests
public bool DoLaunchEditorTests = false ;
public bool DoLaunchEditorGameTests = false ;
public IEnumerable < string > StartupMapList = Enumerable . Empty < string > ( ) ;
2021-02-16 20:13:40 -04:00
public IEnumerable < string > PIEMapList = Enumerable . Empty < string > ( ) ;
2022-07-22 00:56:39 -04:00
public IEnumerable < string > GameMapList = Enumerable . Empty < string > ( ) ;
public IEnumerable < string > CookMapList = Enumerable . Empty < string > ( ) ;
2020-03-05 13:50:48 -05:00
// misc
2022-07-22 00:56:39 -04:00
public int Iterations = 1 ;
public UBTBuildOptions BuildOptions = UBTBuildOptions . None ;
public int TimeBetweenTasks = 0 ;
2020-10-29 13:38:15 -04:00
2023-01-09 13:22:13 -05:00
public List < string > UBTArgs = new List < string > ( ) ;
2020-10-29 13:38:15 -04:00
public List < string > CookArgs = new List < string > ( ) ;
public List < string > PIEArgs = new List < string > ( ) ;
2022-07-22 00:56:39 -04:00
public List < string > LaunchArgs = new List < string > ( ) ;
2020-03-05 13:50:48 -05:00
public string FileName = string . Format ( "{0}_Results.csv" , Environment . MachineName ) ;
2022-07-22 00:56:39 -04:00
public SortedSet < XGETaskOptions > XGEOptions = new SortedSet < XGETaskOptions > ( ) ;
public SortedSet < DDCTaskOptions > DDCOptions = new SortedSet < DDCTaskOptions > ( ) ;
2020-03-09 17:03:55 -04:00
2020-03-05 13:50:48 -05:00
public void ParseParams ( string [ ] InParams )
{
this . Params = InParams ;
bool AllThings = ParseParam ( "all" ) ;
2022-07-22 00:56:39 -04:00
bool AllCompile = AllThings | | ParseParam ( "AllCompile" ) ;
bool AllCooks = AllThings | | ParseParam ( "AllCook" ) ;
bool AllEditor = AllThings | | ParseParam ( "AllEditor" ) ;
bool AllClient = AllThings | | ParseParam ( "AllClient" ) ;
bool AllDDC = AllThings | | ParseParam ( "AllDDC" ) ;
2020-03-05 13:50:48 -05:00
Preview = ParseParam ( "preview" ) ;
2022-01-06 14:16:53 -05:00
DoUETests = AllThings | | ParseParam ( "Unreal" ) ;
2020-03-05 13:50:48 -05:00
2022-09-03 11:10:17 -04:00
// targets
DoBuildEditorTests = AllThings | ParseParam ( "editor" ) ;
DoBuildClientTests = AllThings | ParseParam ( "client" ) ;
// compile tests
DoCompileTests = AllCompile | ParseParam ( "compile" ) ;
DoSingleCompileTests = AllCompile | ParseParam ( "singlecompile" ) ;
DoNoCompileTests = AllCompile | ParseParam ( "nopcompile" ) ;
2020-03-05 13:50:48 -05:00
// cooking
2022-07-22 00:56:39 -04:00
DoCookTests = AllCooks | ParseParam ( "cook" ) ;
DoIterativeCookTests = AllCooks | ParseParam ( "cook-iterative" ) ;
2020-03-11 15:17:54 -04:00
2022-07-22 00:56:39 -04:00
// editor launch tests
DoLaunchEditorTests = AllEditor | ParseParam ( "editor-startup" ) ;
DoLaunchEditorGameTests = AllEditor | ParseParam ( "editor-game" ) ;
DoPIETests = AllEditor | ParseParam ( "editor-pie" ) ;
2020-03-05 13:50:48 -05:00
2022-07-22 00:56:39 -04:00
var DDCCommandLineArgs = new Dictionary < string , DDCTaskOptions >
{
{ "warmddc" , DDCTaskOptions . WarmDDC } ,
{ "coldddc" , DDCTaskOptions . ColdDDC } ,
{ "coldddc-noshared" , DDCTaskOptions . ColdDDCNoShared } ,
{ "noshaderddc" , DDCTaskOptions . NoShaderDDC } ,
{ "hotddc" , DDCTaskOptions . HotDDC } ,
} ;
2020-03-05 13:50:48 -05:00
2022-07-22 00:56:39 -04:00
foreach ( var K in DDCCommandLineArgs . Keys )
{
if ( ParseParam ( K ) )
{
DDCOptions . Add ( DDCCommandLineArgs [ K ] ) ;
}
else if ( K ! = "warmddc" & & AllDDC )
{
DDCOptions . Add ( DDCCommandLineArgs [ K ] ) ;
}
}
var XGECommandLineArgs = new Dictionary < string , XGETaskOptions >
{
{ "xge" , XGETaskOptions . WithXGE } ,
{ "noxge" , XGETaskOptions . NoXGE } ,
{ "noeditorxge" , XGETaskOptions . NoEditorXGE } ,
{ "editorxge" , XGETaskOptions . WithEditorXGE }
} ;
foreach ( var K in XGECommandLineArgs . Keys )
{
if ( ParseParam ( K ) )
{
XGEOptions . Add ( XGECommandLineArgs [ K ] ) ;
}
}
2020-03-05 13:50:48 -05:00
Preview = ParseParam ( "Preview" ) ;
Iterations = ParseParamInt ( "Iterations" , Iterations ) ;
TimeBetweenTasks = ParseParamInt ( "Wait" , TimeBetweenTasks ) ;
2023-01-09 13:22:13 -05:00
// allow up to 10 UBT, Cook, PIE via -UBTArgs=etc, -UBT2Args=etc2, -CookArgs=etc -Cook2Args=etc2 etc
2020-10-29 13:38:15 -04:00
for ( int i = 0 ; i < 10 ; i + + )
{
2023-01-09 13:22:13 -05:00
string PostFix = i = = 0 ? "" : ( i + 1 ) . ToString ( ) ;
// Parse CookArgs, Cook2Args etc
2021-02-16 20:13:40 -04:00
string CookParam = ParseParamValue ( "Cook" + PostFix + "Args" , null ) ;
2020-03-05 13:50:48 -05:00
2021-02-16 20:13:40 -04:00
if ( CookParam ! = null )
2020-10-29 13:38:15 -04:00
{
CookArgs . Add ( CookParam ) ;
}
2023-01-09 13:22:13 -05:00
else if ( i = = 0 )
{
// add a default for the first cook
CookArgs . Add ( "" ) ;
}
2020-10-29 13:38:15 -04:00
2023-01-09 13:22:13 -05:00
// Parse PIEArgs, PIE22Args etc
2021-02-16 20:13:40 -04:00
string PIEParam = ParseParamValue ( "PIE" + PostFix + "Args" , null ) ;
2020-10-29 13:38:15 -04:00
2021-02-16 20:13:40 -04:00
if ( PIEParam ! = null )
2020-10-29 13:38:15 -04:00
{
PIEArgs . Add ( PIEParam ) ;
}
2023-01-09 13:22:13 -05:00
else if ( i = = 0 )
{
// add a default for the first PIE
PIEArgs . Add ( "" ) ;
}
2022-07-22 00:56:39 -04:00
2023-01-09 13:22:13 -05:00
// Parse LaunchArgs, Launch2Args etc
2022-07-22 00:56:39 -04:00
string LaunchParam = ParseParamValue ( "Launch" + PostFix + "Args" , null ) ;
if ( ! string . IsNullOrEmpty ( LaunchParam ) )
{
LaunchArgs . Add ( LaunchParam ) ;
}
2023-01-09 13:22:13 -05:00
else if ( i = = 0 )
{
// add a default for the first launch
LaunchArgs . Add ( "" ) ;
}
// Parse UBTArgs, UBT2Args etc
string UBTParam = ParseParamValue ( "UBT" + PostFix + "Args" , null ) ;
if ( ! string . IsNullOrEmpty ( UBTParam ) )
{
UBTArgs . Add ( UBTParam ) ;
}
else if ( i = = 0 )
{
// add a default for the first compile
UBTArgs . Add ( "" ) ;
}
2020-10-29 13:38:15 -04:00
}
2022-07-22 00:56:39 -04:00
FileName = ParseParamValue ( "csv" , FileName ) ;
2020-03-05 13:50:48 -05:00
// Parse the project arg
{
string ProjectsArg = ParseParamValue ( "project" , null ) ;
ProjectsArg = ParseParamValue ( "projects" , ProjectsArg ) ;
// Look at the project argument and verify it's a valid uproject
if ( ! string . IsNullOrEmpty ( ProjectsArg ) )
{
2020-03-09 17:03:55 -04:00
ProjectsToTest = ProjectsArg . Split ( new [ ] { '+' , ',' } , StringSplitOptions . RemoveEmptyEntries ) ;
2020-03-05 13:50:48 -05:00
}
}
// Parse and validate platform list from arguments
{
string PlatformArg = ParseParamValue ( "platform" , "" ) ;
PlatformArg = ParseParamValue ( "platforms" , PlatformArg ) ;
if ( ! string . IsNullOrEmpty ( PlatformArg ) )
{
List < UnrealTargetPlatform > ClientPlatforms = new List < UnrealTargetPlatform > ( ) ;
var PlatformList = PlatformArg . Split ( new [ ] { '+' , ',' } , StringSplitOptions . RemoveEmptyEntries ) ;
foreach ( var Platform in PlatformList )
{
UnrealTargetPlatform PlatformEnum ;
if ( ! UnrealTargetPlatform . TryParse ( Platform , out PlatformEnum ) )
{
throw new AutomationException ( "{0} is not a valid Unreal Platform" , Platform ) ;
}
ClientPlatforms . Add ( PlatformEnum ) ;
}
PlatformsToTest = ClientPlatforms ;
}
else
{
PlatformsToTest = new [ ] { BuildHostPlatform . Current . Platform } ;
}
}
2022-07-22 00:56:39 -04:00
// clean by default
if ( ! ParseParam ( "noclean" ) )
{
BuildOptions | = UBTBuildOptions . PreClean ;
}
// post-clean if we're building a lot of stuff
2023-01-09 13:22:13 -05:00
if ( ! ( ParseParam ( "nopostclean" ) & & ! ParseParam ( "noclean" ) )
2022-07-22 00:56:39 -04:00
/*&& (PlatformsToTest.Count() > 1 || ProjectsToTest.Count() > 1)*/ )
{
BuildOptions | = UBTBuildOptions . PostClean ;
2023-03-08 12:43:35 -05:00
Logger . LogInformation ( "Building multiple platforms. Will clean each platform after build step to save space. (use -nopostclean to prevent this)" ) ;
2022-07-22 00:56:39 -04:00
}
2020-03-05 13:50:48 -05:00
// parse processor args
{
string ProcessorArg = ParseParamValue ( "cores" , "" ) ;
if ( ! string . IsNullOrEmpty ( ProcessorArg ) )
{
var ProcessorList = ProcessorArg . Split ( new [ ] { '+' , ',' } , StringSplitOptions . RemoveEmptyEntries ) ;
CoresForLocalJobs = ProcessorList . Select ( P = > Convert . ToInt32 ( P ) ) ;
}
2020-03-11 15:17:54 -04:00
}
2020-03-30 12:14:01 -04:00
2022-07-22 00:56:39 -04:00
Func < string , string [ ] > ParseMapList = ( string ArgName ) = >
{
string ArgValue = ParseParamValue ( ArgName , "" ) ;
if ( ! string . IsNullOrEmpty ( ArgValue ) )
{
// don't remove empty entries so people can get the project default and map2 via +map2
return ArgValue . Split ( new [ ] { '+' , ',' } , StringSplitOptions . None ) ;
}
return new string [ ] { } ;
} ;
2020-03-30 12:14:01 -04:00
// parse map args
{
2022-07-22 00:56:39 -04:00
// master arg that sets all three
var EditorMaps = ParseMapList ( "editor-maps" ) ;
2020-03-30 12:14:01 -04:00
2022-07-22 00:56:39 -04:00
if ( EditorMaps . Any ( ) )
2020-03-30 12:14:01 -04:00
{
2022-07-22 00:56:39 -04:00
StartupMapList = EditorMaps ;
PIEMapList = EditorMaps ;
GameMapList = EditorMaps ;
CookMapList = EditorMaps ;
}
else
{
StartupMapList = ParseMapList ( "editor-startup" ) ;
PIEMapList = ParseMapList ( "editor-pie" ) ;
GameMapList = ParseMapList ( "editor-game" ) ;
CookMapList = ParseMapList ( "cook" ) ;
CookMapList = ParseMapList ( "cook-iterative" ) ;
2020-03-30 12:14:01 -04:00
}
}
2022-07-22 00:56:39 -04:00
bool DefaultToXGE = BenchmarkBuildTask . SupportsAcceleration ;
// If they specified cores, ensure NoXGE is on
if ( CoresForLocalJobs . Any ( ) )
{
XGEOptions . Add ( XGETaskOptions . NoXGE ) ;
}
if ( ! DDCOptions . Any ( ) )
{
DDCOptions . Add ( DDCTaskOptions . WarmDDC ) ;
}
// If the user provided no XGE / NoXGE compile flags, then give them a default
if ( ! XGEOptions . Contains ( XGETaskOptions . WithXGE )
& & ! XGEOptions . Contains ( XGETaskOptions . NoXGE ) )
{
XGEOptions . Add ( DefaultToXGE ? XGETaskOptions . WithXGE : XGETaskOptions . NoXGE ) ;
}
// If the user provided no XGE / NoXGE editor flags, then give them a default
if ( ! XGEOptions . Contains ( XGETaskOptions . WithEditorXGE )
& & ! XGEOptions . Contains ( XGETaskOptions . NoEditorXGE ) )
{
XGEOptions . Add ( DefaultToXGE ? XGETaskOptions . WithEditorXGE : XGETaskOptions . NoEditorXGE ) ;
}
// Make sure there's a default here
if ( ! CoresForLocalJobs . Any ( ) )
{
CoresForLocalJobs = new int [ ] { 0 } ;
}
// sanity
if ( ! BenchmarkBuildTask . SupportsAcceleration )
{
if ( XGEOptions . Contains ( XGETaskOptions . WithXGE )
| | XGEOptions . Contains ( XGETaskOptions . WithEditorXGE ) )
{
2023-03-08 12:43:35 -05:00
Logger . LogWarning ( "XGE requested but is not available. Removing XGE options" ) ;
2022-07-22 00:56:39 -04:00
XGEOptions . Remove ( XGETaskOptions . WithXGE ) ;
XGEOptions . Remove ( XGETaskOptions . WithEditorXGE ) ;
}
}
2020-03-05 13:50:48 -05:00
}
}
2020-02-18 17:19:54 -05:00
2021-08-09 18:24:52 -04:00
struct BenchmarkResult
{
public TimeSpan TaskTime { get ; set ; }
public bool Failed { get ; set ; }
}
2022-07-22 00:56:39 -04:00
2020-02-18 17:19:54 -05:00
public BenchmarkBuild ( )
2020-03-11 15:17:54 -04:00
{
2020-02-18 17:19:54 -05:00
}
public override ExitCode Execute ( )
{
2020-03-05 13:50:48 -05:00
BenchmarkOptions Options = new BenchmarkOptions ( ) ;
Options . ParseParams ( this . Params ) ;
2020-02-18 17:19:54 -05:00
2020-03-05 13:50:48 -05:00
List < BenchmarkTaskBase > Tasks = new List < BenchmarkTaskBase > ( ) ;
2020-02-18 17:19:54 -05:00
2021-08-09 18:24:52 -04:00
Dictionary < BenchmarkTaskBase , List < BenchmarkResult > > Results = new Dictionary < BenchmarkTaskBase , List < BenchmarkResult > > ( ) ;
2020-02-18 17:19:54 -05:00
2020-03-11 15:17:54 -04:00
for ( int ProjectIndex = 0 ; ProjectIndex < Options . ProjectsToTest . Count ( ) ; ProjectIndex + + )
2020-02-18 17:19:54 -05:00
{
2020-03-05 13:50:48 -05:00
string Project = Options . ProjectsToTest . ElementAt ( ProjectIndex ) ;
2020-02-18 17:19:54 -05:00
2020-03-09 17:03:55 -04:00
FileReference ProjectFile = ProjectUtils . FindProjectFileFromName ( Project ) ;
2022-01-06 14:16:53 -05:00
if ( ProjectFile = = null & & ! Project . Equals ( "Unreal" , StringComparison . OrdinalIgnoreCase ) )
2020-03-09 17:03:55 -04:00
{
throw new AutomationException ( "Could not find project file for {0}" , Project ) ;
}
2022-07-22 00:56:39 -04:00
bool TargetIsClientBuild = ProjectSupportsClientBuild ( ProjectFile ) ;
ProjectTargetInfo EditorTarget = new ProjectTargetInfo ( ProjectFile , BuildHostPlatform . Current . Platform , TargetIsClientBuild ) ;
// Do compile tests of editor and platforms
2023-01-09 13:22:13 -05:00
if ( Options . DoBuildEditorTests )
{
Tasks . AddRange ( AddBuildTests ( ProjectFile , BuildHostPlatform . Current . Platform , "Editor" , Options ) ) ;
}
if ( Options . DoBuildClientTests )
{
2022-07-22 00:56:39 -04:00
foreach ( var ClientPlatform in Options . PlatformsToTest )
{
ProjectTargetInfo PlatformTarget = new ProjectTargetInfo ( ProjectFile , ClientPlatform , TargetIsClientBuild ) ;
2023-01-09 13:22:13 -05:00
// do build tests
Tasks . AddRange ( AddBuildTests ( ProjectFile , ClientPlatform , TargetIsClientBuild ? "Client" : "Game" , Options ) ) ;
2022-07-22 00:56:39 -04:00
}
2020-02-18 17:19:54 -05:00
}
2023-01-09 13:22:13 -05:00
var XGEEditorOptions = Options . XGEOptions . Where ( Opt = > ( Opt = = XGETaskOptions . WithEditorXGE | | Opt = = XGETaskOptions . NoEditorXGE ) ) ;
2022-07-22 00:56:39 -04:00
List < BenchmarkTaskBase > EditorTasks = new List < BenchmarkTaskBase > ( ) ;
if ( Options . DoLaunchEditorTests )
2020-03-11 13:02:02 -04:00
{
2022-07-22 00:56:39 -04:00
EditorTasks . AddRange ( AddEditorTests < BenchmarkEditorStartupTask > ( EditorTarget , Options . StartupMapList , Options . LaunchArgs , Options . CoresForLocalJobs , XGEEditorOptions , Options . DDCOptions , EditorTasks . Any ( ) ) ) ;
2020-03-11 13:02:02 -04:00
}
2020-03-05 13:50:48 -05:00
2022-07-22 00:56:39 -04:00
// do PIE tests, so long as there's a project
if ( Options . DoPIETests & & EditorTarget . ProjectFile ! = null )
{
EditorTasks . AddRange ( AddEditorTests < BenchmarPIEEditorTask > ( EditorTarget , Options . PIEMapList , Options . PIEArgs , Options . CoresForLocalJobs , XGEEditorOptions , Options . DDCOptions , EditorTasks . Any ( ) ) ) ;
}
// do PIE tests, so long as there's a project
if ( Options . DoLaunchEditorGameTests & & EditorTarget . ProjectFile ! = null )
{
EditorTasks . AddRange ( AddEditorTests < BenchmarkEditorGameTask > ( EditorTarget , Options . GameMapList , Options . LaunchArgs , Options . CoresForLocalJobs , XGEEditorOptions , Options . DDCOptions , EditorTasks . Any ( ) ) ) ;
}
// cook tests
2020-03-05 13:50:48 -05:00
foreach ( var ClientPlatform in Options . PlatformsToTest )
2020-02-18 17:19:54 -05:00
{
2022-07-22 00:56:39 -04:00
ProjectTargetInfo PlatformTarget = new ProjectTargetInfo ( ProjectFile , ClientPlatform , TargetIsClientBuild ) ;
2020-02-18 17:19:54 -05:00
2022-07-22 00:56:39 -04:00
// do cook tests,. so long as there's a project
if ( Options . DoCookTests & & PlatformTarget . ProjectFile ! = null )
2020-03-11 13:02:02 -04:00
{
2022-07-22 00:56:39 -04:00
EditorTasks . AddRange ( AddEditorTests < BenchmarkCookTask > ( PlatformTarget , Options . CookMapList , Options . CookArgs , Options . CoresForLocalJobs , XGEEditorOptions , Options . DDCOptions , EditorTasks . Any ( ) ) ) ;
2020-03-11 13:02:02 -04:00
}
2020-02-21 13:09:10 -05:00
2022-07-22 00:56:39 -04:00
// do cook tests,. so long as there's a project
if ( Options . DoIterativeCookTests & & PlatformTarget . ProjectFile ! = null )
2020-03-11 13:02:02 -04:00
{
2022-07-22 00:56:39 -04:00
int [ ] CoreLimit = { 0 } ;
XGETaskOptions [ ] DefaultXGE = { BenchmarkBuildTask . SupportsAcceleration ? XGETaskOptions . WithEditorXGE : XGETaskOptions . NoEditorXGE } ;
DDCTaskOptions [ ] WarmDDC = { DDCTaskOptions . WarmDDC } ;
// If not running any cooks run a single warm one so we can get iterative values
if ( ! Options . DoCookTests )
{
var WarmupTasks = AddEditorTests < BenchmarkCookTask > ( PlatformTarget , Options . CookMapList , Options . CookArgs , CoreLimit , DefaultXGE , WarmDDC , EditorTasks . Any ( ) ) ;
WarmupTasks . ToList ( ) . ForEach ( T = > T . SkipReport = true ) ;
EditorTasks . AddRange ( WarmupTasks ) ;
}
EditorTasks . AddRange ( AddEditorTests < BenchmarkIterativeCookTask > ( PlatformTarget , Options . CookMapList , Options . CookArgs , CoreLimit , XGEEditorOptions , WarmDDC , EditorTasks . Any ( ) ) ) ;
2020-03-11 13:02:02 -04:00
}
2020-02-18 17:19:54 -05:00
}
2022-07-22 00:56:39 -04:00
Tasks . AddRange ( EditorTasks ) ;
2020-02-18 17:19:54 -05:00
}
2023-03-08 12:43:35 -05:00
Logger . LogInformation ( "Will execute tasks:" ) ;
2020-02-18 17:19:54 -05:00
foreach ( var Task in Tasks )
{
2023-03-08 12:43:35 -05:00
Logger . LogInformation ( "{Arg0}" , Task . FullName ) ;
2020-02-18 17:19:54 -05:00
}
2020-03-05 13:50:48 -05:00
if ( ! Options . Preview )
2020-02-18 17:19:54 -05:00
{
// create results lists
foreach ( var Task in Tasks )
{
2021-08-09 18:24:52 -04:00
Results . Add ( Task , new List < BenchmarkResult > ( ) ) ;
2020-02-18 17:19:54 -05:00
}
DateTime StartTime = DateTime . Now ;
2020-03-05 13:50:48 -05:00
for ( int i = 0 ; i < Options . Iterations ; i + + )
2020-02-18 17:19:54 -05:00
{
foreach ( var Task in Tasks )
{
2023-03-08 12:43:35 -05:00
Logger . LogInformation ( "Starting task {Arg0} (Pass {Arg1})" , Task . FullName , i + 1 ) ;
2020-02-18 17:19:54 -05:00
Task . Run ( ) ;
2023-03-08 12:43:35 -05:00
Logger . LogInformation ( "Task {Arg0} took {Arg1}" , Task . FullName , Task . TaskTime . ToString ( @"hh\:mm\:ss" ) ) ;
2020-02-18 17:19:54 -05:00
2021-08-09 18:24:52 -04:00
if ( Task . Failed )
{
2023-03-08 12:43:35 -05:00
Logger . LogError ( "Task failed! Benchmark time may be inaccurate." ) ;
2021-08-09 18:24:52 -04:00
}
2022-09-03 11:10:17 -04:00
2022-07-22 00:56:39 -04:00
if ( Task . SkipReport )
{
2023-03-08 12:43:35 -05:00
Logger . LogInformation ( "Skipping reporting of {Arg0}" , Task . FullName ) ;
2022-07-22 00:56:39 -04:00
}
2022-09-03 11:10:17 -04:00
else
2021-08-09 18:24:52 -04:00
{
2022-09-03 11:10:17 -04:00
Results [ Task ] . Add ( new BenchmarkResult
{
TaskTime = Task . TaskTime ,
Failed = Task . Failed
} ) ;
// write results so far
WriteCSVResults ( Options . FileName , Tasks , Results ) ;
}
2020-02-18 17:19:54 -05:00
2023-03-08 12:43:35 -05:00
Logger . LogInformation ( "Waiting {Arg0} secs until next task" , Options . TimeBetweenTasks ) ;
2020-03-05 13:50:48 -05:00
Thread . Sleep ( Options . TimeBetweenTasks * 1000 ) ;
2020-02-18 17:19:54 -05:00
}
}
2023-03-08 12:43:35 -05:00
Logger . LogInformation ( "**********************************************************************" ) ;
Logger . LogInformation ( "Test Results:" ) ;
2022-09-03 11:10:17 -04:00
2020-02-18 17:19:54 -05:00
foreach ( var Task in Tasks )
{
string TimeString = "" ;
2022-07-22 00:56:39 -04:00
if ( Task . SkipReport )
{
continue ;
}
2021-08-09 18:24:52 -04:00
IEnumerable < BenchmarkResult > TaskResults = Results [ Task ] ;
2020-02-18 17:19:54 -05:00
2021-08-09 18:24:52 -04:00
foreach ( var Result in TaskResults )
2020-02-18 17:19:54 -05:00
{
if ( TimeString . Length > 0 )
{
TimeString + = ", " ;
}
2021-08-09 18:24:52 -04:00
if ( Result . Failed )
2020-02-18 17:19:54 -05:00
{
2022-07-22 00:56:39 -04:00
TimeString + = "Failed" ;
}
else
{
TimeString + = Result . TaskTime . ToString ( @"hh\:mm\:ss" ) ;
2020-02-18 17:19:54 -05:00
}
}
var AvgTimeString = "" ;
2021-08-09 18:24:52 -04:00
if ( TaskResults . Count ( ) > 1 )
2020-02-18 17:19:54 -05:00
{
2021-08-09 18:24:52 -04:00
var AvgTime = new TimeSpan ( TaskResults . Select ( R = > R . TaskTime ) . Sum ( T = > T . Ticks ) / TaskResults . Count ( ) ) ;
2020-02-18 17:19:54 -05:00
AvgTimeString = string . Format ( " (Avg: {0})" , AvgTime . ToString ( @"hh\:mm\:ss" ) ) ;
2020-03-11 15:17:54 -04:00
}
2020-02-18 17:19:54 -05:00
2023-03-08 12:43:35 -05:00
Logger . LogInformation ( "Task {Arg0}:\t\t{TimeString}{AvgTimeString}" , Task . FullName , TimeString , AvgTimeString ) ;
2020-02-18 17:19:54 -05:00
}
2023-03-08 12:43:35 -05:00
Logger . LogInformation ( "**********************************************************************" ) ;
2020-02-18 17:19:54 -05:00
TimeSpan Elapsed = DateTime . Now - StartTime ;
2023-03-08 12:43:35 -05:00
Logger . LogInformation ( "Total benchmark time: {Arg0}" , Elapsed . ToString ( @"hh\:mm\:ss" ) ) ;
2020-02-18 17:19:54 -05:00
2020-03-05 13:50:48 -05:00
WriteCSVResults ( Options . FileName , Tasks , Results ) ;
2020-02-18 17:19:54 -05:00
}
return ExitCode . Success ;
}
2020-03-09 17:03:55 -04:00
IEnumerable < BenchmarkTaskBase > AddBuildTests ( FileReference InProjectFile , UnrealTargetPlatform InPlatform , string InTargetName , BenchmarkOptions InOptions )
2020-03-05 13:50:48 -05:00
{
List < BenchmarkTaskBase > NewTasks = new List < BenchmarkTaskBase > ( ) ;
2022-09-03 11:10:17 -04:00
if ( InOptions . DoCompileTests )
2020-03-05 13:50:48 -05:00
{
2023-01-09 13:22:13 -05:00
IEnumerable < string > UBTArgList = InOptions . UBTArgs . Any ( ) ? InOptions . UBTArgs : new [ ] { "" } ;
2022-09-03 11:10:17 -04:00
if ( InOptions . XGEOptions . Contains ( XGETaskOptions . WithXGE ) )
2020-03-05 13:50:48 -05:00
{
2023-01-09 13:22:13 -05:00
foreach ( string UBTArgs in UBTArgList )
{
NewTasks . Add ( new BenchmarkBuildTask ( InProjectFile , InTargetName , InPlatform , XGETaskOptions . WithXGE , UBTArgs , 0 , InOptions . BuildOptions ) ) ;
}
2022-09-03 11:10:17 -04:00
}
if ( InOptions . XGEOptions . Contains ( XGETaskOptions . NoXGE ) )
{
foreach ( int ProcessorCount in InOptions . CoresForLocalJobs )
{
2023-01-09 13:22:13 -05:00
foreach ( string UBTArgs in UBTArgList )
{
NewTasks . Add ( new BenchmarkBuildTask ( InProjectFile , InTargetName , InPlatform , XGETaskOptions . NoXGE , UBTArgs , ProcessorCount , InOptions . BuildOptions ) ) ;
}
2022-09-03 11:10:17 -04:00
}
}
2022-07-22 00:56:39 -04:00
}
// If the user requested a single-compile /nop-compile and we haven't built anything, add one now
if ( ( InOptions . DoSingleCompileTests | | InOptions . DoNoCompileTests )
& & NewTasks . Any ( ) = = false )
{
NewTasks . Add ( new BenchmarkBuildTask ( InProjectFile , InTargetName , InPlatform ,
BenchmarkBuildTask . SupportsAcceleration ? XGETaskOptions . WithXGE : XGETaskOptions . NoXGE ,
"" , 0 ) ) ;
2020-03-05 13:50:48 -05:00
}
2022-07-22 00:13:12 -04:00
if ( InOptions . DoSingleCompileTests )
2022-09-03 11:10:17 -04:00
{
2022-07-22 00:13:12 -04:00
// note, don't clean since we build normally then build again
2022-07-22 00:56:39 -04:00
NewTasks . Add ( new BenchmarkSingleCompileTask ( InProjectFile , InTargetName , InPlatform , InOptions . XGEOptions . First ( ) ) ) ;
}
2022-09-03 11:10:17 -04:00
if ( InOptions . DoNoCompileTests )
2022-07-22 00:56:39 -04:00
{
2022-09-03 11:10:17 -04:00
// note, don't clean since we build normally then build a single file
NewTasks . Add ( new BenchmarkNopCompileTask ( InProjectFile , InTargetName , InPlatform , InOptions . XGEOptions . First ( ) ) ) ;
}
// clean stuff if we're doing compilation tasks that aren't the editor as we can use masses of disk space...
if ( InOptions . DoCompileTests )
{
if ( InOptions . BuildOptions . HasFlag ( UBTBuildOptions . PostClean ) & & ! InTargetName . Equals ( "Editor" , StringComparison . OrdinalIgnoreCase ) )
{
var Task = new BenchmarkCleanBuildTask ( InProjectFile , InTargetName , InPlatform ) ;
Task . SkipReport = true ;
NewTasks . Add ( Task ) ;
}
2020-03-05 13:50:48 -05:00
}
return NewTasks ;
}
2022-07-22 00:56:39 -04:00
IEnumerable < BenchmarkTaskBase > AddEditorTests < T > ( ProjectTargetInfo InTargetInfo , IEnumerable < string > InMaps , IEnumerable < string > InArgVariations , IEnumerable < int > CoreVariations , IEnumerable < XGETaskOptions > InXGEOptions , IEnumerable < DDCTaskOptions > InDDCOptions , bool SkipBuildEditor )
where T : BenchmarkEditorTaskBase
2020-03-05 13:50:48 -05:00
{
2022-07-22 00:56:39 -04:00
if ( InTargetInfo = = null )
{
2020-03-05 13:50:48 -05:00
return Enumerable . Empty < BenchmarkTaskBase > ( ) ;
}
List < BenchmarkTaskBase > NewTasks = new List < BenchmarkTaskBase > ( ) ;
2022-07-22 00:56:39 -04:00
IEnumerable < string > ArgVariations = InArgVariations . Any ( ) ? InArgVariations : new List < string > { "" } ;
IEnumerable < string > MapVariations = InMaps . Any ( ) ? InMaps : new List < string > { "" } ;
2020-03-05 13:50:48 -05:00
2022-07-22 00:56:39 -04:00
// If the user is running a hotddc test and there's only one type, run a warm pass first
if ( InDDCOptions . Contains ( DDCTaskOptions . HotDDC ) & & InDDCOptions . Count ( ) = = 1 )
2020-03-05 13:50:48 -05:00
{
2022-07-22 00:56:39 -04:00
ProjectTaskOptions TaskOptions = new ProjectTaskOptions ( DDCTaskOptions . WarmDDC , InXGEOptions . First ( ) , "" , MapVariations . First ( ) , 0 ) ;
var NewTask = Activator . CreateInstance ( typeof ( T ) , new object [ ] { InTargetInfo , TaskOptions , SkipBuildEditor } ) as BenchmarkEditorTaskBase ;
NewTask . SkipReport = true ;
NewTasks . Add ( NewTask ) ;
2020-03-09 17:03:55 -04:00
2022-07-22 00:56:39 -04:00
// don't build the editor again
SkipBuildEditor = true ;
2022-07-22 00:13:12 -04:00
}
2020-03-09 17:03:55 -04:00
2022-07-22 00:56:39 -04:00
foreach ( string Args in ArgVariations )
2022-07-22 00:13:12 -04:00
{
2022-07-22 00:56:39 -04:00
foreach ( var Map in MapVariations )
2022-07-22 00:13:12 -04:00
{
2022-07-22 00:56:39 -04:00
foreach ( XGETaskOptions XGEOption in InXGEOptions )
{
bool bCoreVariations = XGEOption = = XGETaskOptions . NoEditorXGE & & CoreVariations . Any ( ) ;
IEnumerable < int > CoresForJobs = bCoreVariations ? CoreVariations : new int [ ] { 0 } ;
2022-07-22 00:13:12 -04:00
2022-07-22 00:56:39 -04:00
foreach ( var CoreLimit in CoresForJobs )
{
// DDC must be last expansion as things are ordered with assumptions
foreach ( var DDCOption in InDDCOptions )
{
ProjectTaskOptions TaskOptions = new ProjectTaskOptions ( DDCOption , XGEOption , Args , Map , CoreLimit ) ;
var NewTask = Activator . CreateInstance ( typeof ( T ) , new object [ ] { InTargetInfo , TaskOptions , SkipBuildEditor } ) as BenchmarkTaskBase ;
NewTasks . Add ( NewTask ) ;
2022-07-22 00:13:12 -04:00
2022-07-22 00:56:39 -04:00
// don't build the editor again
SkipBuildEditor = true ;
}
}
}
2022-07-22 00:13:12 -04:00
}
2022-07-22 00:56:39 -04:00
}
2022-07-22 00:13:12 -04:00
return NewTasks ;
}
2020-02-18 17:19:54 -05:00
/// <summary>
/// Writes our current result to a CSV file. It's expected that this function is called multiple times so results are
/// updated as we go
/// </summary>
2022-07-22 00:56:39 -04:00
void WriteCSVResults ( string InFileName , IEnumerable < BenchmarkTaskBase > InTasks , Dictionary < BenchmarkTaskBase , List < BenchmarkResult > > InResults )
2020-02-18 17:19:54 -05:00
{
2023-03-08 12:43:35 -05:00
Logger . LogInformation ( "Writing results to {InFileName}" , InFileName ) ;
2020-02-18 17:19:54 -05:00
try
{
List < string > Lines = new List < string > ( ) ;
2020-02-21 13:09:10 -05:00
// first line is machine name,CPU count,Iteration 1, Iteration 2 etc
2022-09-03 11:10:17 -04:00
string FirstLine = string . Format ( "{0},{1} Cores,StartTime" , Environment . MachineName , Environment . ProcessorCount ) ;
2020-02-18 17:19:54 -05:00
2020-03-05 13:50:48 -05:00
if ( InTasks . Count ( ) > 0 )
2020-02-18 17:19:54 -05:00
{
2020-03-05 13:50:48 -05:00
int Iterations = InResults [ InTasks . First ( ) ] . Count ( ) ;
2020-02-18 17:19:54 -05:00
if ( Iterations > 0 )
{
for ( int i = 0 ; i < Iterations ; i + + )
{
FirstLine + = "," ;
FirstLine + = string . Format ( "Iteration {0}" , i + 1 ) ;
}
2022-07-22 00:56:39 -04:00
if ( Iterations > 1 )
{
FirstLine + = ",Average" ;
}
2020-02-18 17:19:54 -05:00
}
}
Lines . Add ( FirstLine ) ;
2022-09-03 11:10:17 -04:00
foreach ( var Task in InTasks . Where ( T = > T . SkipReport = = false ) )
2020-02-18 17:19:54 -05:00
{
// start with Name, StartTime
2022-09-03 11:10:17 -04:00
string Line = string . Format ( "{0},{1},{2}" , Task . ProjectName , Task . TaskNameWithModifiers , Task . StartTime . ToString ( "yyyy-dd-MM HH:mm:ss" ) ) ;
2020-02-18 17:19:54 -05:00
2022-07-22 00:56:39 -04:00
IEnumerable < BenchmarkResult > TaskResults = InResults [ Task ] ;
2022-09-03 11:10:17 -04:00
bool DidFail = false ;
2020-02-18 17:19:54 -05:00
// now append all iteration times
2022-07-22 00:56:39 -04:00
foreach ( BenchmarkResult Result in TaskResults )
2020-02-18 17:19:54 -05:00
{
Line + = "," ;
2021-08-09 18:24:52 -04:00
if ( Result . Failed )
2020-02-18 17:19:54 -05:00
{
2022-09-03 11:10:17 -04:00
Line + = "FAILED" ;
DidFail = true ;
2020-02-18 17:19:54 -05:00
}
2022-07-22 00:56:39 -04:00
else
{
Line + = Result . TaskTime . ToString ( @"hh\:mm\:ss" ) ;
}
}
if ( TaskResults . Count ( ) > 1 )
{
2022-09-03 11:10:17 -04:00
if ( DidFail )
{
Line + = ",FAILED" ;
}
else
{
var AvgTime = new TimeSpan ( TaskResults . Select ( R = > R . TaskTime ) . Sum ( T = > T . Ticks ) / InResults [ Task ] . Count ( ) ) ;
Line + = "," + AvgTime . ToString ( @"hh\:mm\:ss" ) ;
}
2020-02-18 17:19:54 -05:00
}
Lines . Add ( Line ) ;
}
2020-03-05 13:50:48 -05:00
File . WriteAllLines ( InFileName , Lines . ToArray ( ) ) ;
2020-02-18 17:19:54 -05:00
}
catch ( Exception Ex )
{
2023-03-08 12:43:35 -05:00
Logger . LogError ( "Failed to write CSV to {InFileName}. {Ex}" , InFileName , Ex ) ;
2020-02-18 17:19:54 -05:00
}
}
/// <summary>
/// Returns true/false based on whether the project supports a client configuration
/// </summary>
/// <param name="ProjectName"></param>
/// <returns></returns>
2020-03-09 17:03:55 -04:00
bool ProjectSupportsClientBuild ( FileReference InProjectFile )
2020-02-18 17:19:54 -05:00
{
2020-03-09 17:03:55 -04:00
if ( InProjectFile = = null )
2020-02-18 17:19:54 -05:00
{
2022-01-06 14:16:53 -05:00
// UE
2020-02-18 17:19:54 -05:00
return true ;
}
2020-03-09 17:03:55 -04:00
ProjectProperties Properties = ProjectUtils . GetProjectProperties ( InProjectFile ) ;
2020-02-18 17:19:54 -05:00
2022-01-06 14:16:53 -05:00
return Properties . Targets . Where ( T = > T . Rules . Type = = TargetType . Client ) . Any ( ) ;
2020-02-18 17:19:54 -05:00
}
}
}