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();
+    }
+}

Reply via email to