You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@avro.apache.org by ph...@apache.org on 2010/07/28 21:28:54 UTC
svn commit: r980173 [1/4] - in /avro/trunk: ./ lang/java/
lang/java/src/java/org/apache/avro/ipc/stats/
lang/java/src/java/org/apache/avro/ipc/stats/static/
lang/java/src/java/org/apache/avro/ipc/stats/templates/
lang/java/src/test/java/org/apache/avro...
Author: philz
Date: Wed Jul 28 19:28:53 2010
New Revision: 980173
URL: http://svn.apache.org/viewvc?rev=980173&view=rev
Log:
AVRO-587. Add Charts and Templating to Stats View
(Contributed by Patrick Wendell)
Added:
avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/StaticServlet.java
avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/StatsServer.java
avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/static/
avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/static/avro.css
avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/static/avro.js
avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/static/g.bar.js
avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/static/jquery-1.4.2.min.js
avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/static/jquery.tipsy.js
avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/static/protovis-r3.2.js
avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/static/tipsy.css
avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/static/tipsy.js
avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/templates/
avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/templates/statsview.vm
Modified:
avro/trunk/CHANGES.txt
avro/trunk/lang/java/build.xml
avro/trunk/lang/java/ivy.xml
avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/StatsPlugin.java
avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/StatsServlet.java
avro/trunk/lang/java/src/test/java/org/apache/avro/ipc/stats/TestStatsPluginAndServlet.java
avro/trunk/share/rat-excludes.txt
Modified: avro/trunk/CHANGES.txt
URL: http://svn.apache.org/viewvc/avro/trunk/CHANGES.txt?rev=980173&r1=980172&r2=980173&view=diff
==============================================================================
--- avro/trunk/CHANGES.txt (original)
+++ avro/trunk/CHANGES.txt Wed Jul 28 19:28:53 2010
@@ -37,6 +37,10 @@ Avro 1.4.0 (unreleased)
types when no specific class is available. (cutting)
IMPROVEMENTS
+
+ AVRO-587. Add Charts and Templating to Stats View
+ (Patrick Wendell via philz)
+
AVRO-584. Update Histogram for Stats Plugin
(Patrick Wendell via philz)
Modified: avro/trunk/lang/java/build.xml
URL: http://svn.apache.org/viewvc/avro/trunk/lang/java/build.xml?rev=980173&r1=980172&r2=980173&view=diff
==============================================================================
--- avro/trunk/lang/java/build.xml (original)
+++ avro/trunk/lang/java/build.xml Wed Jul 28 19:28:53 2010
@@ -41,11 +41,16 @@
<property name="java.src.dir" value="${src.dir}/java"/>
<property name="build.dir" value="${basedir}/build"/>
<property name="lib.dir" value="${basedir}/lib"/>
+ <property name="template.dir" value="${src.dir}/java/org/apache/avro/ipc/stats/templates"/>
+ <property name="static.dir" value="${src.dir}/java/org/apache/avro/ipc/stats/static"/>
+
<property name="build.classes" value="${build.dir}/classes"/>
<property name="build.doc" value="${build.dir}/doc"/>
<property name="build.javadoc" value="${build.doc}/api/java"/>
<property name="build.javadoc.log" value="${build.dir}/javadoc.log"/>
+ <property name="build.template.dir" value="${build.classes}/org/apache/avro/ipc/stats/templates"/>
+ <property name="build.static.dir" value="${build.classes}/org/apache/avro/ipc/stats/static"/>
<property name="test.count" value="100"/>
<property name="test.junit.output.format" value="plain"/>
@@ -169,6 +174,12 @@
<fileset file="${basedir}/../../NOTICE.txt"/>
<fileset file="${basedir}/../../share/VERSION.txt"/>
</copy>
+ <copy todir="${build.template.dir}">
+ <fileset dir="${template.dir}" includes="**/**"/>
+ </copy>
+ <copy todir="${build.static.dir}">
+ <fileset dir="${static.dir}" includes="**/**"/>
+ </copy>
</target>
<target name="ivy-download" unless="ivy.jar.exists" depends="init">
Modified: avro/trunk/lang/java/ivy.xml
URL: http://svn.apache.org/viewvc/avro/trunk/lang/java/ivy.xml?rev=980173&r1=980172&r2=980173&view=diff
==============================================================================
--- avro/trunk/lang/java/ivy.xml (original)
+++ avro/trunk/lang/java/ivy.xml Wed Jul 28 19:28:53 2010
@@ -45,6 +45,7 @@
rev="2.2"/>
<dependency org="org.mortbay.jetty" name="jetty"
rev="6.1.22"/>
+ <dependency org="org.apache.velocity" name="velocity" rev="1.6.4"/>
<dependency org="junit" name="junit" rev="4.8.1" conf="test->default"/>
<dependency org="checkstyle" name="checkstyle" rev="5.0"
conf="test->default"/>
Added: avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/StaticServlet.java
URL: http://svn.apache.org/viewvc/avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/StaticServlet.java?rev=980173&view=auto
==============================================================================
--- avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/StaticServlet.java (added)
+++ avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/StaticServlet.java Wed Jul 28 19:28:53 2010
@@ -0,0 +1,28 @@
+package org.apache.avro.ipc.stats;
+
+import java.io.IOException;
+import java.net.URL;
+
+import org.mortbay.jetty.servlet.DefaultServlet;
+import org.mortbay.resource.Resource;
+
+/**
+ * Very simple servlet class capable of serving static files.
+ */
+public class StaticServlet extends DefaultServlet {
+ public Resource getResource(String pathInContext) {
+ // Take only last slice of the URL as a filename, so we can adjust path.
+ // This also prevents mischief like '../../foo.css'
+ String[] parts = pathInContext.split("/");
+ String filename = parts[parts.length - 1];
+
+ try {
+ URL resource = getClass().getClassLoader().getResource(
+ "org/apache/avro/ipc/stats/static/" + filename);
+ if (resource == null) { return null; }
+ return Resource.newResource(resource);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+}
Modified: avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/StatsPlugin.java
URL: http://svn.apache.org/viewvc/avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/StatsPlugin.java?rev=980173&r1=980172&r2=980173&view=diff
==============================================================================
--- avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/StatsPlugin.java (original)
+++ avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/StatsPlugin.java Wed Jul 28 19:28:53 2010
@@ -17,8 +17,11 @@
*/
package org.apache.avro.ipc.stats;
+import java.nio.ByteBuffer;
import java.util.Arrays;
+import java.util.Date;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
@@ -32,14 +35,15 @@ import org.apache.avro.ipc.stats.Stopwat
/**
* Collects count and latency statistics about RPC calls. Keeps
- * data for every method.
+ * data for every method. Can be added to a Requestor (client)
+ * or Responder (server).
*
* This uses milliseconds as the standard unit of measure
* throughout the class, stored in floats.
*/
public class StatsPlugin extends RPCPlugin {
/** Static declaration of histogram buckets. */
- static final Segmenter<String, Float> DEFAULT_SEGMENTER =
+ static final Segmenter<String, Float> LATENCY_SEGMENTER =
new Histogram.TreeMapSegmenter<Float>(new TreeSet<Float>(Arrays.asList(
0f,
25f,
@@ -57,28 +61,74 @@ public class StatsPlugin extends RPCPlug
60000f, // 1 minute
600000f)));
+ static final Segmenter<String, Integer> PAYLOAD_SEGMENTER =
+ new Histogram.TreeMapSegmenter<Integer>(new TreeSet<Integer>(Arrays.asList(
+ 0,
+ 25,
+ 50,
+ 75,
+ 100,
+ 200,
+ 300,
+ 500,
+ 750,
+ 1000, // 1 k
+ 2000,
+ 5000,
+ 10000,
+ 50000,
+ 100000)));
+
/** Per-method histograms.
- * Must be accessed while holding a lock on methodTimings. */
+ * Must be accessed while holding a lock. */
Map<Message, FloatHistogram<?>> methodTimings =
new HashMap<Message, FloatHistogram<?>>();
+ Map<Message, IntegerHistogram<?>> sendPayloads =
+ new HashMap<Message, IntegerHistogram<?>>();
+
+ Map<Message, IntegerHistogram<?>> receivePayloads =
+ new HashMap<Message, IntegerHistogram<?>>();
+
/** RPCs in flight. */
ConcurrentMap<RPCContext, Stopwatch> activeRpcs =
new ConcurrentHashMap<RPCContext, Stopwatch>();
private Ticks ticks;
- private Segmenter<?, Float> segmenter;
+ /** How long I've been alive */
+ public Date startupTime = new Date();
+
+ private Segmenter<?, Float> floatSegmenter;
+ private Segmenter<?, Integer> integerSegmenter;
/** Construct a plugin with custom Ticks and Segmenter implementations. */
- StatsPlugin(Ticks ticks, Segmenter<?, Float> segmenter) {
- this.segmenter = segmenter;
+ StatsPlugin(Ticks ticks, Segmenter<?, Float> floatSegmenter,
+ Segmenter<?, Integer> integerSegmenter) {
+ this.floatSegmenter = floatSegmenter;
+ this.integerSegmenter = integerSegmenter;
this.ticks = ticks;
}
/** Construct a plugin with default (system) ticks, and default
* histogram segmentation. */
public StatsPlugin() {
- this(Stopwatch.SYSTEM_TICKS, DEFAULT_SEGMENTER);
+ this(Stopwatch.SYSTEM_TICKS, LATENCY_SEGMENTER, PAYLOAD_SEGMENTER);
+ }
+
+ /**
+ * Helper to get the size of an RPC payload.
+ */
+ private int getPayloadSize(List<ByteBuffer> payload) {
+ if (payload == null) {
+ return 0;
+ }
+
+ int size = 0;
+ for (ByteBuffer bb: payload) {
+ size = size + bb.limit();
+ }
+
+ return size;
}
@Override
@@ -86,15 +136,65 @@ public class StatsPlugin extends RPCPlug
Stopwatch t = new Stopwatch(ticks);
t.start();
this.activeRpcs.put(context, t);
+
+ synchronized(receivePayloads) {
+ IntegerHistogram<?> h = receivePayloads.get(context.getMessage());
+ if (h == null) {
+ h = createNewIntegerHistogram();
+ receivePayloads.put(context.getMessage(), h);
+ }
+ h.add(getPayloadSize(context.getRequestPayload()));
+ }
}
-
+
@Override
public void serverSendResponse(RPCContext context) {
Stopwatch t = this.activeRpcs.remove(context);
t.stop();
publish(context, t);
+
+ synchronized(sendPayloads) {
+ IntegerHistogram<?> h = sendPayloads.get(context.getMessage());
+ if (h == null) {
+ h = createNewIntegerHistogram();
+ sendPayloads.put(context.getMessage(), h);
+ }
+ h.add(getPayloadSize(context.getResponsePayload()));
+ }
}
-
+
+ @Override
+ public void clientSendRequest(RPCContext context) {
+ Stopwatch t = new Stopwatch(ticks);
+ t.start();
+ this.activeRpcs.put(context, t);
+
+ synchronized(sendPayloads) {
+ IntegerHistogram<?> h = sendPayloads.get(context.getMessage());
+ if (h == null) {
+ h = createNewIntegerHistogram();
+ sendPayloads.put(context.getMessage(), h);
+ }
+ h.add(getPayloadSize(context.getRequestPayload()));
+ }
+ }
+
+ @Override
+ public void clientReceiveResponse(RPCContext context) {
+ Stopwatch t = this.activeRpcs.remove(context);
+ t.stop();
+ publish(context, t);
+
+ synchronized(receivePayloads) {
+ IntegerHistogram<?> h = receivePayloads.get(context.getMessage());
+ if (h == null) {
+ h = createNewIntegerHistogram();
+ receivePayloads.put(context.getMessage(), h);
+ }
+ h.add(getPayloadSize(context.getRequestPayload()));
+ }
+ }
+
/** Adds timing to the histograms. */
private void publish(RPCContext context, Stopwatch t) {
Message message = context.getMessage();
@@ -102,7 +202,7 @@ public class StatsPlugin extends RPCPlug
synchronized(methodTimings) {
FloatHistogram<?> h = methodTimings.get(context.getMessage());
if (h == null) {
- h = createNewHistogram();
+ h = createNewFloatHistogram();
methodTimings.put(context.getMessage(), h);
}
h.add(nanosToMillis(t.elapsedNanos()));
@@ -110,10 +210,15 @@ public class StatsPlugin extends RPCPlug
}
@SuppressWarnings("unchecked")
- private FloatHistogram<?> createNewHistogram() {
- return new FloatHistogram(segmenter);
+ private FloatHistogram<?> createNewFloatHistogram() {
+ return new FloatHistogram(floatSegmenter);
}
+ @SuppressWarnings("unchecked")
+ private IntegerHistogram<?> createNewIntegerHistogram() {
+ return new IntegerHistogram(integerSegmenter);
+ }
+
/** Converts nanoseconds to milliseconds. */
static float nanosToMillis(long elapsedNanos) {
return elapsedNanos / 1000000.0f;
Added: avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/StatsServer.java
URL: http://svn.apache.org/viewvc/avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/StatsServer.java?rev=980173&view=auto
==============================================================================
--- avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/StatsServer.java (added)
+++ avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/StatsServer.java Wed Jul 28 19:28:53 2010
@@ -0,0 +1,54 @@
+package org.apache.avro.ipc.stats;
+/**
+ * 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.
+ */
+import org.mortbay.jetty.Server;
+import org.mortbay.jetty.servlet.Context;
+import org.mortbay.jetty.servlet.ServletHolder;
+
+/* This is a server that displays live information from a StatsPlugin.
+ *
+ * Typical usage is as follows:
+ * StatsPlugin plugin = new StatsPlugin();
+ * requestor.addPlugin(plugin);
+ * StatsServer server = new StatsServer(plugin, 8080);
+ *
+ * */
+public class StatsServer {
+ Server httpServer;
+ StatsPlugin plugin;
+
+ /* Start a stats server on the given port,
+ * responsible for the given plugin. */
+ public StatsServer(StatsPlugin plugin, int port) throws Exception {
+ this.httpServer = new Server(port);
+ this.plugin = plugin;
+
+ Context staticContext = new Context(httpServer, "/static");
+ staticContext.addServlet(new ServletHolder(new StaticServlet()), "/");
+
+ Context context = new Context(httpServer, "/");
+ context.addServlet(new ServletHolder(new StatsServlet(plugin)), "/");
+
+ httpServer.start();
+ }
+
+ /* Stops this server. */
+ public void stop() throws Exception {
+ this.httpServer.stop();
+ }
+}
Modified: avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/StatsServlet.java
URL: http://svn.apache.org/viewvc/avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/StatsServlet.java?rev=980173&r1=980172&r2=980173&view=diff
==============================================================================
--- avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/StatsServlet.java (original)
+++ avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/StatsServlet.java Wed Jul 28 19:28:53 2010
@@ -19,12 +19,25 @@ 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;
@@ -36,71 +49,217 @@ import org.apache.avro.ipc.RPCContext;
* 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) {
+ 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");
+ }
+
+ /* 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");
- writeStats(resp.getWriter());
+ 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 {
- w.append("<html><head><title>Avro RPC Stats</title></head>");
- w.append("<body><h1>Avro RPC Stats</h1>");
-
- w.append("<h2>Active RPCs</h2>");
- w.append("<ol>");
- for (Entry<RPCContext, Stopwatch> rpc : this.statsPlugin.activeRpcs.entrySet()) {
- writeActiveRpc(w, rpc.getKey(), rpc.getValue());
+ 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()));
}
- w.append("</ol>");
-
- w.append("<h2>Per-method Timing</h2>");
+
+ // Get set of all seen messages
+ Set<Message> keys = null;
synchronized(this.statsPlugin.methodTimings) {
- for (Entry<Message, FloatHistogram<?>> e :
- this.statsPlugin.methodTimings.entrySet()) {
- writeMethod(w, e.getKey(), e.getValue());
+ keys = this.statsPlugin.methodTimings.keySet();
+
+ for (Message m: keys) {
+ messages.add(renderMethod(m));
}
}
- w.append("</body></html>");
+
+ 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 void writeActiveRpc(Writer w, RPCContext rpc, Stopwatch stopwatch) throws IOException {
- w.append("<li>").append(rpc.getMessage().getName()).append(": ");
- w.append(formatMillis(StatsPlugin.nanosToMillis(stopwatch.elapsedNanos())));
- w.append("</li>");
- }
-
- private void writeMethod(Writer w, Message message, FloatHistogram<?> hist) throws IOException {
- w.append("<h3>").append(message.getName()).append("</h3>");
- w.append("<p>Number of calls: ");
- w.append(Integer.toString(hist.getCount()));
- w.append("</p><p>Average Duration: ");
- w.append(formatMillis(hist.getMean()));
- w.append("</p>");
- w.append("</p><p>Std Dev: ");
- w.append(formatMillis(hist.getUnbiasedStdDev()));
- w.append("</p>");
-
- w.append("<dl>");
-
- for (Histogram.Entry<?> e : hist.entries()) {
- w.append("<dt>");
- w.append(e.bucket.toString());
- w.append("</dt>");
- w.append("<dd>").append(Integer.toString(e.count)).append("</dd>");
- w.append("</dt>");
- }
- w.append("</dl>");
+ 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);
}
Added: avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/static/avro.css
URL: http://svn.apache.org/viewvc/avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/static/avro.css?rev=980173&view=auto
==============================================================================
--- avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/static/avro.css (added)
+++ avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/static/avro.css Wed Jul 28 19:28:53 2010
@@ -0,0 +1,21 @@
+/**
+ * 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.
+ */
+table#charts_table tr {
+ padding-right: 10px;
+ padding-left: 10px;
+}
\ No newline at end of file
Added: avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/static/avro.js
URL: http://svn.apache.org/viewvc/avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/static/avro.js?rev=980173&view=auto
==============================================================================
--- avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/static/avro.js (added)
+++ avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/static/avro.js Wed Jul 28 19:28:53 2010
@@ -0,0 +1,110 @@
+/**
+ * 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.
+ */
+function makeDotChart(yVals) {
+ var xVals = pv.range(1, yVals.length + 1);
+ var data = new Array();
+ var dotColors = pv.Colors.category20().range();
+
+ for (i = 0; i < yVals.length; i = i + 1) {
+ data[i] = {x: xVals[i], y: yVals[i]};
+ }
+
+ /* Sizing and scales. */
+ var w = 200,
+ h = 250,
+ x = pv.Scale.linear(0, Math.max.apply(Math, xVals)).range(0, w),
+ y = pv.Scale.linear(0, Math.max.apply(Math, yVals)).range(0, h),
+ c = pv.Scale.linear(1, 20).range("orange", "brown");
+
+ /* The root panel. */
+ var vis = new pv.Panel()
+ .width(w)
+ .height(h)
+ .bottom(20)
+ .left(50)
+ .right(10)
+ .top(5);
+
+ /* Y-axis and ticks. */
+ vis.add(pv.Rule)
+ .data(y.ticks())
+ .bottom(y)
+ .strokeStyle(function(d) d ? "#eee" : "#000")
+ .anchor("left").add(pv.Label)
+ .text(y.tickFormat);
+
+
+ /* The dot plot! */
+ vis.add(pv.Panel)
+ .data(data)
+ .add(pv.Dot)
+ .left(function(d) x(d.x))
+ .bottom(function(d) y(d.y))
+ .strokeStyle(function(d) dotColors[d.x % 20])
+ .fillStyle(function() this.strokeStyle().alpha(1))
+ .title(function(d) d.y)
+ .event("mouseover", pv.Behavior.tipsy({gravity: "n",
+ fade: false, delayIn: 0}));
+ vis.render();
+}
+
+
+function makeBarChart(labels, boundries, data) {
+ var w = 200,
+ h = 250,
+ x = pv.Scale.ordinal(pv.range(data.length)).splitBanded(0, w, 4/5),
+ y = pv.Scale.linear(0, Math.max.apply(Math, data)).range(0, h),
+ i = -1,
+ c = pv.Scale.linear(1, 5, 20).range("green", "yellow", "red");
+
+ var vis = new pv.Panel()
+ .width(w)
+ .height(h)
+ .bottom(20)
+ .left(40)
+ .right(5)
+ .top(30);
+
+ var bar = vis.add(pv.Bar)
+ .data(data)
+ .left(function(){ return x(this.index); })
+ .width(10)
+ .bottom(0)
+ .height(y)
+ .fillStyle(function(d) "#1f77b4")
+ .title(function() { return boundries[this.index]; })
+ .event("mouseover", pv.Behavior.tipsy({gravity: "n",
+ fade: false, delayIn: 0}));
+
+ bar.anchor("bottom").add(pv.Label)
+ .textMargin(5)
+ .textBaseline("top")
+ .text(function() (this.index % 4 == 0) ? labels[this.index]: "");
+
+ vis.add(pv.Rule)
+ .data(y.ticks())
+ .bottom(function(d) Math.round(y(d)) - .5)
+ .strokeStyle(function(d) d ? "rgba(255,255,255,.3)" : "#000")
+ .add(pv.Rule)
+ .left(0)
+ .width(5)
+ .strokeStyle("#000")
+ .anchor("left").add(pv.Label)
+ .text(function(d) d.toFixed(1));
+ vis.render();
+}
\ No newline at end of file
Added: avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/static/g.bar.js
URL: http://svn.apache.org/viewvc/avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/static/g.bar.js?rev=980173&view=auto
==============================================================================
--- avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/static/g.bar.js (added)
+++ avro/trunk/lang/java/src/java/org/apache/avro/ipc/stats/static/g.bar.js Wed Jul 28 19:28:53 2010
@@ -0,0 +1,386 @@
+/*!
+ * g.Raphael 0.4.1 - Charting library, based on Raphaël
+ *
+ * Copyright (c) 2009 Dmitry Baranovskiy (http://g.raphaeljs.com)
+ * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
+ */
+Raphael.fn.g.barchart = function (x, y, width, height, values, opts) {
+ opts = opts || {};
+ var type = {round: "round", sharp: "sharp", soft: "soft"}[opts.type] || "square",
+ gutter = parseFloat(opts.gutter || "20%"),
+ chart = this.set(),
+ bars = this.set(),
+ covers = this.set(),
+ covers2 = this.set(),
+ total = Math.max.apply(Math, values),
+ stacktotal = [],
+ paper = this,
+ multi = 0,
+ colors = opts.colors || this.g.colors,
+ len = values.length;
+ if (this.raphael.is(values[0], "array")) {
+ total = [];
+ multi = len;
+ len = 0;
+ for (var i = values.length; i--;) {
+ bars.push(this.set());
+ total.push(Math.max.apply(Math, values[i]));
+ len = Math.max(len, values[i].length);
+ }
+ if (opts.stacked) {
+ for (var i = len; i--;) {
+ var tot = 0;
+ for (var j = values.length; j--;) {
+ tot +=+ values[j][i] || 0;
+ }
+ stacktotal.push(tot);
+ }
+ }
+ for (var i = values.length; i--;) {
+ if (values[i].length < len) {
+ for (var j = len; j--;) {
+ values[i].push(0);
+ }
+ }
+ }
+ total = Math.max.apply(Math, opts.stacked ? stacktotal : total);
+ }
+
+ total = (opts.to) || total;
+ var barwidth = width / (len * (100 + gutter) + gutter) * 100,
+ barhgutter = barwidth * gutter / 100,
+ barvgutter = opts.vgutter == null ? 20 : opts.vgutter,
+ stack = [],
+ X = x + barhgutter,
+ Y = (height - 2 * barvgutter) / total;
+ if (!opts.stretch) {
+ barhgutter = Math.round(barhgutter);
+ barwidth = Math.floor(barwidth);
+ }
+ !opts.stacked && (barwidth /= multi || 1);
+ for (var i = 0; i < len; i++) {
+ stack = [];
+ for (var j = 0; j < (multi || 1); j++) {
+ var h = Math.round((multi ? values[j][i] : values[i]) * Y),
+ top = y + height - barvgutter - h,
+ bar = this.g.finger(Math.round(X + barwidth / 2), top + h, barwidth, h, true, type).attr({stroke: "none", fill: colors[multi ? j : i]});
+ if (multi) {
+ bars[j].push(bar);
+ } else {
+ bars.push(bar);
+ }
+ bar.y = top;
+ bar.x = Math.round(X + barwidth / 2);
+ bar.w = barwidth;
+ bar.h = h;
+ bar.value = multi ? values[j][i] : values[i];
+ if (!opts.stacked) {
+ X += barwidth;
+ } else {
+ stack.push(bar);
+ }
+ }
+ if (opts.stacked) {
+ var cvr;
+ covers2.push(cvr = this.rect(stack[0].x - stack[0].w / 2, y, barwidth, height).attr(this.g.shim));
+ cvr.bars = this.set();
+ var size = 0;
+ for (var s = stack.length; s--;) {
+ stack[s].toFront();
+ }
+ for (var s = 0, ss = stack.length; s < ss; s++) {
+ var bar = stack[s],
+ cover,
+ h = (size + bar.value) * Y,
+ path = this.g.finger(bar.x, y + height - barvgutter - !!size * .5, barwidth, h, true, type, 1);
+ cvr.bars.push(bar);
+ size && bar.attr({path: path});
+ bar.h = h;
+ bar.y = y + height - barvgutter - !!size * .5 - h;
+ covers.push(cover = this.rect(bar.x - bar.w / 2, bar.y, barwidth, bar.value * Y).attr(this.g.shim));
+ cover.bar = bar;
+ cover.value = bar.value;
+ size += bar.value;
+ }
+ X += barwidth;
+ }
+ X += barhgutter;
+ }
+ covers2.toFront();
+ X = x + barhgutter;
+ if (!opts.stacked) {
+ for (var i = 0; i < len; i++) {
+ for (var j = 0; j < (multi || 1); j++) {
+ var cover;
+ covers.push(cover = this.rect(Math.round(X), y + barvgutter, barwidth, height - barvgutter).attr(this.g.shim));
+ cover.bar = multi ? bars[j][i] : bars[i];
+ cover.value = cover.bar.value;
+ X += barwidth;
+ }
+ X += barhgutter;
+ }
+ }
+ chart.label = function (labels, isBottom) {
+ labels = labels || [];
+ this.labels = paper.set();
+ var L, l = -Infinity;
+ if (opts.stacked) {
+ for (var i = 0; i < len; i++) {
+ var tot = 0;
+ for (var j = 0; j < (multi || 1); j++) {
+ tot += multi ? values[j][i] : values[i];
+ if (j == multi - 1) {
+ var label = paper.g.labelise(labels[i], tot, total);
+ L = paper.g.text(bars[i * (multi || 1) + j].x, y + height - barvgutter / 2, label).insertBefore(covers[i * (multi || 1) + j]);
+ var bb = L.getBBox();
+ if (bb.x - 7 < l) {
+ L.remove();
+ } else {
+ this.labels.push(L);
+ l = bb.x + bb.width;
+ }
+ }
+ }
+ }
+ } else {
+ for (var i = 0; i < len; i++) {
+ for (var j = 0; j < (multi || 1); j++) {
+ var label = paper.g.labelise(multi ? labels[j] && labels[j][i] : labels[i], multi ? values[j][i] : values[i], total);
+ L = paper.g.text(bars[i * (multi || 1) + j].x, isBottom ? y + height - barvgutter / 2 : bars[i * (multi || 1) + j].y - 10, label).insertBefore(covers[i * (multi || 1) + j]);
+ var bb = L.getBBox();
+ if (bb.x - 7 < l) {
+ L.remove();
+ } else {
+ this.labels.push(L);
+ l = bb.x + bb.width;
+ }
+ }
+ }
+ }
+ return this;
+ };
+ chart.hover = function (fin, fout) {
+ covers2.hide();
+ covers.show();
+ covers.mouseover(fin).mouseout(fout);
+ return this;
+ };
+ chart.hoverColumn = function (fin, fout) {
+ covers.hide();
+ covers2.show();
+ fout = fout || function () {};
+ covers2.mouseover(fin).mouseout(fout);
+ return this;
+ };
+ chart.click = function (f) {
+ covers2.hide();
+ covers.show();
+ covers.click(f);
+ return this;
+ };
+ chart.each = function (f) {
+ if (!Raphael.is(f, "function")) {
+ return this;
+ }
+ for (var i = covers.length; i--;) {
+ f.call(covers[i]);
+ }
+ return this;
+ };
+ chart.eachColumn = function (f) {
+ if (!Raphael.is(f, "function")) {
+ return this;
+ }
+ for (var i = covers2.length; i--;) {
+ f.call(covers2[i]);
+ }
+ return this;
+ };
+ chart.clickColumn = function (f) {
+ covers.hide();
+ covers2.show();
+ covers2.click(f);
+ return this;
+ };
+ chart.push(bars, covers, covers2);
+ chart.bars = bars;
+ chart.covers = covers;
+ return chart;
+};
+Raphael.fn.g.hbarchart = function (x, y, width, height, values, opts) {
+ opts = opts || {};
+ var type = {round: "round", sharp: "sharp", soft: "soft"}[opts.type] || "square",
+ gutter = parseFloat(opts.gutter || "20%"),
+ chart = this.set(),
+ bars = this.set(),
+ covers = this.set(),
+ covers2 = this.set(),
+ total = Math.max.apply(Math, values),
+ stacktotal = [],
+ paper = this,
+ multi = 0,
+ colors = opts.colors || this.g.colors,
+ len = values.length;
+ if (this.raphael.is(values[0], "array")) {
+ total = [];
+ multi = len;
+ len = 0;
+ for (var i = values.length; i--;) {
+ bars.push(this.set());
+ total.push(Math.max.apply(Math, values[i]));
+ len = Math.max(len, values[i].length);
+ }
+ if (opts.stacked) {
+ for (var i = len; i--;) {
+ var tot = 0;
+ for (var j = values.length; j--;) {
+ tot +=+ values[j][i] || 0;
+ }
+ stacktotal.push(tot);
+ }
+ }
+ for (var i = values.length; i--;) {
+ if (values[i].length < len) {
+ for (var j = len; j--;) {
+ values[i].push(0);
+ }
+ }
+ }
+ total = Math.max.apply(Math, opts.stacked ? stacktotal : total);
+ }
+
+ total = (opts.to) || total;
+ var barheight = Math.floor(height / (len * (100 + gutter) + gutter) * 100),
+ bargutter = Math.floor(barheight * gutter / 100),
+ stack = [],
+ Y = y + bargutter,
+ X = (width - 1) / total;
+ !opts.stacked && (barheight /= multi || 1);
+ for (var i = 0; i < len; i++) {
+ stack = [];
+ for (var j = 0; j < (multi || 1); j++) {
+ var val = multi ? values[j][i] : values[i],
+ bar = this.g.finger(x, Y + barheight / 2, Math.round(val * X), barheight - 1, false, type).attr({stroke: "none", fill: colors[multi ? j : i]});
+ if (multi) {
+ bars[j].push(bar);
+ } else {
+ bars.push(bar);
+ }
+ bar.x = x + Math.round(val * X);
+ bar.y = Y + barheight / 2;
+ bar.w = Math.round(val * X);
+ bar.h = barheight;
+ bar.value = +val;
+ if (!opts.stacked) {
+ Y += barheight;
+ } else {
+ stack.push(bar);
+ }
+ }
+ if (opts.stacked) {
+ var cvr = this.rect(x, stack[0].y - stack[0].h / 2, width, barheight).attr(this.g.shim);
+ covers2.push(cvr);
+ cvr.bars = this.set();
+ var size = 0;
+ for (var s = stack.length; s--;) {
+ stack[s].toFront();
+ }
+ for (var s = 0, ss = stack.length; s < ss; s++) {
+ var bar = stack[s],
+ cover,
+ val = Math.round((size + bar.value) * X),
+ path = this.g.finger(x, bar.y, val, barheight - 1, false, type, 1);
+ cvr.bars.push(bar);
+ size && bar.attr({path: path});
+ bar.w = val;
+ bar.x = x + val;
+ covers.push(cover = this.rect(x + size * X, bar.y - bar.h / 2, bar.value * X, barheight).attr(this.g.shim));
+ cover.bar = bar;
+ size += bar.value;
+ }
+ Y += barheight;
+ }
+ Y += bargutter;
+ }
+ covers2.toFront();
+ Y = y + bargutter;
+ if (!opts.stacked) {
+ for (var i = 0; i < len; i++) {
+ for (var j = 0; j < (multi || 1); j++) {
+ var cover = this.rect(x, Y, width, barheight).attr(this.g.shim);
+ covers.push(cover);
+ cover.bar = multi ? bars[j][i] : bars[i];
+ cover.value = cover.bar.value;
+ Y += barheight;
+ }
+ Y += bargutter;
+ }
+ }
+ chart.label = function (labels, isRight) {
+ labels = labels || [];
+ this.labels = paper.set();
+ for (var i = 0; i < len; i++) {
+ for (var j = 0; j < multi; j++) {
+ var label = paper.g.labelise(multi ? labels[j] && labels[j][i] : labels[i], multi ? values[j][i] : values[i], total);
+ var X = isRight ? bars[i * (multi || 1) + j].x - barheight / 2 + 3 : x + 5,
+ A = isRight ? "end" : "start",
+ L;
+ this.labels.push(L = paper.g.text(X, bars[i * (multi || 1) + j].y, label).attr({"text-anchor": A}).insertBefore(covers[0]));
+ if (L.getBBox().x < x + 5) {
+ L.attr({x: x + 5, "text-anchor": "start"});
+ } else {
+ bars[i * (multi || 1) + j].label = L;
+ }
+ }
+ }
+ return this;
+ };
+ chart.hover = function (fin, fout) {
+ covers2.hide();
+ covers.show();
+ fout = fout || function () {};
+ covers.mouseover(fin).mouseout(fout);
+ return this;
+ };
+ chart.hoverColumn = function (fin, fout) {
+ covers.hide();
+ covers2.show();
+ fout = fout || function () {};
+ covers2.mouseover(fin).mouseout(fout);
+ return this;
+ };
+ chart.each = function (f) {
+ if (!Raphael.is(f, "function")) {
+ return this;
+ }
+ for (var i = covers.length; i--;) {
+ f.call(covers[i]);
+ }
+ return this;
+ };
+ chart.eachColumn = function (f) {
+ if (!Raphael.is(f, "function")) {
+ return this;
+ }
+ for (var i = covers2.length; i--;) {
+ f.call(covers2[i]);
+ }
+ return this;
+ };
+ chart.click = function (f) {
+ covers2.hide();
+ covers.show();
+ covers.click(f);
+ return this;
+ };
+ chart.clickColumn = function (f) {
+ covers.hide();
+ covers2.show();
+ covers2.click(f);
+ return this;
+ };
+ chart.push(bars, covers, covers2);
+ chart.bars = bars;
+ chart.covers = covers;
+ return chart;
+};