Skip to content

Commit 446320c

Browse files
committed
Add Application Signals runtime metrics
1 parent b366543 commit 446320c

File tree

13 files changed

+748
-41
lines changed

13 files changed

+748
-41
lines changed

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

Lines changed: 135 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
package software.amazon.opentelemetry.javaagent.providers;
1717

18+
import io.opentelemetry.api.common.Attributes;
19+
import io.opentelemetry.api.common.AttributesBuilder;
1820
import io.opentelemetry.api.metrics.MeterProvider;
1921
import io.opentelemetry.contrib.awsxray.AlwaysRecordSampler;
2022
import io.opentelemetry.contrib.awsxray.ResourceHolder;
@@ -25,18 +27,29 @@
2527
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
2628
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
2729
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
30+
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
2831
import io.opentelemetry.sdk.metrics.Aggregation;
32+
import io.opentelemetry.sdk.metrics.InstrumentSelector;
2933
import io.opentelemetry.sdk.metrics.InstrumentType;
3034
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
31-
import io.opentelemetry.sdk.metrics.export.AggregationTemporalitySelector;
35+
import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder;
36+
import io.opentelemetry.sdk.metrics.View;
3237
import io.opentelemetry.sdk.metrics.export.MetricExporter;
3338
import io.opentelemetry.sdk.metrics.export.MetricReader;
3439
import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader;
40+
import io.opentelemetry.sdk.resources.Resource;
3541
import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder;
3642
import io.opentelemetry.sdk.trace.SpanProcessor;
3743
import io.opentelemetry.sdk.trace.export.SpanExporter;
3844
import io.opentelemetry.sdk.trace.samplers.Sampler;
3945
import java.time.Duration;
46+
import java.util.ArrayList;
47+
import java.util.Collections;
48+
import java.util.HashMap;
49+
import java.util.HashSet;
50+
import java.util.List;
51+
import java.util.Map;
52+
import java.util.Set;
4053
import java.util.logging.Level;
4154
import java.util.logging.Logger;
4255

@@ -61,27 +74,73 @@ public class AwsApplicationSignalsCustomizerProvider
6174
private static final Logger logger =
6275
Logger.getLogger(AwsApplicationSignalsCustomizerProvider.class.getName());
6376

64-
private static final String SMP_ENABLED_CONFIG = "otel.smp.enabled";
65-
private static final String APP_SIGNALS_ENABLED_CONFIG = "otel.aws.app.signals.enabled";
77+
private static final String DEPRECATED_SMP_ENABLED_CONFIG = "otel.smp.enabled";
78+
private static final String DEPRECATED_APP_SIGNALS_ENABLED_CONFIG =
79+
"otel.aws.app.signals.enabled";
6680
private static final String APPLICATION_SIGNALS_ENABLED_CONFIG =
6781
"otel.aws.application.signals.enabled";
68-
private static final String SMP_EXPORTER_ENDPOINT_CONFIG = "otel.aws.smp.exporter.endpoint";
69-
private static final String APP_SIGNALS_EXPORTER_ENDPOINT_CONFIG =
82+
private static final String APPLICATION_SIGNALS_RUNTIME_ENABLED_CONFIG =
83+
"otel.aws.app.signals.exporter.runtime.enabled";
84+
private static final String DEPRECATED_SMP_EXPORTER_ENDPOINT_CONFIG =
85+
"otel.aws.smp.exporter.endpoint";
86+
private static final String DEPRECATED_APP_SIGNALS_EXPORTER_ENDPOINT_CONFIG =
7087
"otel.aws.app.signals.exporter.endpoint";
7188
private static final String APPLICATION_SIGNALS_EXPORTER_ENDPOINT_CONFIG =
7289
"otel.aws.application.signals.exporter.endpoint";
7390

91+
private static final String OTEL_JMX_TARGET_SYSTEM_CONFIG = "otel.jmx.target.system";
92+
7493
public void customize(AutoConfigurationCustomizer autoConfiguration) {
94+
autoConfiguration.addPropertiesCustomizer(this::customizeProperties);
95+
autoConfiguration.addResourceCustomizer(this::customizeResource);
7596
autoConfiguration.addSamplerCustomizer(this::customizeSampler);
7697
autoConfiguration.addTracerProviderCustomizer(this::customizeTracerProviderBuilder);
98+
autoConfiguration.addMeterProviderCustomizer(this::customizeMeterProvider);
7799
autoConfiguration.addSpanExporterCustomizer(this::customizeSpanExporter);
78100
}
79101

80102
private boolean isApplicationSignalsEnabled(ConfigProperties configProps) {
81103
return configProps.getBoolean(
82104
APPLICATION_SIGNALS_ENABLED_CONFIG,
83105
configProps.getBoolean(
84-
APP_SIGNALS_ENABLED_CONFIG, configProps.getBoolean(SMP_ENABLED_CONFIG, false)));
106+
DEPRECATED_APP_SIGNALS_ENABLED_CONFIG,
107+
configProps.getBoolean(DEPRECATED_SMP_ENABLED_CONFIG, false)));
108+
}
109+
110+
private boolean isApplicationSignalsRuntimeEnabled(ConfigProperties configProps) {
111+
return isApplicationSignalsEnabled(configProps)
112+
&& configProps.getBoolean(APPLICATION_SIGNALS_RUNTIME_ENABLED_CONFIG, true);
113+
}
114+
115+
private Map<String, String> customizeProperties(ConfigProperties configProps) {
116+
if (isApplicationSignalsRuntimeEnabled(configProps)) {
117+
List<String> list = configProps.getList(OTEL_JMX_TARGET_SYSTEM_CONFIG);
118+
if (list.contains("jvm")) {
119+
logger.log(Level.INFO, "Found jmx in {0}", OTEL_JMX_TARGET_SYSTEM_CONFIG);
120+
return Collections.emptyMap();
121+
} else {
122+
logger.log(Level.INFO, "Configure jmx in {0}", OTEL_JMX_TARGET_SYSTEM_CONFIG);
123+
List<String> jmxTargets = new ArrayList<>(list);
124+
jmxTargets.add("jvm");
125+
Map<String, String> propsOverride = new HashMap<>(1);
126+
propsOverride.put(OTEL_JMX_TARGET_SYSTEM_CONFIG, String.join(",", jmxTargets));
127+
return propsOverride;
128+
}
129+
}
130+
return Collections.emptyMap();
131+
}
132+
133+
private Resource customizeResource(Resource resource, ConfigProperties configProps) {
134+
if (isApplicationSignalsEnabled(configProps)) {
135+
AttributesBuilder builder = Attributes.builder();
136+
AwsResourceAttributeConfigurator.setServiceAttribute(
137+
resource,
138+
builder,
139+
() -> logger.log(Level.WARNING, "Service name is undefined, use UnknownService instead"));
140+
Resource additionalResource = Resource.create((builder.build()));
141+
return resource.merge(additionalResource);
142+
}
143+
return resource;
85144
}
86145

87146
private Sampler customizeSampler(Sampler sampler, ConfigProperties configProps) {
@@ -95,20 +154,7 @@ private SdkTracerProviderBuilder customizeTracerProviderBuilder(
95154
SdkTracerProviderBuilder tracerProviderBuilder, ConfigProperties configProps) {
96155
if (isApplicationSignalsEnabled(configProps)) {
97156
logger.info("AWS Application Signals enabled");
98-
Duration exportInterval =
99-
configProps.getDuration("otel.metric.export.interval", DEFAULT_METRIC_EXPORT_INTERVAL);
100-
logger.log(
101-
Level.FINE,
102-
String.format("AWS Application Signals Metrics export interval: %s", exportInterval));
103-
// Cap export interval to 60 seconds. This is currently required for metrics-trace correlation
104-
// to work correctly.
105-
if (exportInterval.compareTo(DEFAULT_METRIC_EXPORT_INTERVAL) > 0) {
106-
exportInterval = DEFAULT_METRIC_EXPORT_INTERVAL;
107-
logger.log(
108-
Level.INFO,
109-
String.format(
110-
"AWS Application Signals metrics export interval capped to %s", exportInterval));
111-
}
157+
Duration exportInterval = getMetricExportInterval(configProps);
112158
// Construct and set local and remote attributes span processor
113159
tracerProviderBuilder.addSpanProcessor(
114160
AttributePropagatingSpanProcessorBuilder.create().build());
@@ -133,6 +179,67 @@ private SdkTracerProviderBuilder customizeTracerProviderBuilder(
133179
return tracerProviderBuilder;
134180
}
135181

182+
private SdkMeterProviderBuilder customizeMeterProvider(
183+
SdkMeterProviderBuilder sdkMeterProviderBuilder, ConfigProperties configProps) {
184+
185+
if (isApplicationSignalsRuntimeEnabled(configProps)) {
186+
Set<String> registeredScopeNames = new HashSet<>(1);
187+
String jmxRuntimeScopeName = "io.opentelemetry.jmx";
188+
registeredScopeNames.add(jmxRuntimeScopeName);
189+
190+
configureMetricFilter(configProps, sdkMeterProviderBuilder, registeredScopeNames);
191+
192+
MetricExporter metricsExporter =
193+
ApplicationSignalsExporterProvider.INSTANCE.createExporter(configProps);
194+
MetricReader metricReader =
195+
ScopeBasedPeriodicMetricReader.create(metricsExporter, registeredScopeNames)
196+
.setInterval(getMetricExportInterval(configProps))
197+
.build();
198+
sdkMeterProviderBuilder.registerMetricReader(metricReader);
199+
200+
logger.info("AWS Application Signals runtime metric collection enabled");
201+
}
202+
return sdkMeterProviderBuilder;
203+
}
204+
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+
136243
private SpanExporter customizeSpanExporter(
137244
SpanExporter spanExporter, ConfigProperties configProps) {
138245
if (isApplicationSignalsEnabled(configProps)) {
@@ -159,33 +266,35 @@ public MetricExporter createExporter(ConfigProperties configProps) {
159266
configProps.getString(
160267
APPLICATION_SIGNALS_EXPORTER_ENDPOINT_CONFIG,
161268
configProps.getString(
162-
APP_SIGNALS_EXPORTER_ENDPOINT_CONFIG,
269+
DEPRECATED_APP_SIGNALS_EXPORTER_ENDPOINT_CONFIG,
163270
configProps.getString(
164-
SMP_EXPORTER_ENDPOINT_CONFIG, "http://localhost:4316/v1/metrics")));
271+
DEPRECATED_SMP_EXPORTER_ENDPOINT_CONFIG,
272+
"http://localhost:4316/v1/metrics")));
165273
logger.log(
166274
Level.FINE,
167275
String.format(
168276
"AWS Application Signals export endpoint: %s", applicationSignalsEndpoint));
169277
return OtlpHttpMetricExporter.builder()
170278
.setEndpoint(applicationSignalsEndpoint)
171279
.setDefaultAggregationSelector(this::getAggregation)
172-
.setAggregationTemporalitySelector(AggregationTemporalitySelector.deltaPreferred())
280+
.setAggregationTemporalitySelector(CloudWatchTemporalitySelector.alwaysDelta())
173281
.build();
174282
} else if (protocol.equals(OtlpConfigUtil.PROTOCOL_GRPC)) {
175283
applicationSignalsEndpoint =
176284
configProps.getString(
177285
APPLICATION_SIGNALS_EXPORTER_ENDPOINT_CONFIG,
178286
configProps.getString(
179-
APP_SIGNALS_EXPORTER_ENDPOINT_CONFIG,
180-
configProps.getString(SMP_EXPORTER_ENDPOINT_CONFIG, "http://localhost:4315")));
287+
DEPRECATED_APP_SIGNALS_EXPORTER_ENDPOINT_CONFIG,
288+
configProps.getString(
289+
DEPRECATED_SMP_EXPORTER_ENDPOINT_CONFIG, "http://localhost:4315")));
181290
logger.log(
182291
Level.FINE,
183292
String.format(
184293
"AWS Application Signals export endpoint: %s", applicationSignalsEndpoint));
185294
return OtlpGrpcMetricExporter.builder()
186295
.setEndpoint(applicationSignalsEndpoint)
187296
.setDefaultAggregationSelector(this::getAggregation)
188-
.setAggregationTemporalitySelector(AggregationTemporalitySelector.deltaPreferred())
297+
.setAggregationTemporalitySelector(CloudWatchTemporalitySelector.alwaysDelta())
189298
.build();
190299
}
191300
throw new ConfigurationException(

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

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
package software.amazon.opentelemetry.javaagent.providers;
1717

18-
import static io.opentelemetry.semconv.ResourceAttributes.SERVICE_NAME;
1918
import static io.opentelemetry.semconv.SemanticAttributes.DB_CONNECTION_STRING;
2019
import static io.opentelemetry.semconv.SemanticAttributes.DB_NAME;
2120
import static io.opentelemetry.semconv.SemanticAttributes.DB_OPERATION;
@@ -64,7 +63,6 @@
6463
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.UNKNOWN_OPERATION;
6564
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.UNKNOWN_REMOTE_OPERATION;
6665
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.UNKNOWN_REMOTE_SERVICE;
67-
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.UNKNOWN_SERVICE;
6866
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.isAwsSDKSpan;
6967
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.isDBSpan;
7068
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.isKeyPresent;
@@ -119,11 +117,6 @@ final class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
119117

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

122-
// As per
123-
// https://github.com/open-telemetry/opentelemetry-java/tree/main/sdk-extensions/autoconfigure#opentelemetry-resource
124-
// If service name is not specified, SDK defaults the service name to unknown_service:java
125-
private static final String OTEL_UNKNOWN_SERVICE = "unknown_service:java";
126-
127120
// This method is used by the AwsSpanMetricsProcessor to generate service and dependency metrics
128121
@Override
129122
public Map<String, Attributes> generateMetricAttributeMapFromSpan(
@@ -167,14 +160,8 @@ private Attributes generateDependencyMetricAttributes(SpanData span, Resource re
167160

168161
/** Service is always derived from {@link ResourceAttributes#SERVICE_NAME} */
169162
private static void setService(Resource resource, SpanData span, AttributesBuilder builder) {
170-
String service = resource.getAttribute(SERVICE_NAME);
171-
172-
// In practice the service name is never null, but we can be defensive here.
173-
if (service == null || service.equals(OTEL_UNKNOWN_SERVICE)) {
174-
logUnknownAttribute(AWS_LOCAL_SERVICE, span);
175-
service = UNKNOWN_SERVICE;
176-
}
177-
builder.put(AWS_LOCAL_SERVICE, service);
163+
AwsResourceAttributeConfigurator.setServiceAttribute(
164+
resource, builder, () -> logUnknownAttribute(AWS_LOCAL_SERVICE, span));
178165
}
179166

180167
/**
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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 static io.opentelemetry.semconv.ResourceAttributes.SERVICE_NAME;
19+
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LOCAL_SERVICE;
20+
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.UNKNOWN_SERVICE;
21+
22+
import io.opentelemetry.api.common.AttributesBuilder;
23+
import io.opentelemetry.sdk.resources.Resource;
24+
25+
public class AwsResourceAttributeConfigurator {
26+
// As per
27+
// https://github.com/open-telemetry/opentelemetry-java/tree/main/sdk-extensions/autoconfigure#opentelemetry-resource
28+
// If service name is not specified, SDK defaults the service name to unknown_service:java
29+
private static final String OTEL_UNKNOWN_SERVICE = "unknown_service:java";
30+
31+
public static void setServiceAttribute(
32+
Resource resource, AttributesBuilder attributesBuilder, Runnable handleUnknownService) {
33+
String service = resource.getAttribute(AWS_LOCAL_SERVICE);
34+
if (service == null) {
35+
service = resource.getAttribute(SERVICE_NAME);
36+
// In practice the service name is never null, but we can be defensive here.
37+
if (service == null || service.equals(OTEL_UNKNOWN_SERVICE)) {
38+
service = UNKNOWN_SERVICE;
39+
handleUnknownService.run();
40+
}
41+
}
42+
attributesBuilder.put(AWS_LOCAL_SERVICE, service);
43+
}
44+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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.metrics.data.AggregationTemporality;
19+
import io.opentelemetry.sdk.metrics.export.AggregationTemporalitySelector;
20+
21+
public class CloudWatchTemporalitySelector {
22+
static AggregationTemporalitySelector alwaysDelta() {
23+
return (instrumentType) -> {
24+
return AggregationTemporality.DELTA;
25+
};
26+
}
27+
}

0 commit comments

Comments
 (0)