2022-05-31 15:44:53 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
using EpicGames.Core ;
using Microsoft.Extensions.Logging ;
using System ;
using System.Collections.Generic ;
using System.Diagnostics ;
using System.IO ;
using System.Linq ;
using System.Threading ;
using System.Threading.Tasks ;
namespace UnrealBuildTool
{
/// <summary>
/// This executor uses async Tasks to process the action graph
/// </summary>
class ParallelExecutor : ActionExecutor
{
/// <summary>
/// Maximum processor count for local execution.
/// </summary>
[XmlConfigFile]
2022-09-09 19:16:18 -04:00
[Obsolete("ParallelExecutor.MaxProcessorCount is deprecated. Please update xml to use BuildConfiguration.MaxParallelActions")]
#pragma warning disable 0169
private static int MaxProcessorCount ;
#pragma warning restore 0169
2022-05-31 15:44:53 -04:00
/// <summary>
/// Processor count multiplier for local execution. Can be below 1 to reserve CPU for other tasks.
/// When using the local executor (not XGE), run a single action on each CPU core. Note that you can set this to a larger value
/// to get slightly faster build times in many cases, but your computer's responsiveness during compiling may be much worse.
/// This value is ignored if the CPU does not support hyper-threading.
/// </summary>
[XmlConfigFile]
private static double ProcessorCountMultiplier = 1.0 ;
/// <summary>
/// Free memory per action in bytes, used to limit the number of parallel actions if the machine is memory starved.
/// Set to 0 to disable free memory checking.
/// </summary>
[XmlConfigFile]
private static double MemoryPerActionBytes = 1.5 * 1024 * 1024 * 1024 ;
/// <summary>
/// The priority to set for spawned processes.
/// Valid Settings: Idle, BelowNormal, Normal, AboveNormal, High
2023-01-06 19:29:19 -05:00
/// Default: BelowNormal or Normal for an Asymmetrical processor as BelowNormal can cause scheduling issues.
2022-05-31 15:44:53 -04:00
/// </summary>
[XmlConfigFile]
2023-03-18 01:42:55 -04:00
protected static ProcessPriorityClass ProcessPriority = Utils . IsAsymmetricalProcessor ( ) ? ProcessPriorityClass . Normal : ProcessPriorityClass . BelowNormal ;
2022-05-31 15:44:53 -04:00
/// <summary>
/// When enabled, will stop compiling targets after a compile error occurs.
/// </summary>
[XmlConfigFile]
private static bool bStopCompilationAfterErrors = false ;
/// <summary>
/// Whether to show compilation times along with worst offenders or not.
/// </summary>
[XmlConfigFile]
private static bool bShowCompilationTimes = false ;
/// <summary>
/// Whether to show compilation times for each executed action
/// </summary>
[XmlConfigFile]
private static bool bShowPerActionCompilationTimes = false ;
/// <summary>
/// Whether to log command lines for actions being executed
/// </summary>
[XmlConfigFile]
private static bool bLogActionCommandLines = false ;
/// <summary>
/// Add target names for each action executed
/// </summary>
[XmlConfigFile]
private static bool bPrintActionTargetNames = false ;
2023-03-08 17:08:22 -05:00
/// <summary>
/// Whether to take into account the Action's weight when determining to do more work or not.
/// </summary>
[XmlConfigFile]
2023-03-18 01:42:55 -04:00
protected static bool bUseActionWeights = false ;
2023-03-08 17:08:22 -05:00
/// <summary>
/// Whether to show CPU utilization after the work is complete.
/// </summary>
[XmlConfigFile]
2023-03-18 01:42:55 -04:00
protected static bool bShowCPUUtilization = false ;
2023-03-08 17:08:22 -05:00
2022-05-31 15:44:53 -04:00
/// <summary>
/// Collapse non-error output lines
/// </summary>
private bool bCompactOutput = false ;
/// <summary>
/// How many processes that will be executed in parallel
/// </summary>
public int NumParallelProcesses { get ; private set ; }
private static readonly char [ ] LineEndingSplit = new char [ ] { '\n' , '\r' } ;
2022-09-09 19:16:18 -04:00
public static int GetDefaultNumParallelProcesses ( int MaxLocalActions , bool bAllCores , ILogger Logger )
2022-05-31 15:44:53 -04:00
{
double MemoryPerActionBytesComputed = Math . Max ( MemoryPerActionBytes , MemoryPerActionBytesOverride ) ;
if ( MemoryPerActionBytesComputed > MemoryPerActionBytes )
{
Logger . LogInformation ( "Overriding MemoryPerAction with target-defined value of {Memory} bytes" , MemoryPerActionBytesComputed / 1024 / 1024 / 1024 ) ;
}
2023-03-10 19:27:40 -05:00
return Utils . GetMaxActionsToExecuteInParallel ( MaxLocalActions , bAllCores ? 1.0f : ProcessorCountMultiplier , bAllCores , Convert . ToInt64 ( MemoryPerActionBytesComputed ) ) ;
2022-05-31 15:44:53 -04:00
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="MaxLocalActions">How many actions to execute in parallel</param>
2022-09-09 19:16:18 -04:00
/// <param name="bAllCores">Consider logical cores when determining how many total cpu cores are available</param>
2022-05-31 15:44:53 -04:00
/// <param name="bCompactOutput">Should output be written in a compact fashion</param>
/// <param name="Logger">Logger for output</param>
2022-09-09 19:16:18 -04:00
public ParallelExecutor ( int MaxLocalActions , bool bAllCores , bool bCompactOutput , ILogger Logger )
2023-03-09 14:51:40 -05:00
: base ( Logger )
2022-05-31 15:44:53 -04:00
{
XmlConfig . ApplyTo ( this ) ;
2022-09-09 19:16:18 -04:00
// Figure out how many processors to use
NumParallelProcesses = GetDefaultNumParallelProcesses ( MaxLocalActions , bAllCores , Logger ) ;
2022-05-31 15:44:53 -04:00
this . bCompactOutput = bCompactOutput ;
}
/// <summary>
/// Returns the name of this executor
/// </summary>
public override string Name
{
get { return "Parallel" ; }
}
/// <summary>
/// Checks whether the task executor can be used
/// </summary>
/// <returns>True if the task executor can be used</returns>
public static bool IsAvailable ( )
{
return true ;
}
protected class ExecuteResults
{
public List < string > LogLines { get ; private set ; }
public int ExitCode { get ; private set ; }
public TimeSpan ExecutionTime { get ; private set ; }
public TimeSpan ProcessorTime { get ; private set ; }
public string? AdditionalDescription { get ; protected set ; } = null ;
2023-03-18 01:42:55 -04:00
public ExecuteResults ( List < string > LogLines , int ExitCode , TimeSpan ExecutionTime , TimeSpan ProcessorTime , string? AdditionalDescription = null )
2022-05-31 15:44:53 -04:00
{
this . LogLines = LogLines ;
this . ExitCode = ExitCode ;
this . ProcessorTime = ProcessorTime ;
this . ExecutionTime = ExecutionTime ;
2023-03-18 01:42:55 -04:00
this . AdditionalDescription = AdditionalDescription ;
2022-05-31 15:44:53 -04:00
}
public ExecuteResults ( List < string > LogLines , int ExitCode )
{
this . LogLines = LogLines ;
this . ExitCode = ExitCode ;
}
}
2023-03-18 01:42:55 -04:00
internal enum ActionStatus : byte
2023-01-31 01:16:30 -05:00
{
Queued ,
Running ,
Finished ,
Error ,
}
2022-05-31 15:44:53 -04:00
/// <summary>
/// Executes the specified actions locally.
/// </summary>
/// <returns>True if all the tasks successfully executed, or false if any of them failed.</returns>
2022-10-28 13:27:22 -04:00
public override bool ExecuteActions ( IEnumerable < LinkedAction > InputActions , ILogger Logger )
2022-05-31 15:44:53 -04:00
{
2022-10-28 13:27:22 -04:00
if ( ! InputActions . Any ( ) )
{
return true ;
}
int TotalActions = InputActions . Count ( ) ;
2023-01-31 01:16:30 -05:00
int NumCompletedActions = 0 ;
2022-05-31 15:44:53 -04:00
int ActualNumParallelProcesses = Math . Min ( TotalActions , NumParallelProcesses ) ;
using ManagedProcessGroup ProcessGroup = new ManagedProcessGroup ( ) ;
CancellationTokenSource CancellationTokenSource = new CancellationTokenSource ( ) ;
CancellationToken CancellationToken = CancellationTokenSource . Token ;
2023-01-31 01:16:30 -05:00
using ProgressWriter ProgressWriter = new ProgressWriter ( "Compiling C++ source code..." , false , Logger ) ;
2022-05-31 15:44:53 -04:00
2023-01-31 01:16:30 -05:00
Dictionary < LinkedAction , Task < ExecuteResults > > ActionToTaskLookup = new ( ) ;
Logger . LogInformation ( $"------ Building {TotalActions} action(s) started ------" ) ;
var Actions = new LinkedAction [ TotalActions ] ;
var ActionsStatus = new ActionStatus [ TotalActions ] ;
int Index = 0 ;
foreach ( var Action in InputActions )
2022-05-31 15:44:53 -04:00
{
2023-01-31 01:16:30 -05:00
Actions [ Index ] = Action ;
ActionsStatus [ Index ] = ActionStatus . Queued ;
Action . SortIndex = Index + + ;
}
var ActiveTasks = new Task < ExecuteResults > [ ActualNumParallelProcesses ] ;
var ActiveActionIndices = new int [ ActualNumParallelProcesses ] ;
2023-03-31 16:51:20 -04:00
double MaxParallelProcessWeight = ActualNumParallelProcesses ;
double ActiveTaskWeight = 0.0 ;
2023-01-31 01:16:30 -05:00
int ActiveTaskCount = 0 ;
bool Success = true ;
2023-03-08 17:08:22 -05:00
List < float > LoggedCpuUtilization = new List < float > ( ) ;
Stopwatch CpuQueryUtilization = new Stopwatch ( ) ;
CpuQueryUtilization . Start ( ) ;
2023-01-31 01:16:30 -05:00
int FirstQueuedAction = 0 ;
Task LastLoggingTask = Task . CompletedTask ;
// Loop until all actions has been processed and finished
while ( ! CancellationTokenSource . IsCancellationRequested )
{
// Always fill up to allowed number of processes
2023-03-08 17:08:22 -05:00
while ( bUseActionWeights ?
( ActiveTaskWeight < MaxParallelProcessWeight & & ActiveTaskCount < ActualNumParallelProcesses )
: ActiveTaskCount < ActualNumParallelProcesses )
2022-05-31 15:44:53 -04:00
{
2023-01-31 01:16:30 -05:00
// Always traverse from first not started/finished action. We always want to respect the sorting.
// Note that there might be multiple actions we need to search passed to find the one we can run next
int ActionToRunIndex = - 1 ;
bool FoundFirstQueued = false ;
2023-03-08 17:08:22 -05:00
for ( int i = FirstQueuedAction ; i ! = Actions . Length ; + + i )
2023-01-31 01:16:30 -05:00
{
if ( ActionsStatus [ i ] ! = ActionStatus . Queued )
{
continue ;
}
if ( ! FoundFirstQueued ) // We found the first queued action, let's move our search start index
{
FirstQueuedAction = i ;
FoundFirstQueued = true ;
}
// Let's see if all prerequisite actions are finished
bool ReadyToRun = true ;
bool HasError = false ;
foreach ( var Prereq in Actions [ i ] . PrerequisiteActions )
{
ActionStatus PrereqStatus = ActionsStatus [ Prereq . SortIndex ] ;
ReadyToRun = ReadyToRun & & PrereqStatus = = ActionStatus . Finished ;
HasError = HasError | | PrereqStatus = = ActionStatus . Error ;
}
// Not ready, look for next queued action
if ( ! ReadyToRun )
{
// If Prereq has error, this action inherits the error
if ( HasError )
{
ActionsStatus [ i ] = ActionStatus . Error ;
continue ;
}
continue ;
}
ActionToRunIndex = i ;
break ;
}
2023-03-08 17:08:22 -05:00
if ( bShowCPUUtilization & & CpuQueryUtilization . Elapsed . Seconds > 1 )
{
if ( Utils . GetTotalCpuUtilization ( out float CpuUtilization ) )
{
LoggedCpuUtilization . Add ( CpuUtilization ) ;
}
CpuQueryUtilization . Restart ( ) ;
}
2023-01-31 01:16:30 -05:00
// Didn't find an action we can run. Either there are none left or all depends on currently running actions
if ( ActionToRunIndex = = - 1 )
{
break ;
}
// Start new action and add it to the ActiveTasks
var ActionToRun = Actions [ ActionToRunIndex ] ;
Task < ExecuteResults > NewTask = Task . Run ( ( ) = > RunAction ( ActionToRun , ProcessGroup , CancellationToken ) ) ;
ActionToTaskLookup . Add ( ActionToRun , NewTask ) ;
ActiveTasks [ ActiveTaskCount ] = NewTask ;
ActionsStatus [ ActionToRunIndex ] = ActionStatus . Running ;
ActiveActionIndices [ ActiveTaskCount ] = ActionToRunIndex ;
2023-03-08 17:08:22 -05:00
ActiveTaskWeight + = ActionToRun . Weight ;
2023-01-31 01:16:30 -05:00
+ + ActiveTaskCount ;
2022-05-31 15:44:53 -04:00
}
2023-01-31 01:16:30 -05:00
// We tried starting new actions and none was added and none is running, that means that we are done!
if ( ActiveTaskCount = = 0 )
2022-05-31 15:44:53 -04:00
{
2023-01-31 01:16:30 -05:00
break ;
2022-05-31 15:44:53 -04:00
}
2023-01-31 01:16:30 -05:00
// Wait for all tasks and continue if any is done.
Task < ExecuteResults > [ ] TasksToWaitOn = ActiveTasks ;
if ( ActiveTaskCount < ActiveTasks . Length )
2022-05-31 15:44:53 -04:00
{
2023-01-31 01:16:30 -05:00
TasksToWaitOn = new Task < ExecuteResults > [ ActiveTaskCount ] ;
Array . Copy ( ActiveTasks , TasksToWaitOn , ActiveTaskCount ) ;
2022-05-31 15:44:53 -04:00
}
2023-01-31 01:16:30 -05:00
int ActiveTaskIndex = Task . WaitAny ( TasksToWaitOn , Timeout . Infinite , CancellationToken ) ;
// Timeout? This should never happen.. We have a check here just in case
if ( ActiveTaskIndex = = - 1 )
{
Logger . LogError ( "Task timeout? This should never happen. Report to Epic if you see this" ) ;
Success = false ;
break ;
}
int FinishedActionIndex = ActiveActionIndices [ ActiveTaskIndex ] ;
LinkedAction FinishedAction = Actions [ FinishedActionIndex ] ;
Task < ExecuteResults > FinishedTask = ActiveTasks [ ActiveTaskIndex ] ;
bool ActionSuccess = FinishedTask . Result . ExitCode = = 0 ;
ActionsStatus [ FinishedActionIndex ] = ActionSuccess ? ActionStatus . Finished : ActionStatus . Error ;
// Log (chain log entries together so we don't get annoying overlapping logging)
LastLoggingTask = LastLoggingTask . ContinueWith ( ( foo ) = >
{
LogCompletedAction ( FinishedAction , FinishedTask , CancellationTokenSource , ProgressWriter , TotalActions , ref NumCompletedActions , Logger ) ;
} ) ;
// Swap finished task with last and pop last.
- - ActiveTaskCount ;
ActiveTasks [ ActiveTaskIndex ] = ActiveTasks [ ActiveTaskCount ] ;
ActiveActionIndices [ ActiveTaskIndex ] = ActiveActionIndices [ ActiveTaskCount ] ;
2023-03-08 17:08:22 -05:00
ActiveTaskWeight - = FinishedAction . Weight ;
2023-01-31 01:16:30 -05:00
// Update Success status
Success = Success & & ActionSuccess ;
2022-05-31 15:44:53 -04:00
}
2023-01-31 01:16:30 -05:00
// Wait for last logging entry before showing summary
LastLoggingTask . Wait ( ) ;
// Summary
2023-03-08 17:08:22 -05:00
TraceSummary ( ActionToTaskLookup , ProcessGroup , Logger , LoggedCpuUtilization ) ;
2023-01-31 01:16:30 -05:00
return Success ;
2022-05-31 15:44:53 -04:00
}
protected static async Task < ExecuteResults > RunAction ( LinkedAction Action , ManagedProcessGroup ProcessGroup , CancellationToken CancellationToken )
{
CancellationToken . ThrowIfCancellationRequested ( ) ;
using ManagedProcess Process = new ManagedProcess ( ProcessGroup , Action . CommandPath . FullName , Action . CommandArguments , Action . WorkingDirectory . FullName , null , null , ProcessPriority ) ;
using MemoryStream StdOutStream = new MemoryStream ( ) ;
await Process . CopyToAsync ( StdOutStream , CancellationToken ) ;
CancellationToken . ThrowIfCancellationRequested ( ) ;
Process . WaitForExit ( ) ;
List < string > LogLines = Console . OutputEncoding . GetString ( StdOutStream . GetBuffer ( ) , 0 , Convert . ToInt32 ( StdOutStream . Length ) ) . Split ( LineEndingSplit , StringSplitOptions . RemoveEmptyEntries ) . ToList ( ) ;
int ExitCode = Process . ExitCode ;
TimeSpan ProcessorTime = Process . TotalProcessorTime ;
TimeSpan ExecutionTime = Process . ExitTime - Process . StartTime ;
return new ExecuteResults ( LogLines , ExitCode , ExecutionTime , ProcessorTime ) ;
}
private static int s_previousLineLength = - 1 ;
protected void LogCompletedAction ( LinkedAction Action , Task < ExecuteResults > ExecuteTask , CancellationTokenSource CancellationTokenSource , ProgressWriter ProgressWriter , int TotalActions , ref int NumCompletedActions , ILogger Logger )
{
List < string > LogLines = new List < string > ( ) ;
int ExitCode = int . MaxValue ;
TimeSpan ExecutionTime = TimeSpan . Zero ;
TimeSpan ProcessorTime = TimeSpan . Zero ;
string? AdditionalDescription = null ;
if ( ExecuteTask . Status = = TaskStatus . RanToCompletion )
{
ExecuteResults ExecuteTaskResult = ExecuteTask . Result ;
LogLines = ExecuteTaskResult . LogLines ;
ExitCode = ExecuteTaskResult . ExitCode ;
ExecutionTime = ExecuteTaskResult . ExecutionTime ;
ProcessorTime = ExecuteTaskResult . ProcessorTime ;
AdditionalDescription = ExecuteTaskResult . AdditionalDescription ;
}
// Write it to the log
string Description = string . Empty ;
if ( Action . bShouldOutputStatusDescription | | LogLines . Count = = 0 )
{
Description = $"{(Action.CommandDescription ?? Action.CommandPath.GetFileNameWithoutExtension())} {Action.StatusDescription}" . Trim ( ) ;
}
else if ( LogLines . Count > 0 )
{
Description = $"{(Action.CommandDescription ?? Action.CommandPath.GetFileNameWithoutExtension())} {LogLines[0]}" . Trim ( ) ;
}
if ( ! string . IsNullOrEmpty ( AdditionalDescription ) )
{
2023-03-18 01:42:55 -04:00
Description = $"{Description} {AdditionalDescription}" ;
2022-05-31 15:44:53 -04:00
}
lock ( ProgressWriter )
{
int CompletedActions ;
CompletedActions = Interlocked . Increment ( ref NumCompletedActions ) ;
ProgressWriter . Write ( CompletedActions , TotalActions ) ;
// Cancelled
if ( ExitCode = = int . MaxValue )
{
Logger . LogInformation ( "[{CompletedActions}/{TotalActions}] {Description} cancelled" , CompletedActions , TotalActions , Description ) ;
return ;
}
string TargetDetails = "" ;
TargetDescriptor ? Target = Action . Target ;
if ( bPrintActionTargetNames & & Target ! = null )
{
TargetDetails = $"[{Target.Name} {Target.Platform} {Target.Configuration}]" ;
}
if ( bLogActionCommandLines )
{
Logger . LogDebug ( "[{CompletedActions}/{TotalActions}]{TargetDetails} Command: {CommandPath} {CommandArguments}" , CompletedActions , TotalActions , TargetDetails , Action . CommandPath , Action . CommandArguments ) ;
}
string CompilationTimes = "" ;
if ( bShowPerActionCompilationTimes )
{
if ( ProcessorTime . Ticks > 0 )
{
CompilationTimes = $" (Wall: {ExecutionTime.TotalSeconds:0.00}s CPU: {ProcessorTime.TotalSeconds:0.00}s)" ;
}
else
{
CompilationTimes = $" (Wall: {ExecutionTime.TotalSeconds:0.00}s)" ;
}
}
string message = ( $"[{CompletedActions}/{TotalActions}]{TargetDetails}{CompilationTimes} {Description}" ) ;
if ( bCompactOutput )
{
if ( s_previousLineLength > 0 )
{
// move the cursor to the far left position, one line back
Console . CursorLeft = 0 ;
Console . CursorTop - = 1 ;
// clear the line
Console . Write ( "" . PadRight ( s_previousLineLength ) ) ;
// move the cursor back to the left, so output is written to the desired location
Console . CursorLeft = 0 ;
}
}
s_previousLineLength = message . Length ;
2023-03-09 14:51:40 -05:00
LogEventParser Parser = new LogEventParser ( Logger ) ;
WriteToolOutput ( message ) ;
2022-05-31 15:44:53 -04:00
foreach ( string Line in LogLines . Skip ( Action . bShouldOutputStatusDescription ? 0 : 1 ) )
{
// suppress library creation messages when writing compact output
if ( bCompactOutput & & Line . StartsWith ( " Creating library " ) & & Line . EndsWith ( ".exp" ) )
{
continue ;
}
2023-03-09 14:51:40 -05:00
WriteToolOutput ( Line ) ;
2022-05-31 15:44:53 -04:00
// Prevent overwriting of logged lines
s_previousLineLength = - 1 ;
}
if ( ExitCode ! = 0 )
{
// BEGIN TEMPORARY TO CATCH PVS-STUDIO ISSUES
if ( LogLines . Count = = 0 )
{
Logger . LogError ( "{TargetDetails} {Description}: Exited with error code {ExitCode}" , TargetDetails , Description , ExitCode ) ;
Logger . LogInformation ( "{TargetDetails} {Description}: WorkingDirectory {WorkingDirectory}" , TargetDetails , Description , Action . WorkingDirectory ) ;
Logger . LogInformation ( "{TargetDetails} {Description}: {CommandPath} {CommandArguments}" , TargetDetails , Description , Action . CommandPath , Action . CommandArguments ) ;
}
// END TEMPORARY
2023-03-08 17:08:22 -05:00
// prevent overwriting of error text
2022-05-31 15:44:53 -04:00
s_previousLineLength = - 1 ;
// Cancel all other pending tasks
if ( bStopCompilationAfterErrors )
{
CancellationTokenSource . Cancel ( ) ;
}
}
}
}
2023-03-08 17:08:22 -05:00
protected static void TraceSummary ( Dictionary < LinkedAction , Task < ExecuteResults > > Tasks , ManagedProcessGroup ProcessGroup , ILogger Logger , IList < float > ? LoggedCpuUtilization )
2022-05-31 15:44:53 -04:00
{
2023-03-08 17:08:22 -05:00
if ( bShowCPUUtilization & & LoggedCpuUtilization ! = null & & LoggedCpuUtilization . Any ( ) )
{
Logger . LogInformation ( "" ) ;
Logger . LogInformation ( "Average CPU Utilization: {CPUPercentage}%" , ( int ) ( LoggedCpuUtilization . Average ( ) ) ) ;
}
2022-05-31 15:44:53 -04:00
if ( ! bShowCompilationTimes )
{
return ;
}
Logger . LogInformation ( "" ) ;
if ( ProcessGroup . TotalProcessorTime . Ticks > 0 )
{
Logger . LogInformation ( "Total CPU Time: {TotalSeconds} s" , ProcessGroup . TotalProcessorTime . TotalSeconds ) ;
Logger . LogInformation ( "" ) ;
}
var CompletedTasks = Tasks . Where ( x = > x . Value . Status = = TaskStatus . RanToCompletion ) . OrderByDescending ( x = > x . Value . Result . ExecutionTime ) . Take ( 20 ) ;
if ( CompletedTasks . Any ( ) )
{
Logger . LogInformation ( "Compilation Time Top {CompletedTaskCount}" , CompletedTasks . Count ( ) ) ;
Logger . LogInformation ( "" ) ;
foreach ( var Pair in CompletedTasks )
{
string Description = $"{(Pair.Key.Inner.CommandDescription ?? Pair.Key.Inner.CommandPath.GetFileNameWithoutExtension())} {Pair.Key.Inner.StatusDescription}" . Trim ( ) ;
if ( Pair . Value . Result . ProcessorTime . Ticks > 0 )
{
Logger . LogInformation ( "{Description} [ Wall Time {ExecutionTime:0.00} s / CPU Time {ProcessorTime:0.00} s ]" , Description , Pair . Value . Result . ExecutionTime . TotalSeconds , Pair . Value . Result . ProcessorTime . TotalSeconds ) ;
}
else
{
Logger . LogInformation ( "{Description} [ Time {ExecutionTime:0.00} s ]" , Description , Pair . Value . Result . ExecutionTime . TotalSeconds ) ;
}
}
Logger . LogInformation ( "" ) ;
}
}
}
/// <summary>
/// Publicly visible static class that allows external access to the parallel executor config
/// </summary>
public static class ParallelExecutorConfiguration
{
/// <summary>
/// Maximum number of processes that should be used for execution
/// </summary>
2022-09-09 19:16:18 -04:00
public static int GetMaxParallelProcesses ( ILogger Logger ) = > ParallelExecutor . GetDefaultNumParallelProcesses ( 0 , false , Logger ) ;
2022-09-30 11:12:48 -04:00
/// <summary>
/// Maximum number of processes that should be used for execution
/// </summary>
public static int GetMaxParallelProcesses ( int MaxLocalActions , bool bAllCores , ILogger Logger ) = > ParallelExecutor . GetDefaultNumParallelProcesses ( MaxLocalActions , bAllCores , Logger ) ;
2022-05-31 15:44:53 -04:00
}
}