Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mm #13393

Merged
merged 9 commits into from Mar 6, 2024
Expand Up @@ -182,6 +182,7 @@
{ "name": "camel.metrics.enableRouteEventNotifier", "description": "Set whether to enable the MicrometerRouteEventNotifier for capturing metrics on the total number of routes and total number of routes running.", "sourceType": "org.apache.camel.main.MetricsConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": true },
{ "name": "camel.metrics.enableRoutePolicy", "description": "Set whether to enable the MicrometerRoutePolicyFactory for capturing metrics on route processing times.", "sourceType": "org.apache.camel.main.MetricsConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": true },
{ "name": "camel.metrics.namingStrategy", "description": "Controls the name style to use for metrics. Default = uses micrometer naming convention. Legacy = uses the classic naming style (camelCase)", "sourceType": "org.apache.camel.main.MetricsConfigurationProperties", "type": "string", "javaType": "java.lang.String", "defaultValue": "default", "enum": [ "default", "legacy" ] },
{ "name": "camel.metrics.routePolicyLevel", "description": "Sets the level of information to capture. all = both context and routes.", "sourceType": "org.apache.camel.main.MetricsConfigurationProperties", "type": "string", "javaType": "java.lang.String", "defaultValue": "all", "enum": [ "all", "route", "context" ] },
{ "name": "camel.metrics.textFormatVersion", "description": "The text-format version to use with Prometheus scraping. 0.0.4 = text\/plain; version=0.0.4; charset=utf-8 1.0.0 = application\/openmetrics-text; version=1.0.0; charset=utf-8", "sourceType": "org.apache.camel.main.MetricsConfigurationProperties", "type": "string", "javaType": "java.lang.String", "defaultValue": "0.0.4", "enum": [ "0.0.4", "1.0.0" ] },
{ "name": "camel.opentelemetry.enabled", "description": "To enable OpenTelemetry", "sourceType": "org.apache.camel.main.OtelConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": "false" },
{ "name": "camel.opentelemetry.encoding", "description": "Sets whether the header keys need to be encoded (connector specific) or not. The value is a boolean. Dashes need for instances to be encoded for JMS property keys.", "sourceType": "org.apache.camel.main.OtelConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": "false" },
Expand Down
Expand Up @@ -37,6 +37,8 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj
case "EnableRoutePolicy": target.setEnableRoutePolicy(property(camelContext, boolean.class, value)); return true;
case "namingstrategy":
case "NamingStrategy": target.setNamingStrategy(property(camelContext, java.lang.String.class, value)); return true;
case "routepolicylevel":
case "RoutePolicyLevel": target.setRoutePolicyLevel(property(camelContext, java.lang.String.class, value)); return true;
case "textformatversion":
case "TextFormatVersion": target.setTextFormatVersion(property(camelContext, java.lang.String.class, value)); return true;
default: return false;
Expand All @@ -62,6 +64,8 @@ public Class<?> getOptionType(String name, boolean ignoreCase) {
case "EnableRoutePolicy": return boolean.class;
case "namingstrategy":
case "NamingStrategy": return java.lang.String.class;
case "routepolicylevel":
case "RoutePolicyLevel": return java.lang.String.class;
case "textformatversion":
case "TextFormatVersion": return java.lang.String.class;
default: return null;
Expand All @@ -88,6 +92,8 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) {
case "EnableRoutePolicy": return target.isEnableRoutePolicy();
case "namingstrategy":
case "NamingStrategy": return target.getNamingStrategy();
case "routepolicylevel":
case "RoutePolicyLevel": return target.getRoutePolicyLevel();
case "textformatversion":
case "TextFormatVersion": return target.getTextFormatVersion();
default: return null;
Expand Down
Expand Up @@ -84,6 +84,8 @@ public class MicrometerPrometheus extends ServiceSupport implements CamelMetrics
private String namingStrategy;
@Metadata(defaultValue = "true")
private boolean enableRoutePolicy = true;
@Metadata(defaultValue = "all", enums = "all,route,context")
private String routePolicyLevel = "all";
@Metadata(defaultValue = "false")
private boolean enableMessageHistory;
@Metadata(defaultValue = "true")
Expand Down Expand Up @@ -131,6 +133,17 @@ public void setEnableRoutePolicy(boolean enableRoutePolicy) {
this.enableRoutePolicy = enableRoutePolicy;
}

public String getRoutePolicyLevel() {
return routePolicyLevel;
}

/**
* Sets the level of information to capture. all = both context and routes.
*/
public void setRoutePolicyLevel(String routePolicyLevel) {
this.routePolicyLevel = routePolicyLevel;
}

public boolean isEnableMessageHistory() {
return enableMessageHistory;
}
Expand Down Expand Up @@ -240,7 +253,19 @@ protected void doInit() throws Exception {
if ("legacy".equalsIgnoreCase(namingStrategy)) {
factory.setNamingStrategy(MicrometerRoutePolicyNamingStrategy.LEGACY);
}
if ("all".equalsIgnoreCase(routePolicyLevel)) {
factory.getPolicyConfiguration().setContextEnabled(true);
factory.getPolicyConfiguration().setRouteEnabled(true);
} else if ("context".equalsIgnoreCase(routePolicyLevel)) {
factory.getPolicyConfiguration().setContextEnabled(true);
factory.getPolicyConfiguration().setRouteEnabled(false);
} else {
factory.getPolicyConfiguration().setContextEnabled(false);
factory.getPolicyConfiguration().setRouteEnabled(true);
}
factory.setMeterRegistry(meterRegistry);
// ensure factory will be started and stopped
camelContext.addService(factory);
camelContext.addRoutePolicyFactory(factory);
}

Expand Down
Expand Up @@ -407,6 +407,8 @@ following options:
[width="100%",options="header"]
|=======================================================================
|Name |Default |Description
|contextEnabled | true | whether to include counter for context level metrics
|routeEnabled | true | whether to include counter for route level metrics
|additionalCounters | true | activates all additional counters
|exchangesSucceeded | true | activates counter for succeeded exchanges
|exchangesFailed | true | activates counter for failed exchanges
Expand Down
Expand Up @@ -137,6 +137,6 @@ protected void doStart() {

@Override
protected void doStop() {

// noop
}
}
@@ -0,0 +1,71 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.component.micrometer.routepolicy;

import io.micrometer.core.instrument.MeterRegistry;
import org.apache.camel.CamelContext;
import org.apache.camel.Exchange;
import org.apache.camel.spi.UnitOfWork;

final class ContextMetricsStatistics extends MicrometerRoutePolicy.MetricsStatistics {

private final boolean registerKamelets;
private final boolean registerTemplates;

ContextMetricsStatistics(MeterRegistry meterRegistry, CamelContext camelContext,
MicrometerRoutePolicyNamingStrategy namingStrategy,
MicrometerRoutePolicyConfiguration configuration, boolean registerKamelets,
boolean registerTemplates) {
super(meterRegistry, camelContext, null, namingStrategy, configuration);
this.registerKamelets = registerKamelets;
this.registerTemplates = registerTemplates;
}

@Override
public void onExchangeBegin(Exchange exchange) {
// this metric is triggered for every route
// so we must only trigger on the root level, otherwise this metric
// total counter will be incorrect. For example if an exchange is routed via 3 routes
// we should only count this as 1 instead of 3.
UnitOfWork uow = exchange.getUnitOfWork();
if (uow != null) {
int level = uow.routeStackLevel(registerTemplates, registerKamelets);
if (level <= 1) {
super.onExchangeBegin(exchange);
}
} else {
super.onExchangeBegin(exchange);
}
}

@Override
public void onExchangeDone(Exchange exchange) {
// this metric is triggered for every route
// so we must only trigger on the root level, otherwise this metric
// total counter will be incorrect. For example if an exchange is routed via 3 routes
// we should only count this as 1 instead of 3.
UnitOfWork uow = exchange.getUnitOfWork();
if (uow != null) {
int level = uow.routeStackLevel(registerTemplates, registerKamelets);
if (level <= 1) {
super.onExchangeDone(exchange);
}
} else {
super.onExchangeDone(exchange);
}
}
}
Expand Up @@ -26,6 +26,7 @@
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.Timer;
import org.apache.camel.CamelContext;
import org.apache.camel.Exchange;
import org.apache.camel.NonManagedService;
import org.apache.camel.Route;
Expand All @@ -48,18 +49,21 @@
*/
public class MicrometerRoutePolicy extends RoutePolicySupport implements NonManagedService {

private final MicrometerRoutePolicyFactory factory;
private MeterRegistry meterRegistry;
private boolean prettyPrint;
private TimeUnit durationUnit = TimeUnit.MILLISECONDS;
private MicrometerRoutePolicyNamingStrategy namingStrategy = MicrometerRoutePolicyNamingStrategy.DEFAULT;
private MicrometerRoutePolicyConfiguration configuration = MicrometerRoutePolicyConfiguration.DEFAULT;

private final Map<Route, MetricsStatistics> statisticsMap = new HashMap<>();
private RouteMetric contextStatistic;
boolean registerKamelets;
boolean registerTemplates = true;

private static final class MetricsStatistics {
static class MetricsStatistics implements RouteMetric {
private final MeterRegistry meterRegistry;
private final CamelContext camelContext;
private final Route route;
private final MicrometerRoutePolicyNamingStrategy namingStrategy;
private final MicrometerRoutePolicyConfiguration configuration;
Expand All @@ -71,12 +75,13 @@ private static final class MetricsStatistics {
private Timer timer;
private LongTaskTimer longTaskTimer;

private MetricsStatistics(MeterRegistry meterRegistry, Route route,
MicrometerRoutePolicyNamingStrategy namingStrategy,
MicrometerRoutePolicyConfiguration configuration) {
MetricsStatistics(MeterRegistry meterRegistry, CamelContext camelContext, Route route,
MicrometerRoutePolicyNamingStrategy namingStrategy,
MicrometerRoutePolicyConfiguration configuration) {
this.configuration = ObjectHelper.notNull(configuration, "MicrometerRoutePolicyConfiguration", this);
this.meterRegistry = ObjectHelper.notNull(meterRegistry, "MeterRegistry", this);
this.namingStrategy = ObjectHelper.notNull(namingStrategy, "MicrometerRoutePolicyNamingStrategy", this);
this.camelContext = camelContext;
this.route = route;
if (configuration.isAdditionalCounters()) {
initAdditionalCounters();
Expand Down Expand Up @@ -106,8 +111,8 @@ private void initAdditionalCounters() {
}
if (configuration.isLongTask()) {
LongTaskTimer.Builder builder = LongTaskTimer.builder(namingStrategy.getLongTaskName(route))
.tags(namingStrategy.getTags(route))
.description("Route long task metric");
.tags(route != null ? namingStrategy.getTags(route) : namingStrategy.getTags(camelContext))
.description(route != null ? "Route long task metric" : "CamelContext long task metric");
if (configuration.getLongTaskInitiator() != null) {
configuration.getLongTaskInitiator().accept(builder);
}
Expand All @@ -128,8 +133,8 @@ public void onExchangeDone(Exchange exchange) {
if (sample != null) {
if (timer == null) {
Timer.Builder builder = Timer.builder(namingStrategy.getName(route))
.tags(namingStrategy.getTags(route))
.description("Route performance metrics");
.tags(route != null ? namingStrategy.getTags(route) : namingStrategy.getTags(camelContext))
.description(route != null ? "Route performance metrics" : "CamelContext performance metrics");
if (configuration.getTimerInitiator() != null) {
configuration.getTimerInitiator().accept(builder);
}
Expand All @@ -147,6 +152,30 @@ public void onExchangeDone(Exchange exchange) {
}
}

public void remove() {
if (exchangesSucceeded != null) {
meterRegistry.remove(exchangesSucceeded);
}
if (exchangesFailed != null) {
meterRegistry.remove(exchangesFailed);
}
if (exchangesTotal != null) {
meterRegistry.remove(exchangesTotal);
}
if (externalRedeliveries != null) {
meterRegistry.remove(externalRedeliveries);
}
if (failuresHandled != null) {
meterRegistry.remove(failuresHandled);
}
if (timer != null) {
meterRegistry.remove(timer);
}
if (longTaskTimer != null) {
meterRegistry.remove(longTaskTimer);
}
}

private void updateAdditionalCounters(Exchange exchange) {
if (exchangesTotal != null) {
exchangesTotal.increment();
Expand All @@ -169,17 +198,32 @@ private void updateAdditionalCounters(Exchange exchange) {
}

private String propertyName(Exchange exchange) {
return String.format("%s-%s-%s", DEFAULT_CAMEL_ROUTE_POLICY_METER_NAME, route.getId(), exchange.getExchangeId());
String id;
if (route != null) {
id = route.getId();
} else {
id = "context:" + camelContext.getName();
}
return String.format("%s-%s-%s", DEFAULT_CAMEL_ROUTE_POLICY_METER_NAME, id, exchange.getExchangeId());
}

private Counter createCounter(String meterName, String description) {
return Counter.builder(meterName)
.tags(namingStrategy.getExchangeStatusTags(route))
.tags(route != null
? namingStrategy.getExchangeStatusTags(route) : namingStrategy.getExchangeStatusTags(camelContext))
.description(description)
.register(meterRegistry);
}
}

public MicrometerRoutePolicy() {
this.factory = null;
}

public MicrometerRoutePolicy(MicrometerRoutePolicyFactory factory) {
this.factory = factory;
}

public MeterRegistry getMeterRegistry() {
return meterRegistry;
}
Expand Down Expand Up @@ -249,6 +293,18 @@ public void onInit(Route route) {
} catch (Exception e) {
throw RuntimeCamelException.wrapRuntimeCamelException(e);
}

if (factory != null && configuration.isContextEnabled() && contextStatistic == null) {
contextStatistic = factory.createOrGetContextMetric(this);
}
}

boolean isRegisterKamelets() {
return registerKamelets;
}

boolean isRegisterTemplates() {
return registerTemplates;
}

@Override
Expand All @@ -258,13 +314,17 @@ public void onStart(Route route) {
// we have in-flight / total statistics already from camel-core
statisticsMap.computeIfAbsent(route,
it -> {
boolean skip = !configuration.isRouteEnabled();
// skip routes that should not be included
boolean skip = (it.isCreatedByKamelet() && !registerKamelets)
|| (it.isCreatedByRouteTemplate() && !registerTemplates);
if (!skip) {
skip = (it.isCreatedByKamelet() && !registerKamelets)
|| (it.isCreatedByRouteTemplate() && !registerTemplates);
}
if (skip) {
return null;
}
return new MetricsStatistics(getMeterRegistry(), it, getNamingStrategy(), configuration);
return new MetricsStatistics(
getMeterRegistry(), it.getCamelContext(), it, getNamingStrategy(), configuration);
});
}

Expand All @@ -273,38 +333,24 @@ public void onRemove(Route route) {
// route is removed, so remove metrics from micrometer
MetricsStatistics stats = statisticsMap.remove(route);
if (stats != null) {
if (stats.exchangesSucceeded != null) {
meterRegistry.remove(stats.exchangesSucceeded);
}
if (stats.exchangesFailed != null) {
meterRegistry.remove(stats.exchangesFailed);
}
if (stats.exchangesTotal != null) {
meterRegistry.remove(stats.exchangesTotal);
}
if (stats.externalRedeliveries != null) {
meterRegistry.remove(stats.externalRedeliveries);
}
if (stats.failuresHandled != null) {
meterRegistry.remove(stats.failuresHandled);
}
if (stats.timer != null) {
meterRegistry.remove(stats.timer);
}
if (stats.longTaskTimer != null) {
meterRegistry.remove(stats.longTaskTimer);
}
stats.remove();
}
}

@Override
public void onExchangeBegin(Route route, Exchange exchange) {
if (contextStatistic != null) {
contextStatistic.onExchangeBegin(exchange);
}
Optional.ofNullable(statisticsMap.get(route))
.ifPresent(statistics -> statistics.onExchangeBegin(exchange));
}

@Override
public void onExchangeDone(Route route, Exchange exchange) {
if (contextStatistic != null) {
contextStatistic.onExchangeDone(exchange);
}
Optional.ofNullable(statisticsMap.get(route))
.ifPresent(statistics -> statistics.onExchangeDone(exchange));
}
Expand Down