Skip to content

Commit e8a5a02

Browse files
committed
fix InstrumentationState
1 parent 7301b51 commit e8a5a02

File tree

7 files changed

+105
-81
lines changed

7 files changed

+105
-81
lines changed

src/main/java/rocks/inspectit/gepard/agent/instrumentation/cache/process/BatchInstrumenter.java

+3
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ Set<Class<?>> getNextBatch(int batchSize) {
7070
checkedClassesCount++;
7171

7272
try {
73+
if ("com.example.demo.GreetingController".equals(clazz.getName()))
74+
System.out.println("GREET");
75+
7376
boolean shouldInstrument = configurationResolver.shouldInstrument(clazz);
7477
boolean isInstrumented = instrumentationState.isInstrumented(clazz);
7578

src/main/java/rocks/inspectit/gepard/agent/internal/instrumentation/InstrumentationState.java

+25-13
Original file line numberDiff line numberDiff line change
@@ -2,52 +2,64 @@
22

33
import com.github.benmanes.caffeine.cache.Cache;
44
import com.github.benmanes.caffeine.cache.Caffeine;
5+
import java.util.Map;
56
import java.util.Objects;
67
import rocks.inspectit.gepard.agent.internal.instrumentation.model.ClassInstrumentationConfiguration;
78

89
/** Stores the instrumentation configuration of all instrumented classes. */
910
public class InstrumentationState {
1011

1112
/** Store for every instrumented class */
12-
private final Cache<Class<?>, ClassInstrumentationConfiguration> activeInstrumentations;
13+
private final Cache<InstrumentedType, ClassInstrumentationConfiguration> activeInstrumentations;
1314

1415
private InstrumentationState() {
15-
this.activeInstrumentations = Caffeine.newBuilder().weakKeys().build();
16+
this.activeInstrumentations = Caffeine.newBuilder().build();
1617
}
1718

1819
public static InstrumentationState create() {
1920
return new InstrumentationState();
2021
}
2122

2223
/**
23-
* Checks, if the provided class type needs to be instrumented.
24+
* Checks, if the provided class is already instrumented.
2425
*
2526
* @param clazz the class object
26-
* @return true, if the provided type needs an instrumentation
27+
* @return true, if the provided type is already instrumented.
2728
*/
2829
public boolean isInstrumented(Class<?> clazz) {
29-
ClassInstrumentationConfiguration activeConfig = activeInstrumentations.getIfPresent(clazz);
30+
return activeInstrumentations.asMap().entrySet().stream()
31+
.filter(entry -> entry.getKey().isEqualTo(clazz))
32+
.map(Map.Entry::getValue)
33+
.map(ClassInstrumentationConfiguration::isInstrumented)
34+
.findAny()
35+
.orElse(false);
36+
}
37+
38+
public boolean isInstrumented(InstrumentedType instrumentedType) {
39+
ClassInstrumentationConfiguration activeConfig =
40+
activeInstrumentations.getIfPresent(instrumentedType);
3041

3142
if (Objects.nonNull(activeConfig)) return activeConfig.isInstrumented();
3243
return false;
3344
}
3445

3546
/**
36-
* Adds the provided class type to the active instrumentations
47+
* Adds the instrumented type to the active instrumentations
3748
*
38-
* @param clazz the class object
49+
* @param type the instrumented type
3950
*/
40-
public void addInstrumentation(Class<?> clazz) {
51+
public void addInstrumentedType(InstrumentedType type) {
4152
ClassInstrumentationConfiguration newConfig = new ClassInstrumentationConfiguration(true);
42-
activeInstrumentations.put(clazz, newConfig);
53+
activeInstrumentations.put(type, newConfig);
4354
}
4455

4556
/**
46-
* Removes the provided class type from the active instrumentations
57+
* Removes the type from the active instrumentations
4758
*
48-
* @param clazz the class object
59+
* @param type the uninstrumented type
4960
*/
50-
public void invalidateInstrumentation(Class<?> clazz) {
51-
activeInstrumentations.invalidate(clazz);
61+
public void invalidateInstrumentedType(InstrumentedType type) {
62+
System.out.println("INVALIDATED");
63+
activeInstrumentations.invalidate(type);
5264
}
5365
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package rocks.inspectit.gepard.agent.internal.instrumentation;
2+
3+
import net.bytebuddy.agent.builder.AgentBuilder;
4+
5+
import java.util.Objects;
6+
7+
/**
8+
* Stores the full name as well as the class loader of a specific type.<br>
9+
* Since the method {@link AgentBuilder.Transformer#transform} does not provide a {@code Class}
10+
* object of the transformed type, we need to use the class name and the class loader to store our
11+
* instrumented types.<br>
12+
* We cannot create a {@code Class} object during transformation, since the class's byte-code is
13+
* still modified and not "ready" yet. Thus, {@code Class.forName()} isn't working. However, we
14+
* somehow need to remember which classes have been instrumented, so we store all necessary
15+
* information to identify a specific class.
16+
*/
17+
public class InstrumentedType {
18+
19+
private final String typeName;
20+
21+
private final ClassLoader classLoader;
22+
23+
public InstrumentedType(final String typeName, final ClassLoader classLoader) {
24+
this.typeName = typeName;
25+
this.classLoader = classLoader;
26+
}
27+
28+
/**
29+
* Checks, if the provided class objects references this type
30+
*
31+
* @param clazz the class object
32+
* @return true, if the provided class objects references this type
33+
*/
34+
public boolean isEqualTo(Class<?> clazz) {
35+
return typeName.equals(clazz.getName()) && classLoader.equals(clazz.getClassLoader());
36+
}
37+
38+
@Override
39+
public boolean equals(Object other) {
40+
if (other instanceof InstrumentedType otherType)
41+
return typeName.equals(otherType.typeName) && classLoader.equals(otherType.classLoader);
42+
return false;
43+
}
44+
45+
@Override
46+
public int hashCode() {
47+
return Objects.hash(typeName, classLoader);
48+
}
49+
}

src/main/java/rocks/inspectit/gepard/agent/transformation/DynamicTransformer.java

+7-60
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
44

5-
import java.lang.invoke.TypeDescriptor;
65
import java.security.ProtectionDomain;
76
import java.util.*;
87
import net.bytebuddy.agent.builder.AgentBuilder;
@@ -15,6 +14,7 @@
1514
import org.slf4j.Logger;
1615
import org.slf4j.LoggerFactory;
1716
import rocks.inspectit.gepard.agent.internal.instrumentation.InstrumentationState;
17+
import rocks.inspectit.gepard.agent.internal.instrumentation.InstrumentedType;
1818
import rocks.inspectit.gepard.agent.resolver.ConfigurationResolver;
1919
import rocks.inspectit.gepard.agent.transformation.advice.InspectitAdvice;
2020

@@ -31,16 +31,9 @@ public class DynamicTransformer implements AgentBuilder.Transformer {
3131
/** The instrumentation state of the agent */
3232
private final InstrumentationState instrumentationState;
3333

34-
/**
35-
* Set of instrumented types used by this transformer to prevent to call Class.forName() for every
36-
* call of transform()
37-
*/
38-
private final Set<TypeDescription> instrumentedTypes;
39-
4034
DynamicTransformer(ConfigurationResolver resolver, InstrumentationState instrumentationState) {
4135
this.resolver = resolver;
4236
this.instrumentationState = instrumentationState;
43-
this.instrumentedTypes = new HashSet<>();
4437
}
4538

4639
/**
@@ -60,68 +53,22 @@ public DynamicType.Builder<?> transform(
6053
ClassLoader classLoader,
6154
JavaModule module,
6255
ProtectionDomain protectionDomain) {
56+
InstrumentedType currentType = new InstrumentedType(typeDescription.getName(), classLoader);
6357
if (resolver.shouldInstrument(typeDescription)) {
64-
log.info("Adding transformation to {}", typeDescription.getName()); // TODO log.debug()
58+
log.debug("Adding transformation to {}", typeDescription.getName());
6559

6660
// Currently, all methods of the type are instrumented
6761
ElementMatcher<? super MethodDescription> elementMatcher = isMethod();
6862
builder = builder.visit(Advice.to(InspectitAdvice.class).on(elementMatcher));
6963

7064
// Mark type as instrumented
71-
// TODO das macht noch Probleme im ScopeTest...
72-
addInstrumentation(typeDescription, classLoader);
73-
} else if (instrumentedTypes.contains(typeDescription)) {
74-
log.info("Removing transformation from {}", typeDescription.getName()); // TODO log.debug()
65+
instrumentationState.addInstrumentedType(currentType);
66+
} else if (instrumentationState.isInstrumented(currentType)) {
67+
log.debug("Removing transformation from {}", typeDescription.getName());
7568
// Mark type as uninstrumented or deinstrumented
76-
invalidateInstrumentation(typeDescription, classLoader);
69+
instrumentationState.invalidateInstrumentedType(currentType);
7770
}
7871

7972
return builder;
8073
}
81-
82-
/**
83-
* Marks the class as instrumented.
84-
*
85-
* @param typeDescription the class type
86-
* @param classLoader the classloader, used for accessing the class object
87-
*/
88-
private void addInstrumentation(TypeDescription typeDescription, ClassLoader classLoader) {
89-
Class<?> instrumentedClass = toClass(typeDescription, classLoader);
90-
if (Objects.nonNull(instrumentedClass)) {
91-
instrumentationState.addInstrumentation(instrumentedClass);
92-
instrumentedTypes.add(typeDescription);
93-
}
94-
}
95-
96-
/**
97-
* Removes the class from marked instrumentations.
98-
*
99-
* @param typeDescription the class type description
100-
* @param classLoader the classloader, used for accessing the class object
101-
*/
102-
private void invalidateInstrumentation(TypeDescription typeDescription, ClassLoader classLoader) {
103-
Class<?> deinstrumentedClass = toClass(typeDescription, classLoader);
104-
if (Objects.nonNull(deinstrumentedClass)) {
105-
instrumentationState.invalidateInstrumentation(deinstrumentedClass);
106-
instrumentedTypes.remove(typeDescription);
107-
}
108-
}
109-
110-
/**
111-
* Converts a bytebuddy {@link TypeDescription} to a {@link Class} object. The class is not
112-
* initialized.
113-
*
114-
* @param type the type description
115-
* @param classLoader the classloader, used for accessing the class object
116-
* @return the class object of the provided type description
117-
*/
118-
private Class<?> toClass(TypeDescription type, ClassLoader classLoader) {
119-
Class<?> clazz = null;
120-
try {
121-
clazz = Class.forName(type.getName(), false, classLoader);
122-
} catch (Exception e) {
123-
log.error("Could not update instrumentation state for {}", type.getName(), e);
124-
}
125-
return clazz;
126-
}
12774
}

src/test/java/rocks/inspectit/gepard/agent/instrumentation/cache/process/BatchInstrumenterTest.java

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.lang.instrument.UnmodifiableClassException;
99
import java.util.HashSet;
1010
import java.util.Set;
11+
import net.bytebuddy.description.type.TypeDescription;
1112
import org.junit.jupiter.api.Test;
1213
import org.junit.jupiter.api.extension.ExtendWith;
1314
import org.mockito.Mock;

src/test/java/rocks/inspectit/gepard/agent/integrationtest/IntegrationTestBase.java

-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import com.google.protobuf.util.JsonFormat;
77
import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest;
88
import io.opentelemetry.proto.trace.v1.Span;
9-
109
import java.io.IOException;
1110
import java.util.Collection;
1211
import java.util.Collections;
@@ -30,7 +29,6 @@
3029
import org.testcontainers.utility.MountableFile;
3130
import rocks.inspectit.gepard.agent.integrationtest.utils.OkHttpUtils;
3231

33-
3432
/**
3533
* Base class for integration tests. First starts the Observability Backend Mock and Configuration
3634
* Server Mock as Testcontainers. We can define a target application to test by extending this class

src/test/java/rocks/inspectit/gepard/agent/internal/instrumentation/InstrumentationStateTest.java

+20-6
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,41 @@
33
import static org.junit.jupiter.api.Assertions.assertFalse;
44
import static org.junit.jupiter.api.Assertions.assertTrue;
55

6+
import net.bytebuddy.description.type.TypeDescription;
67
import org.junit.jupiter.api.Test;
78

9+
// TODO Write more tests
10+
811
class InstrumentationStateTest {
912

1013
private static final Class<?> TEST_CLASS = InstrumentationStateTest.class;
1114

15+
private static final InstrumentedType TEST_TYPE = new InstrumentedType(TEST_CLASS.getName(), TEST_CLASS.getClassLoader());
16+
1217
@Test
13-
void typeIsNotInstrumented() {
18+
void classIsNotInstrumented() {
1419
InstrumentationState state = InstrumentationState.create();
1520

1621
boolean isInstrumented = state.isInstrumented(TEST_CLASS);
1722

1823
assertFalse(isInstrumented);
1924
}
2025

26+
@Test
27+
void typeIsNotInstrumented() {
28+
InstrumentationState state = InstrumentationState.create();
29+
30+
boolean isInstrumented = state.isInstrumented(TEST_TYPE);
31+
32+
assertFalse(isInstrumented);
33+
}
34+
2135
@Test
2236
void typeIsInstrumented() {
2337
InstrumentationState state = InstrumentationState.create();
2438

25-
state.addInstrumentation(TEST_CLASS);
26-
boolean isInstrumented = state.isInstrumented(TEST_CLASS);
39+
state.addInstrumentedType(TEST_TYPE);
40+
boolean isInstrumented = state.isInstrumented(TEST_TYPE);
2741

2842
assertTrue(isInstrumented);
2943
}
@@ -32,9 +46,9 @@ void typeIsInstrumented() {
3246
void typeIsDeinstrumented() {
3347
InstrumentationState state = InstrumentationState.create();
3448

35-
state.addInstrumentation(TEST_CLASS);
36-
state.invalidateInstrumentation(TEST_CLASS);
37-
boolean isInstrumented = state.isInstrumented(TEST_CLASS);
49+
state.addInstrumentedType(TEST_TYPE);
50+
state.invalidateInstrumentedType(TEST_TYPE);
51+
boolean isInstrumented = state.isInstrumented(TEST_TYPE);
3852

3953
assertFalse(isInstrumented);
4054
}

0 commit comments

Comments
 (0)