/// <summary>
        /// Constructor
        /// </summary>
        public HybridExecutor()
        {
            this.LocalExecutor  = new ParallelExecutor();
            this.RemoteExecutor = new XGE();

            XmlConfig.ApplyTo(this);

            if (MaxLocalActions == 0)
            {
                MaxLocalActions = Utils.GetPhysicalProcessorCount();
            }
        }
예제 #2
0
        /// <summary>
        /// Determines the maximum number of actions to execute in parallel, taking into account the resources available on this machine.
        /// </summary>
        /// <returns>Max number of actions to execute in parallel</returns>
        public virtual int GetMaxActionsToExecuteInParallel()
        {
            // Get the number of logical processors
            int NumLogicalCores = Utils.GetLogicalProcessorCount();

            // Use WMI to figure out physical cores, excluding hyper threading.
            int NumPhysicalCores = Utils.GetPhysicalProcessorCount();

            if (NumPhysicalCores == -1)
            {
                NumPhysicalCores = NumLogicalCores;
            }

            // The number of actions to execute in parallel is trying to keep the CPU busy enough in presence of I/O stalls.
            int MaxActionsToExecuteInParallel = 0;

            if (NumPhysicalCores < NumLogicalCores && ProcessorCountMultiplier != 1.0)
            {
                // The CPU has more logical cores than physical ones, aka uses hyper-threading.
                // Use multiplier if provided
                MaxActionsToExecuteInParallel = (int)(NumPhysicalCores * ProcessorCountMultiplier);
            }
            else if (NumPhysicalCores < NumLogicalCores && NumPhysicalCores > 4)
            {
                // The CPU has more logical cores than physical ones, aka uses hyper-threading.
                // Use average of logical and physical if we have "lots of cores"
                MaxActionsToExecuteInParallel = Math.Max((int)(NumPhysicalCores + NumLogicalCores) / 2, NumLogicalCores - 4);
            }
            // No hyper-threading. Only kicking off a task per CPU to keep machine responsive.
            else
            {
                MaxActionsToExecuteInParallel = NumPhysicalCores;
            }

#if !NET_CORE
            if (Utils.IsRunningOnMono)
            {
                long PhysicalRAMAvailableMB = (new PerformanceCounter("Mono Memory", "Total Physical Memory").RawValue) / (1024 * 1024);
                // heuristic: give each action at least 1.5GB of RAM (some clang instances will need more) if the total RAM is low, or 1GB on 16+GB machines
                long MinMemoryPerActionMB       = (PhysicalRAMAvailableMB < 16384) ? 3 * 1024 / 2 : 1024;
                int  MaxActionsAffordedByMemory = (int)(Math.Max(1, (PhysicalRAMAvailableMB) / MinMemoryPerActionMB));

                MaxActionsToExecuteInParallel = Math.Min(MaxActionsToExecuteInParallel, MaxActionsAffordedByMemory);
            }
#endif

            MaxActionsToExecuteInParallel = Math.Max(1, Math.Min(MaxActionsToExecuteInParallel, MaxProcessorCount));
            return(MaxActionsToExecuteInParallel);
        }
        public override bool ExecuteActions(List <Action> Actions, bool bLogDetailedActionStats)
        {
            bool SNDBSResult = true;

            if (Actions.Count > 0)
            {
                // Generate any needed templates. Can only generate one per executable, so just use the first Action for reference
                IEnumerable <Action> CommandPaths = Actions.GroupBy(a => a.CommandPath).Select(g => g.FirstOrDefault()).ToList();
                foreach (Action CommandPath in CommandPaths)
                {
                    PrepareToolTemplate(CommandPath);
                }

                // Generate include-rewrite-rules.ini.
                GenerateSNDBSIncludeRewriteRules();

                // Use WMI to figure out physical cores, excluding hyper threading.
                int NumCores = Utils.GetPhysicalProcessorCount();

                // If we failed to detect the number of cores, default to the logical processor count
                if (NumCores == -1)
                {
                    NumCores = System.Environment.ProcessorCount;
                }
                // The number of actions to execute in parallel is trying to keep the CPU busy enough in presence of I/O stalls.
                MaxActionsToExecuteInParallel = 0;
                // The CPU has more logical cores than physical ones, aka uses hyper-threading.
                if (NumCores < System.Environment.ProcessorCount)
                {
                    MaxActionsToExecuteInParallel = (int)(NumCores * ProcessorCountMultiplier);
                }
                // No hyper-threading. Only kicking off a task per CPU to keep machine responsive.
                else
                {
                    MaxActionsToExecuteInParallel = NumCores;
                }
                MaxActionsToExecuteInParallel = Math.Min(MaxActionsToExecuteInParallel, MaxProcessorCount);

                JobNumber = 1;
                Dictionary <Action, ActionThread> ActionThreadDictionary = new Dictionary <Action, ActionThread>();

                while (true)
                {
                    bool bUnexecutedActions = false;
                    foreach (Action Action in Actions)
                    {
                        ActionThread ActionThread        = null;
                        bool         bFoundActionProcess = ActionThreadDictionary.TryGetValue(Action, out ActionThread);
                        if (bFoundActionProcess == false)
                        {
                            bUnexecutedActions = true;
                            if (!ExecuteActions(Actions, ActionThreadDictionary))
                            {
                                return(false);
                            }
                            break;
                        }
                    }

                    if (bUnexecutedActions == false)
                    {
                        break;
                    }
                }

                Log.WriteLineIf(bLogDetailedActionStats, LogEventType.Console, "-------- Begin Detailed Action Stats ----------------------------------------------------------");
                Log.WriteLineIf(bLogDetailedActionStats, LogEventType.Console, "^Action Type^Duration (seconds)^Tool^Task^Using PCH");

                double TotalThreadSeconds = 0;

                // Check whether any of the tasks failed and log action stats if wanted.
                foreach (KeyValuePair <Action, ActionThread> ActionProcess in ActionThreadDictionary)
                {
                    Action       Action       = ActionProcess.Key;
                    ActionThread ActionThread = ActionProcess.Value;

                    // Check for pending actions, preemptive failure
                    if (ActionThread == null)
                    {
                        SNDBSResult = false;
                        continue;
                    }
                    // Check for executed action but general failure
                    if (ActionThread.ExitCode != 0)
                    {
                        SNDBSResult = false;
                    }
                    // Log CPU time, tool and task.
                    double ThreadSeconds = Action.Duration.TotalSeconds;

                    Log.WriteLineIf(bLogDetailedActionStats,
                                    LogEventType.Console,
                                    "^{0}^{1:0.00}^{2}^{3}",
                                    Action.ActionType.ToString(),
                                    ThreadSeconds,
                                    Action.CommandPath.GetFileName(),
                                    Action.StatusDescription);

                    // Keep track of total thread seconds spent on tasks.
                    TotalThreadSeconds += ThreadSeconds;
                }

                Log.TraceInformation("-------- End Detailed Actions Stats -----------------------------------------------------------");

                // Log total CPU seconds and numbers of processors involved in tasks.
                Log.WriteLineIf(bLogDetailedActionStats,
                                LogEventType.Console, "Cumulative thread seconds ({0} processors): {1:0.00}", System.Environment.ProcessorCount, TotalThreadSeconds);
            }

            // Delete the include-rewrite-rules.ini if it was generated.
            if (File.Exists(IncludeRewriteRulesFile.FullName))
            {
                File.Delete(IncludeRewriteRulesFile.FullName);
            }

            return(SNDBSResult);
        }
예제 #4
0
        /// <summary>
        /// Executes the specified actions locally.
        /// </summary>
        /// <returns>True if all the tasks successfully executed, or false if any of them failed.</returns>
        public override bool ExecuteActions(List <Action> Actions, bool bLogDetailedActionStats)
        {
            // Time to sleep after each iteration of the loop in order to not busy wait.
            const float LoopSleepTime = 0.1f;

            // Use WMI to figure out physical cores, excluding hyper threading.
            int NumCores = Utils.GetPhysicalProcessorCount();

            if (NumCores == -1)
            {
                NumCores = System.Environment.ProcessorCount;
            }
            // The number of actions to execute in parallel is trying to keep the CPU busy enough in presence of I/O stalls.
            int MaxActionsToExecuteInParallel = 0;

            if (NumCores < System.Environment.ProcessorCount && ProcessorCountMultiplier != 1.0)
            {
                // The CPU has more logical cores than physical ones, aka uses hyper-threading.
                // Use multiplier if provided
                MaxActionsToExecuteInParallel = (int)(NumCores * ProcessorCountMultiplier);
            }
            else if (NumCores < System.Environment.ProcessorCount && NumCores > 4)
            {
                // The CPU has more logical cores than physical ones, aka uses hyper-threading.
                // Use average of logical and physical if we have "lots of cores"
                MaxActionsToExecuteInParallel = (int)(NumCores + System.Environment.ProcessorCount) / 2;
            }
            // No hyper-threading. Only kicking off a task per CPU to keep machine responsive.
            else
            {
                MaxActionsToExecuteInParallel = NumCores;
            }

            if (Utils.IsRunningOnMono)
            {
                // heuristic: give each action at least 1.5GB of RAM (some clang instances will need more, actually)
                long MinMemoryPerActionMB       = 3 * 1024 / 2;
                long PhysicalRAMAvailableMB     = (new PerformanceCounter("Mono Memory", "Total Physical Memory").RawValue) / (1024 * 1024);
                int  MaxActionsAffordedByMemory = (int)(Math.Max(1, (PhysicalRAMAvailableMB) / MinMemoryPerActionMB));

                MaxActionsToExecuteInParallel = Math.Min(MaxActionsToExecuteInParallel, MaxActionsAffordedByMemory);
            }

            MaxActionsToExecuteInParallel = Math.Max(1, Math.Min(MaxActionsToExecuteInParallel, MaxProcessorCount));

            Log.TraceInformation("Performing {0} actions ({1} in parallel)", Actions.Count, MaxActionsToExecuteInParallel);

            Dictionary <Action, ActionThread> ActionThreadDictionary = new Dictionary <Action, ActionThread>();
            int JobNumber = 1;

            using (ProgressWriter ProgressWriter = new ProgressWriter("Compiling C++ source code...", false))
            {
                int ProgressValue = 0;
                while (true)
                {
                    // Count the number of pending and still executing actions.
                    int NumUnexecutedActions = 0;
                    int NumExecutingActions  = 0;
                    foreach (Action Action in Actions)
                    {
                        ActionThread ActionThread        = null;
                        bool         bFoundActionProcess = ActionThreadDictionary.TryGetValue(Action, out ActionThread);
                        if (bFoundActionProcess == false)
                        {
                            NumUnexecutedActions++;
                        }
                        else if (ActionThread != null)
                        {
                            if (ActionThread.bComplete == false)
                            {
                                NumUnexecutedActions++;
                                NumExecutingActions++;
                            }
                        }
                    }

                    // Update the current progress
                    int NewProgressValue = Actions.Count + 1 - NumUnexecutedActions;
                    if (ProgressValue != NewProgressValue)
                    {
                        ProgressWriter.Write(ProgressValue, Actions.Count + 1);
                        ProgressValue = NewProgressValue;
                    }

                    // If there aren't any pending actions left, we're done executing.
                    if (NumUnexecutedActions == 0)
                    {
                        break;
                    }

                    // If there are fewer actions executing than the maximum, look for pending actions that don't have any outdated
                    // prerequisites.
                    foreach (Action Action in Actions)
                    {
                        ActionThread ActionProcess       = null;
                        bool         bFoundActionProcess = ActionThreadDictionary.TryGetValue(Action, out ActionProcess);
                        if (bFoundActionProcess == false)
                        {
                            if (NumExecutingActions < Math.Max(1, MaxActionsToExecuteInParallel))
                            {
                                // Determine whether there are any prerequisites of the action that are outdated.
                                bool bHasOutdatedPrerequisites = false;
                                bool bHasFailedPrerequisites   = false;
                                foreach (FileItem PrerequisiteItem in Action.PrerequisiteItems)
                                {
                                    if (PrerequisiteItem.ProducingAction != null && Actions.Contains(PrerequisiteItem.ProducingAction))
                                    {
                                        ActionThread PrerequisiteProcess       = null;
                                        bool         bFoundPrerequisiteProcess = ActionThreadDictionary.TryGetValue(PrerequisiteItem.ProducingAction, out PrerequisiteProcess);
                                        if (bFoundPrerequisiteProcess == true)
                                        {
                                            if (PrerequisiteProcess == null)
                                            {
                                                bHasFailedPrerequisites = true;
                                            }
                                            else if (PrerequisiteProcess.bComplete == false)
                                            {
                                                bHasOutdatedPrerequisites = true;
                                            }
                                            else if (PrerequisiteProcess.ExitCode != 0)
                                            {
                                                bHasFailedPrerequisites = true;
                                            }
                                        }
                                        else
                                        {
                                            bHasOutdatedPrerequisites = true;
                                        }
                                    }
                                }

                                // If there are any failed prerequisites of this action, don't execute it.
                                if (bHasFailedPrerequisites)
                                {
                                    // Add a null entry in the dictionary for this action.
                                    ActionThreadDictionary.Add(Action, null);
                                }
                                // If there aren't any outdated prerequisites of this action, execute it.
                                else if (!bHasOutdatedPrerequisites)
                                {
                                    ActionThread ActionThread = new ActionThread(Action, JobNumber, Actions.Count);
                                    JobNumber++;

                                    try
                                    {
                                        ActionThread.Run();
                                    }
                                    catch (Exception ex)
                                    {
                                        throw new BuildException(ex, "Failed to start thread for action: {0} {1}\r\n{2}", Action.CommandPath, Action.CommandArguments, ex.ToString());
                                    }

                                    ActionThreadDictionary.Add(Action, ActionThread);

                                    NumExecutingActions++;
                                }
                            }
                        }
                    }

                    System.Threading.Thread.Sleep(TimeSpan.FromSeconds(LoopSleepTime));
                }
            }

            Log.WriteLineIf(bLogDetailedActionStats, LogEventType.Console, "-------- Begin Detailed Action Stats ----------------------------------------------------------");
            Log.WriteLineIf(bLogDetailedActionStats, LogEventType.Console, "^Action Type^Duration (seconds)^Tool^Task^Using PCH");

            double TotalThreadSeconds = 0;

            // Check whether any of the tasks failed and log action stats if wanted.
            bool   bSuccess = true;
            double TotalBuildProjectTime      = 0;
            double TotalCompileTime           = 0;
            double TotalCreateAppBundleTime   = 0;
            double TotalGenerateDebugInfoTime = 0;
            double TotalLinkTime         = 0;
            double TotalOtherActionsTime = 0;

            foreach (KeyValuePair <Action, ActionThread> ActionProcess in ActionThreadDictionary)
            {
                Action       Action       = ActionProcess.Key;
                ActionThread ActionThread = ActionProcess.Value;

                // Check for pending actions, preemptive failure
                if (ActionThread == null)
                {
                    bSuccess = false;
                    continue;
                }
                // Check for executed action but general failure
                if (ActionThread.ExitCode != 0)
                {
                    bSuccess = false;
                }
                // Log CPU time, tool and task.
                double ThreadSeconds = Action.Duration.TotalSeconds;

                Log.WriteLineIf(bLogDetailedActionStats,
                                LogEventType.Console,
                                "^{0}^{1:0.00}^{2}^{3}^{4}",
                                Action.ActionType.ToString(),
                                ThreadSeconds,
                                Path.GetFileName(Action.CommandPath),
                                Action.StatusDescription,
                                Action.bIsUsingPCH);

                // Update statistics
                switch (Action.ActionType)
                {
                case ActionType.BuildProject:
                    TotalBuildProjectTime += ThreadSeconds;
                    break;

                case ActionType.Compile:
                    TotalCompileTime += ThreadSeconds;
                    break;

                case ActionType.CreateAppBundle:
                    TotalCreateAppBundleTime += ThreadSeconds;
                    break;

                case ActionType.GenerateDebugInfo:
                    TotalGenerateDebugInfoTime += ThreadSeconds;
                    break;

                case ActionType.Link:
                    TotalLinkTime += ThreadSeconds;
                    break;

                default:
                    TotalOtherActionsTime += ThreadSeconds;
                    break;
                }

                // Keep track of total thread seconds spent on tasks.
                TotalThreadSeconds += ThreadSeconds;
            }

            Log.WriteLineIf(bLogDetailedActionStats, LogEventType.Console, "-------- End Detailed Actions Stats -----------------------------------------------------------");

            // Log total CPU seconds and numbers of processors involved in tasks.
            Log.WriteLineIf(bLogDetailedActionStats || UnrealBuildTool.bPrintDebugInfo,
                            LogEventType.Console, "Cumulative thread seconds ({0} processors): {1:0.00}", System.Environment.ProcessorCount, TotalThreadSeconds);

            // Log detailed stats
            Log.WriteLineIf(bLogDetailedActionStats || UnrealBuildTool.bPrintDebugInfo,
                            LogEventType.Console,
                            "Cumulative action seconds ({0} processors): {1:0.00} building projects, {2:0.00} compiling, {3:0.00} creating app bundles, {4:0.00} generating debug info, {5:0.00} linking, {6:0.00} other",
                            System.Environment.ProcessorCount,
                            TotalBuildProjectTime,
                            TotalCompileTime,
                            TotalCreateAppBundleTime,
                            TotalGenerateDebugInfoTime,
                            TotalLinkTime,
                            TotalOtherActionsTime
                            );

            return(bSuccess);
        }