You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2015/07/27 11:57:28 UTC

camel git commit: CAMEL-9014: Added camel command to show endpoint runtime stats.

Repository: camel
Updated Branches:
  refs/heads/master e20760513 -> fae3bb65f


CAMEL-9014: Added camel command to show endpoint runtime stats.


Project: http://git-wip-us.apache.org/repos/asf/camel/repo
Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/fae3bb65
Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/fae3bb65
Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/fae3bb65

Branch: refs/heads/master
Commit: fae3bb65fe15b4cb949db35593eaba859e2f3129
Parents: e207605
Author: Claus Ibsen <da...@apache.org>
Authored: Mon Jul 27 12:00:47 2015 +0200
Committer: Claus Ibsen <da...@apache.org>
Committed: Mon Jul 27 12:04:35 2015 +0200

----------------------------------------------------------------------
 .../mbean/ManagedCamelContextMBean.java         |   3 +
 .../management/mbean/ManagedCamelContext.java   |  13 +-
 .../management/ManagedCamelContextTest.java     |   3 +
 .../commands/AbstractLocalCamelController.java  |  37 ++++
 .../apache/camel/commands/CamelController.java  |   9 +
 .../camel/commands/ContextInfoCommand.java      |   3 +
 .../commands/EndpointStatisticCommand.java      | 216 +++++++++++++++++++
 .../jolokia/DefaultJolokiaCamelController.java  |  36 ++++
 .../camel/karaf/commands/EndpointStats.java     |  43 ++++
 .../OSGI-INF/blueprint/camel-commands.xml       |   9 +
 10 files changed, 369 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/fae3bb65/camel-core/src/main/java/org/apache/camel/api/management/mbean/ManagedCamelContextMBean.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/api/management/mbean/ManagedCamelContextMBean.java b/camel-core/src/main/java/org/apache/camel/api/management/mbean/ManagedCamelContextMBean.java
index abf246e..4cb8905 100644
--- a/camel-core/src/main/java/org/apache/camel/api/management/mbean/ManagedCamelContextMBean.java
+++ b/camel-core/src/main/java/org/apache/camel/api/management/mbean/ManagedCamelContextMBean.java
@@ -43,6 +43,9 @@ public interface ManagedCamelContextMBean extends ManagedPerformanceCounterMBean
     @ManagedAttribute(description = "Uptime")
     String getUptime();
 
+    @ManagedAttribute(description = "Camel Management StatisticsLevel")
+    String getManagementStatisticsLevel();
+
     @ManagedAttribute(description = "Camel Properties")
     Map<String, String> getProperties();
 

http://git-wip-us.apache.org/repos/asf/camel/blob/fae3bb65/camel-core/src/main/java/org/apache/camel/management/mbean/ManagedCamelContext.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/management/mbean/ManagedCamelContext.java b/camel-core/src/main/java/org/apache/camel/management/mbean/ManagedCamelContext.java
index 1cb56a1..a1c1be7 100644
--- a/camel-core/src/main/java/org/apache/camel/management/mbean/ManagedCamelContext.java
+++ b/camel-core/src/main/java/org/apache/camel/management/mbean/ManagedCamelContext.java
@@ -37,9 +37,6 @@ import javax.management.openmbean.CompositeType;
 import javax.management.openmbean.TabularData;
 import javax.management.openmbean.TabularDataSupport;
 
-import org.apache.camel.spi.ManagementStrategy;
-import org.w3c.dom.Document;
-
 import org.apache.camel.CamelContext;
 import org.apache.camel.Component;
 import org.apache.camel.ComponentConfiguration;
@@ -60,9 +57,11 @@ import org.apache.camel.model.RouteDefinition;
 import org.apache.camel.model.RoutesDefinition;
 import org.apache.camel.model.rest.RestDefinition;
 import org.apache.camel.model.rest.RestsDefinition;
+import org.apache.camel.spi.ManagementStrategy;
 import org.apache.camel.util.CamelContextHelper;
 import org.apache.camel.util.JsonSchemaHelper;
 import org.apache.camel.util.ObjectHelper;
+import org.w3c.dom.Document;
 
 /**
  * @version
@@ -107,6 +106,14 @@ public class ManagedCamelContext extends ManagedPerformanceCounter implements Ti
         return context.getUptime();
     }
 
+    public String getManagementStatisticsLevel() {
+        if (context.getManagementStrategy().getManagementAgent() != null) {
+            return context.getManagementStrategy().getManagementAgent().getStatisticsLevel().name();
+        } else {
+            return null;
+        }
+    }
+
     public String getClassResolver() {
         return context.getClassResolver().getClass().getName();
     }

http://git-wip-us.apache.org/repos/asf/camel/blob/fae3bb65/camel-core/src/test/java/org/apache/camel/management/ManagedCamelContextTest.java
----------------------------------------------------------------------
diff --git a/camel-core/src/test/java/org/apache/camel/management/ManagedCamelContextTest.java b/camel-core/src/test/java/org/apache/camel/management/ManagedCamelContextTest.java
index f2fcf7f..46237b7 100644
--- a/camel-core/src/test/java/org/apache/camel/management/ManagedCamelContextTest.java
+++ b/camel-core/src/test/java/org/apache/camel/management/ManagedCamelContextTest.java
@@ -76,6 +76,9 @@ public class ManagedCamelContextTest extends ManagementTestSupport {
         String managementName = (String) mbeanServer.getAttribute(on, "ManagementName");
         assertEquals("19-camel-1", managementName);
 
+        String level = (String) mbeanServer.getAttribute(on, "ManagementStatisticsLevel");
+        assertEquals("Default", level);
+
         String uptime = (String) mbeanServer.getAttribute(on, "Uptime");
         assertNotNull(uptime);
 

http://git-wip-us.apache.org/repos/asf/camel/blob/fae3bb65/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/AbstractLocalCamelController.java
----------------------------------------------------------------------
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/AbstractLocalCamelController.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/AbstractLocalCamelController.java
index 1657a16..4fa7da0 100644
--- a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/AbstractLocalCamelController.java
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/AbstractLocalCamelController.java
@@ -41,8 +41,10 @@ import org.apache.camel.model.ModelHelper;
 import org.apache.camel.model.RouteDefinition;
 import org.apache.camel.model.rest.RestDefinition;
 import org.apache.camel.model.rest.RestsDefinition;
+import org.apache.camel.spi.EndpointRegistry;
 import org.apache.camel.spi.ManagementAgent;
 import org.apache.camel.spi.RestRegistry;
+import org.apache.camel.spi.RuntimeEndpointRegistry;
 import org.apache.camel.util.JsonSchemaHelper;
 
 /**
@@ -71,6 +73,10 @@ public abstract class AbstractLocalCamelController extends AbstractCamelControll
             answer.put("status", context.getStatus().name());
             answer.put("uptime", context.getUptime());
             answer.put("suspended", context.getStatus().isSuspended());
+            if (context.getManagementStrategy().getManagementAgent() != null) {
+                String level = context.getManagementStrategy().getManagementAgent().getStatisticsLevel().name();
+                answer.put("managementStatisticsLevel", level);
+            }
             answer.put("allowUseOriginalMessage", context.isAllowUseOriginalMessage());
             answer.put("messageHistory", context.isMessageHistory());
             answer.put("tracing", context.isTracing());
@@ -424,6 +430,37 @@ public abstract class AbstractLocalCamelController extends AbstractCamelControll
         return answer;
     }
 
+    public List<Map<String, String>> getEndpointRuntimeStatistics(String camelContextName) throws Exception {
+        List<Map<String, String>> answer = new ArrayList<Map<String, String>>();
+
+        if (camelContextName != null) {
+            CamelContext context = this.getLocalCamelContext(camelContextName);
+            if (context != null) {
+                EndpointRegistry staticRegistry = context.getEndpointRegistry();
+                for (RuntimeEndpointRegistry.Statistic stat : context.getRuntimeEndpointRegistry().getEndpointStatistics()) {
+
+                    String url = stat.getUri();
+                    String routeId = stat.getRouteId();
+                    String direction = stat.getDirection();
+                    Boolean isStatic = staticRegistry.isStatic(url);
+                    Boolean isDynamic = staticRegistry.isDynamic(url);
+                    long hits = stat.getHits();
+
+                    Map<String, String> row = new LinkedHashMap<String, String>();
+                    row.put("camelContextName", context.getName());
+                    row.put("uri", url);
+                    row.put("routeId", routeId);
+                    row.put("direction", direction);
+                    row.put("static", isStatic.toString());
+                    row.put("dynamic", isDynamic.toString());
+                    row.put("hits", "" + hits);
+                    answer.add(row);
+                }
+            }
+        }
+        return answer;
+    }
+
     public List<Map<String, String>> getRestServices(String camelContextName) throws Exception {
         List<Map<String, String>> answer = new ArrayList<Map<String, String>>();
 

http://git-wip-us.apache.org/repos/asf/camel/blob/fae3bb65/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/CamelController.java
----------------------------------------------------------------------
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/CamelController.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/CamelController.java
index bb00f9e..67030bc 100644
--- a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/CamelController.java
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/CamelController.java
@@ -191,6 +191,15 @@ public interface CamelController {
     List<Map<String, String>> getEndpoints(String camelContextName) throws Exception;
 
     /**
+     * Return endpoint runtime statistics
+     *
+     * @param camelContextName the Camel context.
+     * @return a list of key/value pairs with endpoint runtime statistics
+     * @throws java.lang.Exception can be thrown
+     */
+    List<Map<String, String>> getEndpointRuntimeStatistics(String camelContextName) throws Exception;
+
+    /**
      * Return the definition of the REST services as XML for the given Camel context.
      *
      * @param camelContextName the Camel context.

http://git-wip-us.apache.org/repos/asf/camel/blob/fae3bb65/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ContextInfoCommand.java
----------------------------------------------------------------------
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ContextInfoCommand.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ContextInfoCommand.java
index d2a9731..698e809 100644
--- a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ContextInfoCommand.java
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/ContextInfoCommand.java
@@ -75,6 +75,9 @@ public class ContextInfoCommand extends AbstractCamelCommand {
         out.println(stringEscape.unescapeJava("\u001B[1mMiscellaneous\u001B[0m"));
         out.println(stringEscape.unescapeJava("\tSuspended: " + row.get("suspended")));
         out.println(stringEscape.unescapeJava("\tShutdown Timeout: " + row.get("shutdownTimeout") + " sec."));
+        if (row.get("managementStatisticsLevel") != null) {
+            out.println(stringEscape.unescapeJava("\tManagement StatisticsLevel: " + row.get("managementStatisticsLevel")));
+        }
         out.println(stringEscape.unescapeJava("\tAllow UseOriginalMessage: " + row.get("allowUseOriginalMessage")));
         out.println(stringEscape.unescapeJava("\tMessage History: " + row.get("messageHistory")));
         out.println(stringEscape.unescapeJava("\tTracing: " + row.get("tracing")));

http://git-wip-us.apache.org/repos/asf/camel/blob/fae3bb65/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/EndpointStatisticCommand.java
----------------------------------------------------------------------
diff --git a/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/EndpointStatisticCommand.java b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/EndpointStatisticCommand.java
new file mode 100644
index 0000000..fb76655
--- /dev/null
+++ b/platforms/commands/commands-core/src/main/java/org/apache/camel/commands/EndpointStatisticCommand.java
@@ -0,0 +1,216 @@
+/**
+ * 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.camel.commands;
+
+import java.io.PrintStream;
+import java.net.URLDecoder;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.camel.util.URISupport;
+
+/**
+ * Display endpoint runtime statistics for a CamelContext
+ */
+public class EndpointStatisticCommand extends AbstractContextCommand {
+
+    private static final String URI_COLUMN_LABEL = "Uri";
+    private static final String ROUTE_COLUMN_LABEL = "Route Id";
+    private static final String DIRECTION_COLUMN_LABEL = "Direction";
+    private static final String STATIC_COLUMN_LABEL = "Static";
+    private static final String DYNAMIC_COLUMN_LABEL = "Dynamic";
+    private static final String HITS_COLUMN_LABEL = "Hits";
+
+    private static final int DEFAULT_COLUMN_WIDTH_INCREMENT = 0;
+    private static final String DEFAULT_FIELD_PREAMBLE = " ";
+    private static final String DEFAULT_FIELD_POSTAMBLE = " ";
+    private static final String DEFAULT_HEADER_PREAMBLE = " ";
+    private static final String DEFAULT_HEADER_POSTAMBLE = " ";
+    private static final int DEFAULT_FORMAT_BUFFER_LENGTH = 24;
+    // endpoint uris can be very long so clip by default after 120 chars
+    private static final int MAX_COLUMN_WIDTH = 120;
+    private static final int MIN_COLUMN_WIDTH = 12;
+
+    boolean decode = true;
+    private String[] filter;
+
+    public EndpointStatisticCommand(String context, boolean decode, String[] filter) {
+        super(context);
+        this.decode = decode;
+        this.filter = filter;
+    }
+
+    @Override
+    protected Object performContextCommand(CamelController camelController, String contextName, PrintStream out, PrintStream err) throws Exception {
+        List<Map<String, String>> endpoints = camelController.getEndpointRuntimeStatistics(contextName);
+
+        final Map<String, Integer> columnWidths = computeColumnWidths(endpoints);
+        final String headerFormat = buildFormatString(columnWidths, true);
+        final String rowFormat = buildFormatString(columnWidths, false);
+
+        if (endpoints.size() > 0) {
+            out.println(String.format(headerFormat, URI_COLUMN_LABEL, ROUTE_COLUMN_LABEL, DIRECTION_COLUMN_LABEL, STATIC_COLUMN_LABEL, DYNAMIC_COLUMN_LABEL, HITS_COLUMN_LABEL));
+            out.println(String.format(headerFormat, "---", "--------", "---------", "------", "-------", "----"));
+            for (Map<String, String> row : endpoints) {
+
+                String uri = row.get("uri");
+                if (decode) {
+                    // decode uri so its more human readable
+                    uri = URLDecoder.decode(uri, "UTF-8");
+                }
+                // sanitize and mask uri so we dont see passwords
+                uri = URISupport.sanitizeUri(uri);
+                String routeId = row.get("routeId");
+                String direction = row.get("direction");
+                String isStatic = row.get("static");
+                String isDynamic = row.get("dynamic");
+                String hits = row.get("hits");
+
+                // should we filter
+                if (isValidRow(direction, isStatic, isDynamic)) {
+                    out.println(String.format(rowFormat, uri, routeId, direction, isStatic, isDynamic, hits));
+                }
+            }
+        }
+
+        return null;
+    }
+
+    protected boolean isValidRow(String direction, String isStatic, String isDynamic) {
+        if (filter == null || filter.length == 0) {
+            return true;
+        }
+
+        boolean answer = false;
+        for (String f : filter) {
+            if ("in".equals(f)) {
+                answer = "in".equals(direction);
+            } else if ("out".equals(f)) {
+                answer = "out".equals(direction);
+            } else if ("static".equals(f)) {
+                answer = "true".equals(isStatic);
+            } else if ("dynamic".equals(f)) {
+                answer = "true".equals(isDynamic);
+            }
+            // all filters must apply to accept when multi valued
+            if (!answer) {
+                return false;
+            }
+        }
+        return answer;
+    }
+
+    private Map<String, Integer> computeColumnWidths(final Iterable<Map<String, String>> endpoints) throws Exception {
+        if (endpoints == null) {
+            throw new IllegalArgumentException("Unable to determine column widths from null Iterable<Endpoint>");
+        } else {
+            int maxUriLen = 0;
+            int maxRouteIdLen = 0;
+            int maxDirectionLen = 0;
+            int maxStaticLen = 0;
+            int maxDynamicLen = 0;
+            int maxHitsLen = 0;
+
+            for (Map<String, String> row : endpoints) {
+                final String name = row.get("camelContextName");
+
+                String uri = row.get("uri");
+                if (decode) {
+                    // decode uri so its more human readable
+                    uri = URLDecoder.decode(uri, "UTF-8");
+                }
+                // sanitize and mask uri so we dont see passwords
+                uri = URISupport.sanitizeUri(uri);
+
+                maxUriLen = Math.max(maxUriLen, uri == null ? 0 : uri.length());
+
+                final String routeId = row.get("routeId");
+                maxRouteIdLen = Math.max(maxRouteIdLen, routeId == null ? 0 : routeId.length());
+
+                final String direction = row.get("direction");
+                maxDirectionLen = Math.max(maxDirectionLen, direction == null ? 0 : direction.length());
+
+                final String isStatic = row.get("static");
+                maxStaticLen = Math.max(maxStaticLen, isStatic == null ? 0 : isStatic.length());
+
+                final String isDynamic = row.get("dynamic");
+                maxDynamicLen = Math.max(maxDynamicLen, isDynamic == null ? 0 : isDynamic.length());
+
+                final String hits = row.get("hits");
+                maxHitsLen = Math.max(maxHitsLen, hits == null ? 0 : hits.length());
+            }
+
+            final Map<String, Integer> retval = new Hashtable<String, Integer>();
+            retval.put(URI_COLUMN_LABEL, maxUriLen);
+            retval.put(ROUTE_COLUMN_LABEL, maxRouteIdLen);
+            retval.put(DIRECTION_COLUMN_LABEL, maxDirectionLen);
+            retval.put(STATIC_COLUMN_LABEL, maxStaticLen);
+            retval.put(DYNAMIC_COLUMN_LABEL, maxDynamicLen);
+            retval.put(HITS_COLUMN_LABEL, maxHitsLen);
+
+            return retval;
+        }
+    }
+
+    private String buildFormatString(final Map<String, Integer> columnWidths, final boolean isHeader) {
+        final String fieldPreamble;
+        final String fieldPostamble;
+        final int columnWidthIncrement;
+
+        if (isHeader) {
+            fieldPreamble = DEFAULT_HEADER_PREAMBLE;
+            fieldPostamble = DEFAULT_HEADER_POSTAMBLE;
+        } else {
+            fieldPreamble = DEFAULT_FIELD_PREAMBLE;
+            fieldPostamble = DEFAULT_FIELD_POSTAMBLE;
+        }
+        columnWidthIncrement = DEFAULT_COLUMN_WIDTH_INCREMENT;
+
+        int uriLen = Math.min(columnWidths.get(URI_COLUMN_LABEL) + columnWidthIncrement, getMaxColumnWidth());
+        uriLen = Math.max(MIN_COLUMN_WIDTH, uriLen);
+
+        int routeIdLen = Math.min(columnWidths.get(ROUTE_COLUMN_LABEL) + columnWidthIncrement, getMaxColumnWidth());
+        routeIdLen = Math.max(MIN_COLUMN_WIDTH, routeIdLen);
+
+        int directionLen = Math.min(columnWidths.get(DIRECTION_COLUMN_LABEL) + columnWidthIncrement, getMaxColumnWidth());
+        directionLen = Math.max(MIN_COLUMN_WIDTH, directionLen);
+
+        int staticLen = Math.min(columnWidths.get(STATIC_COLUMN_LABEL) + columnWidthIncrement, getMaxColumnWidth());
+        staticLen = Math.max(MIN_COLUMN_WIDTH, staticLen);
+
+        int dynamicLen = Math.min(columnWidths.get(DYNAMIC_COLUMN_LABEL) + columnWidthIncrement, getMaxColumnWidth());
+        dynamicLen = Math.max(MIN_COLUMN_WIDTH, staticLen);
+
+        // last row does not have min width
+
+        final StringBuilder retval = new StringBuilder(DEFAULT_FORMAT_BUFFER_LENGTH);
+        retval.append(fieldPreamble).append("%-").append(uriLen).append('.').append(uriLen).append('s').append(fieldPostamble).append(' ');
+        retval.append(fieldPreamble).append("%-").append(routeIdLen).append('.').append(routeIdLen).append('s').append(fieldPostamble).append(' ');
+        retval.append(fieldPreamble).append("%-").append(directionLen).append('.').append(directionLen).append('s').append(fieldPostamble).append(' ');
+        retval.append(fieldPreamble).append("%-").append(staticLen).append('.').append(staticLen).append('s').append(fieldPostamble).append(' ');
+        retval.append(fieldPreamble).append("%-").append(dynamicLen).append('.').append(dynamicLen).append('s').append(fieldPostamble).append(' ');
+        retval.append(fieldPreamble).append("%s").append(fieldPostamble).append(' ');
+
+        return retval.toString();
+    }
+
+    private int getMaxColumnWidth() {
+        return MAX_COLUMN_WIDTH;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/fae3bb65/platforms/commands/commands-jolokia/src/main/java/org/apache/camel/commands/jolokia/DefaultJolokiaCamelController.java
----------------------------------------------------------------------
diff --git a/platforms/commands/commands-jolokia/src/main/java/org/apache/camel/commands/jolokia/DefaultJolokiaCamelController.java b/platforms/commands/commands-jolokia/src/main/java/org/apache/camel/commands/jolokia/DefaultJolokiaCamelController.java
index 8f8ffae..454fbb0 100644
--- a/platforms/commands/commands-jolokia/src/main/java/org/apache/camel/commands/jolokia/DefaultJolokiaCamelController.java
+++ b/platforms/commands/commands-jolokia/src/main/java/org/apache/camel/commands/jolokia/DefaultJolokiaCamelController.java
@@ -538,6 +538,42 @@ public class DefaultJolokiaCamelController extends AbstractCamelController imple
     }
 
     @Override
+    public List<Map<String, String>> getEndpointRuntimeStatistics(String camelContextName) throws Exception {
+        if (jolokia == null) {
+            throw new IllegalStateException("Need to connect to remote jolokia first");
+        }
+
+        List<Map<String, String>> answer = new ArrayList<Map<String, String>>();
+
+        ObjectName found = lookupCamelContext(camelContextName);
+        if (found != null) {
+            String pattern = String.format("%s:context=%s,type=services,name=DefaultRuntimeEndpointRegistry", found.getDomain(), found.getKeyProperty("context"));
+            ObjectName on = ObjectName.getInstance(pattern);
+
+            J4pExecResponse response = jolokia.execute(new J4pExecRequest(on, "endpointStatistics()"));
+            if (response != null) {
+                JSONObject data = response.getValue();
+                for (Object obj : data.values()) {
+                    JSONObject data2 = (JSONObject) obj;
+                    JSONObject service = (JSONObject) data2.values().iterator().next();
+
+                    Map<String, String> row = new LinkedHashMap<String, String>();
+                    row.put("index", asString(service.get("index")));
+                    row.put("url", asString(service.get("url")));
+                    row.put("routeId", asString(service.get("routeId")));
+                    row.put("direction", asString(service.get("direction")));
+                    row.put("static", asString(service.get("static")));
+                    row.put("dynamic", asString(service.get("dynamic")));
+                    row.put("hits", asString(service.get("hits")));
+                    answer.add(row);
+                }
+            }
+        }
+
+        return answer;
+    }
+
+    @Override
     public List<Map<String, String>> getRestServices(String camelContextName) throws Exception {
         if (jolokia == null) {
             throw new IllegalStateException("Need to connect to remote jolokia first");

http://git-wip-us.apache.org/repos/asf/camel/blob/fae3bb65/platforms/karaf/commands/src/main/java/org/apache/camel/karaf/commands/EndpointStats.java
----------------------------------------------------------------------
diff --git a/platforms/karaf/commands/src/main/java/org/apache/camel/karaf/commands/EndpointStats.java b/platforms/karaf/commands/src/main/java/org/apache/camel/karaf/commands/EndpointStats.java
new file mode 100644
index 0000000..a9b64b6
--- /dev/null
+++ b/platforms/karaf/commands/src/main/java/org/apache/camel/karaf/commands/EndpointStats.java
@@ -0,0 +1,43 @@
+/**
+ * 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.camel.karaf.commands;
+
+import org.apache.camel.commands.EndpointStatisticCommand;
+import org.apache.felix.gogo.commands.Argument;
+import org.apache.felix.gogo.commands.Command;
+import org.apache.felix.gogo.commands.Option;
+
+@Command(scope = "camel", name = "endpoint-stats", description = "Display endpoint runtime statistics")
+public class EndpointStats extends CamelCommandSupport {
+
+    @Argument(index = 0, name = "name", description = "The name of the Camel context", required = true, multiValued = false)
+    String name;
+
+    @Option(name = "--filter", aliases = "-f", description = "Filter the list by in,out,static,dynamic",
+            required = false, multiValued = true)
+    String[] filter;
+
+    @Option(name = "--decode", aliases = "-d", description = "Whether to decode the endpoint uri so its human readable",
+            required = false, multiValued = false, valueToShowInHelp = "true")
+    boolean decode = true;
+
+    protected Object doExecute() throws Exception {
+        EndpointStatisticCommand command = new EndpointStatisticCommand(name, decode, filter);
+        return command.execute(camelController, System.out, System.err);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/fae3bb65/platforms/karaf/commands/src/main/resources/OSGI-INF/blueprint/camel-commands.xml
----------------------------------------------------------------------
diff --git a/platforms/karaf/commands/src/main/resources/OSGI-INF/blueprint/camel-commands.xml b/platforms/karaf/commands/src/main/resources/OSGI-INF/blueprint/camel-commands.xml
index 8603db2..ba134bf 100644
--- a/platforms/karaf/commands/src/main/resources/OSGI-INF/blueprint/camel-commands.xml
+++ b/platforms/karaf/commands/src/main/resources/OSGI-INF/blueprint/camel-commands.xml
@@ -179,6 +179,15 @@
         <null/>
       </completers>
     </command>
+    <command name="camel/endpoint-stats">
+      <action class="org.apache.camel.karaf.commands.EndpointStats">
+        <property name="camelController" ref="camelController"/>
+      </action>
+      <completers>
+        <ref component-id="camelContextCompleter"/>
+        <null/>
+      </completers>
+    </command>
     <command name="camel/endpoint-explain">
       <action class="org.apache.camel.karaf.commands.EndpointExplain">
         <property name="camelController" ref="camelController"/>