Skip to content

Commit d97972b

Browse files
bjraraadebayor123
andauthored
Add Application Signals runtime metrics (#892)
## Feature request Add runtime metrics collection into Application Signals. ## Description of changes: This PR is an umbrella PR to track the ongoing changes for runtime metrics. 1. [Add Application Signals runtime metrics with feature disabled #900](#900) [Merged in main] 2. [feat: Add contract tests for runtime metrics #893](#893) 3. Enable runtime metrics by default 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. --------- Co-authored-by: Reno Seo <renoseo@amazon.com>
1 parent d21ac61 commit d97972b

File tree

6 files changed

+225
-14
lines changed

6 files changed

+225
-14
lines changed

appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/base/ContractTestBase.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ public abstract class ContractTestBase {
7979
.withEnv("JAVA_TOOL_OPTIONS", "-javaagent:/opentelemetry-javaagent-all.jar")
8080
.withEnv("OTEL_METRIC_EXPORT_INTERVAL", "100") // 100 ms
8181
.withEnv("OTEL_AWS_APPLICATION_SIGNALS_ENABLED", "true")
82+
.withEnv("OTEL_AWS_APPLICATION_SIGNALS_RUNTIME_ENABLED", isRuntimeEnabled())
8283
.withEnv("OTEL_METRICS_EXPORTER", "none")
8384
.withEnv("OTEL_BSP_SCHEDULE_DELAY", "0") // Don't wait to export spans to the collector
8485
.withEnv(
@@ -159,4 +160,8 @@ protected String getApplicationOtelServiceName() {
159160
protected String getApplicationOtelResourceAttributes() {
160161
return "service.name=" + getApplicationOtelServiceName();
161162
}
163+
164+
protected String isRuntimeEnabled() {
165+
return "false";
166+
}
162167
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
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.appsignals.test.misc;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
20+
import io.opentelemetry.proto.metrics.v1.Metric;
21+
import java.util.List;
22+
import java.util.Set;
23+
import org.junit.jupiter.api.Nested;
24+
import org.junit.jupiter.api.Test;
25+
import org.junit.jupiter.api.TestInstance;
26+
import org.testcontainers.junit.jupiter.Testcontainers;
27+
import software.amazon.opentelemetry.appsignals.test.base.ContractTestBase;
28+
import software.amazon.opentelemetry.appsignals.test.utils.AppSignalsConstants;
29+
import software.amazon.opentelemetry.appsignals.test.utils.ResourceScopeMetric;
30+
31+
public class RuntimeMetricsTest {
32+
private abstract static class RuntimeMetricsContractTestBase extends ContractTestBase {
33+
@Override
34+
protected String getApplicationImageName() {
35+
return "aws-appsignals-tests-http-server-spring-mvc";
36+
}
37+
38+
@Override
39+
protected String isRuntimeEnabled() {
40+
return "true";
41+
}
42+
43+
protected String getApplicationWaitPattern() {
44+
return ".*Started Application.*";
45+
}
46+
47+
protected void doTestRuntimeMetrics() {
48+
var response = appClient.get("/success").aggregate().join();
49+
50+
assertThat(response.status().isSuccess()).isTrue();
51+
assertRuntimeMetrics();
52+
}
53+
54+
protected void assertRuntimeMetrics() {
55+
var metrics =
56+
mockCollectorClient.getRuntimeMetrics(
57+
Set.of(
58+
AppSignalsConstants.JVM_GC_DURATION,
59+
AppSignalsConstants.JVM_GC_COUNT,
60+
AppSignalsConstants.JVM_HEAP_USED,
61+
AppSignalsConstants.JVM_NON_HEAP_USED,
62+
AppSignalsConstants.JVM_AFTER_GC,
63+
AppSignalsConstants.JVM_POOL_USED,
64+
AppSignalsConstants.JVM_THREAD_COUNT,
65+
AppSignalsConstants.JVM_CLASS_LOADED,
66+
AppSignalsConstants.JVM_CPU_TIME,
67+
AppSignalsConstants.JVM_CPU_UTILIZATION,
68+
AppSignalsConstants.LATENCY_METRIC,
69+
AppSignalsConstants.ERROR_METRIC,
70+
AppSignalsConstants.FAULT_METRIC));
71+
72+
testResourceAttributes(metrics);
73+
for (String metricName : List.of(AppSignalsConstants.JVM_POOL_USED)) {
74+
testGaugeMetrics(metrics, metricName, "name");
75+
}
76+
for (String metricName :
77+
List.of(
78+
AppSignalsConstants.JVM_HEAP_USED,
79+
AppSignalsConstants.JVM_NON_HEAP_USED,
80+
AppSignalsConstants.JVM_AFTER_GC,
81+
AppSignalsConstants.JVM_THREAD_COUNT,
82+
AppSignalsConstants.JVM_CLASS_LOADED,
83+
AppSignalsConstants.JVM_CPU_UTILIZATION)) {
84+
testGaugeMetrics(metrics, metricName, "");
85+
}
86+
for (String metricName :
87+
List.of(AppSignalsConstants.JVM_GC_DURATION, AppSignalsConstants.JVM_GC_COUNT)) {
88+
testCounterMetrics(metrics, metricName, "name");
89+
}
90+
for (String metricName : List.of(AppSignalsConstants.JVM_CPU_TIME)) {
91+
testCounterMetrics(metrics, metricName, "");
92+
}
93+
}
94+
95+
private void testGaugeMetrics(
96+
List<ResourceScopeMetric> resourceScopeMetrics, String metricName, String attributeKey) {
97+
for (ResourceScopeMetric rsm : resourceScopeMetrics) {
98+
Metric metric = rsm.getMetric();
99+
if (metricName.equals(metric.getName())) {
100+
assertThat(metric.getGauge().getDataPointsList())
101+
.as(metricName + " is not empty")
102+
.isNotEmpty();
103+
assertThat(metric.getGauge().getDataPointsList())
104+
.as(metricName + " is valid")
105+
.allMatch(
106+
dp -> {
107+
boolean valid = true;
108+
if (!attributeKey.isEmpty()) {
109+
valid =
110+
dp.getAttributesList().stream()
111+
.anyMatch(attribute -> attribute.getKey().equals(attributeKey));
112+
}
113+
return valid && dp.getAsInt() >= 0;
114+
});
115+
}
116+
}
117+
}
118+
119+
private void testCounterMetrics(
120+
List<ResourceScopeMetric> resourceScopeMetrics, String metricName, String attributeKey) {
121+
for (ResourceScopeMetric rsm : resourceScopeMetrics) {
122+
Metric metric = rsm.getMetric();
123+
if (metricName.equals(metric.getName())) {
124+
assertThat(metric.getSum().getDataPointsList())
125+
.as(metricName + " is not empty")
126+
.isNotEmpty();
127+
assertThat(metric.getSum().getDataPointsList())
128+
.as(metricName + " is valid")
129+
.allMatch(
130+
dp -> {
131+
boolean valid = true;
132+
if (!attributeKey.isEmpty()) {
133+
valid =
134+
dp.getAttributesList().stream()
135+
.anyMatch(attribute -> attribute.getKey().equals(attributeKey));
136+
}
137+
return valid && dp.getAsInt() >= 0;
138+
});
139+
}
140+
}
141+
}
142+
143+
private void testResourceAttributes(List<ResourceScopeMetric> resourceScopeMetrics) {
144+
for (ResourceScopeMetric rsm : resourceScopeMetrics) {
145+
assertThat(rsm.getResource().getResource().getAttributesList())
146+
.anyMatch(
147+
attr ->
148+
attr.getKey().equals(AppSignalsConstants.AWS_LOCAL_SERVICE)
149+
&& attr.getValue()
150+
.getStringValue()
151+
.equals(getApplicationOtelServiceName()));
152+
}
153+
}
154+
}
155+
156+
@Testcontainers(disabledWithoutDocker = true)
157+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
158+
@Nested
159+
class ValidateRuntimeMetricsTest extends RuntimeMetricsContractTestBase {
160+
@Test
161+
void testRuntimeMetrics() {
162+
doTestRuntimeMetrics();
163+
}
164+
}
165+
}

appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/utils/AppSignalsConstants.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,16 @@ public class AppSignalsConstants {
3333
public static final String AWS_REMOTE_RESOURCE_IDENTIFIER = "aws.remote.resource.identifier";
3434
public static final String AWS_SPAN_KIND = "aws.span.kind";
3535
public static final String AWS_REMOTE_DB_USER = "aws.remote.db.user";
36+
37+
// JVM Metrics
38+
public static final String JVM_GC_DURATION = "jvm.gc.collections.elapsed";
39+
public static final String JVM_GC_COUNT = "jvm.gc.collections.count";
40+
public static final String JVM_HEAP_USED = "jvm.memory.heap.used";
41+
public static final String JVM_NON_HEAP_USED = "jvm.memory.nonheap.used";
42+
public static final String JVM_AFTER_GC = "jvm.memory.pool.used_after_last_gc";
43+
public static final String JVM_POOL_USED = "jvm.memory.pool.used";
44+
public static final String JVM_THREAD_COUNT = "jvm.threads.count";
45+
public static final String JVM_CLASS_LOADED = "jvm.classes.loaded";
46+
public static final String JVM_CPU_TIME = "jvm.cpu.time";
47+
public static final String JVM_CPU_UTILIZATION = "jvm.cpu.recent_utilization";
3648
}

appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/utils/MockCollectorClient.java

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,13 +133,21 @@ public List<ResourceScopeSpan> getTraces() {
133133
.collect(toImmutableList());
134134
}
135135

136+
public List<ResourceScopeMetric> getRuntimeMetrics(Set<String> presentMetrics) {
137+
return fetchMetrics(presentMetrics, false);
138+
}
139+
140+
public List<ResourceScopeMetric> getMetrics(Set<String> presentMetrics) {
141+
return fetchMetrics(presentMetrics, true);
142+
}
143+
136144
/**
137145
* Get all metrics that are currently stored in the mock collector.
138146
*
139147
* @return List of `ResourceScopeMetric` which is a flat list containing all metrics and their
140148
* related scope and resources.
141149
*/
142-
public List<ResourceScopeMetric> getMetrics(Set<String> presentMetrics) {
150+
private List<ResourceScopeMetric> fetchMetrics(Set<String> presentMetrics, boolean exactMatch) {
143151
List<ExportMetricsServiceRequest> exportedMetrics =
144152
waitForContent(
145153
"/get-metrics",
@@ -152,9 +160,14 @@ public List<ResourceScopeMetric> getMetrics(Set<String> presentMetrics) {
152160
.flatMap(x -> x.getMetricsList().stream())
153161
.map(x -> x.getName())
154162
.collect(Collectors.toSet());
155-
156-
return (!exported.isEmpty() && current.size() == exported.size())
157-
&& receivedMetrics.containsAll(presentMetrics);
163+
if (!exported.isEmpty() && receivedMetrics.containsAll(presentMetrics)) {
164+
if (exactMatch) {
165+
return current.size() == exported.size();
166+
} else {
167+
return true;
168+
}
169+
}
170+
return false;
158171
});
159172

160173
return exportedMetrics.stream()

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,8 @@ private boolean isApplicationSignalsEnabled(ConfigProperties configProps) {
104104
}
105105

106106
private boolean isApplicationSignalsRuntimeEnabled(ConfigProperties configProps) {
107-
return false;
107+
return isApplicationSignalsEnabled(configProps)
108+
&& configProps.getBoolean(APPLICATION_SIGNALS_RUNTIME_ENABLED_CONFIG, true);
108109
}
109110

110111
private Map<String, String> customizeProperties(ConfigProperties configProps) {

instrumentation/jmx-metrics/src/main/resources/jmx/rules/jvm.yaml

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ rules:
2323
unit: ms
2424
desc: The approximate accumulated collection elapsed time in milliseconds
2525
- bean: java.lang:type=Memory
26-
unit: by
26+
unit: By
2727
prefix: jvm.memory.
2828
type: gauge
2929
mapping:
@@ -52,12 +52,15 @@ rules:
5252
metric: nonheap.max
5353
desc: The maximum amount of memory can be used for non-heap purposes
5454
- bean: java.lang:type=MemoryPool,name=*
55-
unit: by
55+
unit: By
5656
prefix: jvm.memory.pool.
5757
type: gauge
5858
metricAttribute:
5959
name: param(name)
6060
mapping:
61+
CollectionUsage.used:
62+
metric: used_after_last_gc
63+
desc: Memory used after the most recent gc event
6164
Usage.init:
6265
metric: init
6366
desc: The initial amount of memory that the JVM requests from the operating system for the memory pool
@@ -81,37 +84,49 @@ rules:
8184
metric: jvm.daemon_threads.count
8285
desc: Number of daemon threads
8386
- bean: java.lang:type=OperatingSystem
84-
type: gauge
8587
mapping:
8688
TotalSwapSpaceSize:
8789
metric: jvm.system.swap.space.total
88-
desc: The host swap memory size in bytes
89-
unit: by
90+
type: gauge
91+
desc: The host swap memory size in Bytes
92+
unit: By
9093
FreeSwapSpaceSize:
9194
metric: jvm.system.swap.space.free
92-
desc: The amount of available swap memory in bytes
93-
unit: by
95+
type: gauge
96+
desc: The amount of available swap memory in Bytes
97+
unit: By
9498
TotalPhysicalMemorySize:
9599
metric: jvm.system.physical.memory.total
100+
type: gauge
96101
desc: The total physical memory size in host
97-
unit: by
102+
unit: By
98103
FreePhysicalMemorySize:
99104
metric: jvm.system.physical.memory.free
105+
type: gauge
100106
desc: The amount of free physical memory in host
101-
unit: by
107+
unit: By
102108
AvailableProcessors:
103109
metric: jvm.system.available.processors
110+
type: gauge
104111
desc: The number of available processors
105112
unit: "1"
106113
SystemCpuLoad:
107114
metric: jvm.system.cpu.utilization
115+
type: gauge
108116
desc: The current load of CPU in host
109117
unit: "1"
118+
ProcessCpuTime:
119+
metric: jvm.cpu.time
120+
type: counter
121+
unit: ns
122+
desc: CPU time used
110123
ProcessCpuLoad:
111124
metric: jvm.cpu.recent_utilization
125+
type: gauge
112126
unit: "1"
113127
desc: Recent CPU utilization for the process
114128
OpenFileDescriptorCount:
115129
metric: jvm.open_file_descriptor.count
130+
type: gauge
116131
desc: The number of opened file descriptors
117132
unit: "1"

0 commit comments

Comments
 (0)