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