| // Copyright 2015 The Bazel Authors. 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.profiler.output; |
| |
| import com.google.common.base.Optional; |
| import com.google.devtools.build.lib.profiler.ProfilePhase; |
| import com.google.devtools.build.lib.profiler.ProfilerTask; |
| import com.google.devtools.build.lib.profiler.statistics.CriticalPathStatistics; |
| import com.google.devtools.build.lib.profiler.statistics.PhaseStatistics; |
| import com.google.devtools.build.lib.profiler.statistics.PhaseSummaryStatistics; |
| import com.google.devtools.build.lib.profiler.statistics.PhaseVfsStatistics; |
| import com.google.devtools.build.lib.profiler.statistics.PhaseVfsStatistics.Stat; |
| import com.google.devtools.build.lib.util.StringUtil; |
| import com.google.devtools.build.lib.util.TimeUtilities; |
| |
| import java.io.PrintStream; |
| import java.util.Arrays; |
| import java.util.EnumMap; |
| |
| import javax.annotation.Nullable; |
| |
| /** |
| * Output {@link PhaseSummaryStatistics}, {@link PhaseStatistics} and {@link PhaseVfsStatistics} |
| * in HTML format. |
| */ |
| public final class PhaseHtml extends HtmlPrinter { |
| |
| private final PhaseSummaryStatistics phaseSummaryStats; |
| private final EnumMap<ProfilePhase, PhaseStatistics> phaseStatistics; |
| private final Optional<CriticalPathStatistics> criticalPathStatistics; |
| private final int vfsStatsLimit; |
| private final Optional<Integer> missingActionsCount; |
| |
| /** |
| * @param vfsStatsLimit maximum number of VFS statistics to print, or -1 for no limit. |
| */ |
| public PhaseHtml( |
| PrintStream out, |
| PhaseSummaryStatistics phaseSummaryStats, |
| EnumMap<ProfilePhase, PhaseStatistics> phaseStatistics, |
| Optional<CriticalPathStatistics> critPathStats, |
| Optional<Integer> missingActionsCount, |
| int vfsStatsLimit) { |
| super(out); |
| this.phaseSummaryStats = phaseSummaryStats; |
| this.phaseStatistics = phaseStatistics; |
| this.criticalPathStatistics = critPathStats; |
| this.missingActionsCount = missingActionsCount; |
| this.vfsStatsLimit = vfsStatsLimit; |
| } |
| |
| public PhaseHtml( |
| PrintStream out, |
| PhaseSummaryStatistics summaryStatistics, |
| EnumMap<ProfilePhase, PhaseStatistics> summaryPhaseStatistics, |
| int vfsStatsLimit) { |
| this( |
| out, |
| summaryStatistics, |
| summaryPhaseStatistics, |
| Optional.<CriticalPathStatistics>absent(), |
| Optional.<Integer>absent(), |
| vfsStatsLimit); |
| } |
| |
| /** |
| * Output a style tag with all necessary CSS directives |
| */ |
| public void printCss() { |
| lnPrint("<style type=\"text/css\"><!--"); |
| down(); |
| lnPrint("div.phase-statistics {"); |
| lnPrint(" margin: 0 10;"); |
| lnPrint(" font-size: small;"); |
| lnPrint(" font-family: monospace;"); |
| lnPrint(" float: left;"); |
| lnPrint("}"); |
| lnPrint("table.phase-statistics {"); |
| lnPrint(" border: 0px; text-align: right;"); |
| lnPrint("}"); |
| lnPrint("table.phase-statistics td {"); |
| lnPrint(" padding: 0 5;"); |
| lnPrint("}"); |
| lnPrint("td.left {"); |
| lnPrint(" text-align: left;"); |
| lnPrint("}"); |
| lnPrint("td.center {"); |
| lnPrint(" text-align: center;"); |
| lnPrint("}"); |
| up(); |
| lnPrint("--></style>"); |
| } |
| |
| /** |
| * Print tables from {@link #phaseSummaryStats} and {@link #phaseStatistics} side by side. |
| */ |
| public void print() { |
| printPhaseSummaryStatistics(); |
| |
| for (ProfilePhase phase : |
| Arrays.asList(ProfilePhase.INIT, ProfilePhase.LOAD, ProfilePhase.ANALYZE)) { |
| PhaseStatistics statistics = phaseStatistics.get(phase); |
| if (statistics == null || !statistics.wasExecuted()) { |
| continue; |
| } |
| printPhaseStatistics(statistics); |
| } |
| printExecutionPhaseStatistics(); |
| lnElement("div", "style", "clear: both;"); |
| } |
| |
| /** |
| * Print header and tables for a single phase. |
| */ |
| private void printPhaseStatistics(PhaseStatistics phaseStat) { |
| printPhaseHead(phaseStat); |
| printTwoColumnStatistic( |
| String.format("Total %s time", phaseStat.getProfilePhase().nick), |
| phaseStat.getPhaseDurationNanos()); |
| |
| printTimingDistribution(phaseStat); |
| lnClose(); // table |
| printVfsStatistics(phaseStat.getVfsStatistics()); |
| lnClose(); // div |
| } |
| |
| private void printPhaseHead(PhaseStatistics phaseStat) { |
| lnOpen("div", "class", "phase-statistics"); |
| lnElement( |
| "h3", |
| String.format( |
| "%s Phase Information", StringUtil.capitalize(phaseStat.getProfilePhase().nick))); |
| lnOpen("table", "class", "phase-statistics"); |
| } |
| |
| private void printExecutionPhaseStatistics() { |
| PhaseStatistics execPhase = phaseStatistics.get(ProfilePhase.EXECUTE); |
| if (execPhase == null || !execPhase.wasExecuted()) { |
| return; |
| } |
| printPhaseHead(execPhase); |
| for (PhaseStatistics phaseStat : |
| Arrays.asList( |
| phaseStatistics.get(ProfilePhase.PREPARE), |
| execPhase, |
| phaseStatistics.get(ProfilePhase.FINISH))) { |
| if (phaseStat.wasExecuted()) { |
| printTwoColumnStatistic( |
| String.format("Total %s time", phaseStat.getProfilePhase().nick), |
| phaseStat.getPhaseDurationNanos()); |
| } |
| } |
| |
| long execTime = execPhase.getPhaseDurationNanos(); |
| |
| printTwoColumnStatistic("Actual execution time", execTime); |
| |
| CriticalPathHtml criticalPaths = null; |
| if (criticalPathStatistics.isPresent()) { |
| criticalPaths = new CriticalPathHtml(out, criticalPathStatistics.get(), execTime); |
| criticalPaths.printTimingBreakdown(); |
| } |
| |
| printTimingDistribution(execPhase); |
| lnClose(); // table opened by printPhaseHead |
| |
| if (criticalPathStatistics.isPresent()) { |
| criticalPaths.printCriticalPaths(); |
| } |
| |
| if (missingActionsCount.isPresent() && missingActionsCount.get() > 0) { |
| lnOpen("p"); |
| lnPrint(missingActionsCount.get()); |
| print( |
| " action(s) are present in the" |
| + " action graph but missing instrumentation data. Most likely the profile file" |
| + " has been created during a failed or aborted build."); |
| lnClose(); |
| } |
| |
| printVfsStatistics(execPhase.getVfsStatistics()); |
| lnClose(); // div |
| } |
| |
| /** |
| * Print the table rows for the {@link ProfilerTask} types and their execution times. |
| */ |
| private void printTimingDistribution(PhaseStatistics phaseStat) { |
| if (!phaseStat.isEmpty()) { |
| lnOpen("tr"); |
| element("td", "class", "left", "colspan", "4", "Total time (across all threads) spent on:"); |
| close(); // tr |
| lnOpen("tr"); |
| element("th", "Type"); |
| element("th", "Total"); |
| element("th", "Count"); |
| element("th", "Average"); |
| close(); // tr |
| for (ProfilerTask taskType : phaseStat) { |
| lnOpen("tr", "class", "phase-task-statistics"); |
| element("td", taskType); |
| element("td", prettyPercentage(phaseStat.getTotalRelativeDuration(taskType))); |
| element("td", phaseStat.getCount(taskType)); |
| element("td", TimeUtilities.prettyTime(phaseStat.getMeanDuration(taskType))); |
| close(); // tr |
| } |
| } |
| } |
| |
| /** |
| * 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. |
| */ |
| private void printVfsStatistics(@Nullable PhaseVfsStatistics stats) { |
| if (vfsStatsLimit == 0 || stats == null || stats.isEmpty()) { |
| return; |
| } |
| |
| lnElement("h4", "VFS path statistics:"); |
| |
| lnOpen("table", "class", "phase-statistics"); |
| lnOpen("tr"); |
| element("td", "Type"); |
| element("td", "Frequency"); |
| element("td", "Duration"); |
| element("td", "class", "left", "Path"); |
| close(); // tr |
| |
| for (ProfilerTask type : stats) { |
| int numPrinted = 0; |
| for (Stat stat : stats.getSortedStatistics(type)) { |
| lnOpen("tr"); |
| if (vfsStatsLimit != -1 && numPrinted++ == vfsStatsLimit) { |
| open("td", "class", "center", "colspan", "4"); |
| printf("... %d more ...", stats.getStatisticsCount(type) - vfsStatsLimit); |
| close(); |
| close(); // tr |
| break; |
| } |
| element("td", type.name()); |
| element("td", stat.getCount()); |
| element("td", TimeUtilities.prettyTime(stat.getDuration())); |
| element("td", "class", "left", stat.path); |
| close(); // tr |
| } |
| } |
| |
| lnClose(); // table |
| } |
| |
| /** |
| * Print a table for the phase overview with runtime and runtime percentage per phase and total. |
| */ |
| private void printPhaseSummaryStatistics() { |
| lnOpen("div", "class", "phase-statistics"); |
| lnElement("h3", "Phase Summary Information"); |
| lnOpen("table", "class", "phase-statistics"); |
| for (ProfilePhase phase : phaseSummaryStats) { |
| lnOpen("tr"); |
| lnOpen("td", "class", "left"); |
| printf("Total %s phase time", phase.nick); |
| close(); |
| element("td", TimeUtilities.prettyTime(phaseSummaryStats.getDurationNanos(phase))); |
| element("td", phaseSummaryStats.getPrettyPercentage(phase)); |
| lnClose(); // tr |
| } |
| lnOpen("tr"); |
| lnElement("td", "class", "left", "Total run time"); |
| element("td", TimeUtilities.prettyTime(phaseSummaryStats.getTotalDuration())); |
| element("td", "100.00%"); |
| lnClose(); // tr |
| lnClose(); // table |
| lnClose(); // div |
| } |
| |
| private void printTwoColumnStatistic(String name, long duration) { |
| lnOpen("tr"); |
| element("td", "class", "left", "colspan", "3", name); |
| element("td", TimeUtilities.prettyTime(duration)); |
| lnClose(); // tr |
| } |
| } |
| |
| |