Skip to content

Commit

Permalink
Implementing OtelJMX metrics exporter for JMX metrics insights (#901)
Browse files Browse the repository at this point in the history
*Issue #, if available:*

*Description of changes:*
1. Implemented custom provider for OtelJMX metrics insights
2. Is enabled when ```AWS_JMX_ENABLED``` is set to true
3. Sets up a metrics exporter over 4314 port only over HTTP protocol

*Testing*
Tested as part of ```amazon-cloudwatch-observability``` helm-chart 
1. Annotate a pod deploying a sprint boot project with
```instrumentation.opentelemetry.io/inject-java: "true"
cloudwatch.aws.amazon.com/inject-jmx-jvm: "true"``` and deploy on the
cluster
2. Deploy the helm-chart with a custom amazon-cloudwatch-operator build
to inject the following environment vars
```
OTEL_EXPORTER_OTLP_PROTOCOL
OTEL_METRICS_EXPORTER
OTEL_LOGS_EXPORTER
AWS_JMX_EXPORTER_METRICS_ENDPOINT
OTEL_JMX_TARGET_SYSTEM
````
3. Verified AwsJMX exporter is enabled in pod logs
```
 INFO software.amazon.opentelemetry.javaagent.providers.AwsJMXMetricsCustomizerProvider - AWS JMX metric collection enabled
```


By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache 2.0 license.
  • Loading branch information
mitali-salvi authored Oct 11, 2024
1 parent e144053 commit 81e799b
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,10 @@
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
import io.opentelemetry.sdk.metrics.Aggregation;
import io.opentelemetry.sdk.metrics.InstrumentSelector;
import io.opentelemetry.sdk.metrics.InstrumentType;
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder;
import io.opentelemetry.sdk.metrics.View;
import io.opentelemetry.sdk.metrics.export.MetricExporter;
import io.opentelemetry.sdk.metrics.export.MetricReader;
import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader;
Expand Down Expand Up @@ -152,7 +149,9 @@ private SdkTracerProviderBuilder customizeTracerProviderBuilder(
SdkTracerProviderBuilder tracerProviderBuilder, ConfigProperties configProps) {
if (isApplicationSignalsEnabled(configProps)) {
logger.info("AWS Application Signals enabled");
Duration exportInterval = getMetricExportInterval(configProps);
Duration exportInterval =
SDKMeterProviderBuilder.getMetricExportInterval(
configProps, DEFAULT_METRIC_EXPORT_INTERVAL, logger);
// Construct and set local and remote attributes span processor
tracerProviderBuilder.addSpanProcessor(
AttributePropagatingSpanProcessorBuilder.create().build());
Expand Down Expand Up @@ -187,13 +186,16 @@ private SdkMeterProviderBuilder customizeMeterProvider(
String jmxRuntimeScopeName = "io.opentelemetry.jmx";
registeredScopeNames.add(jmxRuntimeScopeName);

configureMetricFilter(configProps, sdkMeterProviderBuilder, registeredScopeNames);
SDKMeterProviderBuilder.configureMetricFilter(
configProps, sdkMeterProviderBuilder, registeredScopeNames, logger);

MetricExporter metricsExporter =
ApplicationSignalsExporterProvider.INSTANCE.createExporter(configProps);
MetricReader metricReader =
ScopeBasedPeriodicMetricReader.create(metricsExporter, registeredScopeNames)
.setInterval(getMetricExportInterval(configProps))
.setInterval(
SDKMeterProviderBuilder.getMetricExportInterval(
configProps, DEFAULT_METRIC_EXPORT_INTERVAL, logger))
.build();
sdkMeterProviderBuilder.registerMetricReader(metricReader);

Expand All @@ -202,44 +204,6 @@ private SdkMeterProviderBuilder customizeMeterProvider(
return sdkMeterProviderBuilder;
}

private static void configureMetricFilter(
ConfigProperties configProps,
SdkMeterProviderBuilder sdkMeterProviderBuilder,
Set<String> registeredScopeNames) {
Set<String> exporterNames =
DefaultConfigProperties.getSet(configProps, "otel.metrics.exporter");
if (exporterNames.contains("none")) {
for (String scope : registeredScopeNames) {
sdkMeterProviderBuilder.registerView(
InstrumentSelector.builder().setMeterName(scope).build(),
View.builder().setAggregation(Aggregation.defaultAggregation()).build());

logger.log(Level.FINE, "Registered scope {0}", scope);
}
sdkMeterProviderBuilder.registerView(
InstrumentSelector.builder().setName("*").build(),
View.builder().setAggregation(Aggregation.drop()).build());
}
}

private static Duration getMetricExportInterval(ConfigProperties configProps) {
Duration exportInterval =
configProps.getDuration("otel.metric.export.interval", DEFAULT_METRIC_EXPORT_INTERVAL);
logger.log(
Level.FINE,
String.format("AWS Application Signals Metrics export interval: %s", exportInterval));
// Cap export interval to 60 seconds. This is currently required for metrics-trace correlation
// to work correctly.
if (exportInterval.compareTo(DEFAULT_METRIC_EXPORT_INTERVAL) > 0) {
exportInterval = DEFAULT_METRIC_EXPORT_INTERVAL;
logger.log(
Level.INFO,
String.format(
"AWS Application Signals metrics export interval capped to %s", exportInterval));
}
return exportInterval;
}

private SpanExporter customizeSpanExporter(
SpanExporter spanExporter, ConfigProperties configProps) {
if (isApplicationSignalsEnabled(configProps)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* Copyright Amazon.com, Inc. or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 software.amazon.opentelemetry.javaagent.providers;

import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter;
import io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil;
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer;
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
import io.opentelemetry.sdk.metrics.Aggregation;
import io.opentelemetry.sdk.metrics.InstrumentType;
import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder;
import io.opentelemetry.sdk.metrics.export.MetricExporter;
import io.opentelemetry.sdk.metrics.export.MetricReader;
import java.time.Duration;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* You can control when these customizations are applied using both the properties -
* otel.jmx.enabled and otel.aws.jmx.exporter.metrics.endpoint or the environment variable
* OTEL_JMX_ENABLED_CONFIG and AWS_JMX_EXPORTER_ENDPOINT_CONFIG. These flags are disabled by
* default.
*/
public class AwsJMXMetricsCustomizerProvider implements AutoConfigurationCustomizerProvider {
private static final Duration DEFAULT_METRIC_EXPORT_INTERVAL = Duration.ofMinutes(1);
private static final Logger logger =
Logger.getLogger(AwsJMXMetricsCustomizerProvider.class.getName());

private static final String OTEL_JMX_ENABLED_CONFIG = "otel.jmx.enabled";
private static final String AWS_JMX_EXPORTER_ENDPOINT_CONFIG =
"otel.aws.jmx.exporter.metrics.endpoint";

public void customize(AutoConfigurationCustomizer autoConfiguration) {
autoConfiguration.addMeterProviderCustomizer(this::customizeMeterProvider);
}

private boolean isOtelJMXEnabled(ConfigProperties configProps) {
return configProps.getBoolean(OTEL_JMX_ENABLED_CONFIG, true)
&& configProps.getString(AWS_JMX_EXPORTER_ENDPOINT_CONFIG, "") != "";
}

private SdkMeterProviderBuilder customizeMeterProvider(
SdkMeterProviderBuilder sdkMeterProviderBuilder, ConfigProperties configProps) {

if (isOtelJMXEnabled(configProps)) {
Set<String> registeredScopeNames = new HashSet<>(1);
String jmxRuntimeScopeName = "io.opentelemetry.jmx";
registeredScopeNames.add(jmxRuntimeScopeName);

SDKMeterProviderBuilder.configureMetricFilter(
configProps, sdkMeterProviderBuilder, registeredScopeNames, logger);

MetricExporter metricsExporter = JMXExporterProvider.INSTANCE.createExporter(configProps);
MetricReader metricReader =
ScopeBasedPeriodicMetricReader.create(metricsExporter, registeredScopeNames)
.setInterval(
SDKMeterProviderBuilder.getMetricExportInterval(
configProps, DEFAULT_METRIC_EXPORT_INTERVAL, logger))
.build();
sdkMeterProviderBuilder.registerMetricReader(metricReader);

logger.info("AWS JMX metric collection enabled");
}
return sdkMeterProviderBuilder;
}

private enum JMXExporterProvider {
INSTANCE;

public MetricExporter createExporter(ConfigProperties configProps) {
String protocol =
OtlpConfigUtil.getOtlpProtocol(OtlpConfigUtil.DATA_TYPE_METRICS, configProps);
logger.log(Level.FINE, String.format("AWS JMX metrics export protocol: %s", protocol));

String otelJMXEndpoint;
if (protocol.equals(OtlpConfigUtil.PROTOCOL_HTTP_PROTOBUF)) {
otelJMXEndpoint = configProps.getString(AWS_JMX_EXPORTER_ENDPOINT_CONFIG);
logger.log(
Level.FINE, String.format("AWS JMX metrics export endpoint: %s", otelJMXEndpoint));
return OtlpHttpMetricExporter.builder()
.setEndpoint(otelJMXEndpoint)
.setDefaultAggregationSelector(this::getAggregation)
.build();
}
throw new ConfigurationException("Unsupported AWS JMX metrics export protocol: " + protocol);
}

private Aggregation getAggregation(InstrumentType instrumentType) {
if (instrumentType == InstrumentType.HISTOGRAM) {
return Aggregation.base2ExponentialBucketHistogram();
}
return Aggregation.defaultAggregation();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright Amazon.com, Inc. or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 software.amazon.opentelemetry.javaagent.providers;

import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
import io.opentelemetry.sdk.metrics.Aggregation;
import io.opentelemetry.sdk.metrics.InstrumentSelector;
import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder;
import io.opentelemetry.sdk.metrics.View;
import java.time.Duration;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

public class SDKMeterProviderBuilder {
static void configureMetricFilter(
ConfigProperties configProps,
SdkMeterProviderBuilder sdkMeterProviderBuilder,
Set<String> registeredScopeNames,
Logger logger) {
Set<String> exporterNames =
DefaultConfigProperties.getSet(configProps, "otel.metrics.exporter");
if (exporterNames.contains("none")) {
for (String scope : registeredScopeNames) {
sdkMeterProviderBuilder.registerView(
InstrumentSelector.builder().setMeterName(scope).build(),
View.builder().setAggregation(Aggregation.defaultAggregation()).build());

logger.log(Level.FINE, "Registered scope {0}", scope);
}
sdkMeterProviderBuilder.registerView(
InstrumentSelector.builder().setName("*").build(),
View.builder().setAggregation(Aggregation.drop()).build());
}
}

static Duration getMetricExportInterval(
ConfigProperties configProps, Duration exportIntervalEnvVar, Logger logger) {
Duration exportInterval =
configProps.getDuration("otel.metric.export.interval", exportIntervalEnvVar);
logger.log(Level.FINE, String.format("Metrics export interval: %s", exportInterval));
// Cap export interval to 60 seconds. This is currently required for metrics-trace correlation
// to work correctly.
if (exportInterval.compareTo(exportIntervalEnvVar) > 0) {
exportInterval = exportIntervalEnvVar;
logger.log(Level.INFO, String.format("Metrics export interval capped to %s", exportInterval));
}
return exportInterval;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@
software.amazon.opentelemetry.javaagent.providers.AwsAgentPropertiesCustomizerProvider
software.amazon.opentelemetry.javaagent.providers.AwsTracerCustomizerProvider
software.amazon.opentelemetry.javaagent.providers.AwsApplicationSignalsCustomizerProvider
software.amazon.opentelemetry.javaagent.providers.AwsJMXMetricsCustomizerProvider

0 comments on commit 81e799b

Please sign in to comment.