This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 9ce7be6fb124 CAMEL-16866: add routepolicy 'long task' timer (#19933)
9ce7be6fb124 is described below

commit 9ce7be6fb12455b80185a2b0ea21af4f56dc6beb
Author: Jono Morris <[email protected]>
AuthorDate: Mon Nov 17 19:27:26 2025 +1300

    CAMEL-16866: add routepolicy 'long task' timer (#19933)
    
    * CAMEL-16866 add routepolicy long-task timer
    
    * CAMEL-16866 add routepolicy to doc
---
 .../main/docs/opentelemetry-metrics-component.adoc |  51 +++++++-
 .../metrics/OpenTelemetryConstants.java            |   2 +
 .../camel/opentelemetry/metrics/TaskTimer.java     |   9 +-
 .../OpenTelemetryContextMetricsStatistics.java     |   4 +-
 .../routepolicy/OpenTelemetryLongTaskTimer.java    |  92 ++++++++++++++
 .../routepolicy/OpenTelemetryRoutePolicy.java      |  29 ++++-
 .../OpenTelemetryRoutePolicyConfiguration.java     |  13 ++
 .../OpenTelemetryRoutePolicyFactory.java           |  12 +-
 .../OpenTelemetryRoutePolicyNamingStrategy.java    |  16 +++
 .../OpenTelemetryContextMetricTest.java            | 116 +++++++++++++++++
 .../OpenTelemetryRoutePolicyLongTaskTest.java      | 138 +++++++++++++++++++++
 11 files changed, 465 insertions(+), 17 deletions(-)

diff --git 
a/components/camel-opentelemetry-metrics/src/main/docs/opentelemetry-metrics-component.adoc
 
b/components/camel-opentelemetry-metrics/src/main/docs/opentelemetry-metrics-component.adoc
index 1c434d896401..11eebee91981 100644
--- 
a/components/camel-opentelemetry-metrics/src/main/docs/opentelemetry-metrics-component.adoc
+++ 
b/components/camel-opentelemetry-metrics/src/main/docs/opentelemetry-metrics-component.adoc
@@ -340,11 +340,11 @@ Camel specific metrics that are available via the 
`OpenTelemetryExchangeEventNot
 
 [width="100%",options="header"]
 |=====================================================
-|Default Name |Type |Unit |Description
-|camel.exchange.sent | LongHistogram | TimeUnit.MILLISECONDS | Time taken to 
send a message to the endpoint
-|camel.exchange.elapsed | LongHistogram | TimeUnit.MILLISECONDS | Time taken 
to complete exchange
-|camel.exchanges.last.time | ObservableLongGauge | TimeUnit.MILLISECONDS | 
Last exchange processed time since the Unix epoch
-|camel.exchanges.inflight | ObservableLongGauge | Long | Number of in flight 
messages per route
+|Default Name |Type |Description
+|camel.exchange.sent | LongHistogram | Time taken to send a message to the 
endpoint; Default time unit is milliseconds
+|camel.exchange.elapsed | LongHistogram | Time taken to complete exchange; 
Default time unit is milliseconds
+|camel.exchanges.last.time | ObservableLongGauge | Last exchange processed 
time since the Unix epoch; ; Default time unit is milliseconds
+|camel.exchanges.inflight | ObservableLongGauge | Number of in flight messages 
per route
 |=====================================================
 
 The following options are supported:
@@ -361,6 +361,47 @@ By default, static values are used. When using dynamic 
attributes a dynamic to (
 leading to high cardinality in metrics.
 |=======================================================================
 
+=== OpenTelemetry Route Policy
+
+Route policy metrics can be enabled by adding the 
`OpenTelemetryRoutePolicyFactory` to the `CamelContext`:
+
+[source,java]
+----
+context.addRoutePolicyFactory(new OpenTelemetryRoutePolicyFactory());
+----
+
+[NOTE]
+====
+You can define a dedicated `OpenTelemetryRoutePolicy` per route you want to 
instrument
+in case you only want to instrument a few selected routes.
+====
+
+Camel specific metrics that are available via the `OpenTelemetryRoutePolicy`:
+[width="100%",options="header"]
+[width="100%",options="header"]
+|=====================================================
+|Default Name |Type |Description
+| camel.route.policy | LongHistogram | Time taken to complete processing of 
exchanges for a route
+| camel.exchanges.succeeded | LongCounter | Number of successfully completed 
exchanges
+| camel.exchanges.failed | LongCounter | Number of failed exchanges
+| camel.exchanges.total | LongCounter | Total number of exchanges processed
+| camel.exchanges.external.redeliveries | LongCounter | Number of externally 
initiated redeliveries
+| camel.exchanges.failures.handled | LongCounter | Number of failures handled
+| camel.route.policy.tasks.active | ObservableLongUpDownCounter | Number of 
tasks currently active
+| camel.route.policy.tasks.duration | ObservableLongUpDownCounter | Total 
duration of all active tasks
+|=====================================================
+
+The following options are supported:
+
+[width="100%",options="header"]
+|=======================================================================
+|Name |Default |Description
+| namingStrategy | OpenTelemetryRoutePolicyNamingStrategy.DEFAULT | The 
strategy to use for overriding default metric names.
+| policyConfiguration | - | Enables individual metrics to be enabled or 
disabled.
+| timeUnit | TimeUnit.MILLISECONDS | The time unit for measuring the route 
policy timer.
+| longTaskTimeUnit | TimeUnit.MILLISECONDS | The time unit for the 'long task' 
timer that measures total duration of all active tasks.
+|=======================================================================
+
 == OpenTelemetry Configuration
 
 Applications can export collected metrics to various backends using different 
exporters, including the OpenTelemetry Protocol (OTLP) exporter,
diff --git 
a/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/OpenTelemetryConstants.java
 
b/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/OpenTelemetryConstants.java
index e3748d7b1d80..00071859099d 100644
--- 
a/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/OpenTelemetryConstants.java
+++ 
b/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/OpenTelemetryConstants.java
@@ -48,6 +48,8 @@ public class OpenTelemetryConstants {
     public static final String 
DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_EXTERNAL_REDELIVERIES_METER_NAME
             = "camel.exchanges.external.redeliveries";
     public static final String DEFAULT_CAMEL_ROUTE_POLICY_METER_NAME = 
"camel.route.policy";
+    public static final String DEFAULT_CAMEL_ROUTE_POLICY_TASKS_ACTIVE = 
"camel.route.policy.tasks.active";
+    public static final String DEFAULT_CAMEL_ROUTE_POLICY_TASKS_DURATION = 
"camel.route.policy.tasks.duration";
 
     // Exchange-event metrics
     public static final String DEFAULT_CAMEL_ROUTES_EXCHANGES_INFLIGHT = 
"camel.exchanges.inflight";
diff --git 
a/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/TaskTimer.java
 
b/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/TaskTimer.java
index 89f69fb84a81..c29089b1b30c 100644
--- 
a/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/TaskTimer.java
+++ 
b/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/TaskTimer.java
@@ -20,6 +20,7 @@ import java.util.concurrent.TimeUnit;
 
 public class TaskTimer {
     private long startTime;
+    private volatile boolean stopped;
 
     public TaskTimer() {
         startTime = System.nanoTime();
@@ -30,13 +31,13 @@ public class TaskTimer {
     }
 
     public long duration(TimeUnit unit) {
-        if (startTime > 0) {
-            return unit.convert(System.nanoTime() - startTime, 
TimeUnit.NANOSECONDS);
+        if (stopped) {
+            return -1;
         }
-        return 0;
+        return unit.convert(System.nanoTime() - startTime, 
TimeUnit.NANOSECONDS);
     }
 
     public void stop() {
-        startTime = 0;
+        stopped = true;
     }
 }
diff --git 
a/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryContextMetricsStatistics.java
 
b/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryContextMetricsStatistics.java
index 206183707e36..8dc385feb0de 100644
--- 
a/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryContextMetricsStatistics.java
+++ 
b/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryContextMetricsStatistics.java
@@ -32,8 +32,8 @@ final class OpenTelemetryContextMetricsStatistics extends 
OpenTelemetryRoutePoli
                                           
OpenTelemetryRoutePolicyNamingStrategy namingStrategy,
                                           
OpenTelemetryRoutePolicyConfiguration configuration,
                                           boolean registerKamelets, boolean 
registerTemplates,
-                                          TimeUnit timeUnit) {
-        super(meter, camelContext, null, namingStrategy, configuration, 
timeUnit);
+                                          TimeUnit timeUnit, TimeUnit 
longTaskTimeUnit) {
+        super(meter, camelContext, null, namingStrategy, configuration, 
timeUnit, longTaskTimeUnit);
         this.registerKamelets = registerKamelets;
         this.registerTemplates = registerTemplates;
     }
diff --git 
a/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryLongTaskTimer.java
 
b/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryLongTaskTimer.java
new file mode 100644
index 000000000000..e22737b23ca6
--- /dev/null
+++ 
b/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryLongTaskTimer.java
@@ -0,0 +1,92 @@
+/*
+ * 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.opentelemetry.metrics.routepolicy;
+
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.TimeUnit;
+
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.Meter;
+import io.opentelemetry.api.metrics.ObservableLongUpDownCounter;
+import org.apache.camel.Route;
+import org.apache.camel.opentelemetry.metrics.TaskTimer;
+
+/**
+ * This timer is the equivalent of a 'long task' timer in other metrics 
systems. It measures tasks that are still
+ * running, and publishes the number of active tasks and their total duration.
+ */
+public class OpenTelemetryLongTaskTimer {
+
+    // number of active tasks
+    private final ObservableLongUpDownCounter longTasksActive;
+    // total duration of all in progress tasks
+    private final ObservableLongUpDownCounter longTasksDuration;
+    private final Queue<LongTask> activeTasks = new ConcurrentLinkedQueue<>();
+
+    public OpenTelemetryLongTaskTimer(Route route, Meter meter, Attributes 
attributes,
+                                      OpenTelemetryRoutePolicyConfiguration 
configuration,
+                                      OpenTelemetryRoutePolicyNamingStrategy 
namingStrategy, TimeUnit longTaskTimeUnit) {
+
+        this.longTasksActive = meter
+                
.upDownCounterBuilder(namingStrategy.getLongTasksActiveName(route))
+                .setDescription(route != null ? "Route active long task 
metric" : "CamelContext active long task metric")
+                .buildWithCallback(
+                        observableMeasurement -> {
+                            observableMeasurement.record(activeTasks.size(), 
attributes);
+                        });
+
+        this.longTasksDuration = meter
+                
.upDownCounterBuilder(namingStrategy.getLongTasksDurationName(route))
+                .setDescription(route != null ? "Route long task duration 
metric" : "CamelContext long task duration metric")
+                .setUnit(longTaskTimeUnit.name().toLowerCase())
+                .buildWithCallback(
+                        observableMeasurement -> observableMeasurement.record(
+                                allLongTaskDuration(longTaskTimeUnit), 
attributes));
+    }
+
+    public void remove() {
+        if (longTasksActive != null) {
+            longTasksActive.close();
+        }
+        if (longTasksDuration != null) {
+            longTasksDuration.close();
+        }
+    }
+
+    public TaskTimer start() {
+        LongTask task = new LongTask();
+        activeTasks.add(task);
+        return task;
+    }
+
+    private long allLongTaskDuration(TimeUnit unit) {
+        long sum = 0L;
+        long now = System.nanoTime();
+        for (LongTask task : activeTasks) {
+            sum += now - task.getStartTime();
+        }
+        return unit.convert(sum, TimeUnit.NANOSECONDS);
+    }
+
+    private class LongTask extends TaskTimer {
+        public void stop() {
+            activeTasks.remove(this);
+            super.stop();
+        }
+    }
+}
diff --git 
a/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicy.java
 
b/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicy.java
index 6a4a4f575e95..886cf3fff69d 100644
--- 
a/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicy.java
+++ 
b/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicy.java
@@ -62,12 +62,11 @@ public class OpenTelemetryRoutePolicy extends 
RoutePolicySupport implements NonM
     boolean registerKamelets;
     boolean registerTemplates = true;
 
-    // options
     private OpenTelemetryRoutePolicyNamingStrategy namingStrategy = 
OpenTelemetryRoutePolicyNamingStrategy.DEFAULT;
     private OpenTelemetryRoutePolicyConfiguration configuration = new 
OpenTelemetryRoutePolicyConfiguration();
     private TimeUnit timeUnit = TimeUnit.MILLISECONDS;
+    private TimeUnit longTaskTimeUnit = TimeUnit.MILLISECONDS;
 
-    // metrics
     private final Map<Route, MetricsStatistics> statisticsMap = new 
HashMap<>();
     private RouteMetric contextStatistic;
 
@@ -107,6 +106,14 @@ public class OpenTelemetryRoutePolicy extends 
RoutePolicySupport implements NonM
         this.timeUnit = timeUnit;
     }
 
+    public TimeUnit getLongTaskTimeUnit() {
+        return longTaskTimeUnit;
+    }
+
+    public void setLongTaskTimeUnit(TimeUnit longTaskTimeUnit) {
+        this.longTaskTimeUnit = longTaskTimeUnit;
+    }
+
     boolean isRegisterKamelets() {
         return registerKamelets;
     }
@@ -159,7 +166,7 @@ public class OpenTelemetryRoutePolicy extends 
RoutePolicySupport implements NonM
                         return null;
                     }
                     return new MetricsStatistics(
-                            meter, it.getCamelContext(), it, 
getNamingStrategy(), configuration, timeUnit);
+                            meter, it.getCamelContext(), it, 
getNamingStrategy(), configuration, timeUnit, longTaskTimeUnit);
                 });
     }
 
@@ -194,6 +201,7 @@ public class OpenTelemetryRoutePolicy extends 
RoutePolicySupport implements NonM
         private final OpenTelemetryRoutePolicyNamingStrategy namingStrategy;
         private final OpenTelemetryRoutePolicyConfiguration configuration;
         private final TimeUnit timeUnit;
+        private final TimeUnit longTaskTimeUnit;
 
         // OpenTelemetry objects
         private final Meter meter;
@@ -206,11 +214,12 @@ public class OpenTelemetryRoutePolicy extends 
RoutePolicySupport implements NonM
         private LongCounter exchangesTotal;
         private LongCounter externalRedeliveries;
         private LongCounter failuresHandled;
+        private OpenTelemetryLongTaskTimer longTaskTimer;
 
         MetricsStatistics(Meter meter, CamelContext camelContext, Route route,
                           OpenTelemetryRoutePolicyNamingStrategy 
namingStrategy,
                           OpenTelemetryRoutePolicyConfiguration configuration,
-                          TimeUnit timeUnit) {
+                          TimeUnit timeUnit, TimeUnit longTaskTimeUnit) {
 
             this.configuration = ObjectHelper.notNull(configuration, 
"OpenTelemetryRoutePolicyConfiguration", this);
             this.namingStrategy = ObjectHelper.notNull(namingStrategy, 
"OpenTelemetryRoutePolicyNamingStrategy", this);
@@ -218,6 +227,7 @@ public class OpenTelemetryRoutePolicy extends 
RoutePolicySupport implements NonM
             this.camelContext = camelContext;
             this.route = route;
             this.timeUnit = timeUnit;
+            this.longTaskTimeUnit = longTaskTimeUnit;
             this.attributes = Attributes.of(
                     AttributeKey.stringKey(CAMEL_CONTEXT_ATTRIBUTE),
                     route != null ? route.getCamelContext().getName() : 
camelContext.getName(),
@@ -259,12 +269,19 @@ public class OpenTelemetryRoutePolicy extends 
RoutePolicySupport implements NonM
                         = 
createCounter(namingStrategy.getFailuresHandledName(route),
                                 "Number of failures handled");
             }
+            if (configuration.isLongTask()) {
+                longTaskTimer = new OpenTelemetryLongTaskTimer(
+                        route, meter, attributes, configuration, 
namingStrategy, longTaskTimeUnit);
+            }
         }
 
         @Override
         public void onExchangeBegin(Exchange exchange) {
             String propertyName = propertyName(exchange);
             exchange.setProperty(propertyName, new TaskTimer());
+            if (longTaskTimer != null) {
+                exchange.setProperty(propertyName + "_long_task", 
longTaskTimer.start());
+            }
         }
 
         @Override
@@ -285,7 +302,9 @@ public class OpenTelemetryRoutePolicy extends 
RoutePolicySupport implements NonM
 
         @Override
         public void remove() {
-            // no-op
+            if (longTaskTimer != null) {
+                longTaskTimer.remove();
+            }
         }
 
         private void updateAdditionalCounters(Exchange exchange) {
diff --git 
a/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicyConfiguration.java
 
b/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicyConfiguration.java
index 039331be07ab..f6c9fd766a72 100644
--- 
a/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicyConfiguration.java
+++ 
b/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicyConfiguration.java
@@ -30,6 +30,7 @@ public class OpenTelemetryRoutePolicyConfiguration {
     private boolean exchangesTotal = true;
     private boolean externalRedeliveries = true;
     private boolean failuresHandled = true;
+    private boolean longTask;
 
     public boolean isContextEnabled() {
         return contextEnabled;
@@ -130,4 +131,16 @@ public class OpenTelemetryRoutePolicyConfiguration {
     public void setFailuresHandled(boolean failuresHandled) {
         this.failuresHandled = failuresHandled;
     }
+
+    public boolean isLongTask() {
+        return longTask;
+    }
+
+    /**
+     * Enable 'long task timer' metrics that track the number of active tasks 
and total duration of all tasks in
+     * progress.
+     */
+    public void setLongTask(boolean longTask) {
+        this.longTask = longTask;
+    }
 }
diff --git 
a/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicyFactory.java
 
b/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicyFactory.java
index c31b11335a49..e3fbef79681e 100644
--- 
a/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicyFactory.java
+++ 
b/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicyFactory.java
@@ -40,6 +40,7 @@ public class OpenTelemetryRoutePolicyFactory extends 
ServiceSupport
     private OpenTelemetryRoutePolicyNamingStrategy namingStrategy = 
OpenTelemetryRoutePolicyNamingStrategy.DEFAULT;
     private OpenTelemetryRoutePolicyConfiguration policyConfiguration = new 
OpenTelemetryRoutePolicyConfiguration();
     private TimeUnit timeUnit = TimeUnit.MILLISECONDS;
+    private TimeUnit longTaskTimeUnit = TimeUnit.MILLISECONDS;
 
     @Override
     public CamelContext getCamelContext() {
@@ -83,12 +84,20 @@ public class OpenTelemetryRoutePolicyFactory extends 
ServiceSupport
         this.timeUnit = timeUnit;
     }
 
+    public TimeUnit getLongTaskTimeUnit() {
+        return longTaskTimeUnit;
+    }
+
+    public void setLongTaskTimeUnit(TimeUnit longTaskTimeUnit) {
+        this.longTaskTimeUnit = longTaskTimeUnit;
+    }
+
     public synchronized RouteMetric 
createOrGetContextMetric(OpenTelemetryRoutePolicy policy) {
         if (contextMetric == null) {
             contextMetric = new OpenTelemetryContextMetricsStatistics(
                     meter, camelContext, policy.getNamingStrategy(), 
policy.getConfiguration(),
                     policy.isRegisterKamelets(), policy.isRegisterTemplates(),
-                    policy.getTimeUnit());
+                    policy.getTimeUnit(), policy.getLongTaskTimeUnit());
         }
         return contextMetric;
     }
@@ -99,6 +108,7 @@ public class OpenTelemetryRoutePolicyFactory extends 
ServiceSupport
         routePolicy.setNamingStrategy(getNamingStrategy());
         routePolicy.setConfiguration(getPolicyConfiguration());
         routePolicy.setTimeUnit(getTimeUnit());
+        routePolicy.setLongTaskTimeUnit(getLongTaskTimeUnit());
         routePolicy.setMeter(meter);
         return routePolicy;
     }
diff --git 
a/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicyNamingStrategy.java
 
b/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicyNamingStrategy.java
index 25f58bdb344a..b401411a0f66 100644
--- 
a/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicyNamingStrategy.java
+++ 
b/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicyNamingStrategy.java
@@ -24,6 +24,8 @@ import static 
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.DEFA
 import static 
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_SUCCEEDED_METER_NAME;
 import static 
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_TOTAL_METER_NAME;
 import static 
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.DEFAULT_CAMEL_ROUTE_POLICY_METER_NAME;
+import static 
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.DEFAULT_CAMEL_ROUTE_POLICY_TASKS_ACTIVE;
+import static 
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.DEFAULT_CAMEL_ROUTE_POLICY_TASKS_DURATION;
 
 /**
  * Provides a strategy to provide metric names for OpenTelemetry route policy 
metrics.
@@ -68,6 +70,16 @@ public interface OpenTelemetryRoutePolicyNamingStrategy {
         public String getExternalRedeliveriesName(Route route) {
             return 
formatName(DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_EXTERNAL_REDELIVERIES_METER_NAME);
         }
+
+        @Override
+        public String getLongTasksActiveName(Route route) {
+            return formatName(DEFAULT_CAMEL_ROUTE_POLICY_TASKS_ACTIVE);
+        }
+
+        @Override
+        public String getLongTasksDurationName(Route route) {
+            return formatName(DEFAULT_CAMEL_ROUTE_POLICY_TASKS_DURATION);
+        }
     };
 
     String getName(Route route);
@@ -83,4 +95,8 @@ public interface OpenTelemetryRoutePolicyNamingStrategy {
     String getFailuresHandledName(Route route);
 
     String getExternalRedeliveriesName(Route route);
+
+    String getLongTasksActiveName(Route route);
+
+    String getLongTasksDurationName(Route route);
 }
diff --git 
a/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryContextMetricTest.java
 
b/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryContextMetricTest.java
new file mode 100644
index 000000000000..67430fefacaf
--- /dev/null
+++ 
b/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryContextMetricTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.opentelemetry.metrics.routepolicy;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import io.opentelemetry.sdk.metrics.data.LongPointData;
+import io.opentelemetry.sdk.metrics.data.PointData;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.awaitility.Awaitility;
+import org.junit.jupiter.api.Test;
+
+import static 
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.DEFAULT_CAMEL_ROUTE_POLICY_TASKS_ACTIVE;
+import static 
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.DEFAULT_CAMEL_ROUTE_POLICY_TASKS_DURATION;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Verifies that there are point data for the context and route when context 
metrics are enabled.
+ */
+public class OpenTelemetryContextMetricTest extends 
AbstractOpenTelemetryRoutePolicyTest {
+
+    private static final long DELAY = 1500L;
+    private static final long TOLERANCE = 100L;
+
+    @Override
+    public OpenTelemetryRoutePolicyFactory 
createOpenTelemetryRoutePolicyFactory() {
+        OpenTelemetryRoutePolicyFactory factory = new 
OpenTelemetryRoutePolicyFactory();
+        factory.getPolicyConfiguration().setLongTask(true);
+        return factory;
+    }
+
+    // verify maximum duration of a long task
+    @Test
+    public void testLongTaskDuration() throws Exception {
+        MockEndpoint out = getMockEndpoint("mock:foo");
+        out.expectedMessageCount(1);
+
+        template.asyncSend("direct:foo", x -> {
+        });
+
+        long maxDuration = 
pollLongTimer(DEFAULT_CAMEL_ROUTE_POLICY_TASKS_DURATION);
+        assertTrue(maxDuration >= 0L && maxDuration < DELAY + TOLERANCE, "max 
duration of long task");
+        Awaitility.await().atMost(5, TimeUnit.SECONDS).untilAsserted(
+                () -> MockEndpoint.assertIsSatisfied(context));
+    }
+
+    // verify maximum number of concurrent active long tasks
+    @Test
+    public void testLongTaskActive() throws Exception {
+        final int messageCnt = 2;
+        MockEndpoint out = getMockEndpoint("mock:foo");
+        out.expectedMessageCount(messageCnt);
+
+        for (int i = 0; i < messageCnt; i++) {
+            template.asyncSend("direct:foo", x -> {
+            });
+        }
+        long maxActive = 
pollLongTimer(DEFAULT_CAMEL_ROUTE_POLICY_TASKS_ACTIVE);
+        assertEquals(messageCnt, maxActive, "max active long tasks");
+        Awaitility.await().atMost(5, TimeUnit.SECONDS).untilAsserted(
+                () -> MockEndpoint.assertIsSatisfied(context));
+    }
+
+    // verifies that there are point data for the context and route
+    private long pollLongTimer(String meterName) throws Exception {
+        Thread.sleep(250L);
+        long max = 0L;
+        long curr = 0L;
+        for (int i = 0; i < 10; i++) {
+            Thread.sleep(250L);
+
+            List<PointData> ls = getAllPointData(meterName);
+            // counts from context and route statistics
+            assertEquals(2, ls.size(), "Expected two point data");
+
+            for (var pd : ls) {
+                assertInstanceOf(LongPointData.class, pd);
+                LongPointData lpd = (LongPointData) pd;
+                curr = lpd.getValue();
+                max = Math.max(max, curr);
+            }
+            if (curr == 0L) {
+                break;
+            }
+        }
+        return max;
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:foo").routeId("foo").delay(DELAY).to("mock:foo");
+            }
+        };
+    }
+}
diff --git 
a/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicyLongTaskTest.java
 
b/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicyLongTaskTest.java
new file mode 100644
index 000000000000..d75262441c25
--- /dev/null
+++ 
b/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicyLongTaskTest.java
@@ -0,0 +1,138 @@
+/*
+ * 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.opentelemetry.metrics.routepolicy;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import io.opentelemetry.sdk.metrics.data.LongPointData;
+import io.opentelemetry.sdk.metrics.data.MetricData;
+import io.opentelemetry.sdk.metrics.data.PointData;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.awaitility.Awaitility;
+import org.junit.jupiter.api.Test;
+
+import static 
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.DEFAULT_CAMEL_ROUTE_POLICY_TASKS_ACTIVE;
+import static 
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.DEFAULT_CAMEL_ROUTE_POLICY_TASKS_DURATION;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class OpenTelemetryRoutePolicyLongTaskTest extends 
AbstractOpenTelemetryRoutePolicyTest {
+
+    private static final long DELAY = 1500L;
+    private static final long TOLERANCE = 100L;
+
+    @Override
+    public OpenTelemetryRoutePolicyFactory 
createOpenTelemetryRoutePolicyFactory() {
+        OpenTelemetryRoutePolicyFactory factory = new 
OpenTelemetryRoutePolicyFactory();
+        factory.getPolicyConfiguration().setContextEnabled(false);
+        factory.getPolicyConfiguration().setExchangesSucceeded(false);
+        factory.getPolicyConfiguration().setExchangesFailed(false);
+        factory.getPolicyConfiguration().setExchangesTotal(false);
+        factory.getPolicyConfiguration().setExternalRedeliveries(false);
+        factory.getPolicyConfiguration().setFailuresHandled(false);
+        factory.getPolicyConfiguration().setLongTask(true);
+        return factory;
+    }
+
+    // verify maximum duration of a long task using metric name 
'camel.route.policy.long.task.duration'
+    @Test
+    public void testLongTaskDuration() throws Exception {
+        MockEndpoint out = getMockEndpoint("mock:foo");
+        out.expectedMessageCount(1);
+
+        template.asyncSend("direct:foo", x -> {
+        });
+
+        long maxDuration = 
pollLongTimer(DEFAULT_CAMEL_ROUTE_POLICY_TASKS_DURATION);
+        assertTrue(maxDuration >= 0L && maxDuration < DELAY + TOLERANCE, "max 
duration of long task");
+        Awaitility.await().atMost(5, TimeUnit.SECONDS).untilAsserted(
+                () -> MockEndpoint.assertIsSatisfied(context));
+    }
+
+    // verify maximum number of concurrent active long tasks using metric name 
'camel.route.policy.long.task.active'
+    @Test
+    public void testLongTaskActive() throws Exception {
+        final int messageCnt = 2;
+        MockEndpoint out = getMockEndpoint("mock:foo");
+        out.expectedMessageCount(messageCnt);
+
+        for (int i = 0; i < messageCnt; i++) {
+            template.asyncSend("direct:foo", x -> {
+            });
+        }
+        long maxActive = 
pollLongTimer(DEFAULT_CAMEL_ROUTE_POLICY_TASKS_ACTIVE);
+        assertEquals(messageCnt, maxActive, "max active long tasks");
+
+        MockEndpoint.assertIsSatisfied(context);
+    }
+
+    // verifies that there are metrics for the 'long task' timer for no 
messaging
+    @Test
+    public void testLongTaskMetricData() {
+        List<MetricData> mdList = 
getMetricData(DEFAULT_CAMEL_ROUTE_POLICY_TASKS_DURATION);
+        assertFalse(mdList.isEmpty());
+        MetricData md = mdList.get(0);
+        assertEquals(DEFAULT_CAMEL_ROUTE_POLICY_TASKS_DURATION, md.getName());
+        assertEquals("Route long task duration metric", md.getDescription());
+
+        mdList = getMetricData(DEFAULT_CAMEL_ROUTE_POLICY_TASKS_ACTIVE);
+        assertFalse(mdList.isEmpty());
+        md = mdList.get(0);
+        assertEquals(DEFAULT_CAMEL_ROUTE_POLICY_TASKS_ACTIVE, md.getName());
+        assertEquals("Route active long task metric", md.getDescription());
+    }
+
+    // returns the maximum value recorded by the 'long-timer' until it goes 
back to 0, i.e. messaging is done
+    private long pollLongTimer(String meterName) throws Exception {
+        Thread.sleep(250L);
+        long max = 0L;
+        long curr = 0L;
+
+        for (int i = 0; i < 10; i++) {
+            Thread.sleep(250L);
+
+            List<PointData> ls = getAllPointData(meterName);
+            // contextStatistic is not enabled, so only one point data from 
route statistic
+            assertEquals(1, ls.size(), "Expected one point data");
+
+            for (var pd : ls) {
+                assertInstanceOf(LongPointData.class, pd);
+                LongPointData lpd = (LongPointData) pd;
+                curr = lpd.getValue();
+                max = Math.max(max, curr);
+            }
+            if (curr == 0L) {
+                break;
+            }
+        }
+        return max;
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:foo").routeId("foo").delay(DELAY).to("mock:foo");
+            }
+        };
+    }
+}


Reply via email to