blob: 861baa295319f677d932b088180291516ef49162 [file] [log] [blame]
// Copyright 2014 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.chart;
import java.io.PrintStream;
import java.util.List;
import java.util.Locale;
/**
* {@link ChartVisitor} that builds HTML from the visited chart and prints it
* out to the given {@link PrintStream}.
*/
public class HtmlChartVisitor implements ChartVisitor {
/** The default width of a second in the chart. */
private static final int DEFAULT_PIXEL_PER_SECOND = 50;
/** The horizontal offset of second zero. */
private static final int H_OFFSET = 40;
/** The font size of the row labels. */
private static final int ROW_LABEL_FONT_SIZE = 7;
/** The height of a bar in pixels. */
private static final int BAR_HEIGHT = 8;
/** The space between twp bars in pixels. */
private static final int BAR_SPACE = 2;
/** The height of a row. */
private static final int ROW_HEIGHT = BAR_HEIGHT + BAR_SPACE;
/** The {@link PrintStream} to output the HTML to. */
private final PrintStream out;
/** The maxmimum stop time of any bar in the chart. */
private long maxStop;
/** The width of a second in the chart. */
private final int pixelsPerSecond;
/**
* Creates the visitor, with a default width of a second of 50 pixels.
*
* @param out the {@link PrintStream} to output the HTML to
*/
public HtmlChartVisitor(PrintStream out) {
this(out, DEFAULT_PIXEL_PER_SECOND);
}
/**
* Creates the visitor.
*
* @param out the {@link PrintStream} to output the HTML to
* @param pixelsPerSecond The width of a second in the chart. (In pixels)
*/
public HtmlChartVisitor(PrintStream out, int pixelsPerSecond) {
this.out = out;
this.pixelsPerSecond = pixelsPerSecond;
}
@Override
public void visit(Chart chart) {
maxStop = chart.getMaxStop();
printContentBox();
heading("Tasks", 2);
out.println("<p>To get more information about a task point the mouse at one of the bars.</p>");
out.printf(
"<div style='position:relative; height: %dpx; margin: %dpx'>\n",
chart.getRowCount() * ROW_HEIGHT, H_OFFSET + 10);
}
@Override
public void visit(ChartColumn column) {
int width = scale(column.getWidth());
if (width == 0) {
return;
}
int left = scale(column.getStart());
int height = column.getRowCount() * ROW_HEIGHT;
String style = chartTypeNameAsCSSClass(column.getType().getName());
box(left, 0, width, height, style, column.getLabel(), 10);
}
@Override
public void visit(ChartRow slot) {
String style = slot.getIndex() % 2 == 0 ? "shade-even" : "shade-odd";
int top = slot.getIndex() * ROW_HEIGHT;
int width = scale(maxStop) + 1;
label(-H_OFFSET, top, width + H_OFFSET, ROW_HEIGHT, ROW_LABEL_FONT_SIZE, slot.getId());
box(0, top, width, ROW_HEIGHT, style, "", 0);
}
@Override
public void visit(ChartBar bar) {
int width = scale(bar.getWidth());
if (width == 0) {
return;
}
int left = scale(bar.getStart());
int top = bar.getRow().getIndex() * ROW_HEIGHT;
String style = chartTypeNameAsCSSClass(bar.getType().getName());
if (bar.getHighlight()) {
style += "-highlight";
}
box(left, top + 2, width, BAR_HEIGHT, style, bar.getLabel(), 20);
}
@Override
public void visit(ChartLine chartLine) {
int start = chartLine.getStartRow().getIndex() * ROW_HEIGHT;
int stop = chartLine.getStopRow().getIndex() * ROW_HEIGHT;
int time = scale(chartLine.getStartTime());
if (start < stop) {
verticalLine(time, start + 1, 1, (stop - start) + ROW_HEIGHT, Color.RED);
} else {
verticalLine(time, stop + 1, 1, (start - stop) + ROW_HEIGHT, Color.RED);
}
}
@Override
public void endVisit(Chart chart) {
printTimeAxis(chart);
out.println("</div>");
heading("Legend", 2);
printLegend(chart.getSortedTypes());
}
/** Converts the given value from the bar of the chart to pixels. */
private int scale(long value) {
return (int) (value / (1000000000L / pixelsPerSecond));
}
/**
* Prints a box with links to the sections of the generated HTML document.
*/
private void printContentBox() {
out.println("<div style='position:fixed; top:1em; right:1em; z-index:50; padding: 1ex;"
+ "border:1px solid #888; background-color:#eee; width:100px'><h3>Content</h3>");
out.println("<p style='text-align:left;font-size:small;margin:2px'>"
+ "<a href='#Tasks'>Tasks</a></p>");
out.println("<p style='text-align:left;font-size:small;margin:2px'>"
+ "<a href='#Legend'>Legend</a></p>");
out.println("<p style='text-align:left;font-size:small;margin:2px'>"
+ "<a href='#Statistics'>Statistics</a></p></div>");
}
/**
* Prints the time axis of the chart and vertical lines for every second.
*/
private void printTimeAxis(Chart chart) {
int location = 0;
int second = 0;
int end = scale(chart.getMaxStop());
while (location < end) {
label(location + 4, -17, pixelsPerSecond, ROW_HEIGHT, 0, second + "s");
verticalLine(location, -20, 1, chart.getRowCount() * ROW_HEIGHT + 20, Color.GRAY);
location += pixelsPerSecond;
second += 1;
}
}
public void printCss(List<ChartBarType> types) {
out.println("<style type=\"text/css\"><!--");
out.println("body { font-family: Sans; }");
out.printf("div.shade-even { position:absolute; border: 0px; background-color:#dddddd }\n");
out.printf("div.shade-odd { position:absolute; border: 0px; background-color:#eeeeee }\n");
for (ChartBarType type : types) {
String name = chartTypeNameAsCSSClass(type.getName());
String color = formatColor(type.getColor());
out.printf(
"div.%s-border { position:absolute; border:1px solid grey; background-color:%s }\n",
name, color);
out.printf(
"div.%s-highlight { position:absolute; border:1px solid red; background-color:%s }\n",
name, color);
out.printf("div.%s { position:absolute; border:0px; margin:1px; background-color:%s }\n",
name, color);
}
out.println("--></style>");
}
/**
* Prints the legend for the chart at the current position in the document. The
* legend is printed in columns of 10 rows each.
*
* @param types the list of {@link ChartBarType}s to print in the legend.
*/
private void printLegend(List<ChartBarType> types) {
final int boxHeight = 20;
final int lineHeight = 25;
final int entriesPerColumn = 10;
final int legendWidth = 350;
int legendHeight;
if (types.size() / entriesPerColumn >= 1) {
legendHeight = entriesPerColumn;
} else {
legendHeight = types.size() % entriesPerColumn;
}
out.printf("<div style='position:relative; height: %dpx;'>",
(legendHeight + 1) * lineHeight);
int left = -legendWidth;
int top;
int i = 0;
for (ChartBarType type : types) {
if (i % entriesPerColumn == 0) {
left += legendWidth;
i = 0;
}
top = lineHeight * i;
String style = chartTypeNameAsCSSClass(type.getName()) + "-border";
box(left, top, boxHeight, boxHeight, style, type.getName(), 0);
label(left + lineHeight + 10, top, legendWidth - 10, boxHeight, 0, type.getName());
i++;
}
out.println("</div>");
}
/**
* Prints a head-line at the current position in the document.
*
* @param text the text to print
* @param level the headline level
*/
private void heading(String text, int level) {
anchor(text);
out.printf("<h%d >%s</h%d>\n", level, text, level);
}
/**
* Prints a box with the given location, size, background color and border.
*
* @param x the x location of the top left corner of the box
* @param y the y location of the top left corner of the box
* @param width the width location of the box
* @param height the height location of the box
* @param style the CSS style class to use for the box
* @param title the text displayed when the mouse hovers over the box
*/
private void box(int x, int y, int width, int height, String style, String title, int zIndex) {
out.printf("<div class=\"%s\" title=\"%s\" "
+ "style=\"left:%dpx; top:%dpx; width:%dpx; height:%dpx; z-index:%d\"></div>\n",
style, title, x, y, width, height, zIndex);
}
/**
* Prints a label with the given location, size, background color and border.
*
* @param x the x location of the top left corner of the box
* @param y the y location of the top left corner of the box
* @param width the width location of the box
* @param height the height location of the box
* @param fontSize the font size of text in the box, 0 for default
* @param text the text displayed in the box
*/
private void label(int x, int y, int width, int height, int fontSize, String text) {
if (fontSize > 0) {
out.printf("<div style=\"position:absolute; left:%dpx; top:%dpx; width:%dpx; "
+ "height:%dpx; font-size:%dpt\">%s</div>\n",
x, y, width, height, fontSize, text);
} else {
out.printf("<div style=\"position:absolute; left:%dpx; top:%dpx; width:%dpx; "
+ "height:%dpx\">%s</div>\n",
x, y, width, height, text);
}
}
/**
* Prints a vertical line of given width, height and color at the given
* location.
*
* @param x the x location of the start point of the line
* @param y the y location of the start point of the line
* @param width the width of the line
* @param length the length of the line
* @param color the color of the line
*/
private void verticalLine(int x, int y, int width, int length, Color color) {
out.printf("<div style='position: absolute; left: %dpx; top: %dpx; width: %dpx; "
+ "height: %dpx; border-left: %dpx solid %s'" + "></div>\n",
x, y, width, length, width, formatColor(color));
}
/**
* Prints an HTML anchor with the given name,
*/
private void anchor(String name) {
out.println("<a name='" + name + "'/>");
}
/** Formats the given {@link Color} to a css style color string. */
public static String formatColor(Color color) {
int r = color.getRed();
int g = color.getGreen();
int b = color.getBlue();
int a = color.getAlpha();
// US Locale is used to ensure a dot as decimal separator
return String.format(Locale.US, "rgba(%d,%d,%d,%f)", r, g, b, (a / 255.0));
}
/**
* Transform the name into a form suitable as a css class.
*/
private String chartTypeNameAsCSSClass(String name) {
return name.replace(' ', '_');
}
}