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 6995589abe2d CAMEL-16866 add route-policy (#19929)
6995589abe2d is described below
commit 6995589abe2ded2acff95f254c5a4818e0d36a6c
Author: Jono Morris <[email protected]>
AuthorDate: Sat Nov 15 04:57:01 2025 +1300
CAMEL-16866 add route-policy (#19929)
---
.../catalog/components/opentelemetry-metrics.json | 2 +-
.../metrics/opentelemetry-metrics.json | 2 +-
.../metrics/OpenTelemetryConstants.java | 12 +-
.../routepolicy/OpenTelemetryRoutePolicy.java | 308 +++++++++++++++++++++
.../OpenTelemetryRoutePolicyConfiguration.java | 121 ++++++++
.../OpenTelemetryRoutePolicyFactory.java | 104 +++++++
.../OpenTelemetryRoutePolicyNamingStrategy.java | 86 ++++++
.../metrics/routepolicy/RouteMetric.java | 28 ++
.../AbstractOpenTelemetryRoutePolicyTest.java | 40 +++
...OpenTelemetryRoutePolicyExchangeStatusTest.java | 77 ++++++
...OpenTelemetryRoutePolicyExcludePatternTest.java | 79 ++++++
...nTelemetryRoutePolicyMulticastSubRouteTest.java | 125 +++++++++
.../OpenTelemetryRoutePolicySubRouteTest.java | 81 ++++++
.../routepolicy/OpenTelemetryRoutePolicyTest.java | 141 ++++++++++
.../OpenTelemetrySharedRoutePolicyTest.java | 66 +++++
.../dsl/OpenTelemetryEndpointBuilderFactory.java | 2 +-
16 files changed, 1270 insertions(+), 4 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/opentelemetry-metrics.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/opentelemetry-metrics.json
index fd6253f7178e..8a7c8e9e2733 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/opentelemetry-metrics.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/opentelemetry-metrics.json
@@ -29,7 +29,7 @@
"meter": { "index": 2, "kind": "property", "displayName": "Meter",
"group": "advanced", "label": "advanced", "required": false, "type": "object",
"javaType": "io.opentelemetry.api.metrics.Meter", "deprecated": false,
"autowired": false, "secret": false, "description": "To use a custom configured
Meter." }
},
"headers": {
- "CamelMetricsTimerAction": { "index": 0, "kind": "header", "displayName":
"", "group": "producer", "label": "", "required": false, "javaType":
"org.apache.camel.opentelemetry2.component.OpenTelemetryTimerAction",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "Override timer action in URI", "constantName":
"org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants#HEADER_TIMER_ACTION"
},
+ "CamelMetricsTimerAction": { "index": 0, "kind": "header", "displayName":
"", "group": "producer", "label": "", "required": false, "javaType":
"org.apache.camel.opentelemetry.metrics.OpenTelemetryTimerAction", "enum": [
"START", "STOP" ], "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Override timer action in URI",
"constantName":
"org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants#HEADER_TIMER_ACTION"
},
"CamelMetricsHistogramValue": { "index": 1, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "long", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Override histogram value in URI",
"constantName":
"org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants#HEADER_HISTOGRAM_VALUE"
},
"CamelMetricsCounterDecrement": { "index": 2, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Override decrement value in URI",
"constantName":
"org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants#HEADER_COUNTER_DECREMENT"
},
"CamelMetricsCounterIncrement": { "index": 3, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Override increment value in URI",
"constantName":
"org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants#HEADER_COUNTER_INCREMENT"
},
diff --git
a/components/camel-opentelemetry-metrics/src/generated/resources/META-INF/org/apache/camel/opentelemetry/metrics/opentelemetry-metrics.json
b/components/camel-opentelemetry-metrics/src/generated/resources/META-INF/org/apache/camel/opentelemetry/metrics/opentelemetry-metrics.json
index fd6253f7178e..8a7c8e9e2733 100644
---
a/components/camel-opentelemetry-metrics/src/generated/resources/META-INF/org/apache/camel/opentelemetry/metrics/opentelemetry-metrics.json
+++
b/components/camel-opentelemetry-metrics/src/generated/resources/META-INF/org/apache/camel/opentelemetry/metrics/opentelemetry-metrics.json
@@ -29,7 +29,7 @@
"meter": { "index": 2, "kind": "property", "displayName": "Meter",
"group": "advanced", "label": "advanced", "required": false, "type": "object",
"javaType": "io.opentelemetry.api.metrics.Meter", "deprecated": false,
"autowired": false, "secret": false, "description": "To use a custom configured
Meter." }
},
"headers": {
- "CamelMetricsTimerAction": { "index": 0, "kind": "header", "displayName":
"", "group": "producer", "label": "", "required": false, "javaType":
"org.apache.camel.opentelemetry2.component.OpenTelemetryTimerAction",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "Override timer action in URI", "constantName":
"org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants#HEADER_TIMER_ACTION"
},
+ "CamelMetricsTimerAction": { "index": 0, "kind": "header", "displayName":
"", "group": "producer", "label": "", "required": false, "javaType":
"org.apache.camel.opentelemetry.metrics.OpenTelemetryTimerAction", "enum": [
"START", "STOP" ], "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Override timer action in URI",
"constantName":
"org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants#HEADER_TIMER_ACTION"
},
"CamelMetricsHistogramValue": { "index": 1, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "long", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Override histogram value in URI",
"constantName":
"org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants#HEADER_HISTOGRAM_VALUE"
},
"CamelMetricsCounterDecrement": { "index": 2, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Override decrement value in URI",
"constantName":
"org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants#HEADER_COUNTER_DECREMENT"
},
"CamelMetricsCounterIncrement": { "index": 3, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Override increment value in URI",
"constantName":
"org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants#HEADER_COUNTER_INCREMENT"
},
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 7a3592f0964c..e3748d7b1d80 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
@@ -22,7 +22,7 @@ public class OpenTelemetryConstants {
public static final String HEADER_PREFIX = "CamelMetrics";
@Metadata(description = "Override timer action in URI",
- javaType =
"org.apache.camel.opentelemetry2.component.OpenTelemetryTimerAction")
+ javaType =
"org.apache.camel.opentelemetry.metrics.OpenTelemetryTimerAction")
public static final String HEADER_TIMER_ACTION = HEADER_PREFIX +
"TimerAction";
@Metadata(description = "Override histogram value in URI", javaType =
"long")
public static final String HEADER_HISTOGRAM_VALUE = HEADER_PREFIX +
"HistogramValue";
@@ -39,6 +39,16 @@ public class OpenTelemetryConstants {
public static final String HEADER_METRIC_ATTRIBUTES = HEADER_PREFIX +
"Attributes";
+ // Route-policy metrics
+ public static final String
DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_FAILED_METER_NAME =
"camel.exchanges.failed";
+ public static final String
DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_SUCCEEDED_METER_NAME =
"camel.exchanges.succeeded";
+ public static final String
DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_TOTAL_METER_NAME = "camel.exchanges.total";
+ public static final String
DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_FAILURES_HANDLED_METER_NAME
+ = "camel.exchanges.failures.handled";
+ 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";
+
// Exchange-event metrics
public static final String DEFAULT_CAMEL_ROUTES_EXCHANGES_INFLIGHT =
"camel.exchanges.inflight";
public static final String DEFAULT_CAMEL_EXCHANGE_ELAPSED_TIMER =
"camel.exchange.elapsed";
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
new file mode 100644
index 000000000000..c404bec04585
--- /dev/null
+++
b/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicy.java
@@ -0,0 +1,308 @@
+/*
+ * 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.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.LongCounter;
+import io.opentelemetry.api.metrics.LongHistogram;
+import io.opentelemetry.api.metrics.Meter;
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.NonManagedService;
+import org.apache.camel.Route;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.opentelemetry.metrics.TaskTimer;
+import org.apache.camel.spi.ManagementStrategy;
+import org.apache.camel.support.CamelContextHelper;
+import org.apache.camel.support.ExchangeHelper;
+import org.apache.camel.support.PatternHelper;
+import org.apache.camel.support.RoutePolicySupport;
+import org.apache.camel.util.ObjectHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.CAMEL_CONTEXT_ATTRIBUTE;
+import static
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.DEFAULT_CAMEL_ROUTE_POLICY_METER_NAME;
+import static
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.EVENT_TYPE_ATTRIBUTE;
+import static
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.KIND_ATTRIBUTE;
+import static
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.KIND_ROUTE;
+import static
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.ROUTE_ID_ATTRIBUTE;
+
+/**
+ * A {@link org.apache.camel.spi.RoutePolicy} to plugin and use OpenTelemetry
metrics for gathering route utilization
+ * statistics.
+ */
+public class OpenTelemetryRoutePolicy extends RoutePolicySupport implements
NonManagedService {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(OpenTelemetryRoutePolicy.class);
+
+ private final OpenTelemetryRoutePolicyFactory factory;
+ private Meter meter;
+ boolean registerKamelets;
+ boolean registerTemplates = true;
+
+ private OpenTelemetryRoutePolicyNamingStrategy namingStrategy =
OpenTelemetryRoutePolicyNamingStrategy.DEFAULT;
+ private OpenTelemetryRoutePolicyConfiguration configuration = new
OpenTelemetryRoutePolicyConfiguration();
+ private TimeUnit timeUnit = TimeUnit.MILLISECONDS;
+ private final Map<Route, MetricsStatistics> statisticsMap = new
HashMap<>();
+
+ public OpenTelemetryRoutePolicy(OpenTelemetryRoutePolicyFactory factory) {
+ this.factory = factory;
+ }
+
+ public Meter getMeter() {
+ return meter;
+ }
+
+ public void setMeter(Meter meter) {
+ this.meter = meter;
+ }
+
+ public OpenTelemetryRoutePolicyNamingStrategy getNamingStrategy() {
+ return namingStrategy;
+ }
+
+ public void setNamingStrategy(OpenTelemetryRoutePolicyNamingStrategy
namingStrategy) {
+ this.namingStrategy = namingStrategy;
+ }
+
+ public OpenTelemetryRoutePolicyConfiguration getConfiguration() {
+ return configuration;
+ }
+
+ public void setConfiguration(OpenTelemetryRoutePolicyConfiguration
configuration) {
+ this.configuration = configuration;
+ }
+
+ public TimeUnit getTimeUnit() {
+ return timeUnit;
+ }
+
+ public void setTimeUnit(TimeUnit timeUnit) {
+ this.timeUnit = timeUnit;
+ }
+
+ boolean isRegisterKamelets() {
+ return registerKamelets;
+ }
+
+ boolean isRegisterTemplates() {
+ return registerTemplates;
+ }
+
+ @Override
+ public void onInit(Route route) {
+ super.onInit(route);
+ if (meter == null) {
+ this.meter =
CamelContextHelper.findSingleByType(route.getCamelContext(), Meter.class);
+ }
+ if (meter == null) {
+ this.meter = GlobalOpenTelemetry.get().getMeter("camel");
+ }
+ if (meter == null) {
+ throw new RuntimeCamelException("Could not find any OpenTelemetry
meter!");
+ }
+
+ ManagementStrategy ms =
route.getCamelContext().getManagementStrategy();
+ if (ms != null && ms.getManagementAgent() != null) {
+ registerKamelets =
ms.getManagementAgent().getRegisterRoutesCreateByKamelet();
+ registerTemplates =
ms.getManagementAgent().getRegisterRoutesCreateByTemplate();
+ }
+ }
+
+ @Override
+ public void onStart(Route route) {
+ // create statistics holder
+ statisticsMap.computeIfAbsent(route,
+ it -> {
+ boolean skip = !configuration.isRouteEnabled();
+ // skip routes that should not be included
+ if (!skip) {
+ skip = (it.isCreatedByKamelet() && !registerKamelets)
+ || (it.isCreatedByRouteTemplate() &&
!registerTemplates);
+ }
+ if (!skip && configuration.getExcludePattern() != null) {
+ String[] patterns =
configuration.getExcludePattern().split(",");
+ skip = PatternHelper.matchPatterns(route.getRouteId(),
patterns);
+ }
+ LOG.debug("Capturing metrics for route: {} -> {}",
route.getRouteId(), skip);
+ if (skip) {
+ return null;
+ }
+ return new MetricsStatistics(
+ meter, it.getCamelContext(), it,
getNamingStrategy(), configuration, timeUnit);
+ });
+ }
+
+ @Override
+ public void onRemove(Route route) {
+ statisticsMap.remove(route);
+ }
+
+ @Override
+ public void onExchangeBegin(Route route, Exchange exchange) {
+ Optional.ofNullable(statisticsMap.get(route))
+ .ifPresent(statistics -> statistics.onExchangeBegin(exchange));
+ }
+
+ @Override
+ public void onExchangeDone(Route route, Exchange exchange) {
+ Optional.ofNullable(statisticsMap.get(route))
+ .ifPresent(statistics -> statistics.onExchangeDone(exchange));
+ }
+
+ static class MetricsStatistics implements RouteMetric {
+ private final CamelContext camelContext;
+ private final Route route;
+
+ // Configuration
+ private final OpenTelemetryRoutePolicyNamingStrategy namingStrategy;
+ private final OpenTelemetryRoutePolicyConfiguration configuration;
+ private final TimeUnit timeUnit;
+
+ // OpenTelemetry objects
+ private final Meter meter;
+ private final Attributes attributes;
+
+ // OpenTelemetry instruments
+ private final LongHistogram timer;
+ private LongCounter exchangesSucceeded;
+ private LongCounter exchangesFailed;
+ private LongCounter exchangesTotal;
+ private LongCounter externalRedeliveries;
+ private LongCounter failuresHandled;
+
+ MetricsStatistics(Meter meter, CamelContext camelContext, Route route,
+ OpenTelemetryRoutePolicyNamingStrategy
namingStrategy,
+ OpenTelemetryRoutePolicyConfiguration configuration,
+ TimeUnit timeUnit) {
+
+ this.configuration = ObjectHelper.notNull(configuration,
"OpenTelemetryRoutePolicyConfiguration", this);
+ this.namingStrategy = ObjectHelper.notNull(namingStrategy,
"OpenTelemetryRoutePolicyNamingStrategy", this);
+ this.meter = ObjectHelper.notNull(meter, "Meter", this);
+ this.camelContext = camelContext;
+ this.route = route;
+ this.timeUnit = timeUnit;
+ this.attributes = Attributes.of(
+ AttributeKey.stringKey(CAMEL_CONTEXT_ATTRIBUTE),
+ route != null ? route.getCamelContext().getName() :
camelContext.getName(),
+ AttributeKey.stringKey(KIND_ATTRIBUTE), KIND_ROUTE,
+ AttributeKey.stringKey(ROUTE_ID_ATTRIBUTE), route != null
? route.getId() : "",
+ AttributeKey.stringKey(EVENT_TYPE_ATTRIBUTE), route !=
null ? "route" : "context");
+ this.timer = meter
+ .histogramBuilder(namingStrategy.getName(route))
+ .setDescription(route != null ? "Route performance
metrics" : "CamelContext performance metrics")
+ .setUnit(timeUnit.name().toLowerCase())
+ .ofLongs().build();
+
+ if (configuration.isAdditionalCounters()) {
+ initAdditionalCounters();
+ }
+ }
+
+ private void initAdditionalCounters() {
+ if (configuration.isExchangesSucceeded()) {
+ this.exchangesSucceeded =
createCounter(namingStrategy.getExchangesSucceededName(route),
+ "Number of successfully completed exchanges");
+ }
+ if (configuration.isExchangesFailed()) {
+ this.exchangesFailed
+ =
createCounter(namingStrategy.getExchangesFailedName(route),
+ "Number of failed exchanges");
+ }
+ if (configuration.isExchangesTotal()) {
+ this.exchangesTotal
+ =
createCounter(namingStrategy.getExchangesTotalName(route),
+ "Total number of processed exchanges");
+ }
+ if (configuration.isExternalRedeliveries()) {
+ this.externalRedeliveries =
createCounter(namingStrategy.getExternalRedeliveriesName(route),
+ "Number of external initiated redeliveries (such as
from JMS broker)");
+ }
+ if (configuration.isFailuresHandled()) {
+ this.failuresHandled
+ =
createCounter(namingStrategy.getFailuresHandledName(route),
+ "Number of failures handled");
+ }
+ }
+
+ @Override
+ public void onExchangeBegin(Exchange exchange) {
+ String propertyName = propertyName(exchange);
+ exchange.setProperty(propertyName, new TaskTimer());
+ }
+
+ @Override
+ public void onExchangeDone(Exchange exchange) {
+ String propertyName = propertyName(exchange);
+ TaskTimer task = (TaskTimer) exchange.removeProperty(propertyName);
+ if (task != null) {
+ this.timer.record(task.duration(timeUnit), attributes);
+ }
+ TaskTimer longTask = (TaskTimer)
exchange.removeProperty(propertyName + "_long_task");
+ if (longTask != null) {
+ longTask.stop();
+ }
+ if (configuration.isAdditionalCounters()) {
+ updateAdditionalCounters(exchange);
+ }
+ }
+
+ @Override
+ public void remove() {
+ // no-op
+ }
+
+ private void updateAdditionalCounters(Exchange exchange) {
+ if (exchangesTotal != null) {
+ exchangesTotal.add(1L, attributes);
+ }
+ if (exchange.isFailed()) {
+ if (exchangesFailed != null) {
+ exchangesFailed.add(1L, attributes);
+ }
+ } else {
+ if (exchangesSucceeded != null) {
+ exchangesSucceeded.add(1L, attributes);
+ }
+ if (failuresHandled != null &&
ExchangeHelper.isFailureHandled(exchange)) {
+ failuresHandled.add(1L, attributes);
+ }
+ if (externalRedeliveries != null &&
exchange.isExternalRedelivered()) {
+ externalRedeliveries.add(1L, attributes);
+ }
+ }
+ }
+
+ private String propertyName(Exchange exchange) {
+ String id = route != null ? route.getId() : "context:" +
camelContext.getName();
+ return String.format("%s-%s-%s",
DEFAULT_CAMEL_ROUTE_POLICY_METER_NAME, id, exchange.getExchangeId());
+ }
+
+ private LongCounter createCounter(String meterName, String
description) {
+ return meter.counterBuilder(meterName)
+ .setDescription(description).build();
+ }
+ }
+}
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
new file mode 100644
index 000000000000..7ae0d4c33786
--- /dev/null
+++
b/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicyConfiguration.java
@@ -0,0 +1,121 @@
+/*
+ * 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;
+
+/**
+ * Configuration for enabling and disabling metrics collected by the
OpenTelemetry route policy.
+ */
+public class OpenTelemetryRoutePolicyConfiguration {
+
+ private boolean routeEnabled = true;
+ private String excludePattern;
+ private boolean additionalCounters = true;
+ private boolean exchangesSucceeded = true;
+ private boolean exchangesFailed = true;
+ private boolean exchangesTotal = true;
+ private boolean externalRedeliveries = true;
+ private boolean failuresHandled = true;
+
+ public boolean isRouteEnabled() {
+ return routeEnabled;
+ }
+
+ /**
+ * Enable route level metrics.
+ */
+ public void setRouteEnabled(boolean routeEnabled) {
+ this.routeEnabled = routeEnabled;
+ }
+
+ public String getExcludePattern() {
+ return excludePattern;
+ }
+
+ /**
+ * A comma separated list of regex patterns to that are used to exclude
routes from metrics.
+ */
+ public void setExcludePattern(String excludePattern) {
+ this.excludePattern = excludePattern;
+ }
+
+ public boolean isAdditionalCounters() {
+ return additionalCounters;
+ }
+
+ /**
+ * Enable all additional route metrics such as exchanges succeeded,
failed, total exchanges, external redeliveries
+ * and handled failures are enabled.
+ */
+ public void setAdditionalCounters(boolean additionalCounters) {
+ this.additionalCounters = additionalCounters;
+ }
+
+ public boolean isExchangesSucceeded() {
+ return exchangesSucceeded;
+ }
+
+ /**
+ * Enable 'succeeded exchanges' counter.
+ */
+ public void setExchangesSucceeded(boolean exchangesSucceeded) {
+ this.exchangesSucceeded = exchangesSucceeded;
+ }
+
+ public boolean isExchangesFailed() {
+ return exchangesFailed;
+ }
+
+ /**
+ * Enable 'failed exchanges' counter.
+ */
+ public void setExchangesFailed(boolean exchangesFailed) {
+ this.exchangesFailed = exchangesFailed;
+ }
+
+ public boolean isExchangesTotal() {
+ return exchangesTotal;
+ }
+
+ /**
+ * Enable 'total exchanges' counter.
+ */
+ public void setExchangesTotal(boolean exchangesTotal) {
+ this.exchangesTotal = exchangesTotal;
+ }
+
+ public boolean isExternalRedeliveries() {
+ return externalRedeliveries;
+ }
+
+ /**
+ * Enable 'external redeliveries' counter.
+ */
+ public void setExternalRedeliveries(boolean externalRedeliveries) {
+ this.externalRedeliveries = externalRedeliveries;
+ }
+
+ public boolean isFailuresHandled() {
+ return failuresHandled;
+ }
+
+ /**
+ * Enable 'handled failures' counter.
+ */
+ public void setFailuresHandled(boolean failuresHandled) {
+ this.failuresHandled = failuresHandled;
+ }
+}
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
new file mode 100644
index 000000000000..f451f5785b06
--- /dev/null
+++
b/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicyFactory.java
@@ -0,0 +1,104 @@
+/*
+ * 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.concurrent.TimeUnit;
+
+import io.opentelemetry.api.metrics.Meter;
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.NamedNode;
+import org.apache.camel.NonManagedService;
+import org.apache.camel.StaticService;
+import org.apache.camel.spi.RoutePolicy;
+import org.apache.camel.spi.RoutePolicyFactory;
+import org.apache.camel.support.service.ServiceSupport;
+
+/**
+ * A {@link RoutePolicyFactory} to plugin and use metrics for gathering route
utilization statistics
+ */
+public class OpenTelemetryRoutePolicyFactory extends ServiceSupport
+ implements RoutePolicyFactory, CamelContextAware, NonManagedService,
StaticService {
+
+ private CamelContext camelContext;
+ private Meter meter;
+ private RouteMetric contextMetric;
+ private OpenTelemetryRoutePolicyNamingStrategy namingStrategy =
OpenTelemetryRoutePolicyNamingStrategy.DEFAULT;
+ private OpenTelemetryRoutePolicyConfiguration policyConfiguration = new
OpenTelemetryRoutePolicyConfiguration();
+ private TimeUnit timeUnit = TimeUnit.MILLISECONDS;
+
+ @Override
+ public CamelContext getCamelContext() {
+ return camelContext;
+ }
+
+ @Override
+ public void setCamelContext(CamelContext camelContext) {
+ this.camelContext = camelContext;
+ }
+
+ public void setMeter(Meter meter) {
+ this.meter = meter;
+ }
+
+ public Meter getMeter() {
+ return meter;
+ }
+
+ public OpenTelemetryRoutePolicyNamingStrategy getNamingStrategy() {
+ return namingStrategy;
+ }
+
+ public void setNamingStrategy(OpenTelemetryRoutePolicyNamingStrategy
namingStrategy) {
+ this.namingStrategy = namingStrategy;
+ }
+
+ public OpenTelemetryRoutePolicyConfiguration getPolicyConfiguration() {
+ return policyConfiguration;
+ }
+
+ public void setPolicyConfiguration(OpenTelemetryRoutePolicyConfiguration
policyConfiguration) {
+ this.policyConfiguration = policyConfiguration;
+ }
+
+ public TimeUnit getTimeUnit() {
+ return timeUnit;
+ }
+
+ public void setTimeUnit(TimeUnit timeUnit) {
+ this.timeUnit = timeUnit;
+ }
+
+ @Override
+ public RoutePolicy createRoutePolicy(CamelContext camelContext, String
routeId, NamedNode routeDefinition) {
+ OpenTelemetryRoutePolicy routePolicy = new
OpenTelemetryRoutePolicy(this);
+ routePolicy.setNamingStrategy(getNamingStrategy());
+ routePolicy.setConfiguration(getPolicyConfiguration());
+ routePolicy.setTimeUnit(getTimeUnit());
+ routePolicy.setMeter(meter);
+ return routePolicy;
+ }
+
+ @Override
+ protected void doShutdown() throws Exception {
+ super.doShutdown();
+ if (contextMetric != null) {
+ contextMetric.remove();
+ contextMetric = null;
+ }
+ }
+}
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
new file mode 100644
index 000000000000..25f58bdb344a
--- /dev/null
+++
b/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicyNamingStrategy.java
@@ -0,0 +1,86 @@
+/*
+ * 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 org.apache.camel.Route;
+
+import static
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_EXTERNAL_REDELIVERIES_METER_NAME;
+import static
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_FAILED_METER_NAME;
+import static
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_FAILURES_HANDLED_METER_NAME;
+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;
+
+/**
+ * Provides a strategy to provide metric names for OpenTelemetry route policy
metrics.
+ */
+public interface OpenTelemetryRoutePolicyNamingStrategy {
+
+ /**
+ * Default naming strategy that uses opentelemetry naming convention.
+ */
+ OpenTelemetryRoutePolicyNamingStrategy DEFAULT = new
OpenTelemetryRoutePolicyNamingStrategy() {
+ @Override
+ public String getName(Route route) {
+ return DEFAULT_CAMEL_ROUTE_POLICY_METER_NAME;
+ }
+
+ @Override
+ public String formatName(String name) {
+ return name;
+ }
+
+ @Override
+ public String getExchangesSucceededName(Route route) {
+ return
formatName(DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_SUCCEEDED_METER_NAME);
+ }
+
+ @Override
+ public String getExchangesFailedName(Route route) {
+ return
formatName(DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_FAILED_METER_NAME);
+ }
+
+ @Override
+ public String getExchangesTotalName(Route route) {
+ return
formatName(DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_TOTAL_METER_NAME);
+ }
+
+ @Override
+ public String getFailuresHandledName(Route route) {
+ return
formatName(DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_FAILURES_HANDLED_METER_NAME);
+ }
+
+ @Override
+ public String getExternalRedeliveriesName(Route route) {
+ return
formatName(DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_EXTERNAL_REDELIVERIES_METER_NAME);
+ }
+ };
+
+ String getName(Route route);
+
+ String formatName(String name);
+
+ String getExchangesSucceededName(Route route);
+
+ String getExchangesFailedName(Route route);
+
+ String getExchangesTotalName(Route route);
+
+ String getFailuresHandledName(Route route);
+
+ String getExternalRedeliveriesName(Route route);
+}
diff --git
a/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/routepolicy/RouteMetric.java
b/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/routepolicy/RouteMetric.java
new file mode 100644
index 000000000000..ca4de3d50d28
--- /dev/null
+++
b/components/camel-opentelemetry-metrics/src/main/java/org/apache/camel/opentelemetry/metrics/routepolicy/RouteMetric.java
@@ -0,0 +1,28 @@
+/*
+ * 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 org.apache.camel.Exchange;
+
+public interface RouteMetric {
+
+ void onExchangeBegin(Exchange exchange);
+
+ void onExchangeDone(Exchange exchange);
+
+ void remove();
+}
diff --git
a/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/routepolicy/AbstractOpenTelemetryRoutePolicyTest.java
b/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/routepolicy/AbstractOpenTelemetryRoutePolicyTest.java
new file mode 100644
index 000000000000..2ef5734487d5
--- /dev/null
+++
b/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/routepolicy/AbstractOpenTelemetryRoutePolicyTest.java
@@ -0,0 +1,40 @@
+/*
+ * 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 org.apache.camel.CamelContext;
+import org.apache.camel.opentelemetry.metrics.AbstractOpenTelemetryTest;
+
+public abstract class AbstractOpenTelemetryRoutePolicyTest extends
AbstractOpenTelemetryTest {
+
+ protected OpenTelemetryRoutePolicyFactory
createOpenTelemetryRoutePolicyFactory() {
+ OpenTelemetryRoutePolicyFactory factory = new
OpenTelemetryRoutePolicyFactory();
+ factory.getPolicyConfiguration().setExcludePattern(null);
+ return factory;
+ }
+
+ @Override
+ protected CamelContext createCamelContext() throws Exception {
+ CamelContext context = super.createCamelContext();
+ OpenTelemetryRoutePolicyFactory factory =
createOpenTelemetryRoutePolicyFactory();
+ factory.setCamelContext(context);
+
factory.setMeter(otelExtension.getOpenTelemetry().getMeter("meterTest"));
+ context.addRoutePolicyFactory(factory);
+ context.addService(factory);
+ return context;
+ }
+}
diff --git
a/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicyExchangeStatusTest.java
b/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicyExchangeStatusTest.java
new file mode 100644
index 000000000000..0f72df43ec8c
--- /dev/null
+++
b/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicyExchangeStatusTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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 org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.junit.jupiter.api.Test;
+
+import static
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_FAILED_METER_NAME;
+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.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class OpenTelemetryRoutePolicyExchangeStatusTest extends
AbstractOpenTelemetryRoutePolicyTest {
+
+ @Test
+ public void testMetricsExchangeStatus() throws Exception {
+ int count = 10;
+ MockEndpoint mockEndpoint = getMockEndpoint("mock:result");
+ mockEndpoint.expectedMessageCount(count / 2);
+ for (int i = 0; i < count; i++) {
+ if (i % 2 == 0) {
+ template.sendBody("direct:completing", "Hello");
+ } else {
+ assertThrows(RuntimeException.class, () ->
template.sendBody("direct:failing", "Hello"));
+ }
+ }
+ MockEndpoint.assertIsSatisfied(context);
+
+ // total meter
+ assertEquals(count / 2,
+
getSingleLongPointData(DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_TOTAL_METER_NAME,
"completing").getValue());
+ assertEquals(count / 2,
+
getSingleLongPointData(DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_TOTAL_METER_NAME,
"failing").getValue());
+
+ // succeeded meter
+ assertEquals(count / 2,
+
getSingleLongPointData(DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_SUCCEEDED_METER_NAME,
"completing").getValue());
+
assertTrue(getAllPointDataForRouteId(DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_SUCCEEDED_METER_NAME,
"failing").isEmpty());
+
+ // failed meter
+
assertTrue(getAllPointDataForRouteId(DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_FAILED_METER_NAME,
"completing").isEmpty());
+ assertEquals(count / 2,
+
getSingleLongPointData(DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_FAILED_METER_NAME,
"failing").getValue());
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:completing").routeId("completing")
+ .to("mock:result");
+
+ from("direct:failing").routeId("failing")
+ .throwException(RuntimeException.class, "Failing")
+ .to("mock:result");
+ }
+ };
+ }
+}
diff --git
a/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicyExcludePatternTest.java
b/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicyExcludePatternTest.java
new file mode 100644
index 000000000000..ffaac5fa7dcb
--- /dev/null
+++
b/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicyExcludePatternTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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 io.opentelemetry.sdk.metrics.data.LongPointData;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.junit.jupiter.api.Test;
+
+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.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class OpenTelemetryRoutePolicyExcludePatternTest extends
AbstractOpenTelemetryRoutePolicyTest {
+
+ @Override
+ public OpenTelemetryRoutePolicyFactory
createOpenTelemetryRoutePolicyFactory() {
+ OpenTelemetryRoutePolicyFactory factory = new
OpenTelemetryRoutePolicyFactory();
+ factory.getPolicyConfiguration().setRouteEnabled(true);
+ factory.getPolicyConfiguration().setExcludePattern("bar");
+ return factory;
+ }
+
+ // verify that metrics are collected for route foo but not for route bar
+ @Test
+ public void testMetricsRoutePolicy() throws Exception {
+ int count = 5;
+ getMockEndpoint("mock:foo").expectedMessageCount(count);
+ getMockEndpoint("mock:bar").expectedMessageCount(count);
+
+ for (int i = 0; i < count; i++) {
+ template.sendBody("direct:foo", "Hello World");
+ }
+
+ MockEndpoint.assertIsSatisfied(context);
+
+ for (String metricName : List.of(
+ DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_SUCCEEDED_METER_NAME,
+ DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_TOTAL_METER_NAME)) {
+
+ LongPointData lpd = getSingleLongPointData(metricName, "foo");
+ assertEquals(count, lpd.getValue());
+
+ assertTrue(getAllPointDataForRouteId(metricName, "bar").isEmpty());
+ }
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:foo").routeId("foo")
+ .to("direct:bar")
+ .to("mock:foo");
+
+ from("direct:bar").routeId("bar")
+ .to("mock:bar");
+ }
+ };
+ }
+}
diff --git
a/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicyMulticastSubRouteTest.java
b/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicyMulticastSubRouteTest.java
new file mode 100644
index 000000000000..a916d785d285
--- /dev/null
+++
b/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicyMulticastSubRouteTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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 io.opentelemetry.sdk.metrics.data.HistogramPointData;
+import io.opentelemetry.sdk.metrics.data.LongPointData;
+import io.opentelemetry.sdk.metrics.data.PointData;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Test;
+
+import static
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_FAILED_METER_NAME;
+import static
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_FAILURES_HANDLED_METER_NAME;
+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.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+
+public class OpenTelemetryRoutePolicyMulticastSubRouteTest extends
AbstractOpenTelemetryRoutePolicyTest {
+
+ // verify that metrics are recorded on the 'timer' for 'exchange done'
events
+ @Test
+ public void testRouteTimerMeter() {
+ int count = 5;
+ for (int i = 0; i < count; i++) {
+ template.sendBody("direct:multicast", "Hello World");
+ template.send("direct:failure", e -> e.getMessage().setBody("Hello
World"));
+ }
+ for (String route : List.of("foo", "bar", "multicast",
"failureHandled", "failure")) {
+ PointData pd =
getPointDataForRouteId(DEFAULT_CAMEL_ROUTE_POLICY_METER_NAME, route);
+ assertInstanceOf(HistogramPointData.class, pd);
+ HistogramPointData hpd = (HistogramPointData) pd;
+ assertEquals(count, hpd.getCount(), "count for route " + route);
+ }
+ }
+
+ // verify that 'succeeded' counts are recorded
+ @Test
+ public void testSucceededMeter() {
+ int count = 5;
+ for (int i = 0; i < count; i++) {
+ template.sendBody("direct:multicast", "Hello World");
+ }
+ for (String route : List.of("foo", "multicast", "bar",
"failureHandled")) {
+ LongPointData lpd =
getSingleLongPointData(DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_SUCCEEDED_METER_NAME,
route);
+ assertEquals(count, lpd.getValue(), "count for route " + route);
+ }
+ }
+
+ // verify that 'handled failure' counts are recorded
+ @Test
+ public void testHandledFailuresMeter() {
+ int count = 5;
+ for (int i = 0; i < count; i++) {
+ template.sendBody("direct:multicast", "Hello World");
+ }
+ for (String route : List.of("multicast", "failureHandled")) {
+ LongPointData lpd =
getSingleLongPointData(DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_FAILURES_HANDLED_METER_NAME,
route);
+ assertEquals(count, lpd.getValue(), "count for route " + route);
+ }
+ }
+
+ // verify that 'failed' counts are recorded
+ @Test
+ public void testFailuresMeter() {
+ int count = 5;
+ for (int i = 0; i < count; i++) {
+ template.send("direct:failure", e -> e.getMessage().setBody("Hello
World"));
+ }
+ LongPointData lpd =
getSingleLongPointData(DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_FAILED_METER_NAME,
"failure");
+ assertEquals(count, lpd.getValue(), "count for route failure");
+ }
+
+ // verify that 'total' counts are recorded
+ @Test
+ public void testTotalExchangesMeter() {
+ int count = 5;
+ for (int i = 0; i < count; i++) {
+ template.sendBody("direct:multicast", "Hello World");
+ template.send("direct:failure", e -> e.getMessage().setBody("Hello
World"));
+ }
+ for (String route : List.of("foo", "bar", "multicast",
"failureHandled", "failure")) {
+ LongPointData lpd =
getSingleLongPointData(DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_TOTAL_METER_NAME,
route);
+ assertEquals(count, lpd.getValue(), "count for route " + route);
+ }
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ onException(IllegalStateException.class)
+ .handled(true);
+
+ from("direct:foo").routeId("foo").to("mock:foo");
+
+ from("direct:bar").routeId("bar").multicast().to("mock:bar1",
"mock:bar2");
+
+
from("direct:multicast").routeId("multicast").multicast().to("direct:foo",
"direct:bar",
+ "direct:failureHandled");
+
+ from("direct:failure").routeId("failure").throwException(new
Exception("forced"));
+
+
from("direct:failureHandled").routeId("failureHandled").throwException(new
IllegalStateException("forced"));
+ }
+ };
+ }
+}
diff --git
a/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicySubRouteTest.java
b/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicySubRouteTest.java
new file mode 100644
index 000000000000..348ad8c16d8e
--- /dev/null
+++
b/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicySubRouteTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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 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.junit.jupiter.api.Test;
+
+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.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+public class OpenTelemetryRoutePolicySubRouteTest extends
AbstractOpenTelemetryRoutePolicyTest {
+
+ @Test
+ public void testTotalMetric() throws Exception {
+ int count = 5;
+ getMockEndpoint("mock:foo").expectedMessageCount(count);
+ getMockEndpoint("mock:bar").expectedMessageCount(count);
+
+ for (int i = 0; i < count; i++) {
+ template.sendBody("direct:foo", "Hello World");
+ }
+ MockEndpoint.assertIsSatisfied(context);
+
+ for (String routeId : List.of("foo", "bar")) {
+ PointData pd =
getPointDataForRouteId(DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_TOTAL_METER_NAME,
routeId);
+ assertNotNull(pd, "No metric found for routeId " + routeId);
+ assertEquals(count, ((LongPointData) pd).getValue(), "Value for
routeId " + routeId);
+ }
+ }
+
+ @Test
+ public void testSucceededMetric() throws Exception {
+ int count = 5;
+ for (int i = 0; i < count; i++) {
+ template.sendBody("direct:foo", "Hello World");
+ }
+ MockEndpoint.assertIsSatisfied(context);
+
+ for (String routeId : List.of("foo", "bar")) {
+ PointData pd =
getPointDataForRouteId(DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_SUCCEEDED_METER_NAME,
routeId);
+ assertNotNull(pd, "No metric found for routeId " + routeId);
+ assertEquals(count, ((LongPointData) pd).getValue(), "Value for
routeId " + routeId);
+ }
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:foo").routeId("foo")
+ .to("direct:bar")
+ .to("mock:foo");
+
+ from("direct:bar").routeId("bar")
+ .to("mock:bar");
+ }
+ };
+ }
+}
diff --git
a/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicyTest.java
b/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicyTest.java
new file mode 100644
index 000000000000..708eed5e9471
--- /dev/null
+++
b/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetryRoutePolicyTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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 io.opentelemetry.sdk.metrics.data.HistogramPointData;
+import io.opentelemetry.sdk.metrics.data.PointData;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.junit.jupiter.api.Test;
+
+import static
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_FAILED_METER_NAME;
+import static
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_FAILURES_HANDLED_METER_NAME;
+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.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class OpenTelemetryRoutePolicyTest extends
AbstractOpenTelemetryRoutePolicyTest {
+
+ private static final long DELAY_FOO = 20;
+ private static final long DELAY_BAR = 50;
+ private static final long TOLERANCE = 20L;
+
+ @Override
+ public OpenTelemetryRoutePolicyFactory
createOpenTelemetryRoutePolicyFactory() {
+ OpenTelemetryRoutePolicyFactory factory = new
OpenTelemetryRoutePolicyFactory();
+ factory.getPolicyConfiguration().setExcludePattern(null);
+ factory.getPolicyConfiguration().setAdditionalCounters(false);
+ return factory;
+ }
+
+ @Test
+ public void testMetricsRoutePolicy() throws Exception {
+ int count = 10;
+ MockEndpoint mockEndpoint = getMockEndpoint("mock:result");
+ mockEndpoint.expectedMessageCount(count);
+ for (int i = 0; i < count; i++) {
+ if (i % 2 == 0) {
+ template.sendBody("direct:foo", "Hello " + i);
+ } else {
+ template.sendBody("direct:bar", "Hello " + i);
+ }
+ }
+ MockEndpoint.assertIsSatisfied(context);
+
+ PointData pd =
getPointDataForRouteId(DEFAULT_CAMEL_ROUTE_POLICY_METER_NAME, "foo");
+ verifyHistogramMetric(pd, DELAY_FOO, count / 2);
+
+ pd = getPointDataForRouteId(DEFAULT_CAMEL_ROUTE_POLICY_METER_NAME,
"bar");
+ verifyHistogramMetric(pd, DELAY_BAR, count / 2);
+ }
+
+ // verify no 'succeeded' meter name since 'additionalCounters' is false
+ @Test
+ public void testNoSucceededCounter() throws Exception {
+ MockEndpoint mockEndpoint = getMockEndpoint("mock:result");
+ mockEndpoint.expectedMessageCount(1);
+ template.sendBody("direct:foo", "Hello");
+
+ MockEndpoint.assertIsSatisfied(context);
+
assertTrue(getMetricData(DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_SUCCEEDED_METER_NAME).isEmpty());
+ }
+
+ // verify no 'total' meter name since 'additionalCounters' is false
+ @Test
+ public void testNoTotalCounter() throws Exception {
+ MockEndpoint mockEndpoint = getMockEndpoint("mock:result");
+ mockEndpoint.expectedMessageCount(1);
+ template.sendBody("direct:foo", "Hello");
+
+ MockEndpoint.assertIsSatisfied(context);
+
assertTrue(getMetricData(DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_TOTAL_METER_NAME).isEmpty());
+ }
+
+ // verify no 'failed' meter name since 'additionalCounters' is false
+ @Test
+ public void testNoFailuresMeter() {
+ int count = 3;
+ for (int i = 0; i < count; i++) {
+ template.send("direct:failure", e -> e.getMessage().setBody("Hello
World"));
+ }
+
assertTrue(getMetricData(DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_FAILED_METER_NAME).isEmpty());
+ }
+
+ // verify no 'handled failures' meter name since 'additionalCounters' is
false
+ @Test
+ public void testNoHandledFailuresMeter() {
+ int count = 3;
+ for (int i = 0; i < count; i++) {
+ template.send("direct:failureHandled", e ->
e.getMessage().setBody("Hello World"));
+ }
+
assertTrue(getMetricData(DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_FAILURES_HANDLED_METER_NAME).isEmpty());
+ }
+
+ private void verifyHistogramMetric(PointData pd, long delay, int msgCount)
{
+ assertTrue(pd instanceof HistogramPointData);
+ HistogramPointData hpd = (HistogramPointData) pd;
+ assertTrue(hpd.getMax() < delay + TOLERANCE, "max value");
+ assertTrue(hpd.getMin() >= delay, "min value");
+ assertEquals(msgCount, hpd.getCount(), "count");
+ assertTrue(hpd.getSum() >= msgCount * delay, "sum");
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ onException(IllegalStateException.class)
+ .handled(true);
+
+ from("direct:foo").routeId("foo")
+ .delay(DELAY_FOO)
+ .to("mock:result");
+
+ from("direct:bar").routeId("bar")
+ .delay(DELAY_BAR)
+ .to("mock:result");
+
+ from("direct:failure").routeId("failure").throwException(new
Exception("forced"));
+
+
from("direct:failureHandled").routeId("failureHandled").throwException(new
IllegalStateException("forced"));
+ }
+ };
+ }
+}
diff --git
a/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetrySharedRoutePolicyTest.java
b/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetrySharedRoutePolicyTest.java
new file mode 100644
index 000000000000..71475d985da3
--- /dev/null
+++
b/components/camel-opentelemetry-metrics/src/test/java/org/apache/camel/opentelemetry/metrics/routepolicy/OpenTelemetrySharedRoutePolicyTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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 io.opentelemetry.api.metrics.Meter;
+import io.opentelemetry.sdk.metrics.data.LongPointData;
+import org.apache.camel.BindToRegistry;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Test;
+
+import static
org.apache.camel.opentelemetry.metrics.OpenTelemetryConstants.DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_TOTAL_METER_NAME;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class OpenTelemetrySharedRoutePolicyTest extends
AbstractOpenTelemetryRoutePolicyTest {
+
+ protected Meter meter =
otelExtension.getOpenTelemetry().getMeter("meterTest");
+ // use a single shared instance of the policy
+ protected OpenTelemetryRoutePolicy singletonPolicy = new
OpenTelemetryRoutePolicy(null);
+
+ @BindToRegistry("meter")
+ public Meter addRegistry() {
+ return meter;
+ }
+
+ @Test
+ public void testSharedPolicy() throws Exception {
+ template.request("direct:foo", x -> {
+ });
+ template.request("direct:bar", x -> {
+ });
+
+ LongPointData lpd =
getSingleLongPointData(DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_TOTAL_METER_NAME,
"foo");
+ assertEquals(2L, lpd.getValue());
+
+ lpd =
getSingleLongPointData(DEFAULT_CAMEL_ROUTE_POLICY_EXCHANGES_TOTAL_METER_NAME,
"bar");
+ assertEquals(2L, lpd.getValue());
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:foo").routeId("foo").routePolicy(singletonPolicy)
+ .to("mock:result");
+
+ from("direct:bar").routeId("bar").routePolicy(singletonPolicy)
+ .to("mock:result");
+ }
+ };
+ }
+}
diff --git
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenTelemetryEndpointBuilderFactory.java
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenTelemetryEndpointBuilderFactory.java
index 7e02f0b5d10e..23d96aaf5c13 100644
---
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenTelemetryEndpointBuilderFactory.java
+++
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenTelemetryEndpointBuilderFactory.java
@@ -324,7 +324,7 @@ public interface OpenTelemetryEndpointBuilderFactory {
* Override timer action in URI.
*
* The option is a: {@code
- * org.apache.camel.opentelemetry2.component.OpenTelemetryTimerAction}
+ * org.apache.camel.opentelemetry.metrics.OpenTelemetryTimerAction}
* type.
*
* Group: producer