This is an automated email from the ASF dual-hosted git repository.
Croway pushed a commit to branch camel-4.18.x
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/camel-4.18.x by this push:
new b33412ffd469 [backport camel-4.18.x] CAMEL-26565: camel-micrometer -
skip kamelet routes without a route lookup (#29141)
b33412ffd469 is described below
commit b33412ffd4696b12b82982dcf55d9db664d80ca5
Author: Federico Mariani <[email protected]>
AuthorDate: Wed Nov 4 10:26:56 2026 +0100
[backport camel-4.18.x] CAMEL-26565: camel-micrometer - skip kamelet routes
without a route lookup (#29141)
CAMEL-26565: camel-micrometer - skip kamelet routes without a route lookup
notify() called CamelContext.getRoute(id), which copies the route list
under a
lock, on every event to decide whether the route is created by a kamelet or
a
route template. The decision is now taken once when the route is added.
Partial backport: camel-4.18.x does not have the getOrCreateTimer() registry
search added by CAMEL-23719, its timers already use register().
---
.../MicrometerExchangeEventNotifier.java | 18 ++-
...MicrometerExchangeEventNotifierHotPathTest.java | 135 +++++++++++++++++++++
2 files changed, 147 insertions(+), 6 deletions(-)
diff --git
a/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/eventnotifier/MicrometerExchangeEventNotifier.java
b/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/eventnotifier/MicrometerExchangeEventNotifier.java
index 76e347f17a7c..1d7ddac7ccf8 100644
---
a/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/eventnotifier/MicrometerExchangeEventNotifier.java
+++
b/components/camel-micrometer/src/main/java/org/apache/camel/component/micrometer/eventnotifier/MicrometerExchangeEventNotifier.java
@@ -18,6 +18,8 @@ package org.apache.camel.component.micrometer.eventnotifier;
import java.util.HashMap;
import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Predicate;
@@ -47,6 +49,9 @@ public class MicrometerExchangeEventNotifier extends
AbstractMicrometerEventNoti
private InflightRepository inflightRepository;
private final Map<String, Meter> meterMap = new HashMap<>();
+ // ids of routes not to meter (created by kamelets/templates), decided
once when the route is added so the
+ // exchange events do not need CamelContext.getRoute(id), which copies the
route list on every call
+ private final Set<String> skippedRouteIds = ConcurrentHashMap.newKeySet();
private Predicate<Exchange> ignoreExchanges = exchange -> false;
private MicrometerExchangeEventNotifierNamingStrategy namingStrategy;
boolean registerKamelets;
@@ -141,6 +146,9 @@ public class MicrometerExchangeEventNotifier extends
AbstractMicrometerEventNoti
private void addInflightMeter(Route route) {
boolean skip = (route.isCreatedByKamelet() && !registerKamelets)
|| (route.isCreatedByRouteTemplate() && !registerTemplates);
+ if (skip) {
+ skippedRouteIds.add(route.getRouteId());
+ }
if (!skip) {
String routeId = route.getRouteId();
String name = getNamingStrategy().getInflightExchangesName();
@@ -154,6 +162,7 @@ public class MicrometerExchangeEventNotifier extends
AbstractMicrometerEventNoti
}
private void removeInflightMeter(String routeId) {
+ skippedRouteIds.remove(routeId);
Meter meter = meterMap.remove(routeId);
if (meter != null) {
getMeterRegistry().remove(meter);
@@ -167,6 +176,7 @@ public class MicrometerExchangeEventNotifier extends
AbstractMicrometerEventNoti
// remove all meters
meterMap.values().forEach(m -> getMeterRegistry().remove(m));
meterMap.clear();
+ skippedRouteIds.clear();
}
@Override
@@ -180,12 +190,8 @@ public class MicrometerExchangeEventNotifier extends
AbstractMicrometerEventNoti
} else {
routeId = ExchangeHelper.getAtRouteId(ee.getExchange());
}
- if (routeId != null) {
- Route route = ee.getExchange().getContext().getRoute(routeId);
- if (route != null) {
- skip = (route.isCreatedByKamelet() && !registerKamelets)
- || (route.isCreatedByRouteTemplate() &&
!registerTemplates);
- }
+ if (routeId != null && skippedRouteIds.contains(routeId)) {
+ skip = true;
}
if (skip) {
return;
diff --git
a/components/camel-micrometer/src/test/java/org/apache/camel/component/micrometer/eventnotifier/MicrometerExchangeEventNotifierHotPathTest.java
b/components/camel-micrometer/src/test/java/org/apache/camel/component/micrometer/eventnotifier/MicrometerExchangeEventNotifierHotPathTest.java
new file mode 100644
index 000000000000..819682c92e22
--- /dev/null
+++
b/components/camel-micrometer/src/test/java/org/apache/camel/component/micrometer/eventnotifier/MicrometerExchangeEventNotifierHotPathTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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.component.micrometer.eventnotifier;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import io.micrometer.core.instrument.Meter;
+import io.micrometer.core.instrument.Timer;
+import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
+import io.micrometer.core.instrument.search.RequiredSearch;
+import io.micrometer.core.instrument.search.Search;
+import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.micrometer.MicrometerConstants;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The exchange events must not scan the meter registry (find() copies and
filters every registered meter), and routes
+ * created by kamelets must stay excluded without looking the route up on
every event, including routes added or removed
+ * after startup.
+ */
+public class MicrometerExchangeEventNotifierHotPathTest extends
AbstractMicrometerEventNotifierTest {
+
+ private static final String SENT = "ExchangeSentEvent";
+
+ private final AtomicInteger searches = new AtomicInteger();
+
+ @Override
+ public void addRegistry() {
+ meterRegistry = new CompositeMeterRegistry() {
+ @Override
+ public Search find(String name) {
+ searches.incrementAndGet();
+ return super.find(name);
+ }
+
+ @Override
+ public RequiredSearch get(String name) {
+ searches.incrementAndGet();
+ return super.get(name);
+ }
+ };
+ meterRegistry.add(new SimpleMeterRegistry());
+ }
+
+ @Override
+ protected AbstractMicrometerEventNotifier<?> getEventNotifier() {
+ return new MicrometerExchangeEventNotifier();
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:start").routeId("start")
+ .to("mock:a")
+ .to("direct:kamelet");
+
+
from("direct:kamelet").routeId("kamelet").to("mock:insideKamelet").setKamelet(true);
+ }
+ };
+ }
+
+ @Test
+ public void testNoRegistrySearchPerExchange() {
+ int count = 50;
+ searches.set(0);
+ for (int i = 0; i < count; i++) {
+ template.sendBody("direct:start", "msg-" + i);
+ }
+ assertEquals(0, searches.get(), "the meter registry must not be
searched per exchange");
+
+ // the timers are still found and updated through register()
+ assertEquals(count, sentTimerCount("mock://a"));
+ }
+
+ @Test
+ public void testKameletRoutesAreNotMetered() throws Exception {
+ template.sendBody("direct:start", "msg");
+ assertEquals(1, sentTimerCount("mock://a"));
+ assertEquals(0, sentTimerCount("mock://insideKamelet"), "route created
by a kamelet must not be metered");
+
+ // a kamelet route added after startup is skipped as well
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+
from("direct:late").routeId("late").to("mock:insideLate").setKamelet(true);
+ }
+ });
+ template.sendBody("direct:late", "msg");
+ assertEquals(0, sentTimerCount("mock://insideLate"), "kamelet route
added at runtime must not be metered");
+
+ // once removed, the same route id may be reused by a route that is
metered
+ context.getRouteController().stopRoute("late");
+ assertTrue(context.removeRoute("late"));
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:late").routeId("late").to("mock:insideLate");
+ }
+ });
+ template.sendBody("direct:late", "msg");
+ assertEquals(1, sentTimerCount("mock://insideLate"), "regular route
reusing the id must be metered");
+ }
+
+ private long sentTimerCount(String endpoint) {
+ List<Meter> meters = meterRegistry.getMeters();
+ return meters.stream()
+ .filter(m -> m instanceof Timer)
+ .filter(m ->
MicrometerConstants.DEFAULT_CAMEL_EXCHANGE_EVENT_METER_NAME.equals(m.getId().getName()))
+ .filter(m ->
SENT.equals(m.getId().getTag(MicrometerConstants.EVENT_TYPE_TAG)))
+ .filter(m ->
endpoint.equals(m.getId().getTag(MicrometerConstants.ENDPOINT_NAME)))
+ .mapToLong(m -> ((Timer) m).count())
+ .sum();
+ }
+}