Return TypePool.Resolution that caches
Caching focused on the expensive calls primarily used by our instrumentation.
MODIFY
dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/DDCachingPoolStrategy.java
dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/DDCachingPoolStrategy.java
@@ -6,7 +6,11 @@ import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import java.lang.ref.WeakReference;
import lombok.extern.slf4j.Slf4j;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeList;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.pool.TypePool;
@@ -93,12 +97,12 @@ public class DDCachingPoolStrategy implements PoolStrategy {
return createCachingTypePool(loaderHash, loaderRef, classFileLocator);
}
- private final TypePool.CacheProvider createCacheProvider(
+ private TypePool.CacheProvider createCacheProvider(
final int loaderHash, final WeakReference<ClassLoader> loaderRef) {
return new SharedResolutionCacheAdapter(loaderHash, loaderRef, sharedResolutionCache);
}
- private final TypePool createCachingTypePool(
+ private TypePool createCachingTypePool(
final int loaderHash,
final WeakReference<ClassLoader> loaderRef,
final ClassFileLocator classFileLocator) {
@@ -108,7 +112,7 @@ public class DDCachingPoolStrategy implements PoolStrategy {
TypePool.Default.ReaderMode.FAST);
}
- private final TypePool createCachingTypePool(
+ private TypePool createCachingTypePool(
final TypePool.CacheProvider cacheProvider, final ClassFileLocator classFileLocator) {
return new TypePool.Default.WithLazyResolution(
cacheProvider, classFileLocator, TypePool.Default.ReaderMode.FAST);
@@ -197,7 +201,7 @@ public class DDCachingPoolStrategy implements PoolStrategy {
static final class SharedResolutionCacheAdapter implements TypePool.CacheProvider {
private static final String OBJECT_NAME = "java.lang.Object";
private static final TypePool.Resolution OBJECT_RESOLUTION =
- new TypePool.Resolution.Simple(TypeDescription.OBJECT);
+ new TypePool.Resolution.Simple(new CachingTypeDescription(TypeDescription.OBJECT));
private final int loaderHash;
private final WeakReference<ClassLoader> loaderRef;
@@ -228,12 +232,13 @@ public class DDCachingPoolStrategy implements PoolStrategy {
}
@Override
- public TypePool.Resolution register(
- final String className, final TypePool.Resolution resolution) {
+ public TypePool.Resolution register(final String className, TypePool.Resolution resolution) {
if (OBJECT_NAME.equals(className)) {
return resolution;
}
+ resolution = new CachingResolution(resolution);
+
sharedResolutionCache.put(new TypeCacheKey(loaderHash, loaderRef, className), resolution);
return resolution;
}
@@ -243,4 +248,90 @@ public class DDCachingPoolStrategy implements PoolStrategy {
// Allowing the high-level eviction policy make the clearing decisions
}
}
+
+ private static class CachingResolution implements TypePool.Resolution {
+ private final TypePool.Resolution delegate;
+ private TypeDescription cachedResolution;
+
+ public CachingResolution(final TypePool.Resolution delegate) {
+
+ this.delegate = delegate;
+ }
+
+ @Override
+ public boolean isResolved() {
+ return delegate.isResolved();
+ }
+
+ @Override
+ public TypeDescription resolve() {
+ // Intentionally not "thread safe". Duplicate work deemed an acceptable trade-off.
+ if (cachedResolution == null) {
+ cachedResolution = new CachingTypeDescription(delegate.resolve());
+ }
+ return cachedResolution;
+ }
+ }
+
+ /**
+ * TypeDescription implementation that delegates and caches the results for the expensive calls
+ * commonly used by our instrumentation.
+ */
+ private static class CachingTypeDescription
+ extends TypeDescription.AbstractBase.OfSimpleType.WithDelegation {
+ private final TypeDescription delegate;
+
+ // These fields are intentionally not "thread safe".
+ // Duplicate work deemed an acceptable trade-off.
+ private Generic superClass;
+ private TypeList.Generic interfaces;
+ private AnnotationList annotations;
+ private MethodList<MethodDescription.InDefinedShape> methods;
+
+ public CachingTypeDescription(final TypeDescription delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ protected TypeDescription delegate() {
+ return delegate;
+ }
+
+ @Override
+ public Generic getSuperClass() {
+ if (superClass == null) {
+ superClass = delegate.getSuperClass();
+ }
+ return superClass;
+ }
+
+ @Override
+ public TypeList.Generic getInterfaces() {
+ if (interfaces == null) {
+ interfaces = delegate.getInterfaces();
+ }
+ return interfaces;
+ }
+
+ @Override
+ public AnnotationList getDeclaredAnnotations() {
+ if (annotations == null) {
+ annotations = delegate.getDeclaredAnnotations();
+ }
+ return annotations;
+ }
+
+ @Override
+ public MethodList<MethodDescription.InDefinedShape> getDeclaredMethods() {
+ if (methods == null) {
+ methods = delegate.getDeclaredMethods();
+ }
+ return methods;
+ }
+
+ @Override
+ public String getName() {
+ return delegate.getName();
+ }
+ }
}

MODIFY
dd-java-agent/benchmark/benchmark.gradle
dd-java-agent/benchmark/benchmark.gradle
@@ -10,14 +10,14 @@ dependencies {
}
jmh {
- timeUnit = 'us' // Output time unit. Available time units are: [m, s, ms, us, ns].
+ timeUnit = 'ms' // Output time unit. Available time units are: [m, s, ms, us, ns].
benchmarkMode = ['avgt']
timeOnIteration = '20s'
iterations = 1 // Number of measurement iterations to do.
fork = 1 // How many times to forks a single benchmark. Use 0 to disable forking altogether
jvmArgs = ["-Ddd.jmxfetch.enabled=false", "-Ddd.writer.type=LoggingWriter"]
// jvmArgs += ["-XX:+UnlockDiagnosticVMOptions", "-XX:+DebugNonSafepoints", "-XX:StartFlightRecording=delay=5s,dumponexit=true,name=jmh-benchmark,filename=${rootDir}/dd-java-agent/benchmark/build/reports/jmh/jmh-benchmark.jfr"]
-// jvmArgs += ["-agentpath:${rootDir}/dd-java-agent/benchmark/src/jmh/resources/libasyncProfiler.so=start,collapsed,file=${rootDir}/dd-java-agent/benchmark/build/reports/jmh/profiler.txt"]
+// jvmArgs += ["-agentpath:${rootDir}/dd-java-agent/benchmark/src/jmh/resources/libasyncProfiler.so=start,collapsed,file=${rootDir}/dd-java-agent/benchmark/build/reports/jmh/profiler.txt".toString()]
failOnError = true // Should JMH fail immediately if any benchmark had experienced the unrecoverable error?
warmup = '5s' // Time to spend at each warmup iteration.
// warmupBatchSize = 10 // Warmup batch size: number of benchmark method calls per operation.

MODIFY
dd-smoke-tests/springboot/src/main/java/datadog/smoketest/springboot/SpringbootApplication.java
dd-smoke-tests/springboot/src/main/java/datadog/smoketest/springboot/SpringbootApplication.java
package datadog.smoketest.springboot;
+import java.lang.management.ManagementFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -8,5 +9,6 @@ public class SpringbootApplication {
public static void main(final String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
+ System.out.println("Started in " + ManagementFactory.getRuntimeMXBean().getUptime() + "ms");
}
}

Return TypePool.Resolution that caches

Caching focused on the expensive calls primarily used by our instrumentation.