Skip to content

Commit

Permalink
Add JMX contract tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jefchien authored and bjrara committed Nov 1, 2024
1 parent 536c5e9 commit 13ec00a
Show file tree
Hide file tree
Showing 12 changed files with 800 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,19 @@ public abstract class ContractTestBase {

private final Logger collectorLogger =
LoggerFactory.getLogger("collector " + getApplicationOtelServiceName());
private final Logger applicationLogger =
protected final Logger applicationLogger =
LoggerFactory.getLogger("application " + getApplicationOtelServiceName());

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

protected final Network network = Network.newNetwork();

private static final String COLLECTOR_HOSTNAME = "collector";
private static final int COLLECTOR_PORT = 4317;
protected static final String COLLECTOR_HTTP_ENDPOINT =
"http://" + COLLECTOR_HOSTNAME + ":" + COLLECTOR_PORT;

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

protected final GenericContainer<?> application =
new GenericContainer<>(getApplicationImageName())
.dependsOn(getDependsOn())
.withExposedPorts(getApplicationPort())
.withNetwork(network)
.withLogConsumer(new Slf4jLogConsumer(applicationLogger))
.withCopyFileToContainer(
MountableFile.forHostPath(AGENT_PATH), "/opentelemetry-javaagent-all.jar")
.waitingFor(getApplicationWaitCondition())
.withEnv("JAVA_TOOL_OPTIONS", "-javaagent:/opentelemetry-javaagent-all.jar")
.withEnv("OTEL_METRIC_EXPORT_INTERVAL", "100") // 100 ms
.withEnv("OTEL_AWS_APPLICATION_SIGNALS_ENABLED", "true")
.withEnv("OTEL_AWS_APPLICATION_SIGNALS_RUNTIME_ENABLED", isRuntimeEnabled())
.withEnv("OTEL_METRICS_EXPORTER", "none")
.withEnv("OTEL_BSP_SCHEDULE_DELAY", "0") // Don't wait to export spans to the collector
.withEnv(
"OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT",
"http://" + COLLECTOR_HOSTNAME + ":" + COLLECTOR_PORT)
.withEnv(
"OTEL_EXPORTER_OTLP_TRACES_ENDPOINT",
"http://" + COLLECTOR_HOSTNAME + ":" + COLLECTOR_PORT)
.withEnv("OTEL_RESOURCE_ATTRIBUTES", getApplicationOtelResourceAttributes())
.withEnv(getApplicationExtraEnvironmentVariables())
.withNetworkAliases(getApplicationNetworkAliases().toArray(new String[0]));
protected final GenericContainer<?> application = getApplicationContainer();

protected MockCollectorClient mockCollectorClient;
protected WebClient appClient;
Expand All @@ -109,10 +89,8 @@ private void stopCollector() {
protected void setupClients() {
application.start();

appClient = WebClient.of("http://localhost:" + application.getMappedPort(8080));
mockCollectorClient =
new MockCollectorClient(
WebClient.of("http://localhost:" + mockCollector.getMappedPort(4317)));
appClient = getApplicationClient();
mockCollectorClient = getMockCollectorClient();
}

@AfterEach
Expand All @@ -128,11 +106,55 @@ private List<Startable> getDependsOn() {
return dependencies;
}

protected WebClient getApplicationClient() {
return WebClient.of("http://localhost:" + application.getMappedPort(8080));
}

protected MockCollectorClient getMockCollectorClient() {
return new MockCollectorClient(
WebClient.of("http://localhost:" + mockCollector.getMappedPort(4317)));
}

protected GenericContainer<?> getApplicationContainer() {
return new GenericContainer<>(getApplicationImageName())
.dependsOn(getDependsOn())
.withExposedPorts(getApplicationPort())
.withNetwork(network)
.withLogConsumer(new Slf4jLogConsumer(applicationLogger))
.withCopyFileToContainer(MountableFile.forHostPath(AGENT_PATH), MOUNT_PATH)
.waitingFor(getApplicationWaitCondition())
.withEnv(getApplicationEnvironmentVariables())
.withEnv(getApplicationExtraEnvironmentVariables())
.withNetworkAliases(getApplicationNetworkAliases().toArray(new String[0]));
}

/** Methods that should be overridden in sub classes * */
protected int getApplicationPort() {
return 8080;
}

protected Map<String, String> getApplicationEnvironmentVariables() {
return Map.of(
"JAVA_TOOL_OPTIONS",
"-javaagent:" + MOUNT_PATH,
"OTEL_METRIC_EXPORT_INTERVAL",
"100", // 100 ms
"OTEL_AWS_APPLICATION_SIGNALS_ENABLED",
"true",
"OTEL_AWS_APPLICATION_SIGNALS_RUNTIME_ENABLED",
isRuntimeEnabled(),
"OTEL_METRICS_EXPORTER",
"none",
"OTEL_BSP_SCHEDULE_DELAY",
"0", // Don't wait to export spans to the collector
"OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT",
COLLECTOR_HTTP_ENDPOINT,
"OTEL_EXPORTER_OTLP_TRACES_ENDPOINT",
COLLECTOR_HTTP_ENDPOINT,
"OTEL_RESOURCE_ATTRIBUTES",
getApplicationOtelResourceAttributes());
}

protected Map<String, String> getApplicationExtraEnvironmentVariables() {
return Map.of();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* 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.appsignals.test.base;

import static org.assertj.core.api.Assertions.assertThat;

import io.opentelemetry.proto.metrics.v1.NumberDataPoint;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import software.amazon.opentelemetry.appsignals.test.utils.JMXMetricsConstants;

public abstract class JMXMetricsContractTestBase extends ContractTestBase {

@Override
protected Map<String, String> getApplicationEnvironmentVariables() {
return Map.of(
"JAVA_TOOL_OPTIONS", "-javaagent:" + MOUNT_PATH,
"OTEL_METRIC_EXPORT_INTERVAL", "100", // 100 ms
"OTEL_METRICS_EXPORTER", "none",
"OTEL_LOGS_EXPORTER", "none",
"OTEL_TRACES_EXPORTER", "none",
"OTEL_EXPORTER_OTLP_PROTOCOL", "http/protobuf",
"OTEL_JMX_ENABLED", "true",
"OTEL_AWS_JMX_EXPORTER_METRICS_ENDPOINT", COLLECTOR_HTTP_ENDPOINT + "/v1/metrics");
}

protected void doTestMetrics() {
var response = appClient.get("/success").aggregate().join();

assertThat(response.status().isSuccess()).isTrue();
assertMetrics();
}

protected void assertMetrics() {
var metrics = mockCollectorClient.getRuntimeMetrics(getExpectedMetrics());
metrics.forEach(
metric -> {
var dataPoints = metric.getMetric().getGauge().getDataPointsList();
assertGreaterThanOrEqual(dataPoints, getThreshold(metric.getMetric().getName()));
});
}

protected abstract Set<String> getExpectedMetrics();

protected long getThreshold(String metricName) {
long threshold = 0;
switch (metricName) {
// If maximum memory size is undefined, then value is -1
// https://docs.oracle.com/en/java/javase/17/docs/api/java.management/java/lang/management/MemoryUsage.html#getMax()
case JMXMetricsConstants.JVM_HEAP_MAX:
case JMXMetricsConstants.JVM_NON_HEAP_MAX:
case JMXMetricsConstants.JVM_POOL_MAX:
threshold = -1;
default:
}
return threshold;
}

private void assertGreaterThanOrEqual(List<NumberDataPoint> dps, long threshold) {
assertDataPoints(dps, (value) -> assertThat(value).isGreaterThanOrEqualTo(threshold));
}

private void assertDataPoints(List<NumberDataPoint> dps, Consumer<Long> consumer) {
dps.forEach(datapoint -> consumer.accept(datapoint.getAsInt()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* 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.appsignals.test.misc.jmx;

import java.util.Map;
import java.util.Set;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.testcontainers.junit.jupiter.Testcontainers;
import software.amazon.opentelemetry.appsignals.test.base.JMXMetricsContractTestBase;
import software.amazon.opentelemetry.appsignals.test.utils.JMXMetricsConstants;

/**
* Tests in this class validate that the SDK will emit JVM metrics when Application Signals runtime
* metrics are enabled.
*/
@Testcontainers(disabledWithoutDocker = true)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class JVMMetricsTest extends JMXMetricsContractTestBase {
@Test
void testJVMMetrics() {
doTestMetrics();
}

@Override
protected String getApplicationImageName() {
return "aws-appsignals-tests-http-server-spring-mvc";
}

@Override
protected String getApplicationWaitPattern() {
return ".*Started Application.*";
}

@Override
protected Set<String> getExpectedMetrics() {
return JMXMetricsConstants.JVM_METRICS_SET;
}

@Override
protected Map<String, String> getApplicationExtraEnvironmentVariables() {
return Map.of("OTEL_JMX_TARGET_SYSTEM", "jvm");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* 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.appsignals.test.misc.jmx;

import java.io.IOException;
import java.util.Map;
import java.util.Set;
import org.junit.jupiter.api.*;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.KafkaContainer;
import org.testcontainers.containers.output.Slf4jLogConsumer;
import org.testcontainers.images.PullPolicy;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.DockerImageName;
import org.testcontainers.utility.MountableFile;
import software.amazon.opentelemetry.appsignals.test.base.JMXMetricsContractTestBase;
import software.amazon.opentelemetry.appsignals.test.utils.JMXMetricsConstants;

@Testcontainers(disabledWithoutDocker = true)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class KafkaBrokerMetricsTest extends JMXMetricsContractTestBase {
@Test
void testKafkaMetrics() {
assertMetrics();
}

@Override
protected GenericContainer<?> getApplicationContainer() {
return new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.4.0"))
.withImagePullPolicy(PullPolicy.alwaysPull())
.withNetworkAliases("kafkaBroker")
.withNetwork(network)
.withLogConsumer(new Slf4jLogConsumer(applicationLogger))
.withCopyFileToContainer(MountableFile.forHostPath(AGENT_PATH), MOUNT_PATH)
.withEnv(getApplicationEnvironmentVariables())
.withEnv(getApplicationExtraEnvironmentVariables())
.waitingFor(getApplicationWaitCondition())
.withKraft();
}

@BeforeAll
public void setup() throws IOException, InterruptedException {
application.start();
application.execInContainer(
"/bin/sh",
"-c",
"/usr/bin/kafka-topics --bootstrap-server=localhost:9092 --create --topic kafka_topic --partitions 3 --replication-factor 1");
mockCollectorClient = getMockCollectorClient();
}

// don't use the default clients
@BeforeEach
@Override
protected void setupClients() {}

@Override
protected String getApplicationImageName() {
return "aws-appsignals-tests-kafka";
}

@Override
protected String getApplicationWaitPattern() {
return ".* Kafka Server started .*";
}

@Override
protected Set<String> getExpectedMetrics() {
return JMXMetricsConstants.KAFKA_METRICS_SET;
}

@Override
protected Map<String, String> getApplicationExtraEnvironmentVariables() {
return Map.of(
"JAVA_TOOL_OPTIONS", // kafka broker container will not complete startup if agent is set
"",
"KAFKA_OPTS", // replace java tool options with kafka opts
"-javaagent:" + MOUNT_PATH,
"KAFKA_AUTO_CREATE_TOPICS_ENABLE",
"false",
"OTEL_JMX_TARGET_SYSTEM",
"kafka");
}
}
Loading

0 comments on commit 13ec00a

Please sign in to comment.