Skip to content

Commit

Permalink
feature: update methods hooks according to rules
Browse files Browse the repository at this point in the history
  • Loading branch information
EddeCCC committed Oct 18, 2024
1 parent 7c1e499 commit 6f4c877
Show file tree
Hide file tree
Showing 15 changed files with 265 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,33 @@
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.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;
}

public static Builder builder() {
return new Builder();
}

public MethodHookConfiguration getConfiguration() {
return configuration;
}

@Override
Expand All @@ -33,17 +42,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.methodName());
return new InternalInspectitContext(this, spanScope);
}

Expand All @@ -63,13 +72,45 @@ 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.methodName();
return clazz.getSimpleName() + "." + methodName;
}

/** Builder-pattern for method hooks */
public static class Builder {
private MethodHookConfiguration configuration;

private SpanAction spanAction;

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 @@ -66,11 +66,16 @@ 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);
Set<MethodDescription> instrumentedMethods = getInstrumentedMethods(clazz, configuration);

ClassHookConfiguration classConfiguration = new ClassHookConfiguration();
instrumentedMethods.forEach(classConfiguration::putHookConfiguration);
for (MethodDescription method : instrumentedMethods) {
try {
classConfiguration.putHookConfiguration(method, configuration);
} catch (Exception e) {
log.error("Could not create hook configuration for {}.{}", className, method.getName(), e);
}
}

int removeCounter = hookState.removeObsoleteHooks(clazz, instrumentedMethods);
log.debug("Removed {} obsolete method hooks for {}", removeCounter, className);
Expand All @@ -88,7 +93,7 @@ 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
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.configuration.MethodHookConfiguration;
import rocks.inspectit.gepard.agent.instrumentation.hook.util.MethodHookGenerator;

/** Stores the method hook configurations of all instrumented classes. */
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 Down Expand Up @@ -73,16 +73,13 @@ 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);
MethodHookConfiguration currentConfig = getCurrentHookConfiguration(clazz, signature);
if (!newConfig.equals(currentConfig)) {
MethodHook hook = MethodHookGenerator.createHook(newConfig);
setHook(clazz, signature, hook);
operationCounter.addAndGet(1);
}
});
return operationCounter.get();
Expand Down Expand Up @@ -139,16 +136,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) {
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)
.orElse(null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.util.Map;
import java.util.Set;
import net.bytebuddy.description.method.MethodDescription;
import rocks.inspectit.gepard.agent.internal.instrumentation.model.ClassInstrumentationConfiguration;

/**
* Stores the hook configuration of all methods for a specific class. Currently, there is no complex
Expand All @@ -13,16 +14,19 @@
public class ClassHookConfiguration {

/** Set of methods and their hook configuration. Currently, just true. */
private final Map<MethodDescription, Boolean> hookConfigurations;
private final Map<MethodDescription, MethodHookConfiguration> hookConfigurations;

private final MethodHookConfigurationResolver hookResolver;

public ClassHookConfiguration() {
this.hookConfigurations = new HashMap<>();
this.hookResolver = new MethodHookConfigurationResolver();
}

/**
* @return the configuration as map
*/
public Map<MethodDescription, Boolean> asMap() {
public Map<MethodDescription, MethodHookConfiguration> asMap() {
return hookConfigurations;
}

Expand All @@ -38,7 +42,9 @@ public Set<MethodDescription> getMethods() {
*
* @param method the method, which should be put into the configurations
*/
public void putHookConfiguration(MethodDescription method) {
hookConfigurations.put(method, true);
public void putHookConfiguration(
MethodDescription method, ClassInstrumentationConfiguration classConfig) {
MethodHookConfiguration hookConfig = hookResolver.resolve(method, classConfig);
hookConfigurations.put(method, hookConfig);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/* (C) 2024 */
package rocks.inspectit.gepard.agent.instrumentation.hook.configuration;

import java.util.Objects;
import rocks.inspectit.gepard.config.model.instrumentation.rules.RuleTracingConfiguration;

/**
* Configuration for one specific method hook. Currently just for tracing. Later we will add
* entry-/exit-actions and metrics.
*
* @param methodName the name of the hooked method
* @param tracing the tracing configuration
*/
public record MethodHookConfiguration(String methodName, RuleTracingConfiguration tracing) {

@Override
public boolean equals(Object other) {
if (other instanceof MethodHookConfiguration otherConfig)
return methodName.equals(otherConfig.methodName) && tracing.equals(otherConfig.tracing);
return false;
}

@Override
public int hashCode() {
return Objects.hash(methodName, tracing);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/* (C) 2024 */
package rocks.inspectit.gepard.agent.instrumentation.hook.configuration;

import java.util.Set;
import java.util.stream.Collectors;
import net.bytebuddy.description.method.MethodDescription;
import rocks.inspectit.gepard.agent.instrumentation.hook.configuration.exception.ConflictingConfigurationException;
import rocks.inspectit.gepard.agent.internal.instrumentation.model.ClassInstrumentationConfiguration;
import rocks.inspectit.gepard.agent.internal.instrumentation.model.rules.InstrumentationRule;
import rocks.inspectit.gepard.config.model.instrumentation.rules.RuleTracingConfiguration;

/**
* Resolves a {@link ClassInstrumentationConfiguration} to a {@link MethodHookConfiguration} of a
* specific method.
*/
public class MethodHookConfigurationResolver {

/**
* Resolve the configuration for a specific method hook.
*
* @param method the method of the current class
* @param classConfig the instrumentation configuration of the current class
* @return the hook configuration for the provided method
*/
public MethodHookConfiguration resolve(
MethodDescription method, ClassInstrumentationConfiguration classConfig) {

Set<InstrumentationRule> matchedRules =
classConfig.activeRules().stream()
.filter(rule -> rule.methodMatcher().matches(method))
.collect(Collectors.toSet());

String methodName = method.getName();
RuleTracingConfiguration tracing = resolveTracing(matchedRules);
return new MethodHookConfiguration(methodName, tracing);
}

/**
* Resolve the tracing configuration for a specific method hook.
*
* @param rules the rules for the current method
* @return the tracing configuration
*/
private RuleTracingConfiguration resolveTracing(Set<InstrumentationRule> rules) {
boolean allMatch = rules.stream().allMatch(rule -> rule.tracing().getStartSpan());

if (allMatch && !rules.isEmpty()) return rules.stream().findFirst().get().tracing();
else throw new ConflictingConfigurationException("Conflict in rule tracing configuration");
}
}
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);
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
/* (C) 2024 */
package rocks.inspectit.gepard.agent.instrumentation.hook.util;

import net.bytebuddy.description.method.MethodDescription;
import rocks.inspectit.gepard.agent.instrumentation.hook.MethodHook;
import rocks.inspectit.gepard.agent.instrumentation.hook.action.SpanAction;
import rocks.inspectit.gepard.agent.instrumentation.hook.configuration.MethodHookConfiguration;

/** Creates method hook objects */
public class MethodHookGenerator {

private MethodHookGenerator() {}

/**
* Creates an executable method hook based on the given configuration.
*
* @param method the hooked method
* @param hookConfig the configuration for the hook
* @return the created method hook
*/
public static MethodHook createHook(MethodDescription method) {
SpanAction spanAction = new SpanAction();
return new MethodHook(method.getName(), spanAction);
public static MethodHook createHook(MethodHookConfiguration hookConfig) {
MethodHook.Builder builder = MethodHook.builder().setConfiguration(hookConfig);

if (hookConfig.tracing().getStartSpan()) builder.setSpanAction(new SpanAction());

return builder.build();
}
}
Loading

0 comments on commit 6f4c877

Please sign in to comment.