/*
*
* 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.console.util;
import etm.contrib.console.ConsoleRequest;
import etm.contrib.console.ConsoleResponse;
import etm.contrib.console.HttpConsoleServer;
import etm.contrib.util.ExecutionAggregateComparator;
import etm.core.aggregation.Aggregate;
import etm.core.renderer.MeasurementRenderer;
import etm.core.util.Version;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* Base class for our console views.
*
* @author void.fm
* @version $Revision$
*/
public abstract class ConsoleRenderer implements MeasurementRenderer {
protected 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 " +
Version.getVersion() +
"</a></td></tr>\n";
protected static final String NO_RESULTS = " <tr><td colspan=\"6\">No measurement results available.</td></tr>\n";
protected NumberFormat timeFormatter;
protected NumberFormat numberFormatter;
protected ExecutionAggregateComparator comparator;
protected ConsoleRequest request;
protected ConsoleResponse response;
public ConsoleRenderer(ConsoleRequest aRequest, ConsoleResponse aResponse, ExecutionAggregateComparator aComparator) {
request = aRequest;
response = aResponse;
comparator = aComparator;
timeFormatter = NumberFormat.getNumberInstance();
timeFormatter.setMaximumFractionDigits(3);
timeFormatter.setMinimumFractionDigits(3);
timeFormatter.setGroupingUsed(true);
numberFormatter = NumberFormat.getNumberInstance();
numberFormatter.setMaximumFractionDigits(0);
numberFormatter.setMinimumFractionDigits(0);
numberFormatter.setGroupingUsed(true);
}
protected void writeCommonHtmlHead() throws IOException {
response.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">");
response.write(
"<html>\n" +
" <head> \n" +
" <title>JETM Console</title>\n" +
" <link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\"/>\n" +
" <link rel=\"icon\" href=\"favicon.ico\" type=\"image/x-icon\"/>" +
" <script type=\"text/javascript\">" +
" function confirmReset(text, url) {" +
" if (confirm('Do you really want to reset ' + text + '?')) {" +
" window.location=url;" +
" };" +
" }" +
" </script>" +
" </head>\n");
response.write("<body>\n<h1>JETM Console</h1>");
}
protected void writeDetailHtmlHead(String point) throws IOException {
writeCommonHtmlHead();
String pointEncoded = URLEncoder.encode(point, HttpConsoleServer.DEFAULT_ENCODING);
Date currentTime = new Date();
response.write("<table class=\"noborder\">\n");
response.write(" <tr class=\"noborder\">\n" +
" <td class=\"noborder\">Application start:</td>\n" +
" <td class=\"noborder\">" + request.getEtmMonitor().getMetaData().getStartTime() + "</td>\n" +
" </tr>\n" +
" <tr class=\"noborder\">\n" +
" <td class=\"noborder\">Monitoring period:</td>\n" +
" <td class=\"noborder\">" + request.getEtmMonitor().getMetaData().getLastResetTime() + " - " + currentTime + "</td>\n" +
" </tr>\n");
response.write(" <tr class=\"noborder\">\n" +
" <td class=\"noborder\">Point:</td>\n" +
" <td class=\"noborder\">" + point + "</td>\n" +
" </tr>\n");
response.write(
" <tr class=\"noborder\">\n" +
" <td class=\"noborder\"> </td>\n" +
" <td class=\"noborder\"> </td>\n" +
" </tr>\n");
response.write("<tr class=\"noborder\">");
response.write(" <td class=\"noborder\"><a href=\"#\" onclick=\"confirmReset('");
response.write(point);
response.write("?','");
response.write(ConsoleUtil.appendParameters("reset?point=" + pointEncoded, request.getRequestParameters()));
response.write("');\">Reset point</a></td>");
response.write("<td class=\"noborder\"><a href=\"");
response.write(ConsoleUtil.appendParameters("detail?point=" + pointEncoded, request.getRequestParameters()));
response.write("\">Reload point</a> \n");
response.write(" <a href=\"");
response.write(ConsoleUtil.appendParameters("index", request.getRequestParameters(), true));
response.write("\">Back to overview</a></td>\n");
response.write("</tr>\n</table>\n");
}
protected void writeHtmlHead(boolean expanded) throws IOException {
writeCommonHtmlHead();
Date currentTime = new Date();
response.write("<table class=\"noborder\">\n");
response.write(" <tr class=\"noborder\">\n" +
" <td class=\"noborder\">Application start:</td>\n" +
" <td class=\"noborder\">" + request.getEtmMonitor().getMetaData().getStartTime() + "</td>\n" +
" </tr>\n" +
" <tr class=\"noborder\">\n" +
" <td class=\"noborder\">Monitoring period:</td>\n" +
" <td class=\"noborder\">" + request.getEtmMonitor().getMetaData().getLastResetTime() + " - " + currentTime + "</td>\n" +
" </tr>\n" +
" <tr class=\"noborder\">\n" +
" <td class=\"noborder\">Monitoring status:</td>\n");
if (request.getEtmMonitor().isStarted()) {
response.write(" <td class=\"noborder\"><span class=\"enabled\">enabled</span></td>\n");
} else {
response.write(" <td class=\"noborder\"><span class=\"disabled\">disabled</span></td>\n");
}
response.write(
" </tr>\n" +
" <tr class=\"noborder\">\n" +
" <td class=\"noborder\">Collecting status:</td>\n");
if (request.getEtmMonitor().isCollecting()) {
response.write(" <td class=\"noborder\">\n");
response.write("<a href=\"");
response.write(ConsoleUtil.appendParameters("stop", request.getRequestParameters()));
response.write("\"><span class=\"enabled\">enabled</span></a></td>");
} else {
response.write(" <td class=\"noborder\">\n");
response.write("<a href=\"");
response.write(ConsoleUtil.appendParameters("start", request.getRequestParameters()));
response.write("\"><span class=\"disabled\">disabled</span></a></td>");
}
response.write(
" </tr>\n" +
" <tr class=\"noborder\">\n" +
" <td class=\"noborder\"> </td>\n" +
" <td class=\"noborder\"> </td>\n" +
" </tr>\n");
response.write(" <tr class=\"noborder\">\n");
response.write(" <td class=\"noborder\"><a href=\"#\" onclick=\"confirmReset('all performance results','");
response.write(ConsoleUtil.appendParameters("reset", request.getRequestParameters()));
response.write("')\">Reset monitor</a></td>");
if (expanded) {
response.write(" <td class=\"noborder\"><a href=\"");
response.write(ConsoleUtil.appendParameters("collapse", request.getRequestParameters()));
response.write("\">Collapse results</a> \n");
} else {
response.write(" <td class=\"noborder\"><a href=\"");
response.write(ConsoleUtil.appendParameters("expand", request.getRequestParameters()));
response.write("\">Expand results</a> \n");
}
response.write(" <a href=\"");
response.write(ConsoleUtil.appendParameters("index", request.getRequestParameters()));
response.write("\">Reload monitor</a></td>\n");
response.write(" </tr>\n");
response.write("</table>");
}
protected void writeTableHeader() throws IOException {
response.write(" <tr>\n");
response.write(" <th width=\"200\" ");
if (ExecutionAggregateComparator.TYPE_NAME == comparator.getType()) {
if (comparator.isDescending()) {
response.write("class=\"descending\"><a href=\"?sort=name&order=asc\">Measurement Point</a>");
} else {
response.write("class=\"ascending\"><a href=\"?sort=name&order=desc\">Measurement Point</a>");
}
} else {
response.write("><a href=\"?sort=name&order=asc\">Measurement Point</a>");
}
response.write("</th>\n");
response.write(" <th width=\"30\" ");
if (ExecutionAggregateComparator.TYPE_EXCECUTIONS == comparator.getType()) {
if (comparator.isDescending()) {
response.write("class=\"descending\"><a href=\"?sort=executions&order=asc\">#</a>");
} else {
response.write("class=\"ascending\"><a href=\"?sort=executions&order=desc\">#</a>");
}
} else {
response.write("><a href=\"?sort=executions&order=desc\">#</a> ");
}
response.write("</th>\n");
response.write(" <th width=\"80\" ");
if (ExecutionAggregateComparator.TYPE_AVERAGE == comparator.getType()) {
if (comparator.isDescending()) {
response.write("class=\"descending\"><a href=\"?sort=average&order=asc\">Average</a>");
} else {
response.write("class=\"ascending\"><a href=\"?sort=average&order=desc\">Average</a>");
}
} else {
response.write("><a href=\"?sort=average&order=desc\">Average</a> ");
}
response.write("</th>\n");
response.write(" <th width=\"80\" ");
if (ExecutionAggregateComparator.TYPE_MIN == comparator.getType()) {
if (comparator.isDescending()) {
response.write("class=\"descending\"><a href=\"?sort=min&order=asc\">Min</a>");
} else {
response.write("class=\"ascending\"><a href=\"?sort=min&order=desc\">Min</a>");
}
} else {
response.write("><a href=\"?sort=min&order=desc\">Min</a> ");
}
response.write("</th>\n");
response.write(" <th width=\"80\"");
if (ExecutionAggregateComparator.TYPE_MAX == comparator.getType()) {
if (comparator.isDescending()) {
response.write("class=\"descending\"><a href=\"?sort=max&order=asc\">Max</a>");
} else {
response.write("class=\"ascending\"><a href=\"?sort=max&order=desc\">Max</a>");
}
} else {
response.write("><a href=\"?sort=max&order=desc\">Max</a>");
}
response.write("</th>\n");
response.write(" <th width=\"80\"");
if (ExecutionAggregateComparator.TYPE_TOTAL == comparator.getType()) {
if (comparator.isDescending()) {
response.write("class=\"descending\"><a href=\"?sort=total&order=asc\">Total</a>");
} else {
response.write("class=\"ascending\"><a href=\"?sort=total&order=desc\">Total</a>");
}
} else {
response.write("><a href=\"?sort=total&order=desc\">Total</a> ");
}
response.write("</th>\n");
response.write(" </tr>\n");
}
protected void writeName(Aggregate aPoint) throws IOException {
String link = "detail?point=";
try {
link = link + URLEncoder.encode(aPoint.getName(), HttpConsoleServer.DEFAULT_ENCODING);
} catch (UnsupportedEncodingException e) {
// ignored
}
link = ConsoleUtil.appendParameters(link, request.getRequestParameters());
response.write("<div class=\"parentname\" >");
response.write("<a href=\"");
response.write(link);
response.write("\" >");
response.write(encodeHtml(aPoint.getName()));
response.write("</a></div>");
}
protected void writeTotals(Aggregate aPoint) throws IOException {
response.write("<div class=\"parenttotal\" >");
response.write(timeFormatter.format(aPoint.getTotal()));
response.write("</div>");
}
protected void writeAverage(Aggregate aPoint) throws IOException {
response.write("<div class=\"parenttime\" >");
response.write(timeFormatter.format(aPoint.getAverage()));
response.write("</div>");
}
protected void writeMin(Aggregate aPoint) throws IOException {
response.write("<div class=\"parenttime\" >");
response.write(timeFormatter.format(aPoint.getMin()));
response.write("</div>");
}
protected void writeMax(Aggregate aPoint) throws IOException {
response.write("<div class=\"parenttime\" >");
response.write(timeFormatter.format(aPoint.getMax()));
response.write("</div>");
}
protected void writeMeasurements(Aggregate aPoint) throws IOException {
response.write("<div class=\"parentmeasurement\" >");
response.write(numberFormatter.format(aPoint.getMeasurements()));
response.write("</div>");
}
protected void writeName(SortedExecutionGraph aElement, int depth) throws IOException {
if (depth > 0) {
response.write("<div class=\"childname\" >");
} else {
response.write("<div class=\"parentname\" >");
}
response.write(encodeHtml(aElement.getName()));
if (aElement.hasChilds()) {
int currentDepth = depth + 1;
for (Iterator iterator = aElement.getSortedChilds().iterator(); iterator.hasNext();) {
writeName((SortedExecutionGraph) iterator.next(), currentDepth);
}
}
response.write("</div>");
}
protected void writeTotals(SortedExecutionGraph aElement, int depth) throws IOException {
if (depth > 0) {
response.write("<div class=\"childtotal\" >");
} else {
response.write("<div class=\"parenttotal\" >");
}
response.write(timeFormatter.format(aElement.getTotal()));
if (aElement.hasChilds()) {
int currentDepth = depth + 1;
for (Iterator iterator = aElement.getSortedChilds().iterator(); iterator.hasNext();) {
writeTotals((SortedExecutionGraph) iterator.next(), currentDepth);
}
}
response.write("</div>");
}
protected void writeAverage(SortedExecutionGraph aElement, int depth) throws IOException {
if (depth > 0) {
response.write("<div class=\"childtime\" >");
} else {
response.write("<div class=\"parenttime\" >");
}
response.write(timeFormatter.format(aElement.getAverage()));
if (aElement.hasChilds()) {
int currentDepth = depth + 1;
for (Iterator iterator = aElement.getSortedChilds().iterator(); iterator.hasNext();) {
writeAverage((SortedExecutionGraph) iterator.next(), currentDepth);
}
}
response.write("</div>");
}
protected void writeMin(SortedExecutionGraph aElement, int depth) throws IOException {
if (depth > 0) {
response.write("<div class=\"childtime\" >");
} else {
response.write("<div class=\"parenttime\" >");
}
response.write(timeFormatter.format(aElement.getMin()));
if (aElement.hasChilds()) {
int currentDepth = depth + 1;
for (Iterator iterator = aElement.getSortedChilds().iterator(); iterator.hasNext();) {
writeMin((SortedExecutionGraph) iterator.next(), currentDepth);
}
}
response.write("</div>");
}
protected void writeMax(SortedExecutionGraph aElement, int depth) throws IOException {
if (depth > 0) {
response.write("<div class=\"childtime\" >");
} else {
response.write("<div class=\"parenttime\" >");
}
response.write(timeFormatter.format(aElement.getMax()));
if (aElement.hasChilds()) {
int currentDepth = depth + 1;
for (Iterator iterator = aElement.getSortedChilds().iterator(); iterator.hasNext();) {
writeMax((SortedExecutionGraph) iterator.next(), currentDepth);
}
}
response.write("</div>");
}
protected void writeMeasurements(SortedExecutionGraph aElement, int depth) throws IOException {
if (depth > 0) {
response.write("<div class=\"childmeasurement\" >");
} else {
response.write("<div class=\"parentmeasurement\" >");
}
response.write(numberFormatter.format(aElement.getMeasurements()));
if (aElement.hasChilds()) {
int currentDepth = depth + 1;
for (Iterator iterator = aElement.getSortedChilds().iterator(); iterator.hasNext();) {
writeMeasurements((SortedExecutionGraph) iterator.next(), currentDepth);
}
}
response.write("</div>");
}
protected String encodeHtml(String text) {
StringBuilder result = new StringBuilder();
char[] chars = text.toCharArray();
for (int i = 0; i < chars.length; i++) {
char c = chars[i];
switch(c) {
case '&':
result.append("&");
break;
case '<':
result.append("<");
break;
case '>':
result.append(">");
break;
case '"':
result.append(""");
break;
default:
int charAsInt = (int) c;
if (charAsInt > 0x80) {
result.append("");
result.append(charAsInt);
result.append(';');
} else {
result.append(c);
}
}
}
return result.toString();
}
protected class SortedExecutionGraph implements Aggregate {
private Aggregate aggregate;
private List sortedChilds;
public SortedExecutionGraph() {
}
public SortedExecutionGraph(Aggregate aAggregate, ExecutionAggregateComparator aComparator) {
aggregate = aAggregate;
if (aAggregate.hasChilds()) {
Map childs = aAggregate.getChilds();
sortedChilds = new ArrayList();
for (Iterator iterator = childs.values().iterator(); iterator.hasNext();) {
SortedExecutionGraph child = new SortedExecutionGraph((Aggregate) iterator.next(), aComparator);
sortedChilds.add(child);
}
Collections.sort(sortedChilds, aComparator);
}
}
public String getName() {
return aggregate.getName();
}
public double getAverage() {
return aggregate.getAverage();
}
public double getMin() {
return aggregate.getMin();
}
public double getMax() {
return aggregate.getMax();
}
public long getMeasurements() {
return aggregate.getMeasurements();
}
public double getTotal() {
return aggregate.getTotal();
}
public Map getChilds() {
return aggregate.getChilds();
}
public List getSortedChilds() {
return sortedChilds;
}
public boolean hasChilds() {
return sortedChilds != null;
}
}
}