Skip to content

Commit

Permalink
Feature/add rules (#24)
Browse files Browse the repository at this point in the history
* feature: use configuration as dependency

* test: add config as test dependency

* add PAT to workflow

* feature: adapt to config 3.0.2-dev

* fix: adjust artifact path

* use correct version in build.gradle

* feature: add rules containing scopes and tracing actions

* feature: update methods hooks according to rules

* test: refactor current tests

* test: add tests

* rename test files

* apply requested changes

* move configuration

* improve resolveTracing
  • Loading branch information
EddeCCC authored Oct 29, 2024
1 parent 489bbbe commit c58d6ea
Show file tree
Hide file tree
Showing 62 changed files with 1,766 additions and 777 deletions.
6 changes: 3 additions & 3 deletions inspectit-gepard-agent/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ plugins {
}

group 'rocks.inspectit.gepard'
def configVersion = "3.0.1-dev"
def configVersion = "3.0.2-dev"

sourceCompatibility = "17"
targetCompatibility = "17"
Expand Down Expand Up @@ -149,7 +149,7 @@ def mergeModules = tasks.register("mergeModules", Zip) {
dependsOn(copyModules)
from tasks.copyModules.destinationDir
destinationDirectory = layout.buildDirectory.dir('libs').get().asFile
archiveFileName.set("inspectit-gepard-agent-extension-${version}.jar")
archiveFileName.set("inspectit-gepard-agent-extension-${configVersion}.jar")
}

// Copies the output of your submodules to a temporary directory
Expand Down Expand Up @@ -232,7 +232,7 @@ tasks {
Provider<Directory> agentJar = layout.buildDirectory.dir("/libs/inspectit-gepard-agent.jar")

name "inspectit/inspectit-gepard-agent"
tag 'versioned', "hub.docker.com/${name}:${version}"
tag 'versioned', "hub.docker.com/${name}:${configVersion}"
dockerfile file('docker/Dockerfile')
files 'docker/entrypoint.sh', agentJar.get().asFile
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
import rocks.inspectit.gepard.agent.instrumentation.hook.MethodHookManager;
import rocks.inspectit.gepard.agent.instrumentation.hook.MethodHookState;
import rocks.inspectit.gepard.agent.instrumentation.state.InstrumentationState;
import rocks.inspectit.gepard.agent.instrumentation.state.configuration.ConfigurationResolver;
import rocks.inspectit.gepard.agent.instrumentation.state.configuration.InspectitConfigurationHolder;
import rocks.inspectit.gepard.agent.instrumentation.state.configuration.resolver.ConfigurationResolver;
import rocks.inspectit.gepard.agent.internal.otel.OpenTelemetryAccessor;
import rocks.inspectit.gepard.agent.notification.NotificationManager;
import rocks.inspectit.gepard.agent.transformation.TransformationManager;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* (C) 2024 */
package rocks.inspectit.gepard.agent.configuration.persistence.file;

import java.io.FileNotFoundException;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -27,9 +28,11 @@ public InspectitConfiguration readConfiguration() {
try {
String fileContent = configFileAccessor.readFile();
return ConfigurationMapper.toObject(fileContent);
} catch (FileNotFoundException e) {
log.warn("No local configuration file found");
} catch (IOException e) {
log.warn("No local configuration file found.");
return null;
log.error("Error while reading local configuration file", e);
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,39 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rocks.inspectit.gepard.agent.instrumentation.hook.action.SpanAction;
import rocks.inspectit.gepard.agent.instrumentation.hook.configuration.model.MethodHookConfiguration;
import rocks.inspectit.gepard.bootstrap.context.InternalInspectitContext;
import rocks.inspectit.gepard.bootstrap.instrumentation.IMethodHook;

/**
* Each {@link MethodHook} instance defines for a single method which actions are performed. This
* defines for example which generic actions are executed or which metrics are collected. Currently,
* we just log our method calls.
* defines for example which generic actions are executed or which metrics are collected.
*/
public class MethodHook implements IMethodHook {
private static final Logger log = LoggerFactory.getLogger(MethodHook.class);

private final String methodName;
/** The configuration of this method hook */
private final MethodHookConfiguration configuration;

private final SpanAction spanAction;

public MethodHook(String methodName, SpanAction spanAction) {
this.methodName = methodName;
this.spanAction = spanAction;
public MethodHook(Builder builder) {
this.configuration = builder.configuration;
this.spanAction = builder.spanAction;
}

/**
* @return builder for method hooks
*/
public static Builder builder() {
return new Builder();
}

/**
* @return the configuration of this method hook
*/
public MethodHookConfiguration getConfiguration() {
return configuration;
}

@Override
Expand All @@ -33,17 +48,17 @@ public InternalInspectitContext onEnter(Object[] instrumentedMethodArgs, Object
instrumentedMethodArgs.length, thiz.getClass().getName());
System.out.println(message);

String spanName = thiz.getClass().getSimpleName() + "." + methodName;
String spanName = getSpanName(thiz.getClass());
AutoCloseable spanScope = null;

try {
spanScope = spanAction.startSpan(spanName);
} catch (Exception e) {
log.error("Could not execute start-span-action", e);
}
if (Objects.nonNull(spanAction))
try {
spanScope = spanAction.startSpan(spanName);
} catch (Exception e) {
log.error("Could not execute start-span-action", e);
}

// Using our log4j here will not be visible in the target application...
System.out.println("HELLO GEPARD : " + methodName);
System.out.println("HELLO GEPARD : " + configuration.getMethodName());
return new InternalInspectitContext(this, spanScope);
}

Expand All @@ -63,13 +78,47 @@ public void onExit(
System.out.println(message);

AutoCloseable spanScope = context.getSpanScope();
try {
spanAction.endSpan(spanScope);
} catch (Exception e) {
log.error("Could not execute end-span-action", e);
}
if (Objects.nonNull(spanAction))
try {
spanAction.endSpan(spanScope);
} catch (Exception e) {
log.error("Could not execute end-span-action", e);
}

// Using our log4j here will not be visible in the target application...
System.out.println("BYE GEPARD");
}

/**
* @param clazz the class of the method for which a span will be started
* @return the span name in the format 'SimpleClassName.methodName', for instance
* 'MethodHook.getSpanName'
*/
private String getSpanName(Class<?> clazz) {
String methodName = configuration.getMethodName();
return clazz.getSimpleName() + "." + methodName;
}

/** Builder-pattern for method hooks, because not all properties have to be initialized. */
public static class Builder {
private MethodHookConfiguration configuration;

private SpanAction spanAction;

private Builder() {}

public Builder setConfiguration(MethodHookConfiguration configuration) {
this.configuration = configuration;
return this;
}

public Builder setSpanAction(SpanAction spanAction) {
this.spanAction = spanAction;
return this;
}

public MethodHook build() {
return new MethodHook(this);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
import net.bytebuddy.matcher.ElementMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rocks.inspectit.gepard.agent.instrumentation.hook.configuration.ClassHookConfiguration;
import rocks.inspectit.gepard.agent.instrumentation.hook.configuration.HookedMethods;
import rocks.inspectit.gepard.agent.instrumentation.hook.configuration.model.ClassHookConfiguration;
import rocks.inspectit.gepard.agent.instrumentation.hook.configuration.resolver.ClassHookConfigurationResolver;
import rocks.inspectit.gepard.agent.instrumentation.hook.configuration.resolver.MethodHookConfigurationResolver;
import rocks.inspectit.gepard.agent.internal.instrumentation.model.ClassInstrumentationConfiguration;
import rocks.inspectit.gepard.bootstrap.Instances;
import rocks.inspectit.gepard.bootstrap.instrumentation.IHookManager;
Expand All @@ -33,8 +35,13 @@ public class MethodHookManager implements IHookManager {
/** Stores classes and all of their hooked methods. Will be kept up-to-date during runtime. */
private final MethodHookState hookState;

private MethodHookManager(MethodHookState hookState) {
/** Resolves {@link ClassInstrumentationConfiguration}s to {@link ClassHookConfiguration}s */
private final ClassHookConfigurationResolver classHookResolver;

private MethodHookManager(
MethodHookState hookState, ClassHookConfigurationResolver classHookResolver) {
this.hookState = hookState;
this.classHookResolver = classHookResolver;
}

/**
Expand All @@ -46,7 +53,10 @@ public static MethodHookManager create(MethodHookState hookState) {
log.debug("Creating MethodHookManager...");
if (isAlreadySet()) throw new IllegalStateException("Global HookManager already set");

MethodHookManager methodHookManager = new MethodHookManager(hookState);
MethodHookConfigurationResolver methodHookResolver = new MethodHookConfigurationResolver();
ClassHookConfigurationResolver classHookResolver =
new ClassHookConfigurationResolver(methodHookResolver);
MethodHookManager methodHookManager = new MethodHookManager(hookState, classHookResolver);
Instances.hookManager = methodHookManager;
addShutdownHook();
return methodHookManager;
Expand All @@ -66,11 +76,9 @@ public IMethodHook getHook(Class<?> clazz, String methodSignature) {
public void updateHooksFor(Class<?> clazz, ClassInstrumentationConfiguration configuration) {
String className = clazz.getName();
log.debug("Updating hooks for {}", className);
Set<MethodDescription.InDefinedShape> instrumentedMethods =
getInstrumentedMethods(clazz, configuration);

ClassHookConfiguration classConfiguration = new ClassHookConfiguration();
instrumentedMethods.forEach(classConfiguration::putHookConfiguration);
Set<MethodDescription> instrumentedMethods = getInstrumentedMethods(clazz, configuration);
ClassHookConfiguration classConfiguration =
classHookResolver.resolve(instrumentedMethods, configuration, className);

int removeCounter = hookState.removeObsoleteHooks(clazz, instrumentedMethods);
log.debug("Removed {} obsolete method hooks for {}", removeCounter, className);
Expand All @@ -88,12 +96,12 @@ public void updateHooksFor(Class<?> clazz, ClassInstrumentationConfiguration con
* @param configuration the instrumentation configuration for the class
* @return the set of all instrumented methods of the class
*/
private Set<MethodDescription.InDefinedShape> getInstrumentedMethods(
private Set<MethodDescription> getInstrumentedMethods(
Class<?> clazz, ClassInstrumentationConfiguration configuration) {
if (configuration.equals(ClassInstrumentationConfiguration.NO_INSTRUMENTATION))
return Collections.emptySet();

ElementMatcher.Junction<MethodDescription> methodMatcher = configuration.methodMatcher();
ElementMatcher.Junction<MethodDescription> methodMatcher = configuration.getMethodMatcher();
TypeDescription type = TypeDescription.ForLoadedType.of(clazz);

return type.getDeclaredMethods().stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import net.bytebuddy.description.method.MethodDescription;
import rocks.inspectit.gepard.agent.instrumentation.hook.configuration.ClassHookConfiguration;
import rocks.inspectit.gepard.agent.instrumentation.hook.configuration.HookedMethods;
import rocks.inspectit.gepard.agent.instrumentation.hook.util.MethodHookGenerator;
import rocks.inspectit.gepard.agent.instrumentation.hook.configuration.model.ClassHookConfiguration;
import rocks.inspectit.gepard.agent.instrumentation.hook.configuration.model.MethodHookConfiguration;
import rocks.inspectit.gepard.agent.instrumentation.hook.util.MethodHookFactory;

/** Stores the method hook configurations of all instrumented classes. */
public class MethodHookState {
Expand Down Expand Up @@ -41,8 +42,7 @@ public HookedMethods getIfPresent(Class<?> clazz) {
* @param instrumentedMethods the methods, which should be hooked
* @return the amount of hooks removed
*/
public int removeObsoleteHooks(
Class<?> clazz, Set<MethodDescription.InDefinedShape> instrumentedMethods) {
public int removeObsoleteHooks(Class<?> clazz, Set<MethodDescription> instrumentedMethods) {
Set<String> matchedSignatures =
instrumentedMethods.stream().map(this::getSignature).collect(Collectors.toSet());

Expand All @@ -55,7 +55,7 @@ public int removeObsoleteHooks(
.forEach(
signature -> {
removeHook(clazz, signature);
operationCounter.addAndGet(1);
operationCounter.incrementAndGet();
});
return operationCounter.get();
}
Expand All @@ -73,16 +73,14 @@ public int updateHooks(Class<?> clazz, ClassHookConfiguration classConfiguration
classConfiguration
.asMap()
.forEach(
(method, active) -> {
// Currently always true, later we should compare the current with the new config
if (active) {
String signature = getSignature(method);
Optional<MethodHook> maybeHook = getCurrentHook(clazz, signature);
if (maybeHook.isEmpty()) {
MethodHook hook = MethodHookGenerator.createHook(method);
setHook(clazz, signature, hook);
operationCounter.addAndGet(1);
}
(method, newConfig) -> {
String signature = getSignature(method);
Optional<MethodHookConfiguration> maybeConfig =
getCurrentHookConfiguration(clazz, signature);
if (maybeConfig.isEmpty() || !newConfig.equals(maybeConfig.get())) {
MethodHook hook = MethodHookFactory.createHook(newConfig);
setHook(clazz, signature, hook);
operationCounter.incrementAndGet();
}
});
return operationCounter.get();
Expand All @@ -109,7 +107,8 @@ String getSignature(MethodDescription methodDescription) {
}

/**
* Overwrite the hook for a specific method of the provided class.
* Overwrite the hook for a specific method of the provided class. We overwrite the complete hook,
* to prevent any side effects during execution.
*
* @param declaringClass the class containing the method
* @param methodSignature the method signature to be hooked
Expand Down Expand Up @@ -139,16 +138,18 @@ void removeHook(Class<?> declaringClass, String methodSignature) {
}

/**
* Returns the hook for the specific method of the provided class.
* Returns the hook configuration for the specific method of the provided class.
*
* @param clazz the class containing the method
* @param methodSignature the method, which might be hooked
* @return the hook of the method, if existing
*/
@VisibleForTesting
Optional<MethodHook> getCurrentHook(Class<?> clazz, String methodSignature) {
Optional<MethodHookConfiguration> getCurrentHookConfiguration(
Class<?> clazz, String methodSignature) {
HookedMethods hookedMethods = hooks.getIfPresent(clazz);
return Optional.ofNullable(hookedMethods)
.map(methods -> methods.getActiveHook(methodSignature));
.map(methods -> methods.getActiveHook(methodSignature))
.map(MethodHook::getConfiguration);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* (C) 2024 */
package rocks.inspectit.gepard.agent.instrumentation.hook.configuration.exception;

/** Exception errors, while finding conflicts inside the instrumentation configuration. */
public class ConflictingConfigurationException extends RuntimeException {

public ConflictingConfigurationException(String message) {
super(message);
}
}
Loading

0 comments on commit c58d6ea

Please sign in to comment.