blob: a6dcecc680b4e77cdbe647888924882707d90f31 [file] [log] [blame]
// 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
}
}