Skip to content

Commit 2aa03d5

Browse files
authored
Add JMX contract tests (#935)
*Description of changes:* Adds contract tests to verify the expected behavior of the following changes: - #817 - #898 - #901 Starts up sample apps with the instrumented SDK and verifies that metrics are received for each of the supported JMX target systems. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent 536c5e9 commit 2aa03d5

File tree

12 files changed

+800
-46
lines changed

12 files changed

+800
-46
lines changed

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

Lines changed: 52 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,19 @@ public abstract class ContractTestBase {
4848

4949
private final Logger collectorLogger =
5050
LoggerFactory.getLogger("collector " + getApplicationOtelServiceName());
51-
private final Logger applicationLogger =
51+
protected final Logger applicationLogger =
5252
LoggerFactory.getLogger("application " + getApplicationOtelServiceName());
5353

54-
private static final String AGENT_PATH =
54+
protected static final String AGENT_PATH =
5555
System.getProperty("io.awsobservability.instrumentation.contracttests.agentPath");
56+
protected static final String MOUNT_PATH = "/opentelemetry-javaagent-all.jar";
5657

5758
protected final Network network = Network.newNetwork();
5859

5960
private static final String COLLECTOR_HOSTNAME = "collector";
6061
private static final int COLLECTOR_PORT = 4317;
62+
protected static final String COLLECTOR_HTTP_ENDPOINT =
63+
"http://" + COLLECTOR_HOSTNAME + ":" + COLLECTOR_PORT;
6164

6265
protected final GenericContainer<?> mockCollector =
6366
new GenericContainer<>("aws-appsignals-mock-collector")
@@ -67,30 +70,7 @@ public abstract class ContractTestBase {
6770
.withNetwork(network)
6871
.withNetworkAliases(COLLECTOR_HOSTNAME);
6972

70-
protected final GenericContainer<?> application =
71-
new GenericContainer<>(getApplicationImageName())
72-
.dependsOn(getDependsOn())
73-
.withExposedPorts(getApplicationPort())
74-
.withNetwork(network)
75-
.withLogConsumer(new Slf4jLogConsumer(applicationLogger))
76-
.withCopyFileToContainer(
77-
MountableFile.forHostPath(AGENT_PATH), "/opentelemetry-javaagent-all.jar")
78-
.waitingFor(getApplicationWaitCondition())
79-
.withEnv("JAVA_TOOL_OPTIONS", "-javaagent:/opentelemetry-javaagent-all.jar")
80-
.withEnv("OTEL_METRIC_EXPORT_INTERVAL", "100") // 100 ms
81-
.withEnv("OTEL_AWS_APPLICATION_SIGNALS_ENABLED", "true")
82-
.withEnv("OTEL_AWS_APPLICATION_SIGNALS_RUNTIME_ENABLED", isRuntimeEnabled())
83-
.withEnv("OTEL_METRICS_EXPORTER", "none")
84-
.withEnv("OTEL_BSP_SCHEDULE_DELAY", "0") // Don't wait to export spans to the collector
85-
.withEnv(
86-
"OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT",
87-
"http://" + COLLECTOR_HOSTNAME + ":" + COLLECTOR_PORT)
88-
.withEnv(
89-
"OTEL_EXPORTER_OTLP_TRACES_ENDPOINT",
90-
"http://" + COLLECTOR_HOSTNAME + ":" + COLLECTOR_PORT)
91-
.withEnv("OTEL_RESOURCE_ATTRIBUTES", getApplicationOtelResourceAttributes())
92-
.withEnv(getApplicationExtraEnvironmentVariables())
93-
.withNetworkAliases(getApplicationNetworkAliases().toArray(new String[0]));
73+
protected final GenericContainer<?> application = getApplicationContainer();
9474

9575
protected MockCollectorClient mockCollectorClient;
9676
protected WebClient appClient;
@@ -109,10 +89,8 @@ private void stopCollector() {
10989
protected void setupClients() {
11090
application.start();
11191

112-
appClient = WebClient.of("http://localhost:" + application.getMappedPort(8080));
113-
mockCollectorClient =
114-
new MockCollectorClient(
115-
WebClient.of("http://localhost:" + mockCollector.getMappedPort(4317)));
92+
appClient = getApplicationClient();
93+
mockCollectorClient = getMockCollectorClient();
11694
}
11795

11896
@AfterEach
@@ -128,11 +106,55 @@ private List<Startable> getDependsOn() {
128106
return dependencies;
129107
}
130108

109+
protected WebClient getApplicationClient() {
110+
return WebClient.of("http://localhost:" + application.getMappedPort(8080));
111+
}
112+
113+
protected MockCollectorClient getMockCollectorClient() {
114+
return new MockCollectorClient(
115+
WebClient.of("http://localhost:" + mockCollector.getMappedPort(4317)));
116+
}
117+
118+
protected GenericContainer<?> getApplicationContainer() {
119+
return new GenericContainer<>(getApplicationImageName())
120+
.dependsOn(getDependsOn())
121+
.withExposedPorts(getApplicationPort())
122+
.withNetwork(network)
123+
.withLogConsumer(new Slf4jLogConsumer(applicationLogger))
124+
.withCopyFileToContainer(MountableFile.forHostPath(AGENT_PATH), MOUNT_PATH)
125+
.waitingFor(getApplicationWaitCondition())
126+
.withEnv(getApplicationEnvironmentVariables())
127+
.withEnv(getApplicationExtraEnvironmentVariables())
128+
.withNetworkAliases(getApplicationNetworkAliases().toArray(new String[0]));
129+
}
130+
131131
/** Methods that should be overridden in sub classes * */
132132
protected int getApplicationPort() {
133133
return 8080;
134134
}
135135

136+
protected Map<String, String> getApplicationEnvironmentVariables() {
137+
return Map.of(
138+
"JAVA_TOOL_OPTIONS",
139+
"-javaagent:" + MOUNT_PATH,
140+
"OTEL_METRIC_EXPORT_INTERVAL",
141+
"100", // 100 ms
142+
"OTEL_AWS_APPLICATION_SIGNALS_ENABLED",
143+
"true",
144+
"OTEL_AWS_APPLICATION_SIGNALS_RUNTIME_ENABLED",
145+
isRuntimeEnabled(),
146+
"OTEL_METRICS_EXPORTER",
147+
"none",
148+
"OTEL_BSP_SCHEDULE_DELAY",
149+
"0", // Don't wait to export spans to the collector
150+
"OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT",
151+
COLLECTOR_HTTP_ENDPOINT,
152+
"OTEL_EXPORTER_OTLP_TRACES_ENDPOINT",
153+
COLLECTOR_HTTP_ENDPOINT,
154+
"OTEL_RESOURCE_ATTRIBUTES",
155+
getApplicationOtelResourceAttributes());
156+
}
157+
136158
protected Map<String, String> getApplicationExtraEnvironmentVariables() {
137159
return Map.of();
138160
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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.base;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
20+
import io.opentelemetry.proto.metrics.v1.NumberDataPoint;
21+
import java.util.List;
22+
import java.util.Map;
23+
import java.util.Set;
24+
import java.util.function.Consumer;
25+
import software.amazon.opentelemetry.appsignals.test.utils.JMXMetricsConstants;
26+
27+
public abstract class JMXMetricsContractTestBase extends ContractTestBase {
28+
29+
@Override
30+
protected Map<String, String> getApplicationEnvironmentVariables() {
31+
return Map.of(
32+
"JAVA_TOOL_OPTIONS", "-javaagent:" + MOUNT_PATH,
33+
"OTEL_METRIC_EXPORT_INTERVAL", "100", // 100 ms
34+
"OTEL_METRICS_EXPORTER", "none",
35+
"OTEL_LOGS_EXPORTER", "none",
36+
"OTEL_TRACES_EXPORTER", "none",
37+
"OTEL_EXPORTER_OTLP_PROTOCOL", "http/protobuf",
38+
"OTEL_JMX_ENABLED", "true",
39+
"OTEL_AWS_JMX_EXPORTER_METRICS_ENDPOINT", COLLECTOR_HTTP_ENDPOINT + "/v1/metrics");
40+
}
41+
42+
protected void doTestMetrics() {
43+
var response = appClient.get("/success").aggregate().join();
44+
45+
assertThat(response.status().isSuccess()).isTrue();
46+
assertMetrics();
47+
}
48+
49+
protected void assertMetrics() {
50+
var metrics = mockCollectorClient.getRuntimeMetrics(getExpectedMetrics());
51+
metrics.forEach(
52+
metric -> {
53+
var dataPoints = metric.getMetric().getGauge().getDataPointsList();
54+
assertGreaterThanOrEqual(dataPoints, getThreshold(metric.getMetric().getName()));
55+
});
56+
}
57+
58+
protected abstract Set<String> getExpectedMetrics();
59+
60+
protected long getThreshold(String metricName) {
61+
long threshold = 0;
62+
switch (metricName) {
63+
// If maximum memory size is undefined, then value is -1
64+
// https://docs.oracle.com/en/java/javase/17/docs/api/java.management/java/lang/management/MemoryUsage.html#getMax()
65+
case JMXMetricsConstants.JVM_HEAP_MAX:
66+
case JMXMetricsConstants.JVM_NON_HEAP_MAX:
67+
case JMXMetricsConstants.JVM_POOL_MAX:
68+
threshold = -1;
69+
default:
70+
}
71+
return threshold;
72+
}
73+
74+
private void assertGreaterThanOrEqual(List<NumberDataPoint> dps, long threshold) {
75+
assertDataPoints(dps, (value) -> assertThat(value).isGreaterThanOrEqualTo(threshold));
76+
}
77+
78+
private void assertDataPoints(List<NumberDataPoint> dps, Consumer<Long> consumer) {
79+
dps.forEach(datapoint -> consumer.accept(datapoint.getAsInt()));
80+
}
81+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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.jmx;
17+
18+
import java.util.Map;
19+
import java.util.Set;
20+
import org.junit.jupiter.api.Test;
21+
import org.junit.jupiter.api.TestInstance;
22+
import org.testcontainers.junit.jupiter.Testcontainers;
23+
import software.amazon.opentelemetry.appsignals.test.base.JMXMetricsContractTestBase;
24+
import software.amazon.opentelemetry.appsignals.test.utils.JMXMetricsConstants;
25+
26+
/**
27+
* Tests in this class validate that the SDK will emit JVM metrics when Application Signals runtime
28+
* metrics are enabled.
29+
*/
30+
@Testcontainers(disabledWithoutDocker = true)
31+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
32+
public class JVMMetricsTest extends JMXMetricsContractTestBase {
33+
@Test
34+
void testJVMMetrics() {
35+
doTestMetrics();
36+
}
37+
38+
@Override
39+
protected String getApplicationImageName() {
40+
return "aws-appsignals-tests-http-server-spring-mvc";
41+
}
42+
43+
@Override
44+
protected String getApplicationWaitPattern() {
45+
return ".*Started Application.*";
46+
}
47+
48+
@Override
49+
protected Set<String> getExpectedMetrics() {
50+
return JMXMetricsConstants.JVM_METRICS_SET;
51+
}
52+
53+
@Override
54+
protected Map<String, String> getApplicationExtraEnvironmentVariables() {
55+
return Map.of("OTEL_JMX_TARGET_SYSTEM", "jvm");
56+
}
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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.jmx;
17+
18+
import java.io.IOException;
19+
import java.util.Map;
20+
import java.util.Set;
21+
import org.junit.jupiter.api.*;
22+
import org.testcontainers.containers.GenericContainer;
23+
import org.testcontainers.containers.KafkaContainer;
24+
import org.testcontainers.containers.output.Slf4jLogConsumer;
25+
import org.testcontainers.images.PullPolicy;
26+
import org.testcontainers.junit.jupiter.Testcontainers;
27+
import org.testcontainers.utility.DockerImageName;
28+
import org.testcontainers.utility.MountableFile;
29+
import software.amazon.opentelemetry.appsignals.test.base.JMXMetricsContractTestBase;
30+
import software.amazon.opentelemetry.appsignals.test.utils.JMXMetricsConstants;
31+
32+
@Testcontainers(disabledWithoutDocker = true)
33+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
34+
public class KafkaBrokerMetricsTest extends JMXMetricsContractTestBase {
35+
@Test
36+
void testKafkaMetrics() {
37+
assertMetrics();
38+
}
39+
40+
@Override
41+
protected GenericContainer<?> getApplicationContainer() {
42+
return new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.4.0"))
43+
.withImagePullPolicy(PullPolicy.alwaysPull())
44+
.withNetworkAliases("kafkaBroker")
45+
.withNetwork(network)
46+
.withLogConsumer(new Slf4jLogConsumer(applicationLogger))
47+
.withCopyFileToContainer(MountableFile.forHostPath(AGENT_PATH), MOUNT_PATH)
48+
.withEnv(getApplicationEnvironmentVariables())
49+
.withEnv(getApplicationExtraEnvironmentVariables())
50+
.waitingFor(getApplicationWaitCondition())
51+
.withKraft();
52+
}
53+
54+
@BeforeAll
55+
public void setup() throws IOException, InterruptedException {
56+
application.start();
57+
application.execInContainer(
58+
"/bin/sh",
59+
"-c",
60+
"/usr/bin/kafka-topics --bootstrap-server=localhost:9092 --create --topic kafka_topic --partitions 3 --replication-factor 1");
61+
mockCollectorClient = getMockCollectorClient();
62+
}
63+
64+
// don't use the default clients
65+
@BeforeEach
66+
@Override
67+
protected void setupClients() {}
68+
69+
@Override
70+
protected String getApplicationImageName() {
71+
return "aws-appsignals-tests-kafka";
72+
}
73+
74+
@Override
75+
protected String getApplicationWaitPattern() {
76+
return ".* Kafka Server started .*";
77+
}
78+
79+
@Override
80+
protected Set<String> getExpectedMetrics() {
81+
return JMXMetricsConstants.KAFKA_METRICS_SET;
82+
}
83+
84+
@Override
85+
protected Map<String, String> getApplicationExtraEnvironmentVariables() {
86+
return Map.of(
87+
"JAVA_TOOL_OPTIONS", // kafka broker container will not complete startup if agent is set
88+
"",
89+
"KAFKA_OPTS", // replace java tool options with kafka opts
90+
"-javaagent:" + MOUNT_PATH,
91+
"KAFKA_AUTO_CREATE_TOPICS_ENABLE",
92+
"false",
93+
"OTEL_JMX_TARGET_SYSTEM",
94+
"kafka");
95+
}
96+
}

0 commit comments

Comments
 (0)