2022-05-31 15:44:53 -04:00
|
|
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
2023-05-30 18:38:07 -04:00
|
|
|
using EpicGames.Core;
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
2023-08-10 15:38:17 -04:00
|
|
|
using UnrealBuildBase;
|
2023-05-05 11:19:57 -04:00
|
|
|
using UnrealBuildTool.Artifacts;
|
2022-05-31 15:44:53 -04:00
|
|
|
|
|
|
|
|
namespace UnrealBuildTool
|
|
|
|
|
{
|
2023-05-05 11:19:57 -04:00
|
|
|
|
2022-05-31 15:44:53 -04:00
|
|
|
/// <summary>
|
|
|
|
|
/// This executor uses async Tasks to process the action graph
|
|
|
|
|
/// </summary>
|
|
|
|
|
class ParallelExecutor : ActionExecutor
|
|
|
|
|
{
|
2023-05-30 19:55:37 -04:00
|
|
|
/// <summary>
|
|
|
|
|
/// Maximum processor count for local execution.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[XmlConfigFile]
|
|
|
|
|
[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]
|
2023-05-30 19:55:37 -04:00
|
|
|
private static double ProcessorCountMultiplier = 1.0;
|
2022-05-31 15:44:53 -04:00
|
|
|
|
|
|
|
|
/// <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]
|
2023-05-30 19:55:37 -04:00
|
|
|
private static double MemoryPerActionBytes = 1.5 * 1024 * 1024 * 1024;
|
2022-05-31 15:44:53 -04:00
|
|
|
|
|
|
|
|
/// <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-05-30 19:55:37 -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]
|
2023-05-30 19:55:37 -04:00
|
|
|
private static bool bStopCompilationAfterErrors = false;
|
2022-05-31 15:44:53 -04:00
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Whether to show compilation times along with worst offenders or not.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[XmlConfigFile]
|
2023-08-10 15:38:17 -04:00
|
|
|
private static bool bShowCompilationTimes = Unreal.IsBuildMachine();
|
2022-05-31 15:44:53 -04:00
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Whether to show compilation times for each executed action
|
|
|
|
|
/// </summary>
|
|
|
|
|
[XmlConfigFile]
|
2023-08-10 15:38:17 -04:00
|
|
|
private static bool bShowPerActionCompilationTimes = Unreal.IsBuildMachine();
|
2022-05-31 15:44:53 -04:00
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Whether to log command lines for actions being executed
|
|
|
|
|
/// </summary>
|
|
|
|
|
[XmlConfigFile]
|
2023-05-30 19:55:37 -04:00
|
|
|
private static bool bLogActionCommandLines = false;
|
2022-05-31 15:44:53 -04:00
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Add target names for each action executed
|
|
|
|
|
/// </summary>
|
|
|
|
|
[XmlConfigFile]
|
2023-05-30 19:55:37 -04:00
|
|
|
private static bool bPrintActionTargetNames = false;
|
2022-05-31 15:44:53 -04:00
|
|
|
|
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-05-30 19:55:37 -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-08-10 15:38:17 -04:00
|
|
|
protected static bool bShowCPUUtilization = Unreal.IsBuildMachine();
|
2023-03-08 17:08:22 -05:00
|
|
|
|
2022-05-31 15:44:53 -04:00
|
|
|
/// <summary>
|
|
|
|
|
/// Collapse non-error output lines
|
|
|
|
|
/// </summary>
|
2023-05-30 19:55:37 -04:00
|
|
|
private bool bCompactOutput = false;
|
2022-05-31 15:44:53 -04:00
|
|
|
|
|
|
|
|
/// <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>
|
2023-05-30 18:01:50 -04:00
|
|
|
public override string Name => "Parallel";
|
2022-05-31 15:44:53 -04:00
|
|
|
|
|
|
|
|
/// <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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2023-05-05 11:19:57 -04:00
|
|
|
/// Create an action queue
|
2022-05-31 15:44:53 -04:00
|
|
|
/// </summary>
|
2023-05-05 11:19:57 -04:00
|
|
|
/// <param name="actionsToExecute">Actions to be executed</param>
|
|
|
|
|
/// <param name="actionArtifactCache">Artifact cache</param>
|
|
|
|
|
/// <param name="logger">Logging interface</param>
|
|
|
|
|
/// <returns>Action queue</returns>
|
|
|
|
|
public ImmediateActionQueue CreateActionQueue(IEnumerable<LinkedAction> actionsToExecute, IActionArtifactCache? actionArtifactCache, ILogger logger)
|
2022-05-31 15:44:53 -04:00
|
|
|
{
|
2023-07-28 09:55:20 -04:00
|
|
|
return new(actionsToExecute, actionArtifactCache, NumParallelProcesses, "Compiling C++ source code...", x => WriteToolOutput(x), () => FlushToolOutput(), logger)
|
2023-05-05 11:19:57 -04:00
|
|
|
{
|
|
|
|
|
ShowCompilationTimes = bShowCompilationTimes,
|
|
|
|
|
ShowCPUUtilization = bShowCPUUtilization,
|
|
|
|
|
PrintActionTargetNames = bPrintActionTargetNames,
|
|
|
|
|
LogActionCommandLines = bLogActionCommandLines,
|
|
|
|
|
ShowPerActionCompilationTimes = bShowPerActionCompilationTimes,
|
|
|
|
|
CompactOutput = bCompactOutput,
|
|
|
|
|
StopCompilationAfterErrors = bStopCompilationAfterErrors,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
|
|
public override async Task<bool> ExecuteActionsAsync(IEnumerable<LinkedAction> ActionsToExecute, ILogger Logger, IActionArtifactCache? actionArtifactCache)
|
|
|
|
|
{
|
|
|
|
|
if (!ActionsToExecute.Any())
|
2022-10-28 13:27:22 -04:00
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-05 11:19:57 -04:00
|
|
|
// The "useAutomaticQueue" should always be true unless manual queue is being tested
|
|
|
|
|
bool useAutomaticQueue = true;
|
|
|
|
|
if (useAutomaticQueue)
|
2022-05-31 15:44:53 -04:00
|
|
|
{
|
2023-05-05 11:19:57 -04:00
|
|
|
using ImmediateActionQueue queue = CreateActionQueue(ActionsToExecute, actionArtifactCache, Logger);
|
|
|
|
|
int actionLimit = Math.Min(NumParallelProcesses, queue.TotalActions);
|
2023-07-09 18:50:33 -04:00
|
|
|
queue.CreateAutomaticRunner(action => RunAction(queue, action), bUseActionWeights, actionLimit, NumParallelProcesses);
|
2023-05-05 11:19:57 -04:00
|
|
|
queue.Start();
|
|
|
|
|
queue.StartManyActions();
|
|
|
|
|
return await queue.RunTillDone();
|
2023-01-31 01:16:30 -05:00
|
|
|
}
|
2023-05-05 11:19:57 -04:00
|
|
|
else
|
2023-01-31 01:16:30 -05:00
|
|
|
{
|
2023-05-05 11:19:57 -04:00
|
|
|
using ImmediateActionQueue queue = CreateActionQueue(ActionsToExecute, actionArtifactCache, Logger);
|
|
|
|
|
int actionLimit = Math.Min(NumParallelProcesses, queue.TotalActions);
|
|
|
|
|
ImmediateActionQueueRunner runner = queue.CreateManualRunner(action => RunAction(queue, action), bUseActionWeights, actionLimit, actionLimit);
|
|
|
|
|
queue.Start();
|
|
|
|
|
using Timer timer = new((_) => queue.StartManyActions(runner), null, 0, 500);
|
|
|
|
|
queue.StartManyActions();
|
|
|
|
|
return await queue.RunTillDone();
|
2022-05-31 15:44:53 -04:00
|
|
|
}
|
2023-05-05 11:19:57 -04:00
|
|
|
}
|
2023-01-31 01:16:30 -05:00
|
|
|
|
2023-05-10 08:40:51 -04:00
|
|
|
private static Func<Task>? RunAction(ImmediateActionQueue queue, LinkedAction action)
|
2023-05-05 11:19:57 -04:00
|
|
|
{
|
|
|
|
|
return async () =>
|
|
|
|
|
{
|
2023-05-10 08:40:51 -04:00
|
|
|
ExecuteResults results = await RunAction(action, queue.ProcessGroup, queue.CancellationToken);
|
|
|
|
|
queue.OnActionCompleted(action, results.ExitCode == 0, results);
|
2023-05-05 11:19:57 -04:00
|
|
|
};
|
2022-05-31 15:44:53 -04:00
|
|
|
}
|
|
|
|
|
|
2023-06-13 20:26:15 -04:00
|
|
|
protected static async Task<ExecuteResults> RunAction(LinkedAction Action, ManagedProcessGroup ProcessGroup, CancellationToken CancellationToken, string? AdditionalDescription = null)
|
2022-05-31 15:44:53 -04:00
|
|
|
{
|
|
|
|
|
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();
|
|
|
|
|
|
2023-12-01 18:29:06 -05:00
|
|
|
await Process.WaitForExitAsync(CancellationToken);
|
2022-05-31 15:44:53 -04:00
|
|
|
|
|
|
|
|
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;
|
2023-06-13 20:26:15 -04:00
|
|
|
return new ExecuteResults(LogLines, ExitCode, ExecutionTime, ProcessorTime, AdditionalDescription);
|
2022-05-31 15:44:53 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <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
|
|
|
}
|
|
|
|
|
}
|