Skip to content

Commit

Permalink
Feature: selfmonitoring instrumented classes (#30)
Browse files Browse the repository at this point in the history
* feature: add MetricFactory

* little refactor

* test: add MetricFactoryTest

* refactor MetricFactoryTest

* test: add self-monitoring int-test

* fix sonarcloud issues
  • Loading branch information
EddeCCC authored Nov 21, 2024
1 parent edb8353 commit b367a60
Show file tree
Hide file tree
Showing 10 changed files with 293 additions and 32 deletions.
1 change: 1 addition & 0 deletions inspectit-gepard-agent/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ dependencies {
testCompileOnly("io.opentelemetry.javaagent:opentelemetry-javaagent-extension-api")
testCompileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi")
testCompileOnly("io.opentelemetry.javaagent:opentelemetry-javaagent-bootstrap")
testCompileOnly("io.opentelemetry:opentelemetry-exporter-logging")

testImplementation("org.junit.jupiter:junit-jupiter-api:${versions.junit}")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${versions.junit}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import io.opentelemetry.api.metrics.ObservableDoubleMeasurement;
import java.util.Objects;
import java.util.function.Consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rocks.inspectit.gepard.agent.instrumentation.hook.MethodHookManager;
import rocks.inspectit.gepard.agent.instrumentation.state.configuration.resolver.ConfigurationResolver;
import rocks.inspectit.gepard.agent.internal.instrumentation.InstrumentedType;
import rocks.inspectit.gepard.agent.internal.instrumentation.model.ClassInstrumentationConfiguration;
import rocks.inspectit.gepard.agent.internal.metrics.MetricFactory;

/** Stores the instrumentation configuration of all instrumented classes. */
public class InstrumentationState {
Expand All @@ -36,7 +39,9 @@ private InstrumentationState(
*/
public static InstrumentationState create(
ConfigurationResolver configurationResolver, MethodHookManager methodHookManager) {
return new InstrumentationState(configurationResolver, methodHookManager);
InstrumentationState state = new InstrumentationState(configurationResolver, methodHookManager);
state.setUpSelfMonitoring();
return state;
}

/**
Expand Down Expand Up @@ -124,4 +129,11 @@ private void updateMethodHooks(
log.error("There was an error while updating the hooks of class {}", clazz.getName(), e);
}
}

/** Sets up self-monitoring to record the amount of instrumented classes. */
private void setUpSelfMonitoring() {
Consumer<ObservableDoubleMeasurement> callback =
(measurement) -> measurement.record(activeInstrumentations.estimatedSize());
MetricFactory.createObservableDoubleGauge("inspectit.self.instrumented-classes", callback);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/* (C) 2024 */
package rocks.inspectit.gepard.agent.internal.metrics;

import io.opentelemetry.api.metrics.ObservableDoubleGauge;
import io.opentelemetry.api.metrics.ObservableDoubleMeasurement;
import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader;
import java.util.function.Consumer;
import rocks.inspectit.gepard.agent.internal.otel.OpenTelemetryAccessor;

/** Creates new OpenTelemetry instruments (metrics). */
public class MetricFactory {

private MetricFactory() {}

/**
* Creates an observable gauge metric. This gauge will record a measurement via calling the
* callback function everytime it is observed by the {@link PeriodicMetricReader}.
*
* @param name the name of the gauge
* @param callback the callback function to record a measurement
* @return the created gauge
*/
public static ObservableDoubleGauge createObservableDoubleGauge(
String name, Consumer<ObservableDoubleMeasurement> callback) {
return OpenTelemetryAccessor.getMeter().gaugeBuilder(name).buildWithCallback(callback);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.trace.Tracer;

/**
Expand All @@ -13,7 +14,7 @@
public class OpenTelemetryAccessor {

/** The instrumentation scope name we use for our spans and metrics */
public static final String INSTRUMENTATION_SCOPE_NAME = "inspectit-gepard";
public static final String INSTRUMENTATION_SCOPE_NAME = "rocks.inspectit.gepard";

/** Our global OpenTelemetry instance */
private static OpenTelemetry openTelemetry;
Expand All @@ -36,4 +37,11 @@ public static void setOpenTelemetry(OpenTelemetry otel) {
public static Tracer getTracer() {
return openTelemetry.getTracer(INSTRUMENTATION_SCOPE_NAME);
}

/**
* @return the meter to create metric instruments
*/
public static Meter getMeter() {
return openTelemetry.getMeter(INSTRUMENTATION_SCOPE_NAME);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
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;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import okhttp3.OkHttpClient;
Expand All @@ -32,6 +32,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 +74,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 +133,7 @@ private GenericContainer<?> buildTargetContainer(String agentPath, String extens

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

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

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

Expand Down Expand Up @@ -182,9 +184,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 @@ -197,23 +202,47 @@ protected Collection<ExportTraceServiceRequest> waitForTraces()
}
return builder.build();
})
.collect(Collectors.toList());
.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();
})
.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 +261,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 +277,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
Loading

0 comments on commit b367a60

Please sign in to comment.