diff --git a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProvider.java b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProvider.java index 4cebc0a48..df657538e 100644 --- a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProvider.java +++ b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProvider.java @@ -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; @@ -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()); @@ -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); @@ -202,44 +204,6 @@ private SdkMeterProviderBuilder customizeMeterProvider( return sdkMeterProviderBuilder; } - private static void configureMetricFilter( - ConfigProperties configProps, - SdkMeterProviderBuilder sdkMeterProviderBuilder, - Set registeredScopeNames) { - Set 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)) { diff --git a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsJMXMetricsCustomizerProvider.java b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsJMXMetricsCustomizerProvider.java new file mode 100644 index 000000000..52ef09f5c --- /dev/null +++ b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsJMXMetricsCustomizerProvider.java @@ -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 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(); + } + } +} diff --git a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/SDKMeterProviderBuilder.java b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/SDKMeterProviderBuilder.java new file mode 100644 index 000000000..cf68c0f1a --- /dev/null +++ b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/SDKMeterProviderBuilder.java @@ -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 registeredScopeNames, + Logger logger) { + Set 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; + } +} diff --git a/awsagentprovider/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider b/awsagentprovider/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider index 6cc27f1de..5647da4ad 100644 --- a/awsagentprovider/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider +++ b/awsagentprovider/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider @@ -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 \ No newline at end of file