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 c4f59e5f6a79 CAMEL-23976: Add percentile latency statistics 
(p50/p95/p99) to Extended management statistics
c4f59e5f6a79 is described below

commit c4f59e5f6a790a713b95de0ef92fddb374207b3f
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Jul 9 19:40:59 2026 +0200

    CAMEL-23976: Add percentile latency statistics (p50/p95/p99) to Extended 
management statistics
    
    Closes #24565
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
 .../camel/impl/console/ConsumerDevConsole.java     |   5 +
 .../camel/impl/console/ContextDevConsole.java      |   5 +
 .../camel/impl/console/ProcessorDevConsole.java    |   5 +
 .../apache/camel/impl/console/RouteDevConsole.java |  10 ++
 .../camel/impl/console/RouteGroupDevConsole.java   |   5 +
 .../apache/camel/impl/console/TopDevConsole.java   |  10 ++
 .../mbean/ManagedPerformanceCounterMBean.java      |  18 ++++
 .../management/mbean/ManagedCamelContext.java      |   4 +
 .../mbean/ManagedPerformanceCounter.java           |  62 +++++++++++++
 .../camel/management/mbean/ManagedProcessor.java   |   3 +
 .../camel/management/mbean/ManagedRoute.java       |   3 +
 .../camel/management/mbean/ManagedRouteGroup.java  |   3 +
 ...anagedPercentileStatisticsDefaultLevelTest.java |  67 ++++++++++++++
 .../ManagedPercentileStatisticsTest.java           | 101 +++++++++++++++++++++
 14 files changed, 301 insertions(+)

diff --git 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/ConsumerDevConsole.java
 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/ConsumerDevConsole.java
index 7c8680a23df2..81a9892f047e 100644
--- 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/ConsumerDevConsole.java
+++ 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/ConsumerDevConsole.java
@@ -233,6 +233,11 @@ public class ConsumerDevConsole extends AbstractDevConsole 
{
         stats.put("meanProcessingTime", mr.getMeanProcessingTime());
         stats.put("maxProcessingTime", mr.getMaxProcessingTime());
         stats.put("minProcessingTime", mr.getMinProcessingTime());
+        if (mr.getProcessingTimeP50() >= 0) {
+            stats.put("p50ProcessingTime", mr.getProcessingTimeP50());
+            stats.put("p95ProcessingTime", mr.getProcessingTimeP95());
+            stats.put("p99ProcessingTime", mr.getProcessingTimeP99());
+        }
         if (mr.getExchangesTotal() > 0) {
             stats.put("lastProcessingTime", mr.getLastProcessingTime());
             stats.put("deltaProcessingTime", mr.getDeltaProcessingTime());
diff --git 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/ContextDevConsole.java
 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/ContextDevConsole.java
index 01aeb464a22a..7124fd3302cb 100644
--- 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/ContextDevConsole.java
+++ 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/ContextDevConsole.java
@@ -187,6 +187,11 @@ public class ContextDevConsole extends AbstractDevConsole {
                 stats.put("meanProcessingTime", mb.getMeanProcessingTime());
                 stats.put("maxProcessingTime", mb.getMaxProcessingTime());
                 stats.put("minProcessingTime", mb.getMinProcessingTime());
+                if (mb.getProcessingTimeP50() >= 0) {
+                    stats.put("p50ProcessingTime", mb.getProcessingTimeP50());
+                    stats.put("p95ProcessingTime", mb.getProcessingTimeP95());
+                    stats.put("p99ProcessingTime", mb.getProcessingTimeP99());
+                }
                 if (mb.getExchangesTotal() > 0) {
                     stats.put("lastProcessingTime", 
mb.getLastProcessingTime());
                     stats.put("deltaProcessingTime", 
mb.getDeltaProcessingTime());
diff --git 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/ProcessorDevConsole.java
 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/ProcessorDevConsole.java
index e4a3b32f51d8..ec1525019f47 100644
--- 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/ProcessorDevConsole.java
+++ 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/ProcessorDevConsole.java
@@ -336,6 +336,11 @@ public class ProcessorDevConsole extends 
AbstractDevConsole {
         stats.put("meanProcessingTime", mp.getMeanProcessingTime());
         stats.put("maxProcessingTime", mp.getMaxProcessingTime());
         stats.put("minProcessingTime", mp.getMinProcessingTime());
+        if (mp.getProcessingTimeP50() >= 0) {
+            stats.put("p50ProcessingTime", mp.getProcessingTimeP50());
+            stats.put("p95ProcessingTime", mp.getProcessingTimeP95());
+            stats.put("p99ProcessingTime", mp.getProcessingTimeP99());
+        }
         if (mp.getExchangesTotal() > 0) {
             stats.put("lastProcessingTime", mp.getLastProcessingTime());
             stats.put("deltaProcessingTime", mp.getDeltaProcessingTime());
diff --git 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDevConsole.java
 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDevConsole.java
index 3a89bb74218b..5e77d6f69f5f 100644
--- 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDevConsole.java
+++ 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDevConsole.java
@@ -153,6 +153,11 @@ public class RouteDevConsole extends AbstractDevConsole {
             sb.append(String.format("%n    Mean Time: %s", 
TimeUtils.printDuration(mrb.getMeanProcessingTime(), true)));
             sb.append(String.format("%n    Max Time: %s", 
TimeUtils.printDuration(mrb.getMaxProcessingTime(), true)));
             sb.append(String.format("%n    Min Time: %s", 
TimeUtils.printDuration(mrb.getMinProcessingTime(), true)));
+            if (mrb.getProcessingTimeP50() >= 0) {
+                sb.append(String.format("%n    p50 Time: %s", 
TimeUtils.printDuration(mrb.getProcessingTimeP50(), true)));
+                sb.append(String.format("%n    p95 Time: %s", 
TimeUtils.printDuration(mrb.getProcessingTimeP95(), true)));
+                sb.append(String.format("%n    p99 Time: %s", 
TimeUtils.printDuration(mrb.getProcessingTimeP99(), true)));
+            }
             if (mrb.getExchangesTotal() > 0) {
                 sb.append(String.format("%n    Last Time: %s", 
TimeUtils.printDuration(mrb.getLastProcessingTime(), true)));
                 sb.append(String.format("%n    Delta Time: %s", 
TimeUtils.printDuration(mrb.getDeltaProcessingTime(), true)));
@@ -484,6 +489,11 @@ public class RouteDevConsole extends AbstractDevConsole {
         stats.put("meanProcessingTime", mrb.getMeanProcessingTime());
         stats.put("maxProcessingTime", mrb.getMaxProcessingTime());
         stats.put("minProcessingTime", mrb.getMinProcessingTime());
+        if (mrb.getProcessingTimeP50() >= 0) {
+            stats.put("p50ProcessingTime", mrb.getProcessingTimeP50());
+            stats.put("p95ProcessingTime", mrb.getProcessingTimeP95());
+            stats.put("p99ProcessingTime", mrb.getProcessingTimeP99());
+        }
         if (mrb.getExchangesTotal() > 0) {
             stats.put("lastProcessingTime", mrb.getLastProcessingTime());
             stats.put("deltaProcessingTime", mrb.getDeltaProcessingTime());
diff --git 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteGroupDevConsole.java
 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteGroupDevConsole.java
index d191abbb4f50..80984b9fd8a5 100644
--- 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteGroupDevConsole.java
+++ 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteGroupDevConsole.java
@@ -180,6 +180,11 @@ public class RouteGroupDevConsole extends 
AbstractDevConsole {
             stats.put("meanProcessingTime", mrg.getMeanProcessingTime());
             stats.put("maxProcessingTime", mrg.getMaxProcessingTime());
             stats.put("minProcessingTime", mrg.getMinProcessingTime());
+            if (mrg.getProcessingTimeP50() >= 0) {
+                stats.put("p50ProcessingTime", mrg.getProcessingTimeP50());
+                stats.put("p95ProcessingTime", mrg.getProcessingTimeP95());
+                stats.put("p99ProcessingTime", mrg.getProcessingTimeP99());
+            }
             if (mrg.getExchangesTotal() > 0) {
                 stats.put("lastProcessingTime", mrg.getLastProcessingTime());
                 stats.put("deltaProcessingTime", mrg.getDeltaProcessingTime());
diff --git 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/TopDevConsole.java
 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/TopDevConsole.java
index 909732adf2ce..88ed725daa3e 100644
--- 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/TopDevConsole.java
+++ 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/TopDevConsole.java
@@ -257,6 +257,11 @@ public class TopDevConsole extends AbstractDevConsole {
         stats.put("lastProcessingTime", mpb.getLastProcessingTime());
         stats.put("deltaProcessingTime", mpb.getDeltaProcessingTime());
         stats.put("totalProcessingTime", mpb.getTotalProcessingTime());
+        if (mpb.getProcessingTimeP50() >= 0) {
+            stats.put("p50ProcessingTime", mpb.getProcessingTimeP50());
+            stats.put("p95ProcessingTime", mpb.getProcessingTimeP95());
+            stats.put("p99ProcessingTime", mpb.getProcessingTimeP99());
+        }
         return stats;
     }
 
@@ -271,6 +276,11 @@ public class TopDevConsole extends AbstractDevConsole {
         stats.put("lastProcessingTime", mrb.getLastProcessingTime());
         stats.put("deltaProcessingTime", mrb.getDeltaProcessingTime());
         stats.put("totalProcessingTime", mrb.getTotalProcessingTime());
+        if (mrb.getProcessingTimeP50() >= 0) {
+            stats.put("p50ProcessingTime", mrb.getProcessingTimeP50());
+            stats.put("p95ProcessingTime", mrb.getProcessingTimeP95());
+            stats.put("p99ProcessingTime", mrb.getProcessingTimeP99());
+        }
         return stats;
     }
 
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 53a38810e07c..e821f8be315b 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
@@ -93,6 +93,24 @@ public interface ManagedPerformanceCounterMBean extends 
ManagedCounterMBean {
     @ManagedAttribute(description = "First Exchange Failed ExchangeId")
     String getFirstExchangeFailureExchangeId();
 
+    /**
+     * @since 4.22
+     */
+    @ManagedAttribute(description = "50th percentile (median) of recent 
processing times [milliseconds]. Requires Extended statistics level, returns -1 
otherwise.")
+    long getProcessingTimeP50();
+
+    /**
+     * @since 4.22
+     */
+    @ManagedAttribute(description = "95th percentile of recent processing 
times [milliseconds]. Requires Extended statistics level, returns -1 
otherwise.")
+    long getProcessingTimeP95();
+
+    /**
+     * @since 4.22
+     */
+    @ManagedAttribute(description = "99th percentile of recent processing 
times [milliseconds]. Requires Extended statistics level, returns -1 
otherwise.")
+    long getProcessingTimeP99();
+
     @ManagedAttribute(description = "Statistics enabled")
     boolean isStatisticsEnabled();
 
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 49e7136a3161..751cbabb3d03 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
@@ -88,6 +88,10 @@ public class ManagedCamelContext extends 
ManagedPerformanceCounter implements Ti
         boolean enabled = context.getManagementStrategy().getManagementAgent() 
!= null
                 && 
context.getManagementStrategy().getManagementAgent().getStatisticsLevel() != 
ManagementStatisticsLevel.Off;
         setStatisticsEnabled(enabled);
+        if (context.getManagementStrategy().getManagementAgent() != null
+                && 
context.getManagementStrategy().getManagementAgent().getStatisticsLevel().isExtended())
 {
+            initExtendedStatistics();
+        }
     }
 
     @Override
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 150434e4ec30..e51e60565faa 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
@@ -17,6 +17,7 @@
 package org.apache.camel.management.mbean;
 
 import java.text.SimpleDateFormat;
+import java.util.Arrays;
 import java.util.Date;
 import java.util.Map;
 
@@ -34,6 +35,8 @@ public abstract class ManagedPerformanceCounter extends 
ManagedCounter
 
     public static final String TIMESTAMP_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
 
+    private static final int PERCENTILE_WINDOW_SIZE = 1024;
+
     private Statistic exchangesCompleted;
     private Statistic exchangesFailed;
     private Statistic exchangesInflight;
@@ -58,6 +61,11 @@ public abstract class ManagedPerformanceCounter extends 
ManagedCounter
     private String lastExchangeFailureExchangeId;
     private boolean statisticsEnabled = true;
 
+    // sliding window ring buffer for percentile computation (Extended 
statistics only)
+    private long[] percentileWindow;
+    private int percentileIndex;
+    private int percentileCount;
+
     @Override
     public void init(ManagementStrategy strategy) {
         super.init(strategy);
@@ -84,6 +92,12 @@ public abstract class ManagedPerformanceCounter extends 
ManagedCounter
         this.lastExchangeFailureTimestamp = new StatisticValue();
     }
 
+    public void initExtendedStatistics() {
+        percentileWindow = new long[PERCENTILE_WINDOW_SIZE];
+        percentileIndex = 0;
+        percentileCount = 0;
+    }
+
     @Override
     public void reset() {
         super.reset();
@@ -109,6 +123,10 @@ public abstract class ManagedPerformanceCounter extends 
ManagedCounter
         lastExchangeFailureHandledTimestamp.reset();
         lastExchangeFailureTimestamp.reset();
         lastExchangeFailureExchangeId = null;
+        if (percentileWindow != null) {
+            percentileIndex = 0;
+            percentileCount = 0;
+        }
     }
 
     @Override
@@ -171,6 +189,21 @@ public abstract class ManagedPerformanceCounter extends 
ManagedCounter
         return deltaProcessingTime.getValue();
     }
 
+    @Override
+    public long getProcessingTimeP50() {
+        return getPercentile(0.50);
+    }
+
+    @Override
+    public long getProcessingTimeP95() {
+        return getPercentile(0.95);
+    }
+
+    @Override
+    public long getProcessingTimeP99() {
+        return getPercentile(0.99);
+    }
+
     @Override
     public long getIdleSince() {
         // must not have any inflight
@@ -282,6 +315,14 @@ public abstract class ManagedPerformanceCounter extends 
ManagedCounter
         lastProcessingTime.updateValue(time);
         deltaProcessingTime.updateValue(time);
 
+        if (percentileWindow != null) {
+            percentileWindow[percentileIndex] = time;
+            percentileIndex = (percentileIndex + 1) % PERCENTILE_WINDOW_SIZE;
+            if (percentileCount < PERCENTILE_WINDOW_SIZE) {
+                percentileCount++;
+            }
+        }
+
         long now = System.currentTimeMillis();
         if (!firstExchangeCompletedTimestamp.isUpdated()) {
             firstExchangeCompletedTimestamp.updateValue(now);
@@ -327,6 +368,17 @@ public abstract class ManagedPerformanceCounter extends 
ManagedCounter
         lastExchangeFailureExchangeId = exchange.getExchangeId();
     }
 
+    private long getPercentile(double percentile) {
+        if (percentileWindow == null || percentileCount == 0) {
+            return -1;
+        }
+        long[] snapshot = new long[percentileCount];
+        System.arraycopy(percentileWindow, 0, snapshot, 0, percentileCount);
+        Arrays.sort(snapshot);
+        int index = (int) Math.ceil(percentile * percentileCount) - 1;
+        return snapshot[Math.max(0, index)];
+    }
+
     @Override
     public String dumpStatsAsXml(boolean fullStats) {
         StringBuilder sb = new StringBuilder();
@@ -343,6 +395,11 @@ public abstract class ManagedPerformanceCounter extends 
ManagedCounter
         sb.append(String.format(" lastProcessingTime=\"%s\"", 
lastProcessingTime.getValue()));
         sb.append(String.format(" deltaProcessingTime=\"%s\"", 
deltaProcessingTime.getValue()));
         sb.append(String.format(" meanProcessingTime=\"%s\"", 
meanProcessingTime.getValue()));
+        if (percentileWindow != null) {
+            sb.append(String.format(" p50ProcessingTime=\"%s\"", 
getProcessingTimeP50()));
+            sb.append(String.format(" p95ProcessingTime=\"%s\"", 
getProcessingTimeP95()));
+            sb.append(String.format(" p99ProcessingTime=\"%s\"", 
getProcessingTimeP99()));
+        }
         sb.append(String.format(" idleSince=\"%s\"", getIdleSince()));
 
         if (fullStats) {
@@ -390,6 +447,11 @@ public abstract class ManagedPerformanceCounter extends 
ManagedCounter
         jo.put("lastProcessingTime", lastProcessingTime.getValue());
         jo.put("deltaProcessingTime", deltaProcessingTime.getValue());
         jo.put("meanProcessingTime", meanProcessingTime.getValue());
+        if (percentileWindow != null) {
+            jo.put("p50ProcessingTime", getProcessingTimeP50());
+            jo.put("p95ProcessingTime", getProcessingTimeP95());
+            jo.put("p99ProcessingTime", getProcessingTimeP99());
+        }
         jo.put("idleSince", getIdleSince());
         if (fullStats) {
             jo.put("startTimestamp", startTimestamp.getTime());
diff --git 
a/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedProcessor.java
 
b/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedProcessor.java
index 84fc090e157a..04935145ff82 100644
--- 
a/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedProcessor.java
+++ 
b/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedProcessor.java
@@ -78,6 +78,9 @@ public class ManagedProcessor extends 
ManagedPerformanceCounter
         super.init(strategy);
         boolean enabled = 
context.getManagementStrategy().getManagementAgent().getStatisticsLevel().isDefaultOrExtended();
         setStatisticsEnabled(enabled);
+        if 
(context.getManagementStrategy().getManagementAgent().getStatisticsLevel().isExtended())
 {
+            initExtendedStatistics();
+        }
     }
 
     public CamelContext getContext() {
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 ff1ca3bd36a1..86cd24c50af2 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
@@ -105,6 +105,9 @@ public class ManagedRoute extends ManagedPerformanceCounter 
implements TimerList
         boolean enabled
                 = 
context.getManagementStrategy().getManagementAgent().getStatisticsLevel() != 
ManagementStatisticsLevel.Off;
         setStatisticsEnabled(enabled);
+        if 
(context.getManagementStrategy().getManagementAgent().getStatisticsLevel().isExtended())
 {
+            initExtendedStatistics();
+        }
     }
 
     public Route getRoute() {
diff --git 
a/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedRouteGroup.java
 
b/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedRouteGroup.java
index 4c9a1f3e04e3..efcf5aadffc9 100644
--- 
a/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedRouteGroup.java
+++ 
b/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedRouteGroup.java
@@ -53,6 +53,9 @@ public class ManagedRouteGroup extends 
ManagedPerformanceCounter implements Time
         boolean enabled
                 = 
context.getManagementStrategy().getManagementAgent().getStatisticsLevel() != 
ManagementStatisticsLevel.Off;
         setStatisticsEnabled(enabled);
+        if 
(context.getManagementStrategy().getManagementAgent().getStatisticsLevel().isExtended())
 {
+            initExtendedStatistics();
+        }
     }
 
     public CamelContext getContext() {
diff --git 
a/core/camel-management/src/test/java/org/apache/camel/management/ManagedPercentileStatisticsDefaultLevelTest.java
 
b/core/camel-management/src/test/java/org/apache/camel/management/ManagedPercentileStatisticsDefaultLevelTest.java
new file mode 100644
index 000000000000..ae435ef44eb4
--- /dev/null
+++ 
b/core/camel-management/src/test/java/org/apache/camel/management/ManagedPercentileStatisticsDefaultLevelTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.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;
+
+@DisabledOnOs(OS.AIX)
+public class ManagedPercentileStatisticsDefaultLevelTest extends 
ManagementTestSupport {
+
+    @Test
+    public void testPercentileStatsDisabledByDefault() throws Exception {
+        MBeanServer mbeanServer = getMBeanServer();
+        ObjectName on = getCamelObjectName(TYPE_ROUTE, 
context.getRoutes().get(0).getRouteId());
+
+        getMockEndpoint("mock:result").expectedMessageCount(3);
+
+        for (int i = 0; i < 3; i++) {
+            template.sendBody("direct:start", "Message " + i);
+        }
+
+        assertMockEndpointsSatisfied();
+
+        Long completed = (Long) mbeanServer.getAttribute(on, 
"ExchangesCompleted");
+        assertEquals(3, completed.longValue());
+
+        Long p50 = (Long) mbeanServer.getAttribute(on, "ProcessingTimeP50");
+        Long p95 = (Long) mbeanServer.getAttribute(on, "ProcessingTimeP95");
+        Long p99 = (Long) mbeanServer.getAttribute(on, "ProcessingTimeP99");
+
+        assertEquals(-1, p50.longValue(), "p50 should be -1 when Extended is 
not enabled");
+        assertEquals(-1, p95.longValue(), "p95 should be -1 when Extended is 
not enabled");
+        assertEquals(-1, p99.longValue(), "p99 should be -1 when Extended is 
not enabled");
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:start").to("mock:result");
+            }
+        };
+    }
+}
diff --git 
a/core/camel-management/src/test/java/org/apache/camel/management/ManagedPercentileStatisticsTest.java
 
b/core/camel-management/src/test/java/org/apache/camel/management/ManagedPercentileStatisticsTest.java
new file mode 100644
index 000000000000..f95d02c8d9a4
--- /dev/null
+++ 
b/core/camel-management/src/test/java/org/apache/camel/management/ManagedPercentileStatisticsTest.java
@@ -0,0 +1,101 @@
+/*
+ * 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.CamelContext;
+import org.apache.camel.ManagementStatisticsLevel;
+import org.apache.camel.builder.RouteBuilder;
+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.assertTrue;
+
+@DisabledOnOs(OS.AIX)
+public class ManagedPercentileStatisticsTest extends ManagementTestSupport {
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        CamelContext context = super.createCamelContext();
+        
context.getManagementStrategy().getManagementAgent().setStatisticsLevel(ManagementStatisticsLevel.Extended);
+        return context;
+    }
+
+    @Test
+    public void testPercentileStatsOnRoute() throws Exception {
+        MBeanServer mbeanServer = getMBeanServer();
+        ObjectName on = getCamelObjectName(TYPE_ROUTE, 
context.getRoutes().get(0).getRouteId());
+
+        getMockEndpoint("mock:result").expectedMessageCount(5);
+
+        for (int i = 0; i < 5; i++) {
+            template.sendBody("direct:start", "Message " + i);
+        }
+
+        assertMockEndpointsSatisfied();
+
+        Long completed = (Long) mbeanServer.getAttribute(on, 
"ExchangesCompleted");
+        assertEquals(5, completed.longValue());
+
+        Long p50 = (Long) mbeanServer.getAttribute(on, "ProcessingTimeP50");
+        Long p95 = (Long) mbeanServer.getAttribute(on, "ProcessingTimeP95");
+        Long p99 = (Long) mbeanServer.getAttribute(on, "ProcessingTimeP99");
+
+        assertTrue(p50 >= 0, "p50 should be >= 0, was: " + p50);
+        assertTrue(p95 >= 0, "p95 should be >= 0, was: " + p95);
+        assertTrue(p99 >= 0, "p99 should be >= 0, was: " + p99);
+        assertTrue(p50 <= p95, "p50 should be <= p95, was p50=" + p50 + " 
p95=" + p95);
+        assertTrue(p95 <= p99, "p95 should be <= p99, was p95=" + p95 + " 
p99=" + p99);
+    }
+
+    @Test
+    public void testPercentileStatsOnContext() throws Exception {
+        MBeanServer mbeanServer = getMBeanServer();
+        ObjectName on = getContextObjectName();
+
+        getMockEndpoint("mock:result").expectedMessageCount(3);
+
+        for (int i = 0; i < 3; i++) {
+            template.sendBody("direct:start", "Message " + i);
+        }
+
+        assertMockEndpointsSatisfied();
+
+        Long p50 = (Long) mbeanServer.getAttribute(on, "ProcessingTimeP50");
+        Long p95 = (Long) mbeanServer.getAttribute(on, "ProcessingTimeP95");
+        Long p99 = (Long) mbeanServer.getAttribute(on, "ProcessingTimeP99");
+
+        assertTrue(p50 >= 0, "p50 should be >= 0, was: " + p50);
+        assertTrue(p95 >= 0, "p95 should be >= 0, was: " + p95);
+        assertTrue(p99 >= 0, "p99 should be >= 0, was: " + p99);
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:start").to("mock:result");
+            }
+        };
+    }
+}

Reply via email to