tmielke commented on code in PR #27576: URL: https://github.com/apache/camel/pull/27576#discussion_r4220771974
########## core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteGroupExchangeCountTest.java: ########## @@ -0,0 +1,188 @@ +/* + * 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 java.util.concurrent.TimeUnit; + +import org.apache.camel.api.management.ManagedCamelContext; +import org.apache.camel.api.management.mbean.ManagedRouteGroupMBean; +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.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * Tests that a route group counts each exchange once per entry into the group: calls nested inside another route of the + * same group are not counted again (CAMEL-24887). + */ +@DisabledOnOs(OS.AIX) +public class ManagedRouteGroupExchangeCountTest extends ManagementTestSupport { + + @Test + public void testGroupCountsExchangeOnce() throws Exception { + ManagedCamelContext mcc = context.getCamelContextExtension().getContextPlugin(ManagedCamelContext.class); + + // send 1 message through x1 -> x2 (both in group "g") + template.sendBody("direct:x1", "Hello"); + + ManagedRouteGroupMBean group = mcc.getManagedRouteGroup("g"); + assertNotNull(group); + assertEquals(1, group.getExchangesCompleted()); + assertEquals(1, group.getExchangesTotal()); + assertEquals(0, group.getExchangesInflight()); + + // send 2 more messages + template.sendBody("direct:x1", "World"); + template.sendBody("direct:x1", "!"); + + assertEquals(3, group.getExchangesCompleted()); + assertEquals(3, group.getExchangesTotal()); + } + + @Test + public void testGroupCountsOnceWhenRoutingThroughNonGroupRoute() throws Exception { + ManagedCamelContext mcc = context.getCamelContextExtension().getContextPlugin(ManagedCamelContext.class); + + // send 1 message through x1 (group "g") -> y (no group) -> x2 (group "g") + template.sendBody("direct:x1-via-y", "Hello"); + + ManagedRouteGroupMBean group = mcc.getManagedRouteGroup("g"); + assertNotNull(group); + assertEquals(1, group.getExchangesCompleted()); + assertEquals(1, group.getExchangesTotal()); + } + + @Test + public void testGroupCountsWhenEntryIsOutsideGroup() throws Exception { + ManagedCamelContext mcc = context.getCamelContextExtension().getContextPlugin(ManagedCamelContext.class); + + // send 1 message to a route outside the group that calls x2 (in group "g") directly + template.sendBody("direct:outside", "Hello"); + + ManagedRouteGroupMBean group = mcc.getManagedRouteGroup("g"); + assertNotNull(group); + assertEquals(1, group.getExchangesCompleted()); + } + + @Test + public void testGroupCountsFailure() throws Exception { + ManagedCamelContext mcc = context.getCamelContextExtension().getContextPlugin(ManagedCamelContext.class); + + // send 1 message that will fail in x2-fail (group "g") + try { + template.sendBody("direct:x1-fail", "Hello"); + } catch (Exception e) { + // expected + } + + ManagedRouteGroupMBean group = mcc.getManagedRouteGroup("g"); + assertNotNull(group); + assertEquals(1, group.getExchangesFailed()); + assertEquals(0, group.getExchangesCompleted()); + assertEquals(0, group.getExchangesInflight()); + } + + @Test + public void testTwoGroupsOnOnePath() throws Exception { + ManagedCamelContext mcc = context.getCamelContextExtension().getContextPlugin(ManagedCamelContext.class); + + // send 1 message through g1-route (group "g1") -> g2-route (group "g2") + template.sendBody("direct:g1-route", "Hello"); + + ManagedRouteGroupMBean group1 = mcc.getManagedRouteGroup("g1"); + ManagedRouteGroupMBean group2 = mcc.getManagedRouteGroup("g2"); + assertNotNull(group1); + assertNotNull(group2); + assertEquals(1, group1.getExchangesCompleted()); + assertEquals(1, group2.getExchangesCompleted()); + } + + @Test + public void testWireTapGroupCounts() throws Exception { + ManagedCamelContext mcc = context.getCamelContextExtension().getContextPlugin(ManagedCamelContext.class); + + getMockEndpoint("mock:wt-done").expectedMessageCount(1); + + template.sendBody("direct:wt", "Hello"); + + assertMockEndpointsSatisfied(); + + // wait for the wireTapped exchange to complete in route "wt-target" + await().atMost(5, TimeUnit.SECONDS) + .untilAsserted(() -> assertEquals(1L, mcc.getManagedRoute("wt-target").getExchangesCompleted())); + + // each route counts independently at the route level + assertEquals(1, mcc.getManagedRoute("wt").getExchangesCompleted()); + assertEquals(1, mcc.getManagedRoute("wt-target").getExchangesCompleted()); + + // the group count is consistent with how the CamelContext MBean counts exchanges + long ctxCompleted = mcc.getManagedCamelContext().getExchangesCompleted(); + + ManagedRouteGroupMBean group = mcc.getManagedRouteGroup("g"); + assertNotNull(group); + assertEquals(ctxCompleted, group.getExchangesCompleted()); + assertEquals(ctxCompleted, group.getExchangesTotal()); Review Comment: Thanks for resolving the doc, upgrade-guide, and Javadoc threads. Regarding the wireTap test — I investigated this in detail by adding stack trace tracing to `DefaultUnitOfWork.pushRoute()`, and the copy's `routeStackLevel` is actually 2, not 1. The suggested test change would fail (the await times out because the group stays at 1, and MCC also reports 1). Here's what happens: 1. `createCorrelatedCopy(exchange, false)` sets `copy.setUnitOfWork(null) `— the copy starts with no UoW. ✓ 2. `WireTapTask.run()` calls `asyncProcessor.process(copy)`. This `asyncProcessor` is a `CamelInternalProcessor` belonging to the source route (`"wt"`). Its `UnitOfWorkProcessorAdvice.before()` creates a fresh UoW and pushes `"wt"` onto the stack → `level = 1`. 3. Inside that CIP's processor chain, a `SendProcessor` sends the copy to `direct:wt-target`. The `DirectProducer` enters a second `CamelInternalProcessor` for route `"wt-target"`. Its `UnitOfWorkProcessorAdvice.before()`sees the UoW already exists (created in step 2) and pushes "`wt-target"` → `level = 2`. 4. When `completedExchange` fires for `"wt-target",` both `ManagedCamelContext (routeStackLevel(includeRT, includeK)<= 1 → false`) and `ManagedRouteGroup (routeStackLevel(filter) <= 1 → false)` skip the copy. The key is step 2: the WireTap wraps its child processor in a `CamelInternalProcessor` from the source route (for error handling and UoW lifecycle). This pushes the source route onto the copy's fresh UoW before the copy ever reaches the target route. Verified output: ``` MCC.completedExchange: exchangeId=...0000 routeStackLevel=1 route=wt counting=true MCC.completedExchange: exchangeId=...0001 routeStackLevel=2 route=wt-target counting=false ``` So `ManagedCamelContext.getExchangesCompleted()` returns 1, not 2. The group also returns 1. Both are consistent. Regarding raciness: `CompositePerformanceCounter.completedExchange()` calls counter1 (route), counter2 (context), counter3 (group) sequentially in the same thread. By the time the route counter is updated, the context and group counters have already been updated in the same invocation — there is no async gap between them. If we want wireTap copies counted as separate exchanges (total = 2), the fix would need to change how `WireTapProcessor` wires its inner `CamelInternalProcessor`, or change the dedup logic in both `ManagedCamelContext` and `ManagedRouteGroup`. That seems like a separate concern from CAMEL-24887. I'll fix the stray blank line on line 183. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
