/** * 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); } }