Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ public class JmxManagementLifecycleStrategy extends ServiceSupport implements Li
private final Map<BacklogTracer, ManagedBacklogTracer> managedBacklogTracers = new HashMap<>();
private final Map<DefaultBacklogDebugger, ManagedBacklogDebugger> managedBacklogDebuggers = new HashMap<>();
private final Map<Object, Object> managedThreadPools = new HashMap<>();
// route group MBean is shared by all routes in the same group, so its performance counters
// aggregate the statistics across all the member routes
private final Map<String, ManagedRouteGroup> managedRouteGroups = new HashMap<>();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking: managedRouteGroups is a plain HashMap, so computeIfAbsent/remove here aren't synchronized. This is consistent with the existing convention in this class (managedThreadPools, managedBacklogTracers, managedBacklogDebuggers are all plain HashMap) and route lifecycle events aren't concurrent, so no new risk is introduced — just noting it for the record. No change requested.


public JmxManagementLifecycleStrategy() {
}
Expand Down Expand Up @@ -675,8 +678,15 @@ public void onRoutesAdd(Collection<Route> routes) {
LOG.trace("The route is already managed: {}", route);
continue;
}
ManagedRouteGroup mrg = (ManagedRouteGroup) getManagementObjectStrategy()
.getManagedObjectForRouteGroup(camelContext, route.getGroup());
// the route group MBean is shared by all the routes in the same group, so its
// performance counters aggregate the statistics across all the member routes. Only
// the first route in a group creates and registers it; the rest reuse the same instance
ManagedRouteGroup mrg = null;
String group = route.getGroup();
if (group != null) {
mrg = managedRouteGroups.computeIfAbsent(group, g -> (ManagedRouteGroup) getManagementObjectStrategy()
.getManagedObjectForRouteGroup(camelContext, g));
}

// get the wrapped instrumentation processor from this route
// and set me as the counter
Expand Down Expand Up @@ -746,12 +756,13 @@ public void onRoutesRemove(Collection<Route> routes) {
int size = camelContext.getRoutesByGroup(route.getGroup()).size();
// if size is 1 then it is because its ourselves that we are currently removing
if (size <= 1) {
ManagedRouteGroup mrg = (ManagedRouteGroup) getManagementObjectStrategy()
.getManagedObjectForRouteGroup(camelContext, route.getGroup());
try {
unmanageObject(mrg);
} catch (Exception e) {
LOG.warn("Could not unregister Route Group MBean", e);
ManagedRouteGroup mrg = managedRouteGroups.remove(route.getGroup());
if (mrg != null) {
try {
unmanageObject(mrg);
} catch (Exception e) {
LOG.warn("Could not unregister Route Group MBean", e);
}
}
}
}
Expand Down Expand Up @@ -1129,6 +1140,7 @@ protected void doStop() throws Exception {
managedBacklogTracers.clear();
managedBacklogDebuggers.clear();
managedThreadPools.clear();
managedRouteGroups.clear();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* 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 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.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

/**
* Tests that a route group's performance counters aggregate handled failures recorded on any of its member routes
* (CAMEL-24590).
*/
@DisabledOnOs(OS.AIX)
public class ManagedRouteGroupFailuresHandledTest extends ManagementTestSupport {

@Test
public void testGroupAggregatesFailuresHandled() throws Exception {
// the trigger route throws an exception that is handled and then hops to sibling routes in the same group
template.sendBody("direct:trigger", "Hello World");

ManagedCamelContext mcc = context.getCamelContextExtension().getContextPlugin(ManagedCamelContext.class);
ManagedRouteGroupMBean group = mcc.getManagedRouteGroup("flow");
assertNotNull(group);

// group stats must aggregate across all member routes: trigger, step1 and step2 each completed once
assertEquals(3, group.getExchangesCompleted());

// the handled failure recorded on the trigger route must be reflected at the group level
assertEquals(1, group.getFailuresHandled());
assertNotNull(group.getLastExchangeFailureHandledTimestamp(),
"Group should report the last handled-failure timestamp of its member route");
}

@Override
protected RouteBuilder createRouteBuilder() {
return new RouteBuilder() {
@Override
public void configure() {
onException(Exception.class)
.maximumRedeliveries(0)
.handled(true)
.to("direct:step1");

from("direct:step1")
.routeGroup("flow")
.routeId("step1")
.setBody(constant("123"))
.to("direct:step2");

from("direct:step2")
.routeGroup("flow")
.routeId("step2")
.setBody(constant("456"));

from("direct:trigger")
.routeGroup("flow")
.routeId("trigger")
.throwException(new RuntimeException("Test failure"));
}
};
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,15 @@ public void testRouteGroup() throws Exception {
String group = (String) mbeanServer.getAttribute(on, "RouteGroup");
assertTrue(group.equals("first") || group.equals("second"));
Long val = (Long) mbeanServer.getAttribute(on, "ExchangesTotal");
assertEquals(1, val);
Integer size = (Integer) mbeanServer.getAttribute(on, "GroupSize");
if ("first".equals(group)) {
assertEquals(3, size);
// group stats aggregate across all member routes (start, a, e each processed the exchange)
assertEquals(3, val);
} else {
assertEquals(2, size);
// group stats aggregate across all member routes (c, d each processed the exchange)
assertEquals(2, val);
}

// stop all the route
Expand Down