Skip to content

Commit

Permalink
[WFLY-19351] Add Micrometer test to verify that applications with sha…
Browse files Browse the repository at this point in the history
…red metrics names are not merged when exported

Add test with multiple war deployments
Add test with ear with multiple war subdeployments
Rework how Prometheus metrics are retrieved and handled
Add test to verify that metrics with identical names are exported separately
Add a TestEnricher to manage lifecycle and injection of the Otel container
  • Loading branch information
jasondlee committed Jun 25, 2024
1 parent 06d7a63 commit 8bd50a0
Show file tree
Hide file tree
Showing 25 changed files with 616 additions and 209 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.wildfly.test.integration.observability;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER})
public @interface TestContainer {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package org.wildfly.test.integration.observability;

import org.jboss.arquillian.test.spi.TestEnricher;
import org.jboss.as.test.shared.util.AssumeTestGroupUtil;
import org.wildfly.test.integration.observability.container.OpenTelemetryCollectorContainer;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.List;

public class TestContainersEnricher implements TestEnricher {
private static final ThreadLocal<OpenTelemetryCollectorContainer> otelCollectorContainer = new ThreadLocal<>();

@Override
public void enrich(Object testCase) {
AssumeTestGroupUtil.assumeDockerAvailable();

for (Field field : getFieldsWithAnnotation(testCase.getClass())) {
OpenTelemetryCollectorContainer value = lookup(field.getType());
try {
if (!field.canAccess(testCase)) {
field.setAccessible(true);
}
field.set(testCase, value);
} catch (Exception e) {
throw new RuntimeException("Could not set value on field " + field + " using " + value, e);
}
}
}

private OpenTelemetryCollectorContainer lookup(Class<?> type) {
if (type.isAssignableFrom(OpenTelemetryCollectorContainer.class)) {

OpenTelemetryCollectorContainer container = otelCollectorContainer.get();
if (container == null) {
container = new OpenTelemetryCollectorContainer();
container.start();
otelCollectorContainer.set(container);
}
return container;
}
return null;
}

@Override
public Object[] resolve(Method method) {
return new Object[0];
}

private List<Field> getFieldsWithAnnotation(final Class<?> source) {
return AccessController.doPrivileged((PrivilegedAction<List<Field>>) () -> {
List<Field> foundFields = new ArrayList<>();
Class<?> nextSource = source;
while (nextSource != Object.class) {
for (Field field : nextSource.getDeclaredFields()) {
if (field.isAnnotationPresent(TestContainer.class)) {
field.setAccessible(true);
foundFields.add(field);
}
}
nextSource = nextSource.getSuperclass();
}
return foundFields;
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.wildfly.test.integration.observability;

import org.jboss.arquillian.core.spi.LoadableExtension;
import org.jboss.arquillian.test.spi.TestEnricher;

public class TestContainersExtension implements LoadableExtension {
@Override
public void register(ExtensionBuilder builder) {
builder.service(TestEnricher.class, TestContainersEnricher.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
*/
package org.wildfly.test.integration.observability.container;

import java.time.Duration;
import java.util.List;

import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.WaitAllStrategy;
import org.testcontainers.containers.wait.strategy.WaitStrategy;
import org.testcontainers.utility.DockerImageName;

import java.time.Duration;
import java.util.List;

public abstract class BaseContainer<SELF extends GenericContainer<SELF>> extends GenericContainer<SELF> {
private final String containerName;
private final List<Integer> exposedPorts;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@
*/
package org.wildfly.test.integration.observability.container;

import java.util.List;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.WebTarget;

import org.junit.Assert;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.wait.strategy.Wait;
import org.wildfly.common.annotation.NotNull;
import org.wildfly.test.integration.observability.opentelemetry.jaeger.JaegerResponse;
import org.wildfly.test.integration.observability.opentelemetry.jaeger.JaegerTrace;

import java.util.List;

/*
* This class is really intended to be called ONLY from OpenTelemetryCollectorContainer. Any test working with
* tracing data should be passing through the otel collector and any methods on its Container.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,22 @@
*/
package org.wildfly.test.integration.observability.container;

import java.util.Collections;
import java.util.List;

import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.WebTarget;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.utility.MountableFile;
import org.wildfly.common.annotation.NotNull;
import org.wildfly.test.integration.observability.opentelemetry.jaeger.JaegerTrace;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class OpenTelemetryCollectorContainer extends BaseContainer<OpenTelemetryCollectorContainer> {
private static OpenTelemetryCollectorContainer INSTANCE = null;
private static JaegerContainer jaegerContainer;
Expand All @@ -29,13 +36,20 @@ public class OpenTelemetryCollectorContainer extends BaseContainer<OpenTelemetry
private String prometheusUrl;


private OpenTelemetryCollectorContainer() {
public OpenTelemetryCollectorContainer() {
super("OpenTelemetryCollector",
"otel/opentelemetry-collector",
"0.93.0",
List.of(OTLP_GRPC_PORT, OTLP_HTTP_PORT, HEALTH_CHECK_PORT, PROMETHEUS_PORT),
List.of(Wait.forHttp("/").forPort(HEALTH_CHECK_PORT)));
withNetwork(Network.SHARED)
.withCopyToContainer(MountableFile.forClasspathResource(
"org/wildfly/test/integration/observability/container/otel-collector-config.yaml"),
OpenTelemetryCollectorContainer.OTEL_COLLECTOR_CONFIG_YAML)
.withCommand("--config " + OpenTelemetryCollectorContainer.OTEL_COLLECTOR_CONFIG_YAML);
jaegerContainer = JaegerContainer.getInstance();
}
/*
@NotNull
public static synchronized OpenTelemetryCollectorContainer getInstance() {
Expand All @@ -52,6 +66,7 @@ public static synchronized OpenTelemetryCollectorContainer getInstance() {
}
return INSTANCE;
}
*/

@Override
public void start() {
Expand Down Expand Up @@ -86,4 +101,63 @@ public String getPrometheusUrl() {
public List<JaegerTrace> getTraces(String serviceName) throws InterruptedException {
return (jaegerContainer != null ? jaegerContainer.getTraces(serviceName) : Collections.emptyList());
}

public List<PrometheusMetric> fetchMetrics(String nameToMonitor) throws InterruptedException {
String body = "";
try (Client client = ClientBuilder.newClient()) {
WebTarget target = client.target(prometheusUrl);

int attemptCount = 0;
boolean found = false;

// Request counts can vary. Setting high to help ensure test stability
while (!found && attemptCount < 30) {
// Wait to give Micrometer time to export
Thread.sleep(1000);

body = target.request().get().readEntity(String.class);
found = body.contains(nameToMonitor);
attemptCount++;
}
}

return buildPrometheusMetrics(body);
}

private List<PrometheusMetric> buildPrometheusMetrics(String body) {
if (body.isEmpty()) {
return Collections.emptyList();
}
String[] entries = body.split("\n");
Map<String, String> help = new HashMap<>();
Map<String, String> type = new HashMap<>();
List<PrometheusMetric> metrics = new LinkedList<>();
Arrays.stream(entries).forEach(e -> {
if (e.startsWith("# HELP")) {
extractMetadata(help, e);
} else if (e.startsWith("# TYPE")) {
extractMetadata(type, e);
} else {
String[] parts = e.split("[{}]");
String key = parts[0];
Map<String, String> tags = Arrays.stream(parts[1].split(","))
.map(t -> t.split("="))
.collect(Collectors.toMap(i -> i[0],
i -> i[1]
.replaceAll("^\"", "")
.replaceAll("\"$", "")
));
metrics.add(new PrometheusMetric(key, tags, parts[2].trim(), type.get(key), help.get(key)));
}
});

return metrics;
}

private void extractMetadata(Map<String, String> target, String source) {
String[] parts = source.split(" ");
target.put(parts[2],
Arrays.stream(Arrays.copyOfRange(parts, 3, parts.length))
.reduce("", (total, element) -> total + " " + element));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright The WildFly Authors
* SPDX-License-Identifier: Apache-2.0
*/
package org.wildfly.test.integration.observability.container;

import java.util.Collections;
import java.util.Map;

public class PrometheusMetric {
private final String key;
private final Map<String, String> tags;
private final String value;
private final String type;
private final String help;

public PrometheusMetric(String key,
Map<String, String> tags,
String value,
String type,
String help) {
this.key = key;
this.tags = Collections.unmodifiableMap(tags);
this.value = value;
this.type = type;
this.help = help;
}

public String getKey() {
return key;
}

public Map<String, String> getTags() {
return tags;
}

public String getValue() {
return value;
}

public String getType() {
return type;
}

public String getHelp() {
return help;
}

@Override
public String toString() {
return "PrometheusMetric{" +
"key='" + key + '\'' +
", tags=" + tags +
", value='" + value + '\'' +
", type='" + type + '\'' +
", help='" + help + '\'' +
'}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,16 @@

package org.wildfly.test.integration.observability.micrometer;

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.Timer;
import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.Timer;

/**
* @author <a href="mailto:jasondlee@redhat.com">Jason Lee</a>
*/
Expand All @@ -38,7 +37,7 @@ public double getCount() {

timer.record(() -> {
try {
Thread.sleep((long) (Math.random() * 1000L));
Thread.sleep((long) (Math.random() * 100L));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
Expand Down
Loading

0 comments on commit 8bd50a0

Please sign in to comment.