/* * * Copyright (c) void.fm * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this list * of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, this * list of conditions and the following disclaimer in the documentation and/or * other materials provided with the distribution. * * Neither the name void.fm nor the names of its contributors may be * used to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * */ package etm.contrib.renderer; import etm.core.aggregation.Aggregate; import etm.core.monitor.EtmException; import etm.core.renderer.MeasurementRenderer; import java.io.IOException; import java.io.Writer; import java.text.NumberFormat; import java.util.Iterator; import java.util.Locale; import java.util.Map; import java.util.TreeMap; /** * <p/> * The SimpleHtmlRenderer renders an html table fragment containing flat * or nested measurement results. * </p> * <p/> * We encourage to use CSS to change the visualization. Example CSS: * </p> * <pre> * table { * border: 2px solid #999; * margin: 0px 0px 25px 0px; * padding: 0px 0px 0px 0px; * border-collapse: collapse; * } * <p/> * th { * border: 1px solid #999; * margin: 0px 0px 0px 0px; * padding: 10px 5px 10px 5px; * } * <p/> * td { * border: 1px solid #999; * margin: 0px 0px 0px 0px; * padding: 10px 15px 10px 10px; * } * <p/> * .childname { * text-align: left; * border-top: 1px dotted #999; * position: relative; * margin: 2px 0px 0px 15px; * } * <p/> * .parentname { * text-align: left; * } * <p/> * .parentmeasurement { * text-align: right; * } * <p/> * .childmeasurement { * border-top: 1px dotted #999; * text-align: right; * margin: 2px 0px 0px 0px; * } * <p/> * .parenttime { * text-align: right; * } * <p/> * .childtime { * border-top: 1px dotted #999; * text-align: right; * margin: 2px 0px 0px 0px; * } * <p/> * .parenttotal { * text-align: right; * } * <p/> * .childtotal { * border-top: 1px dotted #999; * text-align: right; * margin: 2px 0px 0px 0px; * } * <p/> * </pre> * * @author void.fm * @version $Revision$ */ public class SimpleHtmlRenderer implements MeasurementRenderer { private static final String HEAD = "<table>\n" + " <tr>\n" + " <th>Measurement Point</th>\n" + " <th>#</th>\n" + " <th>Average</th>\n" + " <th>Min</th>\n" + " <th>Max</th>\n" + " <th>Total</th>\n" + " </tr>\n"; private static final String FOOTER = " <tr><td class=\"footer\" colspan=\"6\">All times in miliseconds. Measurements provided by <a href=\"http://jetm.void.fm\" target=\"_default\">JETM</a></td></tr>\n</table>"; private static final String NO_RESULTS = " <tr><td colspan=\"6\">No measurement results available.</td></tr>\n"; private NumberFormat timeFormatter; private NumberFormat numberFormatter; private Writer writer; /** * Constructs a SimpleHtmlRenderer using the default locale * and the provided writer. * * @param aWriter The writer. */ public SimpleHtmlRenderer(Writer aWriter) { this(aWriter, Locale.getDefault()); } /** * Constructs a SimpleTextRenderer using the provided locale * and provided writer. * * @param aWriter The writer to write to. * @param aLocale The locale to use. */ public SimpleHtmlRenderer(Writer aWriter, Locale aLocale) { writer = aWriter; timeFormatter = NumberFormat.getNumberInstance(aLocale); timeFormatter.setMaximumFractionDigits(3); timeFormatter.setMinimumFractionDigits(3); timeFormatter.setGroupingUsed(true); numberFormatter = NumberFormat.getNumberInstance(aLocale); numberFormatter.setMaximumFractionDigits(0); numberFormatter.setMinimumFractionDigits(0); numberFormatter.setGroupingUsed(true); } public void render(Map points) { try { writer.write(HEAD.toCharArray()); if (points.isEmpty()) { writer.write(NO_RESULTS.toCharArray()); } else { StringBuffer buffer = new StringBuffer(); TreeMap map = new TreeMap(points); for (Iterator iterator = map.values().iterator(); iterator.hasNext();) { Aggregate point = (Aggregate) iterator.next(); buffer.append(" <tr>\n"); buffer.append(" <td>"); writeNames(buffer, point, 0); buffer.append("</td>\n"); buffer.append(" <td>"); writeMeasurements(buffer, point, 0); buffer.append("</td>\n"); buffer.append(" <td>"); writeAverage(buffer, point, 0); buffer.append("</td>\n"); buffer.append(" <td>"); writeMin(buffer, point, 0); buffer.append("</td>\n"); buffer.append(" <td>"); writeMax(buffer, point, 0); buffer.append("</td>\n"); buffer.append(" <td>"); writeTotals(buffer, point, 0); buffer.append("</td>\n"); buffer.append(" </tr>\n"); } writer.write(buffer.toString().toCharArray()); } writer.write(FOOTER.toCharArray()); writer.flush(); } catch (IOException e) { throw new EtmException("Unable to write to writer: " + e); } } private void writeNames(StringBuffer aBuffer, Aggregate aPoint, int depth) { if (depth > 0) { aBuffer.append("<div class=\"childname\" >"); } else { aBuffer.append("<div class=\"parentname\" >"); } aBuffer.append(aPoint.getName()); if (aPoint.hasChilds()) { int currentDepth = depth + 1; Map childs = aPoint.getChilds(); for (Iterator iterator = childs.values().iterator(); iterator.hasNext();) { Aggregate child = (Aggregate) iterator.next(); writeNames(aBuffer, child, currentDepth); } } aBuffer.append("</div>"); } private void writeTotals(StringBuffer aBuffer, Aggregate aPoint, int depth) { if (depth > 0) { aBuffer.append("<div class=\"childtotal\" >"); } else { aBuffer.append("<div class=\"parenttotal\" >"); } aBuffer.append(timeFormatter.format(aPoint.getTotal())); if (aPoint.hasChilds()) { Map childs = aPoint.getChilds(); int currentDepth = depth + 1; for (Iterator iterator = childs.values().iterator(); iterator.hasNext();) { Aggregate child = (Aggregate) iterator.next(); writeTotals(aBuffer, child, currentDepth); } } aBuffer.append("</div>"); } private void writeAverage(StringBuffer aBuffer, Aggregate aPoint, int depth) { if (depth > 0) { aBuffer.append("<div class=\"childtime\" >"); } else { aBuffer.append("<div class=\"parenttime\" >"); } aBuffer.append(timeFormatter.format(aPoint.getAverage())); if (aPoint.hasChilds()) { Map childs = aPoint.getChilds(); int currentDepth = depth + 1; for (Iterator iterator = childs.values().iterator(); iterator.hasNext();) { Aggregate child = (Aggregate) iterator.next(); writeAverage(aBuffer, child, currentDepth + 1); } } aBuffer.append("</div>"); } private void writeMin(StringBuffer aBuffer, Aggregate aPoint, int depth) { if (depth > 0) { aBuffer.append("<div class=\"childtime\" >"); } else { aBuffer.append("<div class=\"parenttime\" >"); } aBuffer.append(timeFormatter.format(aPoint.getMin())); if (aPoint.hasChilds()) { Map childs = aPoint.getChilds(); int currentDepth = depth + 1; for (Iterator iterator = childs.values().iterator(); iterator.hasNext();) { Aggregate child = (Aggregate) iterator.next(); writeMin(aBuffer, child, currentDepth + 1); } } aBuffer.append("</div>"); } private void writeMax(StringBuffer aBuffer, Aggregate aPoint, int depth) { if (depth > 0) { aBuffer.append("<div class=\"childtime\" >"); } else { aBuffer.append("<div class=\"parenttime\" >"); } aBuffer.append(timeFormatter.format(aPoint.getMax())); if (aPoint.hasChilds()) { Map childs = aPoint.getChilds(); int currentDepth = depth + 1; for (Iterator iterator = childs.values().iterator(); iterator.hasNext();) { Aggregate child = (Aggregate) iterator.next(); writeMax(aBuffer, child, currentDepth + 1); } } aBuffer.append("</div>"); } private void writeMeasurements(StringBuffer aBuffer, Aggregate aPoint, int depth) { if (depth > 0) { aBuffer.append("<div class=\"childmeasurement\" >"); } else { aBuffer.append("<div class=\"parentmeasurement\" >"); } aBuffer.append(numberFormatter.format(aPoint.getMeasurements())); if (aPoint.hasChilds()) { Map childs = aPoint.getChilds(); int currentDepth = depth + 1; for (Iterator iterator = childs.values().iterator(); iterator.hasNext();) { Aggregate child = (Aggregate) iterator.next(); writeMeasurements(aBuffer, child, currentDepth); } } aBuffer.append("</div>"); } }