diff options
author | Han-Wen Nienhuys <hanwen@google.com> | 2015-02-25 16:45:20 +0100 |
---|---|---|
committer | Han-Wen Nienhuys <hanwen@google.com> | 2015-02-25 16:45:20 +0100 |
commit | d08b27fa9701fecfdb69e1b0d1ac2459efc2129b (patch) | |
tree | 5d50963026239ca5aebfb47ea5b8db7e814e57c8 /src/main/java/com/google/devtools/build/lib/runtime/commands/ProfileCommand.java |
Update from Google.
--
MOE_MIGRATED_REVID=85702957
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/runtime/commands/ProfileCommand.java')
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/runtime/commands/ProfileCommand.java | 771 |
1 files changed, 771 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/ProfileCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/ProfileCommand.java new file mode 100644 index 0000000000..7b91dc7fa3 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/ProfileCommand.java @@ -0,0 +1,771 @@ +// Copyright 2014 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.google.devtools.build.lib.runtime.commands; + +import com.google.common.base.Function; +import com.google.common.base.Functions; +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; +import com.google.common.collect.Ordering; +import com.google.common.collect.TreeMultimap; +import com.google.devtools.build.lib.actions.MiddlemanAction; +import com.google.devtools.build.lib.events.Event; +import com.google.devtools.build.lib.events.EventHandler; +import com.google.devtools.build.lib.profiler.ProfileInfo; +import com.google.devtools.build.lib.profiler.ProfileInfo.CriticalPathEntry; +import com.google.devtools.build.lib.profiler.ProfileInfo.InfoListener; +import com.google.devtools.build.lib.profiler.ProfilePhase; +import com.google.devtools.build.lib.profiler.ProfilePhaseStatistics; +import com.google.devtools.build.lib.profiler.ProfilerTask; +import com.google.devtools.build.lib.profiler.chart.AggregatingChartCreator; +import com.google.devtools.build.lib.profiler.chart.Chart; +import com.google.devtools.build.lib.profiler.chart.ChartCreator; +import com.google.devtools.build.lib.profiler.chart.DetailedChartCreator; +import com.google.devtools.build.lib.profiler.chart.HtmlChartVisitor; +import com.google.devtools.build.lib.runtime.BlazeCommand; +import com.google.devtools.build.lib.runtime.BlazeRuntime; +import com.google.devtools.build.lib.runtime.Command; +import com.google.devtools.build.lib.util.ExitCode; +import com.google.devtools.build.lib.util.StringUtil; +import com.google.devtools.build.lib.util.TimeUtilities; +import com.google.devtools.build.lib.vfs.Path; +import com.google.devtools.common.options.Converters; +import com.google.devtools.common.options.Option; +import com.google.devtools.common.options.OptionsBase; +import com.google.devtools.common.options.OptionsParser; +import com.google.devtools.common.options.OptionsProvider; + +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; + +/** + * Command line wrapper for analyzing Blaze build profiles. + */ +@Command(name = "analyze-profile", + options = { ProfileCommand.ProfileOptions.class }, + shortDescription = "Analyzes build profile data.", + help = "resource:analyze-profile.txt", + allowResidue = true, + mustRunInWorkspace = false) +public final class ProfileCommand implements BlazeCommand { + + private final String TWO_COLUMN_FORMAT = "%-37s %10s\n"; + private final String THREE_COLUMN_FORMAT = "%-28s %10s %8s\n"; + + public static class DumpConverter extends Converters.StringSetConverter { + public DumpConverter() { + super("text", "raw", "text-unsorted", "raw-unsorted"); + } + } + + public static class ProfileOptions extends OptionsBase { + @Option(name = "dump", + abbrev='d', + converter = DumpConverter.class, + defaultValue = "null", + help = "output full profile data dump either in human-readable 'text' format or" + + " script-friendly 'raw' format, either sorted or unsorted.") + public String dumpMode; + + @Option(name = "html", + defaultValue = "false", + help = "If present, an HTML file visualizing the tasks of the profiled build is created. " + + "The name of the html file is the name of the profile file plus '.html'.") + public boolean html; + + @Option(name = "html_pixels_per_second", + defaultValue = "50", + help = "Defines the scale of the time axis of the task diagram. The unit is " + + "pixels per second. Default is 50 pixels per second. ") + public int htmlPixelsPerSecond; + + @Option(name = "html_details", + defaultValue = "false", + help = "If --html_details is present, the task diagram contains all tasks of the profile. " + + "If --nohtml_details is present, an aggregated diagram is generated. The default is " + + "to generate an aggregated diagram.") + public boolean htmlDetails; + + @Option(name = "vfs_stats", + defaultValue = "false", + help = "If present, include VFS path statistics.") + public boolean vfsStats; + + @Option(name = "vfs_stats_limit", + defaultValue = "-1", + help = "Maximum number of VFS path statistics to print.") + public int vfsStatsLimit; + } + + private Function<String, String> currentPathMapping = Functions.<String>identity(); + + private InfoListener getInfoListener(final BlazeRuntime runtime) { + return new InfoListener() { + private final EventHandler reporter = runtime.getReporter(); + + @Override + public void info(String text) { + reporter.handle(Event.info(text)); + } + + @Override + public void warn(String text) { + reporter.handle(Event.warn(text)); + } + }; + } + + @Override + public void editOptions(BlazeRuntime runtime, OptionsParser optionsParser) {} + + @Override + public ExitCode exec(final BlazeRuntime runtime, OptionsProvider options) { + ProfileOptions opts = + options.getOptions(ProfileOptions.class); + + if (!opts.vfsStats) { + opts.vfsStatsLimit = 0; + } + + currentPathMapping = new Function<String, String>() { + @Override + public String apply(String input) { + if (runtime.getWorkspaceName().isEmpty()) { + return input; + } else { + return input.substring(input.lastIndexOf("/" + runtime.getWorkspaceName()) + 1); + } + } + }; + + PrintStream out = new PrintStream(runtime.getReporter().getOutErr().getOutputStream()); + try { + runtime.getReporter().handle(Event.warn( + null, "This information is intended for consumption by Blaze developers" + + " only, and may change at any time. Script against it at your own risk")); + + for (String name : options.getResidue()) { + Path profileFile = runtime.getWorkingDirectory().getRelative(name); + try { + ProfileInfo info = ProfileInfo.loadProfileVerbosely( + profileFile, getInfoListener(runtime)); + if (opts.dumpMode != null) { + dumpProfile(runtime, info, out, opts.dumpMode); + } else if (opts.html) { + createHtml(runtime, info, profileFile, opts); + } else { + createText(runtime, info, out, opts); + } + } catch (IOException e) { + runtime.getReporter().handle(Event.error( + null, "Failed to process file " + name + ": " + e.getMessage())); + } + } + } finally { + out.flush(); + } + return ExitCode.SUCCESS; + } + + private void createText(BlazeRuntime runtime, ProfileInfo info, PrintStream out, + ProfileOptions opts) { + List<ProfilePhaseStatistics> statistics = getStatistics(runtime, info, opts); + + for (ProfilePhaseStatistics stat : statistics) { + String title = stat.getTitle(); + + if (!title.equals("")) { + out.println("\n=== " + title.toUpperCase() + " ===\n"); + } + out.print(stat.getStatistics()); + } + } + + private void createHtml(BlazeRuntime runtime, ProfileInfo info, Path profileFile, + ProfileOptions opts) + throws IOException { + Path htmlFile = + profileFile.getParentDirectory().getChild(profileFile.getBaseName() + ".html"); + List<ProfilePhaseStatistics> statistics = getStatistics(runtime, info, opts); + + runtime.getReporter().handle(Event.info("Creating HTML output in " + htmlFile)); + + ChartCreator chartCreator = + opts.htmlDetails ? new DetailedChartCreator(info, statistics) + : new AggregatingChartCreator(info, statistics); + Chart chart = chartCreator.create(); + OutputStream out = new BufferedOutputStream(htmlFile.getOutputStream()); + try { + chart.accept(new HtmlChartVisitor(new PrintStream(out), opts.htmlPixelsPerSecond)); + } finally { + try { + out.close(); + } catch (IOException e) { + // Ignore + } + } + } + + private List<ProfilePhaseStatistics> getStatistics( + BlazeRuntime runtime, ProfileInfo info, ProfileOptions opts) { + try { + ProfileInfo.aggregateProfile(info, getInfoListener(runtime)); + runtime.getReporter().handle(Event.info("Analyzing relationships")); + + info.analyzeRelationships(); + + List<ProfilePhaseStatistics> statistics = new ArrayList<>(); + + // Print phase durations and total execution time + ByteArrayOutputStream byteOutput = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(byteOutput, false, "UTF-8"); + long duration = 0; + for (ProfilePhase phase : ProfilePhase.values()) { + ProfileInfo.Task phaseTask = info.getPhaseTask(phase); + if (phaseTask != null) { + duration += info.getPhaseDuration(phaseTask); + } + } + for (ProfilePhase phase : ProfilePhase.values()) { + ProfileInfo.Task phaseTask = info.getPhaseTask(phase); + if (phaseTask != null) { + long phaseDuration = info.getPhaseDuration(phaseTask); + out.printf(THREE_COLUMN_FORMAT, "Total " + phase.nick + " phase time", + TimeUtilities.prettyTime(phaseDuration), prettyPercentage(phaseDuration, duration)); + } + } + out.printf(THREE_COLUMN_FORMAT, "Total run time", TimeUtilities.prettyTime(duration), + "100.00%"); + statistics.add(new ProfilePhaseStatistics("Phase Summary Information", + new String(byteOutput.toByteArray(), "UTF-8"))); + + // Print details of major phases + if (duration > 0) { + statistics.add(formatInitPhaseStatistics(info, opts)); + statistics.add(formatLoadingPhaseStatistics(info, opts)); + statistics.add(formatAnalysisPhaseStatistics(info, opts)); + ProfilePhaseStatistics stat = formatExecutionPhaseStatistics(info, opts); + if (stat != null) { + statistics.add(stat); + } + } + + return statistics; + } catch (UnsupportedEncodingException e) { + throw new AssertionError("Should not happen since, UTF8 is available on all JVMs"); + } + } + + private void dumpProfile( + BlazeRuntime runtime, ProfileInfo info, PrintStream out, String dumpMode) { + if (!dumpMode.contains("unsorted")) { + ProfileInfo.aggregateProfile(info, getInfoListener(runtime)); + } + if (dumpMode.contains("raw")) { + for (ProfileInfo.Task task : info.allTasksById) { + dumpRaw(task, out); + } + } else if (dumpMode.contains("unsorted")) { + for (ProfileInfo.Task task : info.allTasksById) { + dumpTask(task, out, 0); + } + } else { + for (ProfileInfo.Task task : info.rootTasksById) { + dumpTask(task, out, 0); + } + } + } + + private void dumpTask(ProfileInfo.Task task, PrintStream out, int indent) { + StringBuilder builder = new StringBuilder(String.format( + "\n%s %s\nThread: %-6d Id: %-6d Parent: %d\nStart time: %-12s Duration: %s", + task.type, task.getDescription(), task.threadId, task.id, task.parentId, + TimeUtilities.prettyTime(task.startTime), TimeUtilities.prettyTime(task.duration))); + if (task.hasStats()) { + builder.append("\n"); + ProfileInfo.AggregateAttr[] stats = task.getStatAttrArray(); + for (ProfilerTask type : ProfilerTask.values()) { + ProfileInfo.AggregateAttr attr = stats[type.ordinal()]; + if (attr != null) { + builder.append(type.toString().toLowerCase()).append("=("). + append(attr.count).append(", "). + append(TimeUtilities.prettyTime(attr.totalTime)).append(") "); + } + } + } + out.println(StringUtil.indent(builder.toString(), indent)); + for (ProfileInfo.Task subtask : task.subtasks) { + dumpTask(subtask, out, indent + 1); + } + } + + private void dumpRaw(ProfileInfo.Task task, PrintStream out) { + StringBuilder aggregateString = new StringBuilder(); + ProfileInfo.AggregateAttr[] stats = task.getStatAttrArray(); + for (ProfilerTask type : ProfilerTask.values()) { + ProfileInfo.AggregateAttr attr = stats[type.ordinal()]; + if (attr != null) { + aggregateString.append(type.toString().toLowerCase()).append(","). + append(attr.count).append(",").append(attr.totalTime).append(" "); + } + } + out.println( + task.threadId + "|" + task.id + "|" + task.parentId + "|" + + task.startTime + "|" + task.duration + "|" + + aggregateString.toString().trim() + "|" + + task.type + "|" + task.getDescription()); + } + + /** + * Converts relative duration to the percentage string + * @return formatted percentage string or "N/A" if result is undefined. + */ + private static String prettyPercentage(long duration, long total) { + if (total == 0) { + // Return "not available" string if total is 0 and result is undefined. + return "N/A"; + } + return String.format("%5.2f%%", duration*100.0/total); + } + + private void printCriticalPath(String title, PrintStream out, CriticalPathEntry path) { + out.println(String.format("\n%s (%s):", title, + TimeUtilities.prettyTime(path.cumulativeDuration))); + + boolean lightCriticalPath = isLightCriticalPath(path); + out.println(lightCriticalPath ? + String.format("%6s %11s %8s %s", "Id", "Time", "Percentage", "Description") + : String.format("%6s %11s %8s %8s %s", "Id", "Time", "Share", "Critical", "Description")); + + long totalPathTime = path.cumulativeDuration; + int middlemanCount = 0; + long middlemanDuration = 0L; + long middlemanCritTime = 0L; + + for (; path != null ; path = path.next) { + if (path.task.id < 0) { + // Ignore fake actions. + continue; + } else if (path.task.getDescription().startsWith(MiddlemanAction.MIDDLEMAN_MNEMONIC + " ") + || path.task.getDescription().startsWith("TargetCompletionMiddleman")) { + // Aggregate middleman actions. + middlemanCount++; + middlemanDuration += path.duration; + middlemanCritTime += path.getCriticalTime(); + } else { + String desc = path.task.getDescription().replace(':', ' '); + if (lightCriticalPath) { + out.println(String.format("%6d %11s %8s %s", path.task.id, + TimeUtilities.prettyTime(path.duration), + prettyPercentage(path.duration, totalPathTime), + desc)); + } else { + out.println(String.format("%6d %11s %8s %8s %s", path.task.id, + TimeUtilities.prettyTime(path.duration), + prettyPercentage(path.duration, totalPathTime), + prettyPercentage(path.getCriticalTime(), totalPathTime), desc)); + } + } + } + if (middlemanCount > 0) { + if (lightCriticalPath) { + out.println(String.format(" %11s %8s [%d middleman actions]", + TimeUtilities.prettyTime(middlemanDuration), + prettyPercentage(middlemanDuration, totalPathTime), + middlemanCount)); + } else { + out.println(String.format(" %11s %8s %8s [%d middleman actions]", + TimeUtilities.prettyTime(middlemanDuration), + prettyPercentage(middlemanDuration, totalPathTime), + prettyPercentage(middlemanCritTime, totalPathTime), middlemanCount)); + } + } + } + + private boolean isLightCriticalPath(CriticalPathEntry path) { + return path.task.type == ProfilerTask.CRITICAL_PATH_COMPONENT; + } + + private void printShortPhaseAnalysis(ProfileInfo info, PrintStream out, ProfilePhase phase) { + ProfileInfo.Task phaseTask = info.getPhaseTask(phase); + if (phaseTask != null) { + long phaseDuration = info.getPhaseDuration(phaseTask); + out.printf(TWO_COLUMN_FORMAT, "Total " + phase.nick + " phase time", + TimeUtilities.prettyTime(phaseDuration)); + printTimeDistributionByType(info, out, phaseTask); + } + } + + private void printTimeDistributionByType(ProfileInfo info, PrintStream out, + ProfileInfo.Task phaseTask) { + List<ProfileInfo.Task> taskList = info.getTasksForPhase(phaseTask); + long phaseDuration = info.getPhaseDuration(phaseTask); + long totalDuration = phaseDuration; + for (ProfileInfo.Task task : taskList) { + // Tasks on the phaseTask thread already accounted for in the phaseDuration. + if (task.threadId != phaseTask.threadId) { + totalDuration += task.duration; + } + } + boolean headerNeeded = true; + for (ProfilerTask type : ProfilerTask.values()) { + ProfileInfo.AggregateAttr stats = info.getStatsForType(type, taskList); + if (stats.count > 0 && stats.totalTime > 0) { + if (headerNeeded) { + out.println("\nTotal time (across all threads) spent on:"); + out.println(String.format("%18s %8s %8s %11s", "Type", "Total", "Count", "Average")); + headerNeeded = false; + } + out.println(String.format("%18s %8s %8d %11s", type.toString(), + prettyPercentage(stats.totalTime, totalDuration), stats.count, + TimeUtilities.prettyTime(stats.totalTime / stats.count))); + } + } + } + + static class Stat implements Comparable<Stat> { + public long duration; + public long frequency; + + @Override + public int compareTo(Stat o) { + return this.duration == o.duration ? Long.compare(this.frequency, o.frequency) + : Long.compare(this.duration, o.duration); + } + } + + /** + * Print the time spent on VFS operations on each path. Output is grouped by operation and sorted + * by descending duration. If multiple of the same VFS operation were logged for the same path, + * print the total duration. + * + * @param info profiling data. + * @param out output stream. + * @param phase build phase. + * @param limit maximum number of statistics to print, or -1 for no limit. + */ + private void printVfsStatistics(ProfileInfo info, PrintStream out, + ProfilePhase phase, int limit) { + ProfileInfo.Task phaseTask = info.getPhaseTask(phase); + if (phaseTask == null) { + return; + } + + if (limit == 0) { + return; + } + + // Group into VFS operations and build maps from path to duration. + + List<ProfileInfo.Task> taskList = info.getTasksForPhase(phaseTask); + EnumMap<ProfilerTask, Map<String, Stat>> stats = Maps.newEnumMap(ProfilerTask.class); + + collectVfsEntries(stats, taskList); + + if (!stats.isEmpty()) { + out.printf("\nVFS path statistics:\n"); + out.printf("%15s %10s %10s %s\n", "Type", "Frequency", "Duration", "Path"); + } + + // Reverse the maps to get maps from duration to path. We use a TreeMultimap to sort by duration + // and because durations are not unique. + + for (ProfilerTask type : stats.keySet()) { + Map<String, Stat> statsForType = stats.get(type); + TreeMultimap<Stat, String> sortedStats = + TreeMultimap.create(Ordering.natural().reverse(), Ordering.natural()); + + for (Map.Entry<String, Stat> stat : statsForType.entrySet()) { + sortedStats.put(stat.getValue(), stat.getKey()); + } + + int numPrinted = 0; + for (Map.Entry<Stat, String> stat : sortedStats.entries()) { + if (limit != -1 && numPrinted++ == limit) { + out.printf("... %d more ...\n", sortedStats.size() - limit); + break; + } + out.printf("%15s %10d %10s %s\n", + type.name(), stat.getKey().frequency, TimeUtilities.prettyTime(stat.getKey().duration), + stat.getValue()); + } + } + } + + private void collectVfsEntries(EnumMap<ProfilerTask, Map<String, Stat>> stats, + List<ProfileInfo.Task> taskList) { + for (ProfileInfo.Task task : taskList) { + collectVfsEntries(stats, Arrays.asList(task.subtasks)); + if (!task.type.name().startsWith("VFS_")) { + continue; + } + + Map<String, Stat> statsForType = stats.get(task.type); + if (statsForType == null) { + statsForType = Maps.newHashMap(); + stats.put(task.type, statsForType); + } + + String path = currentPathMapping.apply(task.getDescription()); + + Stat stat = statsForType.get(path); + if (stat == null) { + stat = new Stat(); + } + + stat.duration += task.duration; + stat.frequency++; + statsForType.put(path, stat); + } + } + + /** + * Returns set of profiler tasks to be filtered from critical path. + * Also always filters out ACTION_LOCK and WAIT tasks to simulate + * unlimited resource critical path (see comments inside formatExecutionPhaseStatistics() + * method). + */ + private EnumSet<ProfilerTask> getTypeFilter(ProfilerTask... tasks) { + EnumSet<ProfilerTask> filter = EnumSet.of(ProfilerTask.ACTION_LOCK, ProfilerTask.WAIT); + for (ProfilerTask task : tasks) { + filter.add(task); + } + return filter; + } + + private ProfilePhaseStatistics formatInitPhaseStatistics(ProfileInfo info, ProfileOptions opts) + throws UnsupportedEncodingException { + return formatSimplePhaseStatistics(info, opts, "Init", ProfilePhase.INIT); + } + + private ProfilePhaseStatistics formatLoadingPhaseStatistics(ProfileInfo info, ProfileOptions opts) + throws UnsupportedEncodingException { + return formatSimplePhaseStatistics(info, opts, "Loading", ProfilePhase.LOAD); + } + + private ProfilePhaseStatistics formatAnalysisPhaseStatistics(ProfileInfo info, + ProfileOptions opts) + throws UnsupportedEncodingException { + return formatSimplePhaseStatistics(info, opts, "Analysis", ProfilePhase.ANALYZE); + } + + private ProfilePhaseStatistics formatSimplePhaseStatistics(ProfileInfo info, + ProfileOptions opts, + String name, + ProfilePhase phase) + throws UnsupportedEncodingException { + ByteArrayOutputStream byteOutput = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(byteOutput, false, "UTF-8"); + + printShortPhaseAnalysis(info, out, phase); + printVfsStatistics(info, out, phase, opts.vfsStatsLimit); + return new ProfilePhaseStatistics(name + " Phase Information", + new String(byteOutput.toByteArray(), "UTF-8")); + } + + private ProfilePhaseStatistics formatExecutionPhaseStatistics(ProfileInfo info, + ProfileOptions opts) + throws UnsupportedEncodingException { + ByteArrayOutputStream byteOutput = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(byteOutput, false, "UTF-8"); + + ProfileInfo.Task prepPhase = info.getPhaseTask(ProfilePhase.PREPARE); + ProfileInfo.Task execPhase = info.getPhaseTask(ProfilePhase.EXECUTE); + ProfileInfo.Task finishPhase = info.getPhaseTask(ProfilePhase.FINISH); + if (execPhase == null) { + return null; + } + + List<ProfileInfo.Task> execTasks = info.getTasksForPhase(execPhase); + long graphTime = info.getStatsForType(ProfilerTask.ACTION_GRAPH, execTasks).totalTime; + long execTime = info.getPhaseDuration(execPhase) - graphTime; + + if (prepPhase != null) { + out.printf(TWO_COLUMN_FORMAT, "Total preparation time", + TimeUtilities.prettyTime(info.getPhaseDuration(prepPhase))); + } + out.printf(TWO_COLUMN_FORMAT, "Total execution phase time", + TimeUtilities.prettyTime(info.getPhaseDuration(execPhase))); + if (finishPhase != null) { + out.printf(TWO_COLUMN_FORMAT, "Total time finalizing build", + TimeUtilities.prettyTime(info.getPhaseDuration(finishPhase))); + } + out.println(""); + out.printf(TWO_COLUMN_FORMAT, "Action dependency map creation", + TimeUtilities.prettyTime(graphTime)); + out.printf(TWO_COLUMN_FORMAT, "Actual execution time", + TimeUtilities.prettyTime(execTime)); + + EnumSet<ProfilerTask> typeFilter = EnumSet.noneOf(ProfilerTask.class); + CriticalPathEntry totalPath = info.getCriticalPath(typeFilter); + info.analyzeCriticalPath(typeFilter, totalPath); + + typeFilter = getTypeFilter(); + CriticalPathEntry optimalPath = info.getCriticalPath(typeFilter); + info.analyzeCriticalPath(typeFilter, optimalPath); + + if (totalPath != null) { + printCriticalPathTimingBreakdown(info, totalPath, optimalPath, execTime, out); + } else { + out.println("\nCritical path not available because no action graph was generated."); + } + + printTimeDistributionByType(info, out, execPhase); + + if (totalPath != null) { + printCriticalPath("Critical path", out, totalPath); + // In light critical path we do not record scheduling delay data so it does not make sense + // to differentiate it. + if (!isLightCriticalPath(totalPath)) { + printCriticalPath("Critical path excluding scheduling delays", out, optimalPath); + } + } + + if (info.getMissingActionsCount() > 0) { + out.println("\n" + info.getMissingActionsCount() + " action(s) are present in the" + + " action graph but missing instrumentation data. Most likely profile file" + + " has been created for the failed or aborted build."); + } + + printVfsStatistics(info, out, ProfilePhase.EXECUTE, opts.vfsStatsLimit); + + return new ProfilePhaseStatistics("Execution Phase Information", + new String(byteOutput.toByteArray(), "UTF-8")); + } + + void printCriticalPathTimingBreakdown(ProfileInfo info, CriticalPathEntry totalPath, + CriticalPathEntry optimalPath, long execTime, PrintStream out) { + Preconditions.checkNotNull(totalPath); + Preconditions.checkNotNull(optimalPath); + // TODO(bazel-team): Print remote vs build stats recorded by CriticalPathStats + if (isLightCriticalPath(totalPath)) { + return; + } + out.println(totalPath.task.type); + // Worker thread pool scheduling delays for the actual critical path. + long workerWaitTime = 0; + long mainThreadWaitTime = 0; + for (ProfileInfo.CriticalPathEntry entry = totalPath; entry != null; entry = entry.next) { + workerWaitTime += info.getActionWaitTime(entry.task); + mainThreadWaitTime += info.getActionQueueTime(entry.task); + } + out.printf(TWO_COLUMN_FORMAT, "Worker thread scheduling delays", + TimeUtilities.prettyTime(workerWaitTime)); + out.printf(TWO_COLUMN_FORMAT, "Main thread scheduling delays", + TimeUtilities.prettyTime(mainThreadWaitTime)); + + out.println("\nCritical path time:"); + // Actual critical path. + long totalTime = totalPath.cumulativeDuration; + out.printf("%-37s %10s (%s of execution time)\n", "Actual time", + TimeUtilities.prettyTime(totalTime), + prettyPercentage(totalTime, execTime)); + // Unlimited resource critical path. Essentially, we assume that if we + // remove all scheduling delays caused by resource semaphore contention, + // each action execution time would not change (even though load now would + // be substantially higher - so this assumption might be incorrect but it is + // still useful for modeling). Given those assumptions we calculate critical + // path excluding scheduling delays. + long optimalTime = optimalPath.cumulativeDuration; + out.printf("%-37s %10s (%s of execution time)\n", "Time excluding scheduling delays", + TimeUtilities.prettyTime(optimalTime), + prettyPercentage(optimalTime, execTime)); + + // Artificial critical path if we ignore all the time spent in all tasks, + // except time directly attributed to the ACTION tasks. + out.println("\nTime related to:"); + + EnumSet<ProfilerTask> typeFilter = EnumSet.allOf(ProfilerTask.class); + ProfileInfo.CriticalPathEntry path = info.getCriticalPath(typeFilter); + out.printf(TWO_COLUMN_FORMAT, "the builder overhead", + prettyPercentage(path.cumulativeDuration, totalTime)); + + typeFilter = getTypeFilter(); + for (ProfilerTask task : ProfilerTask.values()) { + if (task.name().startsWith("VFS_")) { + typeFilter.add(task); + } + } + path = info.getCriticalPath(typeFilter); + out.printf(TWO_COLUMN_FORMAT, "the VFS calls", + prettyPercentage(optimalTime - path.cumulativeDuration, optimalTime)); + + typeFilter = getTypeFilter(ProfilerTask.ACTION_CHECK); + path = info.getCriticalPath(typeFilter); + out.printf(TWO_COLUMN_FORMAT, "the dependency checking", + prettyPercentage(optimalTime - path.cumulativeDuration, optimalTime)); + + typeFilter = getTypeFilter(ProfilerTask.ACTION_EXECUTE); + path = info.getCriticalPath(typeFilter); + out.printf(TWO_COLUMN_FORMAT, "the execution setup", + prettyPercentage(optimalTime - path.cumulativeDuration, optimalTime)); + + typeFilter = getTypeFilter(ProfilerTask.SPAWN, ProfilerTask.LOCAL_EXECUTION); + path = info.getCriticalPath(typeFilter); + out.printf(TWO_COLUMN_FORMAT, "local execution", + prettyPercentage(optimalTime - path.cumulativeDuration, optimalTime)); + + typeFilter = getTypeFilter(ProfilerTask.SCANNER); + path = info.getCriticalPath(typeFilter); + out.printf(TWO_COLUMN_FORMAT, "the include scanner", + prettyPercentage(optimalTime - path.cumulativeDuration, optimalTime)); + + typeFilter = getTypeFilter(ProfilerTask.REMOTE_EXECUTION, ProfilerTask.PROCESS_TIME, + ProfilerTask.LOCAL_PARSE, ProfilerTask.UPLOAD_TIME, + ProfilerTask.REMOTE_QUEUE, ProfilerTask.REMOTE_SETUP, ProfilerTask.FETCH); + path = info.getCriticalPath(typeFilter); + out.printf(TWO_COLUMN_FORMAT, "Remote execution (cumulative)", + prettyPercentage(optimalTime - path.cumulativeDuration, optimalTime)); + + typeFilter = getTypeFilter( ProfilerTask.UPLOAD_TIME, ProfilerTask.REMOTE_SETUP); + path = info.getCriticalPath(typeFilter); + out.printf(TWO_COLUMN_FORMAT, " file uploads", + prettyPercentage(optimalTime - path.cumulativeDuration, optimalTime)); + + typeFilter = getTypeFilter(ProfilerTask.FETCH); + path = info.getCriticalPath(typeFilter); + out.printf(TWO_COLUMN_FORMAT, " file fetching", + prettyPercentage(optimalTime - path.cumulativeDuration, optimalTime)); + + typeFilter = getTypeFilter(ProfilerTask.PROCESS_TIME); + path = info.getCriticalPath(typeFilter); + out.printf(TWO_COLUMN_FORMAT, " process time", + prettyPercentage(optimalTime - path.cumulativeDuration, optimalTime)); + + typeFilter = getTypeFilter(ProfilerTask.REMOTE_QUEUE); + path = info.getCriticalPath(typeFilter); + out.printf(TWO_COLUMN_FORMAT, " remote queueing", + prettyPercentage(optimalTime - path.cumulativeDuration, optimalTime)); + + typeFilter = getTypeFilter(ProfilerTask.LOCAL_PARSE); + path = info.getCriticalPath(typeFilter); + out.printf(TWO_COLUMN_FORMAT, " remote execution parse", + prettyPercentage(optimalTime - path.cumulativeDuration, optimalTime)); + + typeFilter = getTypeFilter(ProfilerTask.REMOTE_EXECUTION); + path = info.getCriticalPath(typeFilter); + out.printf(TWO_COLUMN_FORMAT, " other remote activities", + prettyPercentage(optimalTime - path.cumulativeDuration, optimalTime)); + } +} |