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");
+            }
+        };
+    }
+
+}

Reply via email to