Skip to content

Commit 81e799b

Browse files
authored
Implementing OtelJMX metrics exporter for JMX metrics insights (#901)
*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.
1 parent e144053 commit 81e799b

File tree

4 files changed

+185
-44
lines changed

4 files changed

+185
-44
lines changed

awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProvider.java

Lines changed: 8 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,10 @@
2626
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
2727
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
2828
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
29-
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
3029
import io.opentelemetry.sdk.metrics.Aggregation;
31-
import io.opentelemetry.sdk.metrics.InstrumentSelector;
3230
import io.opentelemetry.sdk.metrics.InstrumentType;
3331
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
3432
import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder;
35-
import io.opentelemetry.sdk.metrics.View;
3633
import io.opentelemetry.sdk.metrics.export.MetricExporter;
3734
import io.opentelemetry.sdk.metrics.export.MetricReader;
3835
import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader;
@@ -152,7 +149,9 @@ private SdkTracerProviderBuilder customizeTracerProviderBuilder(
152149
SdkTracerProviderBuilder tracerProviderBuilder, ConfigProperties configProps) {
153150
if (isApplicationSignalsEnabled(configProps)) {
154151
logger.info("AWS Application Signals enabled");
155-
Duration exportInterval = getMetricExportInterval(configProps);
152+
Duration exportInterval =
153+
SDKMeterProviderBuilder.getMetricExportInterval(
154+
configProps, DEFAULT_METRIC_EXPORT_INTERVAL, logger);
156155
// Construct and set local and remote attributes span processor
157156
tracerProviderBuilder.addSpanProcessor(
158157
AttributePropagatingSpanProcessorBuilder.create().build());
@@ -187,13 +186,16 @@ private SdkMeterProviderBuilder customizeMeterProvider(
187186
String jmxRuntimeScopeName = "io.opentelemetry.jmx";
188187
registeredScopeNames.add(jmxRuntimeScopeName);
189188

190-
configureMetricFilter(configProps, sdkMeterProviderBuilder, registeredScopeNames);
189+
SDKMeterProviderBuilder.configureMetricFilter(
190+
configProps, sdkMeterProviderBuilder, registeredScopeNames, logger);
191191

192192
MetricExporter metricsExporter =
193193
ApplicationSignalsExporterProvider.INSTANCE.createExporter(configProps);
194194
MetricReader metricReader =
195195
ScopeBasedPeriodicMetricReader.create(metricsExporter, registeredScopeNames)
196-
.setInterval(getMetricExportInterval(configProps))
196+
.setInterval(
197+
SDKMeterProviderBuilder.getMetricExportInterval(
198+
configProps, DEFAULT_METRIC_EXPORT_INTERVAL, logger))
197199
.build();
198200
sdkMeterProviderBuilder.registerMetricReader(metricReader);
199201

@@ -202,44 +204,6 @@ private SdkMeterProviderBuilder customizeMeterProvider(
202204
return sdkMeterProviderBuilder;
203205
}
204206

205-
private static void configureMetricFilter(
206-
ConfigProperties configProps,
207-
SdkMeterProviderBuilder sdkMeterProviderBuilder,
208-
Set<String> registeredScopeNames) {
209-
Set<String> exporterNames =
210-
DefaultConfigProperties.getSet(configProps, "otel.metrics.exporter");
211-
if (exporterNames.contains("none")) {
212-
for (String scope : registeredScopeNames) {
213-
sdkMeterProviderBuilder.registerView(
214-
InstrumentSelector.builder().setMeterName(scope).build(),
215-
View.builder().setAggregation(Aggregation.defaultAggregation()).build());
216-
217-
logger.log(Level.FINE, "Registered scope {0}", scope);
218-
}
219-
sdkMeterProviderBuilder.registerView(
220-
InstrumentSelector.builder().setName("*").build(),
221-
View.builder().setAggregation(Aggregation.drop()).build());
222-
}
223-
}
224-
225-
private static Duration getMetricExportInterval(ConfigProperties configProps) {
226-
Duration exportInterval =
227-
configProps.getDuration("otel.metric.export.interval", DEFAULT_METRIC_EXPORT_INTERVAL);
228-
logger.log(
229-
Level.FINE,
230-
String.format("AWS Application Signals Metrics export interval: %s", exportInterval));
231-
// Cap export interval to 60 seconds. This is currently required for metrics-trace correlation
232-
// to work correctly.
233-
if (exportInterval.compareTo(DEFAULT_METRIC_EXPORT_INTERVAL) > 0) {
234-
exportInterval = DEFAULT_METRIC_EXPORT_INTERVAL;
235-
logger.log(
236-
Level.INFO,
237-
String.format(
238-
"AWS Application Signals metrics export interval capped to %s", exportInterval));
239-
}
240-
return exportInterval;
241-
}
242-
243207
private SpanExporter customizeSpanExporter(
244208
SpanExporter spanExporter, ConfigProperties configProps) {
245209
if (isApplicationSignalsEnabled(configProps)) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.opentelemetry.javaagent.providers;
17+
18+
import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter;
19+
import io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil;
20+
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer;
21+
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
22+
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
23+
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
24+
import io.opentelemetry.sdk.metrics.Aggregation;
25+
import io.opentelemetry.sdk.metrics.InstrumentType;
26+
import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder;
27+
import io.opentelemetry.sdk.metrics.export.MetricExporter;
28+
import io.opentelemetry.sdk.metrics.export.MetricReader;
29+
import java.time.Duration;
30+
import java.util.HashSet;
31+
import java.util.Set;
32+
import java.util.logging.Level;
33+
import java.util.logging.Logger;
34+
35+
/**
36+
* You can control when these customizations are applied using both the properties -
37+
* otel.jmx.enabled and otel.aws.jmx.exporter.metrics.endpoint or the environment variable
38+
* OTEL_JMX_ENABLED_CONFIG and AWS_JMX_EXPORTER_ENDPOINT_CONFIG. These flags are disabled by
39+
* default.
40+
*/
41+
public class AwsJMXMetricsCustomizerProvider implements AutoConfigurationCustomizerProvider {
42+
private static final Duration DEFAULT_METRIC_EXPORT_INTERVAL = Duration.ofMinutes(1);
43+
private static final Logger logger =
44+
Logger.getLogger(AwsJMXMetricsCustomizerProvider.class.getName());
45+
46+
private static final String OTEL_JMX_ENABLED_CONFIG = "otel.jmx.enabled";
47+
private static final String AWS_JMX_EXPORTER_ENDPOINT_CONFIG =
48+
"otel.aws.jmx.exporter.metrics.endpoint";
49+
50+
public void customize(AutoConfigurationCustomizer autoConfiguration) {
51+
autoConfiguration.addMeterProviderCustomizer(this::customizeMeterProvider);
52+
}
53+
54+
private boolean isOtelJMXEnabled(ConfigProperties configProps) {
55+
return configProps.getBoolean(OTEL_JMX_ENABLED_CONFIG, true)
56+
&& configProps.getString(AWS_JMX_EXPORTER_ENDPOINT_CONFIG, "") != "";
57+
}
58+
59+
private SdkMeterProviderBuilder customizeMeterProvider(
60+
SdkMeterProviderBuilder sdkMeterProviderBuilder, ConfigProperties configProps) {
61+
62+
if (isOtelJMXEnabled(configProps)) {
63+
Set<String> registeredScopeNames = new HashSet<>(1);
64+
String jmxRuntimeScopeName = "io.opentelemetry.jmx";
65+
registeredScopeNames.add(jmxRuntimeScopeName);
66+
67+
SDKMeterProviderBuilder.configureMetricFilter(
68+
configProps, sdkMeterProviderBuilder, registeredScopeNames, logger);
69+
70+
MetricExporter metricsExporter = JMXExporterProvider.INSTANCE.createExporter(configProps);
71+
MetricReader metricReader =
72+
ScopeBasedPeriodicMetricReader.create(metricsExporter, registeredScopeNames)
73+
.setInterval(
74+
SDKMeterProviderBuilder.getMetricExportInterval(
75+
configProps, DEFAULT_METRIC_EXPORT_INTERVAL, logger))
76+
.build();
77+
sdkMeterProviderBuilder.registerMetricReader(metricReader);
78+
79+
logger.info("AWS JMX metric collection enabled");
80+
}
81+
return sdkMeterProviderBuilder;
82+
}
83+
84+
private enum JMXExporterProvider {
85+
INSTANCE;
86+
87+
public MetricExporter createExporter(ConfigProperties configProps) {
88+
String protocol =
89+
OtlpConfigUtil.getOtlpProtocol(OtlpConfigUtil.DATA_TYPE_METRICS, configProps);
90+
logger.log(Level.FINE, String.format("AWS JMX metrics export protocol: %s", protocol));
91+
92+
String otelJMXEndpoint;
93+
if (protocol.equals(OtlpConfigUtil.PROTOCOL_HTTP_PROTOBUF)) {
94+
otelJMXEndpoint = configProps.getString(AWS_JMX_EXPORTER_ENDPOINT_CONFIG);
95+
logger.log(
96+
Level.FINE, String.format("AWS JMX metrics export endpoint: %s", otelJMXEndpoint));
97+
return OtlpHttpMetricExporter.builder()
98+
.setEndpoint(otelJMXEndpoint)
99+
.setDefaultAggregationSelector(this::getAggregation)
100+
.build();
101+
}
102+
throw new ConfigurationException("Unsupported AWS JMX metrics export protocol: " + protocol);
103+
}
104+
105+
private Aggregation getAggregation(InstrumentType instrumentType) {
106+
if (instrumentType == InstrumentType.HISTOGRAM) {
107+
return Aggregation.base2ExponentialBucketHistogram();
108+
}
109+
return Aggregation.defaultAggregation();
110+
}
111+
}
112+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.opentelemetry.javaagent.providers;
17+
18+
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
19+
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
20+
import io.opentelemetry.sdk.metrics.Aggregation;
21+
import io.opentelemetry.sdk.metrics.InstrumentSelector;
22+
import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder;
23+
import io.opentelemetry.sdk.metrics.View;
24+
import java.time.Duration;
25+
import java.util.Set;
26+
import java.util.logging.Level;
27+
import java.util.logging.Logger;
28+
29+
public class SDKMeterProviderBuilder {
30+
static void configureMetricFilter(
31+
ConfigProperties configProps,
32+
SdkMeterProviderBuilder sdkMeterProviderBuilder,
33+
Set<String> registeredScopeNames,
34+
Logger logger) {
35+
Set<String> exporterNames =
36+
DefaultConfigProperties.getSet(configProps, "otel.metrics.exporter");
37+
if (exporterNames.contains("none")) {
38+
for (String scope : registeredScopeNames) {
39+
sdkMeterProviderBuilder.registerView(
40+
InstrumentSelector.builder().setMeterName(scope).build(),
41+
View.builder().setAggregation(Aggregation.defaultAggregation()).build());
42+
43+
logger.log(Level.FINE, "Registered scope {0}", scope);
44+
}
45+
sdkMeterProviderBuilder.registerView(
46+
InstrumentSelector.builder().setName("*").build(),
47+
View.builder().setAggregation(Aggregation.drop()).build());
48+
}
49+
}
50+
51+
static Duration getMetricExportInterval(
52+
ConfigProperties configProps, Duration exportIntervalEnvVar, Logger logger) {
53+
Duration exportInterval =
54+
configProps.getDuration("otel.metric.export.interval", exportIntervalEnvVar);
55+
logger.log(Level.FINE, String.format("Metrics export interval: %s", exportInterval));
56+
// Cap export interval to 60 seconds. This is currently required for metrics-trace correlation
57+
// to work correctly.
58+
if (exportInterval.compareTo(exportIntervalEnvVar) > 0) {
59+
exportInterval = exportIntervalEnvVar;
60+
logger.log(Level.INFO, String.format("Metrics export interval capped to %s", exportInterval));
61+
}
62+
return exportInterval;
63+
}
64+
}

awsagentprovider/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@
1616
software.amazon.opentelemetry.javaagent.providers.AwsAgentPropertiesCustomizerProvider
1717
software.amazon.opentelemetry.javaagent.providers.AwsTracerCustomizerProvider
1818
software.amazon.opentelemetry.javaagent.providers.AwsApplicationSignalsCustomizerProvider
19+
software.amazon.opentelemetry.javaagent.providers.AwsJMXMetricsCustomizerProvider

0 commit comments

Comments
 (0)