blob: 075bff2545f362f2942e7a1c0eb4528441e9dc37 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.avro.ipc.stats;
import java.io.IOException;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.Map.Entry;
import javax.servlet.ServletException;
import javax.servlet.UnavailableException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.avro.Protocol.Message;
import org.apache.avro.ipc.RPCContext;
/**
* Exposes information provided by a StatsPlugin as
* a web page.
*
* This class follows the same synchronization conventions
* as StatsPlugin, to avoid requiring StatsPlugin to serve
* a copy of the data.
*/
public class StatsServlet extends HttpServlet {
private final StatsPlugin statsPlugin;
private VelocityEngine velocityEngine;
private static final SimpleDateFormat FORMATTER =
new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss");
public StatsServlet(StatsPlugin statsPlugin) throws UnavailableException {
this.statsPlugin = statsPlugin;
this.velocityEngine = new VelocityEngine();
// These two properties tell Velocity to use its own classpath-based loader
velocityEngine.addProperty("resource.loader", "class");
velocityEngine.addProperty("class.resource.loader.class",
"org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
velocityEngine.setProperty("runtime.references.strict", true);
String logChuteName = "org.apache.velocity.runtime.log.NullLogChute";
velocityEngine.setProperty("runtime.log.logsystem.class", logChuteName);
}
/* Helper class to store per-message data which is passed to templates.
*
* The template expects a list of charts, each of which is parameterized by
* map key-value string attributes. */
public class RenderableMessage { // Velocity brakes if not public
public String name;
public int numCalls;
public ArrayList<HashMap<String, String>> charts;
public RenderableMessage(String name) {
this.name = name;
this.charts = new ArrayList<HashMap<String, String>>();
}
public ArrayList<HashMap<String, String>> getCharts() {
return this.charts;
}
public String getname() {
return this.name;
}
public int getNumCalls() {
return this.numCalls;
}
}
/* Surround each string in an array with
* quotation marks and escape existing quotes.
*
* This is useful when we have an array of strings that we want to turn into
* a javascript array declaration.
*/
protected static List<String> escapeStringArray(List<String> input) {
for (int i = 0; i < input.size(); i++) {
input.set(i, "\"" + input.get(i).replace("\"", "\\\"") + "\"");
}
return input;
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html");
String url = req.getRequestURL().toString();
String[] parts = url.split("//")[1].split("/");
try {
writeStats(resp.getWriter());
}
catch (Exception e) {
e.printStackTrace();
}
}
void writeStats(Writer w) throws IOException {
VelocityContext context = new VelocityContext();
context.put("title", "Avro RPC Stats");
ArrayList<String> rpcs = new ArrayList<String>(); // in flight rpcs
ArrayList<RenderableMessage> messages =
new ArrayList<RenderableMessage>();
for (Entry<RPCContext, Stopwatch> rpc :
this.statsPlugin.activeRpcs.entrySet()) {
rpcs.add(renderActiveRpc(rpc.getKey(), rpc.getValue()));
}
// Get set of all seen messages
Set<Message> keys = null;
synchronized(this.statsPlugin.methodTimings) {
keys = this.statsPlugin.methodTimings.keySet();
for (Message m: keys) {
messages.add(renderMethod(m));
}
}
context.put("inFlightRpcs", rpcs);
context.put("messages", messages);
context.put("currTime", FORMATTER.format(new Date()));
context.put("startupTime", FORMATTER.format(statsPlugin.startupTime));
Template t;
try {
t = velocityEngine.getTemplate(
"org/apache/avro/ipc/stats/templates/statsview.vm");
} catch (ResourceNotFoundException e) {
throw new IOException();
} catch (ParseErrorException e) {
throw new IOException();
} catch (Exception e) {
throw new IOException();
}
t.merge(context, w);
}
private String renderActiveRpc(RPCContext rpc, Stopwatch stopwatch)
throws IOException {
String out = new String();
out += rpc.getMessage().getName() + ": " +
formatMillis(StatsPlugin.nanosToMillis(stopwatch.elapsedNanos()));
return out;
}
private RenderableMessage renderMethod(Message message) {
RenderableMessage out = new RenderableMessage(message.getName());
synchronized(this.statsPlugin.methodTimings) {
FloatHistogram<?> hist = this.statsPlugin.methodTimings.get(message);
out.numCalls = hist.getCount();
HashMap<String, String> latencyBar = new HashMap<String, String>();
// Fill in chart attributes for velocity
latencyBar.put("type", "bar");
latencyBar.put("title", "All-Time Latency");
latencyBar.put("units", "ms");
latencyBar.put("numCalls", Integer.toString(hist.getCount()));
latencyBar.put("avg", Float.toString(hist.getMean()));
latencyBar.put("stdDev", Float.toString(hist.getUnbiasedStdDev()));
latencyBar.put("labelStr",
Arrays.toString(hist.getSegmenter().getBoundaryLabels().toArray()));
latencyBar.put("boundaryStr",
Arrays.toString(escapeStringArray(hist.getSegmenter().
getBucketLabels()).toArray()));
latencyBar.put("dataStr", Arrays.toString(hist.getHistogram()));
out.charts.add(latencyBar);
HashMap<String, String> latencyDot = new HashMap<String, String>();
latencyDot.put("title", "Latency");
latencyDot.put("type", "dot");
latencyDot.put("dataStr",
Arrays.toString(hist.getRecentAdditions().toArray()));
out.charts.add(latencyDot);
}
synchronized(this.statsPlugin.sendPayloads) {
IntegerHistogram<?> hist = this.statsPlugin.sendPayloads.get(message);
HashMap<String, String> latencyBar = new HashMap<String, String>();
// Fill in chart attributes for velocity
latencyBar.put("type", "bar");
latencyBar.put("title", "All-Time Send Payload");
latencyBar.put("units", "ms");
latencyBar.put("numCalls", Integer.toString(hist.getCount()));
latencyBar.put("avg", Float.toString(hist.getMean()));
latencyBar.put("stdDev", Float.toString(hist.getUnbiasedStdDev()));
latencyBar.put("labelStr",
Arrays.toString(hist.getSegmenter().getBoundaryLabels().toArray()));
latencyBar.put("boundaryStr",
Arrays.toString(escapeStringArray(hist.getSegmenter().
getBucketLabels()).toArray()));
latencyBar.put("dataStr", Arrays.toString(hist.getHistogram()));
out.charts.add(latencyBar);
HashMap<String, String> latencyDot = new HashMap<String, String>();
latencyDot.put("title", "Send Payload");
latencyDot.put("type", "dot");
latencyDot.put("dataStr",
Arrays.toString(hist.getRecentAdditions().toArray()));
out.charts.add(latencyDot);
}
synchronized(this.statsPlugin.receivePayloads) {
IntegerHistogram<?> hist = this.statsPlugin.receivePayloads.get(message);
HashMap<String, String> latencyBar = new HashMap<String, String>();
// Fill in chart attributes for velocity
latencyBar.put("type", "bar");
latencyBar.put("title", "All-Time Receive Payload");
latencyBar.put("units", "ms");
latencyBar.put("numCalls", Integer.toString(hist.getCount()));
latencyBar.put("avg", Float.toString(hist.getMean()));
latencyBar.put("stdDev", Float.toString(hist.getUnbiasedStdDev()));
latencyBar.put("labelStr",
Arrays.toString(hist.getSegmenter().getBoundaryLabels().toArray()));
latencyBar.put("boundaryStr",
Arrays.toString(escapeStringArray(hist.getSegmenter().
getBucketLabels()).toArray()));
latencyBar.put("dataStr", Arrays.toString(hist.getHistogram()));
out.charts.add(latencyBar);
HashMap<String, String> latencyDot = new HashMap<String, String>();
latencyDot.put("title", "Recv Payload");
latencyDot.put("type", "dot");
latencyDot.put("dataStr",
Arrays.toString(hist.getRecentAdditions().toArray()));
out.charts.add(latencyDot);
}
return out;
}
private CharSequence formatMillis(float millis) {
return String.format("%.0fms", millis);
}
}