示例#1
0
        internal static ExecutionResult ExecuteLocalActions(List<Action> InLocalActions, Dictionary<Action, ActionThread> InActionThreadDictionary, int TotalNumJobs)
        {
            // Time to sleep after each iteration of the loop in order to not busy wait.
            const float LoopSleepTime = 0.1f;

            ExecutionResult LocalActionsResult = ExecutionResult.TasksSucceeded;

            while (true)
            {
                // Count the number of pending and still executing actions.
                int NumUnexecutedActions = 0;
                int NumExecutingActions = 0;
                foreach (Action Action in InLocalActions)
                {
                    ActionThread ActionThread = null;
                    bool bFoundActionProcess = InActionThreadDictionary.TryGetValue(Action, out ActionThread);
                    if (bFoundActionProcess == false)
                    {
                        NumUnexecutedActions++;
                    }
                    else if (ActionThread != null)
                    {
                        if (ActionThread.bComplete == false)
                        {
                            NumUnexecutedActions++;
                            NumExecutingActions++;
                        }
                    }
                }

                // 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 InLocalActions)
                {
                    ActionThread ActionProcess = null;
                    bool bFoundActionProcess = InActionThreadDictionary.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 && InLocalActions.Contains(PrerequisiteItem.ProducingAction))
                                {
                                    ActionThread PrerequisiteProcess = null;
                                    bool bFoundPrerequisiteProcess = InActionThreadDictionary.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.
                                InActionThreadDictionary.Add(Action, null);
                            }
                            // If there aren't any outdated prerequisites of this action, execute it.
                            else if (!bHasOutdatedPrerequisites)
                            {
                                ActionThread ActionThread = new ActionThread(Action, JobNumber, TotalNumJobs);
                                ActionThread.Run();

                                InActionThreadDictionary.Add(Action, ActionThread);

                                NumExecutingActions++;
                                JobNumber++;
                            }
                        }
                    }
                }

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

            return LocalActionsResult;
        }
示例#2
0
        /**
         * Executes the specified actions locally.
         * @return True if all the tasks successfully executed, or false if any of them failed.
         */
        public static bool ExecuteActions(List<Action> Actions)
        {
            // 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 = 0;
            if (!Utils.IsRunningOnMono)
            {
                try
                {
                    using (var Mos = new System.Management.ManagementObjectSearcher("Select * from Win32_Processor"))
                    {
                        var MosCollection = Mos.Get();
                        foreach (var Item in MosCollection)
                        {
                            NumCores += int.Parse(Item["NumberOfCores"].ToString());
                        }
                    }
                }
                catch (Exception Ex)
                {
                    Log.TraceWarning("Unable to get the number of Cores: {0}", Ex.ToString());
                    Log.TraceWarning("Falling back to processor count.");
                }
            }
            // On some systems this requires a hot fix to work so we fall back to using the (logical) processor count.
            if (NumCores == 0)
            {
                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;
            // The CPU has more logical cores than physical ones, aka uses hyper-threading.
            if (NumCores < System.Environment.ProcessorCount)
            {
                MaxActionsToExecuteInParallel = (int)(NumCores * BuildConfiguration.ProcessorCountMultiplier);
            }
            // 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.Min(MaxActionsToExecuteInParallel, BuildConfiguration.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++;
                                    ActionThread.Run();

                                    ActionThreadDictionary.Add(Action, ActionThread);

                                    NumExecutingActions++;
                                }
                            }
                        }
                    }

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

            Log.WriteLineIf(BuildConfiguration.bLogDetailedActionStats, TraceEventType.Information, "-------- Begin Detailed Action Stats ----------------------------------------------------------");
            Log.WriteLineIf(BuildConfiguration.bLogDetailedActionStats, TraceEventType.Information, "^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;
            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(BuildConfiguration.bLogDetailedActionStats,
                    TraceEventType.Information,
                    "^{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:
                        STBuildTool.TotalBuildProjectTime += ThreadSeconds;
                        break;

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

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

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

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

                    default:
                        STBuildTool.TotalOtherActionsTime += ThreadSeconds;
                        break;
                }

                // 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(BuildConfiguration.bLogDetailedActionStats || BuildConfiguration.bPrintDebugInfo,
                TraceEventType.Information, "Cumulative thread seconds ({0} processors): {1:0.00}", System.Environment.ProcessorCount, TotalThreadSeconds);

            return bSuccess;
        }
示例#3
0
        internal static ExecutionResult ExecuteActions(List<Action> InActions, Dictionary<Action, ActionThread> InActionThreadDictionary)
        {
            // Build the script file that will be executed by SN-DBS
            StreamWriter ScriptFile;
            string ScriptFilename = Path.Combine(BuildConfiguration.BaseIntermediatePath, "SNDBS.bat");

            FileStream ScriptFileStream = new FileStream(ScriptFilename, FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
            ScriptFile = new StreamWriter(ScriptFileStream);
            ScriptFile.AutoFlush = true;

            int NumScriptedActions = 0;
            List<Action> LocalActions = new List<Action>();
            ActionThread DummyActionThread = new ActionThread(null, 1, 1);
            foreach (Action Action in InActions)
            {
                ActionThread ActionProcess = null;
                bool bFoundActionProcess = InActionThreadDictionary.TryGetValue(Action, out ActionProcess);
                if (bFoundActionProcess == false)
                {
                    // 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 && InActions.Contains(PrerequisiteItem.ProducingAction))
                        {
                            ActionThread PrerequisiteProcess = null;
                            bool bFoundPrerequisiteProcess = InActionThreadDictionary.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.
                        InActionThreadDictionary.Add(Action, null);
                    }
                    // If there aren't any outdated prerequisites of this action, execute it.
                    else if (!bHasOutdatedPrerequisites)
                    {
                        if (Action.bCanExecuteRemotely == false)
                        {
                            // Execute locally
                            LocalActions.Add(Action);
                        }
                        else
                        {
                            // Add to script for execution by SN-DBS
                            string NewCommandArguments = "\"" + Action.CommandPath + "\"" + " " + Action.CommandArguments;
                            ScriptFile.WriteLine(ActionThread.ExpandEnvironmentVariables(NewCommandArguments));
                            InActionThreadDictionary.Add(Action, DummyActionThread);
                            Action.StartTime = Action.EndTime = DateTimeOffset.Now;
                            Log.TraceInformation("[{0}/{1}] {2} {3}", JobNumber, InActions.Count, Action.CommandDescription, Action.StatusDescription);
                            JobNumber++;
                            NumScriptedActions++;
                        }
                    }
                }
            }

            ScriptFile.Flush();
            ScriptFile.Close();
            ScriptFile.Dispose();
            ScriptFile = null;

            if (NumScriptedActions > 0)
            {
                // Create the process
                ProcessStartInfo PSI = new ProcessStartInfo("dbsbuild", "-p UE4 -s " + BuildConfiguration.BaseIntermediatePath + "/sndbs.bat");
                PSI.RedirectStandardOutput = true;
                PSI.RedirectStandardError = true;
                PSI.UseShellExecute = false;
                PSI.CreateNoWindow = true;
                PSI.WorkingDirectory = Path.GetFullPath("."); ;
                Process NewProcess = new Process();
                NewProcess.StartInfo = PSI;
                NewProcess.OutputDataReceived += new DataReceivedEventHandler(ActionDebugOutput);
                NewProcess.ErrorDataReceived += new DataReceivedEventHandler(ActionDebugOutput);
                DateTimeOffset StartTime = DateTimeOffset.Now;
                NewProcess.Start();
                NewProcess.BeginOutputReadLine();
                NewProcess.BeginErrorReadLine();

                NewProcess.WaitForExit();

                TimeSpan Duration;
                DateTimeOffset EndTime = DateTimeOffset.Now;
                if (EndTime == DateTimeOffset.MinValue)
                {
                    Duration = DateTimeOffset.Now - StartTime;
                }
                else
                {
                    Duration = EndTime - StartTime;
                }

                DummyActionThread.bComplete = true;
                int ExitCode = NewProcess.ExitCode;
                if (ExitCode != 0)
                {
                    return ExecutionResult.TasksFailed;
                }
                else
                {
                    STBuildTool.TotalCompileTime += Duration.TotalSeconds;
                }
            }

            // Execute local tasks
            if (LocalActions.Count > 0)
            {
                return ExecuteLocalActions(LocalActions, InActionThreadDictionary, InActions.Count);
            }

            return ExecutionResult.TasksSucceeded;
        }
示例#4
0
        public static bool ExecuteActions(List<Action> Actions)
        {
            bool bDistccResult = true;
            if (Actions.Count > 0)
            {
                // Time to sleep after each iteration of the loop in order to not busy wait.
                const float LoopSleepTime = 0.1f;

                int MaxActionsToExecuteInParallel = 0;
                string UserDir = Environment.GetEnvironmentVariable("HOME");
                string HostsInfo = UserDir + "/.dmucs/hosts-info";
                System.IO.StreamReader File = new System.IO.StreamReader(HostsInfo);
                string Line = null;
                while ((Line = File.ReadLine()) != null)
                {
                    var HostInfo = Line.Split(' ');
                    if (HostInfo.Count() == 3)
                    {
                        int NumCPUs = 0;
                        if (System.Int32.TryParse(HostInfo[1], out NumCPUs))
                        {
                            MaxActionsToExecuteInParallel += NumCPUs;
                        }
                    }
                }
                File.Close();

                if (BuildConfiguration.bAllowDistccLocalFallback == false)
                {
                    Environment.SetEnvironmentVariable("DISTCC_FALLBACK", "0");
                }

                string DistccExecutable = BuildConfiguration.DistccExecutablesPath + "/distcc";
                string GetHostExecutable = BuildConfiguration.DistccExecutablesPath + "/gethost";

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

                Dictionary<Action, ActionThread> ActionThreadDictionary = new Dictionary<Action, ActionThread>();
                int JobNumber = 1;
                using (ProgressWriter ProgressWriter = new ProgressWriter("Compiling 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)
                                    {
                                        if ((Action.ActionType == ActionType.Compile || Action.ActionType == ActionType.Link) && DistccExecutable != null && GetHostExecutable != null)
                                        {
                                            string NewCommandArguments = "--wait -1 \"" + DistccExecutable + "\" " + Action.CommandPath + " " + Action.CommandArguments;
                                            Action.CommandPath = GetHostExecutable;
                                            Action.CommandArguments = NewCommandArguments;
                                        }

                                        ActionThread ActionThread = new ActionThread(Action, JobNumber, Actions.Count);
                                        JobNumber++;
                                        ActionThread.Run();

                                        ActionThreadDictionary.Add(Action, ActionThread);

                                        NumExecutingActions++;
                                    }
                                }
                            }
                        }

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

                Log.WriteLineIf(BuildConfiguration.bLogDetailedActionStats, TraceEventType.Information, "-------- Begin Detailed Action Stats ----------------------------------------------------------");
                Log.WriteLineIf(BuildConfiguration.bLogDetailedActionStats, TraceEventType.Information, "^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)
                    {
                        bDistccResult = false;
                        continue;
                    }
                    // Check for executed action but general failure
                    if (ActionThread.ExitCode != 0)
                    {
                        bDistccResult = false;
                    }
                    // Log CPU time, tool and task.
                    double ThreadSeconds = Action.Duration.TotalSeconds;

                    Log.WriteLineIf(BuildConfiguration.bLogDetailedActionStats,
                        TraceEventType.Information,
                        "^{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:
                            STBuildTool.TotalBuildProjectTime += ThreadSeconds;
                            break;

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

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

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

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

                        default:
                            STBuildTool.TotalOtherActionsTime += ThreadSeconds;
                            break;
                    }

                    // 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(BuildConfiguration.bLogDetailedActionStats || BuildConfiguration.bPrintDebugInfo,
                    TraceEventType.Information, "Cumulative thread seconds ({0} processors): {1:0.00}", System.Environment.ProcessorCount, TotalThreadSeconds);
            }
            return bDistccResult;
        }