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