Skip to content

Commit

Permalink
Add Application Signals runtime metrics with feature disabled (#900)
Browse files Browse the repository at this point in the history
## Feature request
Add runtime metrics collection into Application Signals.

## Description of changes:
This PR is (mostly) equivalent to [Add Application Signals runtime
metrics
#881](#881).
The difference is that it disables runtime metrics for now.

We split the core function from feature enablement because there are
other ongoing feature developments depending on the changes made by
runtime metrics.

By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache 2.0 license.

*Issue #, if available:*

*Description of changes:*


By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache 2.0 license.
  • Loading branch information
bjrara authored Oct 4, 2024
1 parent c8b5e0e commit 048535a
Show file tree
Hide file tree
Showing 12 changed files with 747 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -61,27 +74,72 @@ 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 false;
}

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) {
Expand All @@ -95,20 +153,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());
Expand All @@ -133,6 +178,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());
}
}

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 All @@ -159,33 +265,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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

package software.amazon.opentelemetry.javaagent.providers;

import static io.opentelemetry.semconv.ResourceAttributes.SERVICE_NAME;
import static io.opentelemetry.semconv.SemanticAttributes.DB_CONNECTION_STRING;
import static io.opentelemetry.semconv.SemanticAttributes.DB_NAME;
import static io.opentelemetry.semconv.SemanticAttributes.DB_OPERATION;
Expand Down Expand Up @@ -64,7 +63,6 @@
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.UNKNOWN_OPERATION;
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.UNKNOWN_REMOTE_OPERATION;
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.UNKNOWN_REMOTE_SERVICE;
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.UNKNOWN_SERVICE;
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.isAwsSDKSpan;
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.isDBSpan;
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.isKeyPresent;
Expand Down Expand Up @@ -119,11 +117,6 @@ final class AwsMetricAttributeGenerator implements MetricAttributeGenerator {

private static final String DB_CONNECTION_RESOURCE_TYPE = "DB::Connection";

// 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";

// This method is used by the AwsSpanMetricsProcessor to generate service and dependency metrics
@Override
public Map<String, Attributes> generateMetricAttributeMapFromSpan(
Expand Down Expand Up @@ -167,14 +160,8 @@ private Attributes generateDependencyMetricAttributes(SpanData span, Resource re

/** Service is always derived from {@link ResourceAttributes#SERVICE_NAME} */
private static void setService(Resource resource, SpanData span, AttributesBuilder builder) {
String 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)) {
logUnknownAttribute(AWS_LOCAL_SERVICE, span);
service = UNKNOWN_SERVICE;
}
builder.put(AWS_LOCAL_SERVICE, service);
AwsResourceAttributeConfigurator.setServiceAttribute(
resource, builder, () -> logUnknownAttribute(AWS_LOCAL_SERVICE, span));
}

/**
Expand Down
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;
};
}
}
Loading

0 comments on commit 048535a

Please sign in to comment.