-
Notifications
You must be signed in to change notification settings - Fork 66
Add Application Signals runtime metrics #881
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
Changes from all commits
f0dce5d
3ae10c1
07e3763
2910dd3
5d2719a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,8 @@ | |
|
|
||
| package software.amazon.opentelemetry.javaagent.providers; | ||
|
|
||
| import io.opentelemetry.api.common.Attributes; | ||
| import io.opentelemetry.api.common.AttributesBuilder; | ||
| import io.opentelemetry.api.metrics.MeterProvider; | ||
| import io.opentelemetry.contrib.awsxray.AlwaysRecordSampler; | ||
| import io.opentelemetry.contrib.awsxray.ResourceHolder; | ||
|
|
@@ -25,18 +27,29 @@ | |
| 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.export.AggregationTemporalitySelector; | ||
| 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; | ||
| import io.opentelemetry.sdk.resources.Resource; | ||
| import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; | ||
| import io.opentelemetry.sdk.trace.SpanProcessor; | ||
| import io.opentelemetry.sdk.trace.export.SpanExporter; | ||
| import io.opentelemetry.sdk.trace.samplers.Sampler; | ||
| import java.time.Duration; | ||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
| import java.util.HashMap; | ||
| import java.util.HashSet; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Set; | ||
| import java.util.logging.Level; | ||
| import java.util.logging.Logger; | ||
|
|
||
|
|
@@ -61,27 +74,73 @@ public class AwsApplicationSignalsCustomizerProvider | |
| private static final Logger logger = | ||
| Logger.getLogger(AwsApplicationSignalsCustomizerProvider.class.getName()); | ||
|
|
||
| private static final String SMP_ENABLED_CONFIG = "otel.smp.enabled"; | ||
| private static final String APP_SIGNALS_ENABLED_CONFIG = "otel.aws.app.signals.enabled"; | ||
| private static final String DEPRECATED_SMP_ENABLED_CONFIG = "otel.smp.enabled"; | ||
| private static final String DEPRECATED_APP_SIGNALS_ENABLED_CONFIG = | ||
| "otel.aws.app.signals.enabled"; | ||
| private static final String APPLICATION_SIGNALS_ENABLED_CONFIG = | ||
| "otel.aws.application.signals.enabled"; | ||
| private static final String SMP_EXPORTER_ENDPOINT_CONFIG = "otel.aws.smp.exporter.endpoint"; | ||
| private static final String APP_SIGNALS_EXPORTER_ENDPOINT_CONFIG = | ||
| private static final String APPLICATION_SIGNALS_RUNTIME_ENABLED_CONFIG = | ||
| "otel.aws.application.signals.runtime.enabled"; | ||
| private static final String DEPRECATED_SMP_EXPORTER_ENDPOINT_CONFIG = | ||
| "otel.aws.smp.exporter.endpoint"; | ||
| private static final String DEPRECATED_APP_SIGNALS_EXPORTER_ENDPOINT_CONFIG = | ||
| "otel.aws.app.signals.exporter.endpoint"; | ||
| private static final String APPLICATION_SIGNALS_EXPORTER_ENDPOINT_CONFIG = | ||
| "otel.aws.application.signals.exporter.endpoint"; | ||
|
|
||
| private static final String OTEL_JMX_TARGET_SYSTEM_CONFIG = "otel.jmx.target.system"; | ||
|
|
||
| public void customize(AutoConfigurationCustomizer autoConfiguration) { | ||
| autoConfiguration.addPropertiesCustomizer(this::customizeProperties); | ||
| autoConfiguration.addResourceCustomizer(this::customizeResource); | ||
| autoConfiguration.addSamplerCustomizer(this::customizeSampler); | ||
| autoConfiguration.addTracerProviderCustomizer(this::customizeTracerProviderBuilder); | ||
| autoConfiguration.addMeterProviderCustomizer(this::customizeMeterProvider); | ||
| autoConfiguration.addSpanExporterCustomizer(this::customizeSpanExporter); | ||
| } | ||
|
|
||
| private boolean isApplicationSignalsEnabled(ConfigProperties configProps) { | ||
| return configProps.getBoolean( | ||
| APPLICATION_SIGNALS_ENABLED_CONFIG, | ||
| configProps.getBoolean( | ||
| APP_SIGNALS_ENABLED_CONFIG, configProps.getBoolean(SMP_ENABLED_CONFIG, false))); | ||
| DEPRECATED_APP_SIGNALS_ENABLED_CONFIG, | ||
| configProps.getBoolean(DEPRECATED_SMP_ENABLED_CONFIG, false))); | ||
| } | ||
|
|
||
| private boolean isApplicationSignalsRuntimeEnabled(ConfigProperties configProps) { | ||
| return isApplicationSignalsEnabled(configProps) | ||
| && configProps.getBoolean(APPLICATION_SIGNALS_RUNTIME_ENABLED_CONFIG, true); | ||
| } | ||
|
|
||
| private Map<String, String> customizeProperties(ConfigProperties configProps) { | ||
| if (isApplicationSignalsRuntimeEnabled(configProps)) { | ||
| List<String> list = configProps.getList(OTEL_JMX_TARGET_SYSTEM_CONFIG); | ||
| if (list.contains("jvm")) { | ||
| logger.log(Level.INFO, "Found jmx in {0}", OTEL_JMX_TARGET_SYSTEM_CONFIG); | ||
| return Collections.emptyMap(); | ||
| } else { | ||
| logger.log(Level.INFO, "Configure jmx in {0}", OTEL_JMX_TARGET_SYSTEM_CONFIG); | ||
| List<String> jmxTargets = new ArrayList<>(list); | ||
| jmxTargets.add("jvm"); | ||
| Map<String, String> propsOverride = new HashMap<>(1); | ||
| propsOverride.put(OTEL_JMX_TARGET_SYSTEM_CONFIG, String.join(",", jmxTargets)); | ||
| return propsOverride; | ||
| } | ||
| } | ||
| return Collections.emptyMap(); | ||
| } | ||
|
|
||
| private Resource customizeResource(Resource resource, ConfigProperties configProps) { | ||
| if (isApplicationSignalsEnabled(configProps)) { | ||
| AttributesBuilder builder = Attributes.builder(); | ||
| AwsResourceAttributeConfigurator.setServiceAttribute( | ||
| resource, | ||
| builder, | ||
| () -> logger.log(Level.WARNING, "Service name is undefined, use UnknownService instead")); | ||
| Resource additionalResource = Resource.create((builder.build())); | ||
| return resource.merge(additionalResource); | ||
| } | ||
| return resource; | ||
| } | ||
|
|
||
| private Sampler customizeSampler(Sampler sampler, ConfigProperties configProps) { | ||
|
|
@@ -95,20 +154,7 @@ private SdkTracerProviderBuilder customizeTracerProviderBuilder( | |
| SdkTracerProviderBuilder tracerProviderBuilder, ConfigProperties configProps) { | ||
| if (isApplicationSignalsEnabled(configProps)) { | ||
| logger.info("AWS Application Signals enabled"); | ||
| 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)); | ||
| } | ||
| Duration exportInterval = getMetricExportInterval(configProps); | ||
| // Construct and set local and remote attributes span processor | ||
| tracerProviderBuilder.addSpanProcessor( | ||
| AttributePropagatingSpanProcessorBuilder.create().build()); | ||
|
|
@@ -133,6 +179,67 @@ private SdkTracerProviderBuilder customizeTracerProviderBuilder( | |
| return tracerProviderBuilder; | ||
| } | ||
|
|
||
| private SdkMeterProviderBuilder customizeMeterProvider( | ||
| SdkMeterProviderBuilder sdkMeterProviderBuilder, ConfigProperties configProps) { | ||
|
|
||
| if (isApplicationSignalsRuntimeEnabled(configProps)) { | ||
| Set<String> registeredScopeNames = new HashSet<>(1); | ||
| String jmxRuntimeScopeName = "io.opentelemetry.jmx"; | ||
| registeredScopeNames.add(jmxRuntimeScopeName); | ||
|
|
||
| configureMetricFilter(configProps, sdkMeterProviderBuilder, registeredScopeNames); | ||
|
|
||
| MetricExporter metricsExporter = | ||
| ApplicationSignalsExporterProvider.INSTANCE.createExporter(configProps); | ||
| MetricReader metricReader = | ||
| ScopeBasedPeriodicMetricReader.create(metricsExporter, registeredScopeNames) | ||
| .setInterval(getMetricExportInterval(configProps)) | ||
| .build(); | ||
| sdkMeterProviderBuilder.registerMetricReader(metricReader); | ||
|
|
||
| logger.info("AWS Application Signals runtime metric collection enabled"); | ||
| } | ||
| 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()); | ||
| } | ||
|
Comment on lines
+211
to
+222
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could you help to add some comment on this method? I am not sure if I get the intention correctly?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
|
|
||
| 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)) { | ||
|
|
@@ -159,33 +266,35 @@ public MetricExporter createExporter(ConfigProperties configProps) { | |
| configProps.getString( | ||
| APPLICATION_SIGNALS_EXPORTER_ENDPOINT_CONFIG, | ||
| configProps.getString( | ||
| APP_SIGNALS_EXPORTER_ENDPOINT_CONFIG, | ||
| DEPRECATED_APP_SIGNALS_EXPORTER_ENDPOINT_CONFIG, | ||
| configProps.getString( | ||
| SMP_EXPORTER_ENDPOINT_CONFIG, "http://localhost:4316/v1/metrics"))); | ||
| DEPRECATED_SMP_EXPORTER_ENDPOINT_CONFIG, | ||
| "http://localhost:4316/v1/metrics"))); | ||
| logger.log( | ||
| Level.FINE, | ||
| String.format( | ||
| "AWS Application Signals export endpoint: %s", applicationSignalsEndpoint)); | ||
| return OtlpHttpMetricExporter.builder() | ||
| .setEndpoint(applicationSignalsEndpoint) | ||
| .setDefaultAggregationSelector(this::getAggregation) | ||
| .setAggregationTemporalitySelector(AggregationTemporalitySelector.deltaPreferred()) | ||
| .setAggregationTemporalitySelector(CloudWatchTemporalitySelector.alwaysDelta()) | ||
| .build(); | ||
| } else if (protocol.equals(OtlpConfigUtil.PROTOCOL_GRPC)) { | ||
| applicationSignalsEndpoint = | ||
| configProps.getString( | ||
| APPLICATION_SIGNALS_EXPORTER_ENDPOINT_CONFIG, | ||
| configProps.getString( | ||
| APP_SIGNALS_EXPORTER_ENDPOINT_CONFIG, | ||
| configProps.getString(SMP_EXPORTER_ENDPOINT_CONFIG, "http://localhost:4315"))); | ||
| DEPRECATED_APP_SIGNALS_EXPORTER_ENDPOINT_CONFIG, | ||
| configProps.getString( | ||
| DEPRECATED_SMP_EXPORTER_ENDPOINT_CONFIG, "http://localhost:4315"))); | ||
| logger.log( | ||
| Level.FINE, | ||
| String.format( | ||
| "AWS Application Signals export endpoint: %s", applicationSignalsEndpoint)); | ||
| return OtlpGrpcMetricExporter.builder() | ||
| .setEndpoint(applicationSignalsEndpoint) | ||
| .setDefaultAggregationSelector(this::getAggregation) | ||
| .setAggregationTemporalitySelector(AggregationTemporalitySelector.deltaPreferred()) | ||
| .setAggregationTemporalitySelector(CloudWatchTemporalitySelector.alwaysDelta()) | ||
| .build(); | ||
| } | ||
| throw new ConfigurationException( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| /* | ||
| * 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 static io.opentelemetry.semconv.ResourceAttributes.SERVICE_NAME; | ||
| import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LOCAL_SERVICE; | ||
| import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.UNKNOWN_SERVICE; | ||
|
|
||
| import io.opentelemetry.api.common.AttributesBuilder; | ||
| import io.opentelemetry.sdk.resources.Resource; | ||
|
|
||
| public class AwsResourceAttributeConfigurator { | ||
| // As per | ||
| // https://github.com/open-telemetry/opentelemetry-java/tree/main/sdk-extensions/autoconfigure#opentelemetry-resource | ||
| // If service name is not specified, SDK defaults the service name to unknown_service:java | ||
| private static final String OTEL_UNKNOWN_SERVICE = "unknown_service:java"; | ||
|
|
||
| public static void setServiceAttribute( | ||
| Resource resource, AttributesBuilder attributesBuilder, Runnable handleUnknownService) { | ||
| String service = resource.getAttribute(AWS_LOCAL_SERVICE); | ||
| if (service == null) { | ||
| service = resource.getAttribute(SERVICE_NAME); | ||
| // In practice the service name is never null, but we can be defensive here. | ||
| if (service == null || service.equals(OTEL_UNKNOWN_SERVICE)) { | ||
| service = UNKNOWN_SERVICE; | ||
| handleUnknownService.run(); | ||
| } | ||
| } | ||
| attributesBuilder.put(AWS_LOCAL_SERVICE, service); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| /* | ||
| * 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.metrics.data.AggregationTemporality; | ||
| import io.opentelemetry.sdk.metrics.export.AggregationTemporalitySelector; | ||
|
|
||
| public class CloudWatchTemporalitySelector { | ||
| static AggregationTemporalitySelector alwaysDelta() { | ||
| return (instrumentType) -> { | ||
| return AggregationTemporality.DELTA; | ||
| }; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
q: with AppSignals enabled, if customer sets OTEL_JMX_TARGET_SYSTEM_CONFIG with value such as
kafka, what will be AppSignals behavior? will we OTel Gatherer also collectorkafkametrics but dropped in CWAgent eventually?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. They will be dropped.
Metrics are not exported directly in CWA. The metrics will be further transformed in CWA, and some of them will be added a new attribute
Telemetry.Sourceto be selectable by EMF exporter. Metrics not declared in the transformation rules will be dropped in the end.