Skip to content

Commit db02416

Browse files
author
Reno Seo
authored
[DO NOT MERGE] feat: Add contract tests for runtime metrics (#893)
*Issue #, if available:* N/A *Description of changes:* This PR adds contract tests for runtime metrics feature, where it validates: 1. Are all runtime metrics captured? 2. Do the runtime metrics have a realistic value? (non-negative) These changes can easily be extended for higher coverage in the future. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent a178246 commit db02416

File tree

4 files changed

+121
-24
lines changed

4 files changed

+121
-24
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +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", "false")
82+
.withEnv("OTEL_AWS_APPLICATION_SIGNALS_RUNTIME_ENABLED", isRuntimeEnabled())
8383
.withEnv("OTEL_METRICS_EXPORTER", "none")
8484
.withEnv("OTEL_BSP_SCHEDULE_DELAY", "0") // Don't wait to export spans to the collector
8585
.withEnv(
@@ -160,4 +160,6 @@ protected String getApplicationOtelServiceName() {
160160
protected String getApplicationOtelResourceAttributes() {
161161
return "service.name=" + getApplicationOtelServiceName();
162162
}
163+
164+
protected String isRuntimeEnabled() { return "false"; }
163165
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package software.amazon.opentelemetry.appsignals.test.misc;
2+
3+
import io.opentelemetry.proto.metrics.v1.NumberDataPoint;
4+
import org.junit.Assert;
5+
import org.junit.jupiter.api.Nested;
6+
import org.junit.jupiter.api.Test;
7+
import org.junit.jupiter.api.TestInstance;
8+
import org.testcontainers.junit.jupiter.Testcontainers;
9+
import software.amazon.opentelemetry.appsignals.test.base.ContractTestBase;
10+
import software.amazon.opentelemetry.appsignals.test.utils.AppSignalsConstants;
11+
12+
import java.util.List;
13+
import java.util.Set;
14+
15+
import static org.assertj.core.api.Assertions.assertThat;
16+
17+
public class RuntimeMetricsTest {
18+
private abstract static class RuntimeMetricsContractTestBase extends ContractTestBase {
19+
@Override
20+
protected String getApplicationImageName() {
21+
return "aws-appsignals-tests-http-server-spring-mvc";
22+
}
23+
24+
@Override
25+
protected String isRuntimeEnabled() { return "true"; }
26+
27+
protected String getApplicationWaitPattern() {
28+
return ".*Started Application.*";
29+
}
30+
31+
protected void doTestRuntimeMetrics() {
32+
var response = appClient.get("/success").aggregate().join();
33+
34+
assertThat(response.status().isSuccess()).isTrue();
35+
assertRuntimeMetrics();
36+
}
37+
protected void assertRuntimeMetrics() {
38+
var metrics =
39+
mockCollectorClient.getRuntimeMetrics(
40+
Set.of(
41+
AppSignalsConstants.JVM_GC_METRIC,
42+
AppSignalsConstants.JVM_GC_COUNT,
43+
AppSignalsConstants.JVM_HEAP_USED,
44+
AppSignalsConstants.JVM_NON_HEAP_USED,
45+
AppSignalsConstants.JVM_AFTER_GC,
46+
AppSignalsConstants.JVM_POOL_USED,
47+
AppSignalsConstants.JVM_THREAD_COUNT,
48+
AppSignalsConstants.JVM_CLASS_LOADED,
49+
AppSignalsConstants.JVM_CPU_TIME,
50+
AppSignalsConstants.JVM_CPU_UTILIZATION,
51+
AppSignalsConstants.LATENCY_METRIC,
52+
AppSignalsConstants.ERROR_METRIC,
53+
AppSignalsConstants.FAULT_METRIC
54+
));
55+
metrics.forEach(metric -> {
56+
var dataPoints = metric.getMetric().getGauge().getDataPointsList();
57+
assertNonNegativeValue(dataPoints);
58+
});
59+
}
60+
61+
private void assertNonNegativeValue(List<NumberDataPoint> dps) {
62+
dps.forEach(datapoint -> {
63+
Assert.assertTrue(datapoint.getAsInt() >= 0);
64+
});
65+
}
66+
}
67+
68+
@Testcontainers(disabledWithoutDocker = true)
69+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
70+
@Nested
71+
class ValidateRuntimeMetricsTest extends RuntimeMetricsContractTestBase {
72+
@Test
73+
void testRuntimeMetrics() {
74+
doTestRuntimeMetrics();
75+
}
76+
}
77+
}

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_METRIC = "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: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -132,39 +132,45 @@ public List<ResourceScopeSpan> getTraces() {
132132
.map(x -> new ResourceScopeSpan(ss.getFirst(), ss.getSecond(), x)))
133133
.collect(toImmutableList());
134134
}
135+
public List<ResourceScopeMetric> getRuntimeMetrics(Set<String> presentMetrics) {
136+
return fetchMetrics(presentMetrics, true);
137+
}
138+
139+
public List<ResourceScopeMetric> getMetrics(Set<String> presentMetrics) {
140+
return fetchMetrics(presentMetrics, false);
141+
}
135142

136143
/**
137144
* Get all metrics that are currently stored in the mock collector.
138145
*
139146
* @return List of `ResourceScopeMetric` which is a flat list containing all metrics and their
140147
* related scope and resources.
141148
*/
142-
public List<ResourceScopeMetric> getMetrics(Set<String> presentMetrics) {
149+
private List<ResourceScopeMetric> fetchMetrics(Set<String> presentMetrics, boolean isRuntime) {
143150
List<ExportMetricsServiceRequest> exportedMetrics =
144-
waitForContent(
145-
"/get-metrics",
146-
EXPORT_METRICS_SERVICE_REQUEST_LIST,
147-
(exported, current) -> {
148-
Set<String> receivedMetrics =
149-
current.stream()
150-
.flatMap(x -> x.getResourceMetricsList().stream())
151-
.flatMap(x -> x.getScopeMetricsList().stream())
152-
.flatMap(x -> x.getMetricsList().stream())
153-
.map(x -> x.getName())
154-
.collect(Collectors.toSet());
155-
156-
return (!exported.isEmpty() && current.size() == exported.size())
157-
&& receivedMetrics.containsAll(presentMetrics);
158-
});
151+
waitForContent(
152+
"/get-metrics",
153+
EXPORT_METRICS_SERVICE_REQUEST_LIST,
154+
(exported, current) -> {
155+
Set<String> receivedMetrics =
156+
current.stream()
157+
.flatMap(x -> x.getResourceMetricsList().stream())
158+
.flatMap(x -> x.getScopeMetricsList().stream())
159+
.flatMap(x -> x.getMetricsList().stream())
160+
.map(x -> x.getName())
161+
.collect(Collectors.toSet());
162+
163+
return (isRuntime ? !exported.isEmpty() && receivedMetrics.size() == presentMetrics.size() : !exported.isEmpty() && current.size() == exported.size())
164+
&& receivedMetrics.containsAll(presentMetrics);
165+
});
159166

160167
return exportedMetrics.stream()
161-
.flatMap(req -> req.getResourceMetricsList().stream())
162-
.flatMap(rm -> rm.getScopeMetricsList().stream().map(x -> new Pair<>(rm, x)))
163-
.flatMap(
164-
sm ->
165-
sm.getSecond().getMetricsList().stream()
166-
.map(x -> new ResourceScopeMetric(sm.getFirst(), sm.getSecond(), x)))
167-
.collect(toImmutableList());
168+
.flatMap(req -> req.getResourceMetricsList().stream())
169+
.flatMap(rm -> rm.getScopeMetricsList().stream().map(x -> new Pair<>(rm, x)))
170+
.flatMap(sm ->
171+
sm.getSecond().getMetricsList().stream()
172+
.map(x -> new ResourceScopeMetric(sm.getFirst(), sm.getSecond(), x)))
173+
.collect(toImmutableList());
168174
}
169175

170176
private <T> List<T> waitForContent(String url, TypeReference<List<T>> t) {

0 commit comments

Comments
 (0)