This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch feature/CAMEL-23976-percentile-latency-stats in repository https://gitbox.apache.org/repos/asf/camel.git
commit d8b7bcbf2ac4261a5014a6adbfe537c129a9aef0 Author: Claus Ibsen <[email protected]> AuthorDate: Thu Jul 9 15:27:42 2026 +0200 CAMEL-23976: Add percentile latency statistics (p50/p95/p99) to Extended management statistics Co-Authored-By: Claude Opus 4.6 <[email protected]> Signed-off-by: Claus Ibsen <[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 | 9 ++ .../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, 292 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..9e8f35315c21 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,15 @@ public interface ManagedPerformanceCounterMBean extends ManagedCounterMBean { @ManagedAttribute(description = "First Exchange Failed ExchangeId") String getFirstExchangeFailureExchangeId(); + @ManagedAttribute(description = "50th percentile (median) of recent processing times [milliseconds]. Requires Extended statistics level, returns -1 otherwise.") + long getProcessingTimeP50(); + + @ManagedAttribute(description = "95th percentile of recent processing times [milliseconds]. Requires Extended statistics level, returns -1 otherwise.") + long getProcessingTimeP95(); + + @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"); + } + }; + } +}
