// 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; /** * {@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 endVisit(Chart chart) { printTimeAxis(chart); out.println("</div>"); heading("Legend", 2); printLegend(chart.getSortedTypes()); } @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); } } /** * 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. */ private String formatColor(Color color) { int r = color.getRed(); int g = color.getGreen(); int b = color.getBlue(); int a = color.getAlpha(); return String.format("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(' ', '_'); } }