Skip to content

Commit

Permalink
test: add self-monitoring int-test
Browse files Browse the repository at this point in the history
  • Loading branch information
EddeCCC committed Nov 20, 2024
1 parent 57af9fd commit 83ed409
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.util.JsonFormat;
import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest;
import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest;
import io.opentelemetry.proto.trace.v1.Span;
import java.io.IOException;
Expand All @@ -32,6 +33,8 @@
import org.testcontainers.containers.output.Slf4jLogConsumer;
import org.testcontainers.containers.wait.strategy.WaitStrategy;
import org.testcontainers.utility.MountableFile;
import rocks.inspectit.gepard.agent.configuration.http.HttpConfigurationCallback;
import rocks.inspectit.gepard.agent.instrumentation.hook.MethodHookManager;
import rocks.inspectit.gepard.agent.integrationtest.utils.OkHttpUtils;

/**
Expand Down Expand Up @@ -72,14 +75,14 @@ protected Map<String, String> getExtraEnv() {
}

// The Observability Backend Mock
private static TracingBackendMock tracingBackendMock;
private static ObservabilityBackendMock backendMock;
// The configuration Server Mock
public static ConfigurationServerMock configurationServerMock;

@BeforeAll
static void setup() {
tracingBackendMock = TracingBackendMock.create(network);
tracingBackendMock.start();
backendMock = ObservabilityBackendMock.create(network);
backendMock.start();

configurationServerMock = ConfigurationServerMock.create(network);
configurationServerMock.start();
Expand Down Expand Up @@ -131,7 +134,7 @@ private GenericContainer<?> buildTargetContainer(String agentPath, String extens

@AfterEach
void reset() throws IOException {
tracingBackendMock.reset();
backendMock.reset();
configurationServerMock.reset();
}

Expand All @@ -141,7 +144,7 @@ protected void stopTarget() {

@AfterAll
static void cleanup() {
tracingBackendMock.stop();
backendMock.stop();
configurationServerMock.stop();
}

Expand Down Expand Up @@ -182,9 +185,12 @@ protected static Stream<Span> getSpanStream(Collection<ExportTraceServiceRequest
.flatMap(it -> it.getSpansList().stream());
}

/**
* @return the traces received by the backend mock
*/
protected Collection<ExportTraceServiceRequest> waitForTraces()
throws IOException, InterruptedException {
String content = waitForContent();
String content = waitForContent("traces");

return StreamSupport.stream(OBJECT_MAPPER.readTree(content).spliterator(), false)
.map(
Expand All @@ -200,20 +206,44 @@ protected Collection<ExportTraceServiceRequest> waitForTraces()
.collect(Collectors.toList());
}

private String waitForContent() throws IOException, InterruptedException {
/**
* @return the metrics received by the backend mock
*/
protected Collection<ExportMetricsServiceRequest> waitForMetrics()
throws IOException, InterruptedException {
String content = waitForContent("metrics");

return StreamSupport.stream(OBJECT_MAPPER.readTree(content).spliterator(), false)
.map(
it -> {
ExportMetricsServiceRequest.Builder builder =
ExportMetricsServiceRequest.newBuilder();
try {
JsonFormat.parser().merge(OBJECT_MAPPER.writeValueAsString(it), builder);
} catch (InvalidProtocolBufferException | JsonProcessingException e) {
e.printStackTrace();
}
return builder.build();
})
.collect(Collectors.toList());
}

/**
* Waits for specific content from the backend mock
*
* @param type traces, metrics or logs
*/
private String waitForContent(String type) throws IOException, InterruptedException {
long previousSize = 0;
long deadline = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(30);
String content = "[]";
while (System.currentTimeMillis() < deadline) {

Request request =
new Request.Builder()
.url(
String.format(
"http://%s:%d/get-traces",
tracingBackendMock.getServer().getHost(),
tracingBackendMock.getServer().getMappedPort(8080)))
.build();
String url =
String.format(
"http://%s:%d/get-%s",
backendMock.getServer().getHost(), backendMock.getServer().getMappedPort(8080), type);
Request request = new Request.Builder().url(url).build();

try (ResponseBody body = client.newCall(request).execute().body()) {
content = body.string();
Expand All @@ -232,7 +262,8 @@ private String waitForContent() throws IOException, InterruptedException {

/**
* Waits until the instrumentation was applied in the method hooks for the specified amount of
* times. The test should not fail here, if no further update message was found.
* times. The test should not fail here, if no further update message was found. <br>
* We use the log message from {@link MethodHookManager}.
*/
protected void awaitInstrumentationUpdate(int amount) {
String updateMessage =
Expand All @@ -247,7 +278,8 @@ protected void awaitInstrumentationUpdate(int amount) {

/**
* Waits until the configuration was polled one more time. The test should not fail here, if no
* further update message was found.
* further update message was found. <br>
* We use the log message from {@link HttpConfigurationCallback}.
*/
protected void awaitConfigurationUpdate() {
String updateMessage =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
* This class is used to create a mock for a tracing backend. We can write and retrieve traces
* there.
*/
public class TracingBackendMock {
public class ObservabilityBackendMock {

private static final Logger logger = LoggerFactory.getLogger(TracingBackendMock.class);
private static final Logger logger = LoggerFactory.getLogger(ObservabilityBackendMock.class);
private static final DockerImageName MOCK_IMAGE =
DockerImageName.parse(
"ghcr.io/open-telemetry/opentelemetry-java-instrumentation/smoke-test-fake-backend")
Expand All @@ -28,7 +28,7 @@ public class TracingBackendMock {

private final GenericContainer<?> server;

private TracingBackendMock(Network network) {
private ObservabilityBackendMock(Network network) {
server =
new GenericContainer<>(MOCK_IMAGE)
.withNetwork(network)
Expand All @@ -38,8 +38,8 @@ private TracingBackendMock(Network network) {
.withLogConsumer(new Slf4jLogConsumer(logger));
}

static TracingBackendMock create(Network network) {
return new TracingBackendMock(network);
static ObservabilityBackendMock create(Network network) {
return new ObservabilityBackendMock(network);
}

void start() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/* (C) 2024 */
package rocks.inspectit.gepard.agent.integrationtest.spring.metrics;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static rocks.inspectit.gepard.agent.internal.otel.OpenTelemetryAccessor.INSTRUMENTATION_SCOPE_NAME;

import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest;
import io.opentelemetry.proto.metrics.v1.Metric;
import io.opentelemetry.proto.metrics.v1.NumberDataPoint;
import io.opentelemetry.proto.metrics.v1.ResourceMetrics;
import io.opentelemetry.proto.metrics.v1.ScopeMetrics;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import rocks.inspectit.gepard.agent.integrationtest.spring.SpringTestBase;

/**
* Note: Currently self-monitoring is always enabled. <br>
* Later, this should be configurable. Thus, we will have to provide a proper self-monitoring
* configuration.
*/
class SelfMonitoringIntTest extends SpringTestBase {

@Override
protected Map<String, String> getExtraEnv() {
// Set metric export interval to 10s (default 60s)
// Might have to be adjusted
return Map.of("OTEL_METRIC_EXPORT_INTERVAL", "10000");
}

@Test
void shouldRecordMetricForInstrumentedClassWhenScopesAreActive() throws Exception {
configurationServerMock.configServerSetup(configDir + "multiple-scopes.json");
startTarget("/opentelemetry-extensions.jar");
awaitInstrumentationUpdate(1);

Collection<ExportMetricsServiceRequest> exportedMetrics = waitForMetrics();

assertGaugeMetric(exportedMetrics, "inspectit.self.instrumented-classes", 2.0);
}

@Test
void shouldNotRecordMetricWhenScopesAreInactive() throws Exception {
configurationServerMock.configServerSetup(configDir + "empty-config.json");
startTarget("/opentelemetry-extensions.jar");
awaitInstrumentationUpdate(1);

Collection<ExportMetricsServiceRequest> exportedMetrics = waitForMetrics();

assertGaugeMetric(exportedMetrics, "inspectit.self.instrumented-classes", 0.0);
}

/**
* This method asserts that a metric with the given name exists and that the last recorded value
* of this metric equals the expected value. The metric has to be a gauge.
*
* @param exportedMetrics the collection of exported metrics
* @param name the metric name
* @param expectedValue the expected value
*/
private void assertGaugeMetric(
Collection<ExportMetricsServiceRequest> exportedMetrics, String name, Double expectedValue) {
List<Metric> metrics = getMetrics(exportedMetrics);
Optional<Metric> maybeMetric = findMetric(metrics, name);

assertTrue(maybeMetric.isPresent());

List<NumberDataPoint> dataPoints = maybeMetric.get().getGauge().getDataPointsList();
double lastValue = dataPoints.get(dataPoints.size() - 1).getAsDouble();

assertEquals(expectedValue, lastValue);
}

/**
* Maps the provided {@link ExportMetricsServiceRequest}s to simple {@link Metric} objects.
*
* @param exportedMetrics the exported metrics
* @return the collection of metric objects
*/
private List<Metric> getMetrics(Collection<ExportMetricsServiceRequest> exportedMetrics) {
return exportedMetrics.stream()
.flatMap(
exportedMetric ->
exportedMetric.getResourceMetricsList().stream()
.flatMap(
resourceMetrics ->
filterAndExtractMetrics(resourceMetrics.getScopeMetricsList())))
.toList();
}

/**
* Filters and extracts all {@link Metric}s from {@link ScopeMetrics}. We filter for metrics
* created by inspectIT.
*
* @param scopeMetricsList the list of {@link ScopeMetrics} from {@link ResourceMetrics}
* @return the collection of filtered metrics as stream
*/
private Stream<Metric> filterAndExtractMetrics(List<ScopeMetrics> scopeMetricsList) {
return scopeMetricsList.stream()
.filter(
scopeMetrics -> scopeMetrics.getScope().getName().equals(INSTRUMENTATION_SCOPE_NAME))
.flatMap(scopeMetric -> scopeMetric.getMetricsList().stream());
}

/**
* Tries to find the provided metric name in list of metrics.
*
* @param metrics the list of metrics
* @param metricName the metric name to look for
* @return the found metric or empty
*/
private Optional<Metric> findMetric(List<Metric> metrics, String metricName) {
return metrics.stream().filter(metric -> metric.getName().equals(metricName)).findFirst();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import org.junit.jupiter.api.Test;
import rocks.inspectit.gepard.agent.integrationtest.spring.SpringTestBase;

public class ScopeTest extends SpringTestBase {
public class ScopeIntTest extends SpringTestBase {

@Test
void scopeWithoutMethodInstrumentsAllMethods() throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import rocks.inspectit.gepard.agent.integrationtest.spring.SpringTestBase;

/** Should check, if traces are received in our tracing backend according to our configuration. */
class TracingTest extends SpringTestBase {
class TracingIntTest extends SpringTestBase {

private static final String parentSpanName = "WebController.greeting";

Expand Down

0 comments on commit 83ed409

Please sign in to comment.