[
https://issues.apache.org/jira/browse/CAMEL-24887?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18124442#comment-18124442
]
Claus Ibsen commented on CAMEL-24887:
-------------------------------------
_Analysis and implementation plan by Claude Code on behalf of davsclaus.
Torsten, the issue is yours. This is written so that you or your agent can pick
it up directly._
h3. Decision
The group MBean should count an exchange *once per group*, as the CamelContext
MBean already counts it once per context. We are not adding {{getAggregated*}}
or "transaction" methods. The 4.22.1 behaviour is a regression from
CAMEL-24590, so please change the issue type to *Bug*.
Why:
* The original design was 1 per exchange per group. {{ManagedRouteGroupTest}}
from CAMEL-22310 ({{dfb7d5cb9b1c}}) asserted {{ExchangesTotal == 1}} for both
groups, although group "first" has 3 member routes on the exchange's path and
"second" has 2. CAMEL-24590 ({{7c231133c27e}}, backported to 4.22.x as
{{b9e1da150b80}}) fixed the wiring (one shared {{ManagedRouteGroup}} per group,
which is correct and stays). It also changed those assertions to 3 and 2, and
nothing deduplicates the counts.
* The same problem was solved for the context before:
{{ManagedCamelContext.completedExchange/failedExchange/processExchange}} only
count when {{uow.routeStackLevel(includeRouteTemplates, includeKamelets) <= 1}}
(CAMEL-18557, comment: "if an exchange is routed via 3 routes we should only
count this as 1 instead of 3").
* Summing over the member routes also double counts the processing times. X1
calling X2 via {{direct:}} means X1's time already includes X2's, so the
group's total, mean and max processing times are wrong too, not just the
counts. That is why an "aggregated" variant is not worth keeping.
* An exchange property marker is not needed. It would add a property to every
exchange, and with "first callback wins" the innermost route (which completes
first) would be the one recorded, so the processing time would be wrong.
h3. Rule
A route event (process, completed, failed) counts for group G only when the
route raising it is the *outermost route of group G on the exchange's route
stack*. In other words, the number of routes of group G on the stack is {{<=
1}}. Examples:
* X1 -> X2 (both in G): counted once, when X1 runs.
* X1 -> Y (not in G) -> X2: counted once. Only X1 counts, because X1 is still
on the stack while X2 runs.
* Y (not in G) -> X2: counted once, when X2 runs.
* wireTap, seda, multicast with parallel processing or other copies into a
member route: the copy is a new exchange with its own UnitOfWork, so it counts
separately. The context MBean behaves the same way.
* The rule has to be the same in {{processExchange}}, {{completedExchange}} and
{{failedExchange}}, otherwise the inflight counter drifts.
h3. Implementation
*1. camel-api: a filtered route stack level*
{{core/camel-api/src/main/java/org/apache/camel/spi/UnitOfWork.java}}: add next
to {{routeStackLevel(boolean, boolean)}}:
{code:java}
/**
* Gets the number of routes on the route stack that this {@link UnitOfWork}
currently is being routed through,
* which match the given filter.
* <p/>
* For example to know whether the current route is the outermost route of a
route group on the stack.
*
* @param filter the filter
* @return the number of matching routes on the route stack
* @since 4.23
*/
default int routeStackLevel(Predicate<Route> filter) {
return 0;
}
{code}
* The {{default}} keeps third-party {{UnitOfWork}} implementations compiling.
It returns 0, so those implementations keep the current, summed behaviour. In
Camel only {{DefaultUnitOfWork}} and {{MDCUnitOfWork extends
DefaultUnitOfWork}} implement it. Please search camel-quarkus and
camel-spring-boot to confirm.
* Use the {{@since}} of the release the change goes into. If it misses 4.23.0,
which is cut on Friday 2026-10-09, it becomes {{4.24}}.
{{core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultUnitOfWork.java}}:
implement it under the existing {{lock}}, counting {{route}} plus
{{routeStack}}, like {{routeStackLevel(boolean, boolean)}} at line ~430 does.
*2. camel-management: deduplicate in ManagedRouteGroup*
{{core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedRouteGroup.java}}:
override the three counter methods, following {{ManagedCamelContext}} lines
106-190:
{code:java}
@Override
public void completedExchange(Exchange exchange, long time) {
if (isOutermostRouteOfGroup(exchange)) {
super.completedExchange(exchange, time);
}
}
// same for failedExchange(Exchange) and processExchange(Exchange, String)
private boolean isOutermostRouteOfGroup(Exchange exchange) {
UnitOfWork uow = exchange.getUnitOfWork();
// no unit of work: count, as ManagedCamelContext does
return uow == null || uow.routeStackLevel(r -> group.equals(r.getGroup()))
<= 1;
}
{code}
* Do not change {{CompositePerformanceCounter}} or the caching in
{{JmxManagementLifecycleStrategy}}. The shared instance from CAMEL-24590 is
right.
* Check first that the route counter's callbacks fire while the route is still
on the UnitOfWork stack. {{ManagedCamelContext}}'s {{level <= 1}} relies on
this too, so a breakpoint on the X2 completion should show both X1 and X2 on
the stack.
* Unlike the context MBean, no {{includeRouteTemplates}}/{{includeKamelets}}
filtering: membership is the group name.
*3. Tests (camel-management, JUnit style as in the existing ManagedRouteGroup
tests)*
* {{ManagedRouteGroupTest}}: restore the original expectation, {{ExchangesTotal
== 1}} for both "first" and "second", and drop the "aggregate" comments added
by CAMEL-24590.
* {{ManagedRouteGroupFailuresHandledTest}}: {{trigger}} is the outermost route
and the {{onException}} sends to {{step1 -> step2}} under it. Expect
{{ExchangesCompleted == 1}} (currently 3), and {{FailuresHandled == 1}} plus
the timestamp unchanged. This keeps the CAMEL-24590 bug fixed.
* New test (for example {{ManagedRouteGroupExchangeCountTest}}) covering the
reporter's case and the rule:
** X1 -> X2, send 1 message: completed 1, total 1, inflight 0. Send 3:
completed 3.
** X1 -> Y (no group) -> X2: 1.
** a route outside the group calling X2 directly: 1.
** a failure in X2 that is not handled: group {{ExchangesFailed == 1}},
{{ExchangesCompleted == 0}}, inflight 0.
** a failure handled in a *nested* member (onException in X2, continuing in
X1): the group still records 1 failure handled. That is, the handled flag is
still on the exchange when X1 completes. If it is not, decide whether that is
acceptable. The context MBean has the same semantics, so assert that the group
and the context agree when all routes are in one group.
** two groups on one path (X1 in G1 -> Z1 in G2): each group counts 1.
** {{wireTap("direct:x2")}}: G counts 2, consistent with the context MBean.
* A unit test in camel-base-engine for
{{DefaultUnitOfWork.routeStackLevel(Predicate)}}.
*4. Docs*
* {{docs/user-manual/modules/ROOT/pages/route-group.adoc}}, "Monitoring Route
Groups": add one sentence. Each exchange is counted once per group, even when
it is routed through several routes of the group, as the CamelContext MBean
counts it once. The per-route numbers are on the route MBeans.
* Upgrade guide, on {{main}}:
** {{camel-4x-upgrade-guide-4_22.adoc}}, 4.22.2 section: the group exchange
counters and processing times count each exchange once per group again, as in
4.22.0. In 4.22.1 they counted it once per member route.
** the upgrade guide of the release the fix goes into ({{4_23}} or {{4_24}}):
the same note, for users coming from 4.22.1.
* No other code needs changing: {{RouteGroupDevConsole}} and {{camel get
group}} read the MBean, so they pick up the fix.
*5. Backport*
* {{camel-4.22.x}}: yes. It is a human-reported regression introduced by a
4.22.x backport. The API addition is a {{default}} method, so it is additive.
Add {{port/camel-4.22.x}} to the PR.
* {{camel-4.18.x}}: no. CAMEL-24590 is not on that branch ({{git log
origin/camel-4.18.x --grep CAMEL-24590}} is empty), so 4.18 still has the old
single-route wiring.
* fixVersions: 4.22.2 plus 4.23.0 or 4.24.0, depending on when it is merged.
*6. Build*
{{cd core/camel-api && mvn -DskipTests install}}, then
{{core/camel-base-engine}}, then {{cd core/camel-management && mvn install}}.
Run the {{ManagedRouteGroup*}} and {{ManagedCamelContext*}} tests, and the
camel-console tests for the route group console.
h3. Answer for the reporter
{{getExchangesCompleted()}} goes back to 1 per message for the group (the
4.22.0 numbers). The per-route counts stay available on each route's
{{ManagedRouteMBean}}.
> ManagedRouteGroupMBean exchange stats changed
> ---------------------------------------------
>
> Key: CAMEL-24887
> URL: https://issues.apache.org/jira/browse/CAMEL-24887
> Project: Camel
> Issue Type: Improvement
> Components: camel-jmx
> Affects Versions: 4.22.1
> Reporter: Raymond
> Assignee: Torsten Mielke
> Priority: Minor
>
> In 4.22.1 the exchange stats in the {{ManagedRouteGroupMBean}} changed.
> As an example I have created a route group X with two routes X1 and X2. Now I
> would like to get how many "transactions" the group processed. I used:
> {{ManagedRouteGroupMBean managedRouteGroup =
> managedContext.getManagedRouteGroup("X");}}
> {{managedRouteGroup.getExchangesCompleted()}}
> Now when I processed 1 message/transaction I would like to get the number 1,
> etc. This worked until 4.22.0, but this recently changed.
> In 4.22.0 the result was 1
> In 4.22.1 the result is 2
> The second is the aggregated processed exchanges of all routes in the group.
> This is useful too, but I would still like to have the old result. This can
> be in a separated method:
> {{managedRouteGroup.getExchangesCompleted() --> 1}}
> {{managedRouteGroup.getAggegatedExchangesCompleted() --> 2}}
> or
> {{managedRouteGroup.getTransactionsCompleted() --> 1}}
> {{managedRouteGroup.getExchangesCompleted() --> 2}}
> This may have changed after this issue:
> https://issues.apache.org/jira/browse/CAMEL-24590
--
This message was sent by Atlassian Jira
(v8.20.10#820010)