Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/selfmonitoring instrumented classes #30

Merged
merged 6 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading