This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 84aa0841f06 CAMEL-22307: camel-core - Add dumpRouteStatAsJson JMX
operations
84aa0841f06 is described below
commit 84aa0841f0610a73ff3becf0a41f7a60e7a3920d
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Jul 31 18:45:00 2025 +0200
CAMEL-22307: camel-core - Add dumpRouteStatAsJson JMX operations
---
.../management/mbean/ManagedCamelContextMBean.java | 3 +
.../mbean/ManagedPerformanceCounterMBean.java | 7 ++
.../api/management/mbean/ManagedRouteMBean.java | 3 +
.../management/mbean/ManagedCamelContext.java | 81 ++++++++++++++++++
.../mbean/ManagedPerformanceCounter.java | 39 +++++++++
.../camel/management/mbean/ManagedRoute.java | 98 ++++++++++++++++++++++
.../ManagedRouteDumpStatsAsJSonTest.java | 71 ++++++++++++++++
7 files changed, 302 insertions(+)
diff --git
a/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedCamelContextMBean.java
b/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedCamelContextMBean.java
index 320348ab36d..b716a80ef26 100644
---
a/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedCamelContextMBean.java
+++
b/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedCamelContextMBean.java
@@ -222,6 +222,9 @@ public interface ManagedCamelContextMBean extends
ManagedPerformanceCounterMBean
@ManagedOperation(description = "Dumps the CamelContext and routes stats
as XML")
String dumpRoutesStatsAsXml(boolean fullStats, boolean includeProcessors)
throws Exception;
+ @ManagedOperation(description = "Dumps the CamelContext and routes stats
as JSon")
+ String dumpRouteStatsAsJSon(boolean fullStats, boolean includeProcessors)
throws Exception;
+
@ManagedOperation(description = "Dumps the CamelContext and routes and
steps stats as XML")
String dumpStepStatsAsXml(boolean fullStats) throws Exception;
diff --git
a/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedPerformanceCounterMBean.java
b/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedPerformanceCounterMBean.java
index 511e874d229..b7b1f83d72a 100644
---
a/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedPerformanceCounterMBean.java
+++
b/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedPerformanceCounterMBean.java
@@ -17,6 +17,7 @@
package org.apache.camel.api.management.mbean;
import java.util.Date;
+import java.util.Map;
import org.apache.camel.api.management.ManagedAttribute;
import org.apache.camel.api.management.ManagedOperation;
@@ -98,4 +99,10 @@ public interface ManagedPerformanceCounterMBean extends
ManagedCounterMBean {
@ManagedOperation(description = "Dumps the statistics as XML")
String dumpStatsAsXml(boolean fullStats);
+ @ManagedOperation(description = "Dumps the statistics as JSon")
+ String dumpStatsAsJSon(boolean fullStats);
+
+ @ManagedOperation(description = "Adds statistics to the Map")
+ void statsAsJSon(Map<String, Object> json, boolean fullStats);
+
}
diff --git
a/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedRouteMBean.java
b/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedRouteMBean.java
index 1644dbabe87..7b5d2daf1fa 100644
---
a/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedRouteMBean.java
+++
b/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedRouteMBean.java
@@ -151,6 +151,9 @@ public interface ManagedRouteMBean extends
ManagedPerformanceCounterMBean {
@ManagedOperation(description = "Dumps the route stats as XML")
String dumpRouteStatsAsXml(boolean fullStats, boolean includeProcessors)
throws Exception;
+ @ManagedOperation(description = "Dumps the route stats as JSon")
+ String dumpRouteStatsAsJSon(boolean fullStats, boolean includeProcessors)
throws Exception;
+
@ManagedOperation(description = "Dumps the route and steps stats as XML")
String dumpStepStatsAsXml(boolean fullStats) throws Exception;
diff --git
a/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedCamelContext.java
b/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedCamelContext.java
index 1802f30cdc5..ab9f6653b02 100644
---
a/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedCamelContext.java
+++
b/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedCamelContext.java
@@ -54,6 +54,8 @@ import org.apache.camel.spi.ManagementStrategy;
import org.apache.camel.spi.UnitOfWork;
import org.apache.camel.support.CamelContextHelper;
import org.apache.camel.support.PluginHelper;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
@ManagedResource(description = "Managed CamelContext")
public class ManagedCamelContext extends ManagedPerformanceCounter implements
TimerListener, ManagedCamelContextMBean {
@@ -708,6 +710,85 @@ public class ManagedCamelContext extends
ManagedPerformanceCounter implements Ti
return sb.toString();
}
+ @Override
+ public String dumpRouteStatsAsJSon(boolean fullStats, boolean
includeProcessors) throws Exception {
+ JsonObject root = new JsonObject();
+ root.put("id", getCamelId());
+ root.put("state", getState());
+ root.put("uptime", getUptimeMillis());
+
+ statsAsJSon(root, fullStats);
+ root.put("exchangesInflight", getInflightExchanges());
+
+ MBeanServer server =
getContext().getManagementStrategy().getManagementAgent().getMBeanServer();
+ if (server != null) {
+ // gather all the routes for this CamelContext, which requires JMX
+ String prefix =
getContext().getManagementStrategy().getManagementAgent().getIncludeHostName()
? "*/" : "";
+ ObjectName query = ObjectName
+ .getInstance(jmxDomain + ":context=" + prefix +
getContext().getManagementName() + ",type=routes,*");
+ Set<ObjectName> routes = server.queryNames(query, null);
+
+ List<ManagedProcessorMBean> processors = new ArrayList<>();
+ if (includeProcessors) {
+ // gather all the processors for this CamelContext, which
requires JMX
+ query = ObjectName.getInstance(
+ jmxDomain + ":context=" + prefix +
getContext().getManagementName() + ",type=processors,*");
+ Set<ObjectName> names = server.queryNames(query, null);
+ for (ObjectName on : names) {
+ ManagedProcessorMBean processor =
context.getManagementStrategy().getManagementAgent().newProxyClient(on,
+ ManagedProcessorMBean.class);
+ processors.add(processor);
+ }
+ }
+ processors.sort(new OrderProcessorMBeans());
+
+ // loop the routes, and append the processor stats if needed
+ JsonArray arr = new JsonArray();
+ root.put("routes", arr);
+ for (ObjectName on : routes) {
+ JsonObject jo = new JsonObject();
+ arr.add(jo);
+
+ ManagedRouteMBean route
+ =
context.getManagementStrategy().getManagementAgent().newProxyClient(on,
ManagedRouteMBean.class);
+ jo.put("id", route.getRouteId());
+ jo.put("state", route.getState());
+ jo.put("uptime", route.getUptimeMillis());
+ if (route.getRouteGroup() != null) {
+ jo.put("group", route.getRouteGroup());
+ }
+ if (route.getSourceLocation() != null) {
+ jo.put("sourceLocation", route.getSourceLocation());
+ }
+
+ // use substring as we only want the attributes
+ route.statsAsJSon(jo, fullStats);
+
+ // add processor details if needed
+ if (includeProcessors) {
+ JsonArray arr2 = new JsonArray();
+ jo.put("processors", arr2);
+ for (ManagedProcessorMBean processor : processors) {
+ int line = processor.getSourceLineNumber() != null ?
processor.getSourceLineNumber() : -1;
+ // the processor must belong to this route
+ if (route.getRouteId().equals(processor.getRouteId()))
{
+ JsonObject jo2 = new JsonObject();
+ arr2.add(jo2);
+ jo2.put("id", processor.getProcessorId());
+ jo2.put("index", processor.getIndex());
+ jo2.put("state", processor.getState());
+ jo2.put("sourceLineNumber", line);
+ processor.statsAsJSon(jo2, fullStats);
+ jo2.put("exchangesInflight",
processor.getExchangesInflight());
+ }
+ }
+ }
+ }
+ }
+
+ return root.toJson();
+ }
+
@Override
public String dumpStepStatsAsXml(boolean fullStats) throws Exception {
StringBuilder sb = new StringBuilder();
diff --git
a/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedPerformanceCounter.java
b/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedPerformanceCounter.java
index 6cbe78bc42e..f60378d8a5f 100644
---
a/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedPerformanceCounter.java
+++
b/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedPerformanceCounter.java
@@ -18,6 +18,7 @@ package org.apache.camel.management.mbean;
import java.text.SimpleDateFormat;
import java.util.Date;
+import java.util.Map;
import org.apache.camel.Exchange;
import org.apache.camel.api.management.ManagedResource;
@@ -25,6 +26,7 @@ import
org.apache.camel.api.management.mbean.ManagedPerformanceCounterMBean;
import org.apache.camel.management.PerformanceCounter;
import org.apache.camel.spi.ManagementStrategy;
import org.apache.camel.support.ExchangeHelper;
+import org.apache.camel.util.json.JsonObject;
@ManagedResource(description = "Managed PerformanceCounter")
public abstract class ManagedPerformanceCounter extends ManagedCounter
@@ -355,6 +357,43 @@ public abstract class ManagedPerformanceCounter extends
ManagedCounter
return sb.toString();
}
+ @Override
+ public String dumpStatsAsJSon(boolean fullStats) {
+ JsonObject jo = new JsonObject();
+ statsAsJSon(jo, fullStats);
+ return jo.toJson();
+ }
+
+ @Override
+ public void statsAsJSon(Map<String, Object> jo, boolean fullStats) {
+ jo.put("exchangesTotal", exchangesTotal.getValue());
+ jo.put("exchangesCompleted", exchangesCompleted.getValue());
+ jo.put("exchangesFailed", exchangesFailed.getValue());
+ jo.put("failuresHandled", failuresHandled.getValue());
+ jo.put("redeliveries", redeliveries.getValue());
+ jo.put("externalRedeliveries", externalRedeliveries.getValue());
+ jo.put("minProcessingTime", minProcessingTime.getValue());
+ jo.put("maxProcessingTime", maxProcessingTime.getValue());
+ jo.put("totalProcessingTime", totalProcessingTime.getValue());
+ jo.put("lastProcessingTime", lastProcessingTime.getValue());
+ jo.put("deltaProcessingTime", deltaProcessingTime.getValue());
+ jo.put("meanProcessingTime", meanProcessingTime.getValue());
+ jo.put("idleSince", getIdleSince());
+ if (fullStats) {
+ jo.put("startTimestamp", startTimestamp.getTime());
+ jo.put("resetTimestamp", resetTimestamp.getTime());
+ jo.put("firstExchangeCompletedTimestamp",
firstExchangeCompletedTimestamp.getValue());
+ jo.put("firstExchangeCompletedExchangeId",
firstExchangeCompletedExchangeId);
+ jo.put("firstExchangeFailureTimestamp",
firstExchangeFailureTimestamp.getValue());
+ jo.put("firstExchangeFailureExchangeId",
firstExchangeFailureExchangeId);
+ jo.put("lastExchangeCreatedTimestamp",
lastExchangeCreatedTimestamp.getValue());
+ jo.put("lastExchangeCompletedTimestamp",
lastExchangeCompletedTimestamp.getValue());
+ jo.put("lastExchangeCompletedExchangeId",
lastExchangeCompletedExchangeId);
+ jo.put("lastExchangeFailureTimestamp",
lastExchangeFailureTimestamp.getValue());
+ jo.put("lastExchangeFailureExchangeId",
lastExchangeFailureExchangeId);
+ }
+ }
+
private static String dateAsString(long value) {
if (value <= 0) {
return "";
diff --git
a/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedRoute.java
b/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedRoute.java
index 7436975fccd..4c02b018d0b 100644
---
a/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedRoute.java
+++
b/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedRoute.java
@@ -64,6 +64,8 @@ import org.apache.camel.spi.ManagementStrategy;
import org.apache.camel.spi.RoutePolicy;
import org.apache.camel.support.PluginHelper;
import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
import org.apache.camel.xml.LwModelHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -576,6 +578,102 @@ public class ManagedRoute extends
ManagedPerformanceCounter implements TimerList
return answer.toString();
}
+ @Override
+ public String dumpRouteStatsAsJSon(boolean fullStats, boolean
includeProcessors) throws Exception {
+ // in this logic we need to calculate the accumulated processing time
for the processor in the route
+ // and hence why the logic is a bit more complicated to do this, as we
need to calculate that from
+ // the bottom -> top of the route but this information is valuable for
profiling routes
+ JsonObject root = new JsonObject();
+
+ root.put("id", getRouteId());
+ root.put("state", getState());
+ root.put("uptime", getUptimeMillis());
+ if (getRouteGroup() != null) {
+ root.put("group", getRouteGroup());
+ }
+ if (sourceLocation != null) {
+ root.put("sourceLocation", sourceLocation);
+ }
+ statsAsJSon(root, fullStats);
+ root.put("exchangesInflight", getInflightExchanges());
+ InflightRepository.InflightExchange oldest = getOldestInflightEntry();
+ if (oldest != null) {
+ root.put("oldestInflightExchangeId",
oldest.getExchange().getExchangeId());
+ root.put("oldestInflightDuration", oldest.getDuration());
+ }
+
+ // need to calculate this value first, as we need that value for the
route stat
+ long processorAccumulatedTime = 0L;
+
+ // gather all the processors for this route, which requires JMX
+ JsonArray arr = null;
+ if (includeProcessors) {
+ arr = new JsonArray();
+ MBeanServer server =
getContext().getManagementStrategy().getManagementAgent().getMBeanServer();
+ if (server != null) {
+ // get all the processor mbeans and sort them accordingly to
their index
+ String prefix =
getContext().getManagementStrategy().getManagementAgent().getIncludeHostName()
? "*/" : "";
+ ObjectName query = ObjectName.getInstance(
+ jmxDomain + ":context=" + prefix +
getContext().getManagementName() + ",type=processors,*");
+ Set<ObjectName> names = server.queryNames(query, null);
+ List<ManagedProcessorMBean> mps = new ArrayList<>();
+ for (ObjectName on : names) {
+ ManagedProcessorMBean processor =
context.getManagementStrategy().getManagementAgent().newProxyClient(on,
+ ManagedProcessorMBean.class);
+
+ // the processor must belong to this route
+ if (getRouteId().equals(processor.getRouteId())) {
+ mps.add(processor);
+ }
+ }
+ mps.sort(new OrderProcessorMBeans());
+
+ // walk the processors in reverse order, and calculate the
accumulated total time
+ Map<String, Long> accumulatedTimes = new HashMap<>();
+ Collections.reverse(mps);
+ for (ManagedProcessorMBean processor : mps) {
+ processorAccumulatedTime +=
processor.getTotalProcessingTime();
+ accumulatedTimes.put(processor.getProcessorId(),
processorAccumulatedTime);
+ }
+ // and reverse back again
+ Collections.reverse(mps);
+
+ // and now add the sorted list of processors to the xml output
+ for (ManagedProcessorMBean processor : mps) {
+ JsonObject jo = new JsonObject();
+ arr.add(jo);
+ processor.statsAsJSon(jo, fullStats);
+
+ int line = processor.getSourceLineNumber() != null ?
processor.getSourceLineNumber() : -1;
+ jo.put("id", processor.getProcessorId());
+ jo.put("index", processor.getIndex());
+ jo.put("state", processor.getState());
+ jo.put("sourceLineNumber", line);
+
+ // do we have an accumulated time then append that
+ Long accTime =
accumulatedTimes.get(processor.getProcessorId());
+ if (accTime != null) {
+ jo.put("accumulatedProcessingTime", accTime);
+ }
+ }
+ }
+ }
+
+ // route self time is route total - processor accumulated total
+ long routeSelfTime = getTotalProcessingTime() -
processorAccumulatedTime;
+ if (routeSelfTime < 0) {
+ // ensure we don't calculate that as negative
+ routeSelfTime = 0;
+ }
+ root.put("selfProcessingTime", routeSelfTime);
+ if (arr != null) {
+ // processors should be last
+ root.put("processors", arr);
+ }
+
+ return root.toJson();
+ }
+
@Override
public String dumpStepStatsAsXml(boolean fullStats) throws Exception {
// in this logic we need to calculate the accumulated processing time
for the processor in the route
diff --git
a/core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteDumpStatsAsJSonTest.java
b/core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteDumpStatsAsJSonTest.java
new file mode 100644
index 00000000000..f67cc43bc52
--- /dev/null
+++
b/core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteDumpStatsAsJSonTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.management;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.util.json.JsonObject;
+import org.apache.camel.util.json.Jsoner;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledOnOs;
+import org.junit.jupiter.api.condition.OS;
+
+import static
org.apache.camel.management.DefaultManagementObjectNameStrategy.TYPE_ROUTE;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+@DisabledOnOs(OS.AIX)
+public class ManagedRouteDumpStatsAsJSonTest extends ManagementTestSupport {
+
+ @Test
+ public void testPerformanceCounterStats() throws Exception {
+ // get the stats for the route
+ MBeanServer mbeanServer = getMBeanServer();
+ ObjectName on = getCamelObjectName(TYPE_ROUTE, "foo");
+
+ getMockEndpoint("mock:result").expectedMessageCount(1);
+
+ template.asyncSendBody("direct:start", "Hello World");
+
+ assertMockEndpointsSatisfied();
+
+ String json = (String) mbeanServer.invoke(on, "dumpRouteStatsAsJSon",
new Object[] { false, true },
+ new String[] { "boolean", "boolean" });
+ log.info(json);
+
+ // should be valid json
+ JsonObject jo = (JsonObject) Jsoner.deserialize(json);
+ assertNotNull(jo);
+ assertEquals(3, jo.getCollection("processors").size());
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:start").routeId("foo")
+ .to("log:foo").id("to-log")
+ .delay(100)
+ .to("mock:result").id("to-mock");
+ }
+ };
+ }
+
+}