Skip to content

Commit

Permalink
Mm (#13393)
Browse files Browse the repository at this point in the history
CAMEL-20461: camel-micrometer - Add statistics for context level
  • Loading branch information
davsclaus committed Mar 6, 2024
1 parent 30d2191 commit 0fec0d4
Show file tree
Hide file tree
Showing 25 changed files with 546 additions and 64 deletions.
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

0 comments on commit 0fec0d4

Please sign in to comment.