diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/ClassCodeFilter.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/ClassCodeFilter.java deleted file mode 100644 index 5f9e5c7f86b..00000000000 --- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/ClassCodeFilter.java +++ /dev/null @@ -1,85 +0,0 @@ -package datadog.trace.agent.tooling.bytebuddy; - -import java.util.Arrays; - -/** - * Compact filter that records class membership by their hash and short 'class-code'. - * - *

The 'class-code' includes the length of the package prefix and simple name, as well as the - * first and last characters of the simple name. These elements coupled with the hash of the full - * class name should minimize the probability of collisions without needing to store full names, - * which would otherwise make the filter overly large. - */ -public abstract class ClassCodeFilter { - - private static final int MAX_CAPACITY = 1 << 16; - private static final int MIN_CAPACITY = 1 << 8; - private static final int MAX_HASH_ATTEMPTS = 3; - - protected final long[] slots; - protected final int slotMask; - - protected ClassCodeFilter(int capacity) { - if (capacity < MIN_CAPACITY) { - capacity = MIN_CAPACITY; - } else if (capacity > MAX_CAPACITY) { - capacity = MAX_CAPACITY; - } - - // choose enough slot bits to cover the chosen capacity - slotMask = 0xFFFFFFFF >>> Integer.numberOfLeadingZeros(capacity - 1); - slots = new long[slotMask + 1]; - } - - public final boolean contains(String name) { - int hash = name.hashCode(); - for (int i = 1, h = hash; true; i++) { - long value = slots[slotMask & h]; - if (value == 0) { - return false; - } else if ((int) value == hash) { - return (int) (value >>> 32) == classCode(name); - } else if (i == MAX_HASH_ATTEMPTS) { - return false; - } - h = rehash(h); - } - } - - public final void add(String name) { - int index; - int hash = name.hashCode(); - for (int i = 1, h = hash; true; i++) { - index = slotMask & h; - if (slots[index] == 0) { - break; - } else if (i == MAX_HASH_ATTEMPTS) { - index = slotMask & hash; // overwrite original slot - break; - } - h = rehash(h); - } - slots[index] = (long) classCode(name) << 32 | 0xFFFFFFFFL & hash; - } - - public final void clear() { - Arrays.fill(slots, 0); - } - - /** - * Computes a 32-bit 'class-code' that includes the length of the package prefix and simple name, - * plus the first and last characters of the simple name (each truncated to fit into 8-bits.) - */ - private static int classCode(String name) { - int start = name.lastIndexOf('.') + 1; - int end = name.length() - 1; - int code = 0xFF & start; - code = (code << 8) | (0xFF & name.charAt(start)); - code = (code << 8) | (0xFF & name.charAt(end)); - return (code << 8) | (0xFF & (end - start)); - } - - private static int rehash(int oldHash) { - return Integer.reverseBytes(oldHash * 0x9e3775cd) * 0x9e3775cd; - } -} diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/memoize/Memoizer.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/memoize/Memoizer.java index 604912dabce..1ffb277d4c4 100644 --- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/memoize/Memoizer.java +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/memoize/Memoizer.java @@ -2,6 +2,7 @@ import static net.bytebuddy.matcher.ElementMatchers.not; +import datadog.instrument.utils.ClassNameFilter; import datadog.trace.agent.tooling.InstrumenterMetrics; import datadog.trace.agent.tooling.bytebuddy.TypeInfoCache; import datadog.trace.agent.tooling.bytebuddy.TypeInfoCache.SharedTypeInfo; @@ -61,7 +62,7 @@ enum MatcherKind { private static final boolean namesAreUnique = InstrumenterConfig.get().isResolverNamesAreUnique(); // compact filter recording uninteresting types - private static final NoMatchFilter noMatchFilter = new NoMatchFilter(); + private static final ClassNameFilter noMatchFilter = NoMatchFilter.build(); // caches positive memoized matches private static final TypeInfoCache memos = diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/memoize/NoMatchFilter.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/memoize/NoMatchFilter.java index a4f08d2edf2..ca87baa876d 100644 --- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/memoize/NoMatchFilter.java +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/memoize/NoMatchFilter.java @@ -2,7 +2,7 @@ import static datadog.trace.util.AgentThreadFactory.AGENT_THREAD_GROUP; -import datadog.trace.agent.tooling.bytebuddy.ClassCodeFilter; +import datadog.instrument.utils.ClassNameFilter; import datadog.trace.api.Config; import datadog.trace.api.DDTraceApiInfo; import datadog.trace.api.InstrumenterConfig; @@ -19,21 +19,37 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** Compact filter that records uninteresting types. */ -final class NoMatchFilter extends ClassCodeFilter { +/** Builds a persistable compact filter that records uninteresting types. */ +final class NoMatchFilter { private static final Logger log = LoggerFactory.getLogger(NoMatchFilter.class); - NoMatchFilter() { - super(InstrumenterConfig.get().getResolverNoMatchesSize()); + private static final String TRACER_VERSION_HEADER = "dd-java-agent"; + private static final String NO_MATCH_FILTER_HEADER = "NoMatchFilter"; - // seed filter from previously collected results? + private NoMatchFilter() {} + + public static ClassNameFilter build() { Path noMatchFile = discoverNoMatchFile(); if (null != noMatchFile) { - seedNoMatchFilter(noMatchFile); + // support persisting/restoring no-match results + try { + if (Files.exists(noMatchFile)) { + // restore existing filter with previously collected results + return seedNoMatchFilter(noMatchFile); + } else { + // populate filter from current run and persist at shutdown + ClassNameFilter filter = emptyNoMatchFilter(); + Runtime.getRuntime().addShutdownHook(new ShutdownHook(noMatchFile, filter)); + return filter; + } + } catch (Throwable e) { + log.debug("Unable to use NoMatchFilter at {}", noMatchFile, e); + } } + return emptyNoMatchFilter(); } - static Path discoverNoMatchFile() { + private static Path discoverNoMatchFile() { String cacheDir = InstrumenterConfig.get().getResolverCacheDir(); if (null == cacheDir) { return null; @@ -53,51 +69,49 @@ static Path discoverNoMatchFile() { return Paths.get(cacheDir, noMatchFilterName); } - void seedNoMatchFilter(Path noMatchFile) { - if (!Files.exists(noMatchFile)) { - Runtime.getRuntime().addShutdownHook(new ShutdownHook(noMatchFile)); - } else { - log.debug("Seeding NoMatchFilter from {}", noMatchFile); - try (DataInputStream in = - new DataInputStream(new BufferedInputStream(Files.newInputStream(noMatchFile)))) { - while (true) { - switch (in.readUTF()) { - case "dd-java-agent": - expectVersion(in, DDTraceApiInfo.VERSION); - break; - case "NoMatchFilter": - if (in.readInt() != slots.length) { - throw new IOException("filter size mismatch"); - } - for (int i = 0; i < slots.length; i++) { - slots[i] = in.readLong(); - } - return; - default: - throw new IOException("unexpected content"); - } - } - } catch (IOException e) { - if (log.isDebugEnabled()) { - log.info("Unable to seed NoMatchFilter from {}", noMatchFile, e); - } else { - log.info("Unable to seed NoMatchFilter from {}: {}", noMatchFile, e.getMessage()); + private static ClassNameFilter emptyNoMatchFilter() { + return new ClassNameFilter(InstrumenterConfig.get().getResolverNoMatchesSize()); + } + + private static ClassNameFilter seedNoMatchFilter(Path noMatchFile) { + log.debug("Seeding NoMatchFilter from {}", noMatchFile); + try (DataInputStream in = + new DataInputStream(new BufferedInputStream(Files.newInputStream(noMatchFile)))) { + while (true) { + switch (in.readUTF()) { + case TRACER_VERSION_HEADER: + expectVersion(in, DDTraceApiInfo.VERSION); + break; + case NO_MATCH_FILTER_HEADER: + return ClassNameFilter.readFrom(in); + default: + throw new IOException("unexpected content"); } } + } catch (Throwable e) { + if (log.isDebugEnabled()) { + log.info("Unable to seed NoMatchFilter from {}", noMatchFile, e); + } else { + log.info("Unable to seed NoMatchFilter from {}: {}", noMatchFile, e.getMessage()); + } + return emptyNoMatchFilter(); } } - void persistNoMatchFilter(Path noMatchFile) { + private static void expectVersion(DataInputStream in, String version) throws IOException { + if (!version.equals(in.readUTF())) { + throw new IOException("version mismatch"); + } + } + + static void persistNoMatchFilter(Path noMatchFile, ClassNameFilter filter) { log.debug("Persisting NoMatchFilter to {}", noMatchFile); try (DataOutputStream out = new DataOutputStream(new BufferedOutputStream(Files.newOutputStream(noMatchFile)))) { - out.writeUTF("dd-java-agent"); + out.writeUTF(TRACER_VERSION_HEADER); out.writeUTF(DDTraceApiInfo.VERSION); - out.writeUTF("NoMatchFilter"); - out.writeInt(slots.length); - for (long slot : slots) { - out.writeLong(slot); - } + out.writeUTF(NO_MATCH_FILTER_HEADER); + filter.writeTo(out); } catch (IOException e) { if (log.isDebugEnabled()) { log.info("Unable to persist NoMatchFilter to {}", noMatchFile, e); @@ -107,23 +121,19 @@ void persistNoMatchFilter(Path noMatchFile) { } } - static void expectVersion(DataInputStream in, String version) throws IOException { - if (!version.equals(in.readUTF())) { - throw new IOException("version mismatch"); - } - } - - class ShutdownHook extends Thread { + private static final class ShutdownHook extends Thread { private final Path noMatchFile; + private final ClassNameFilter filter; - ShutdownHook(Path noMatchFile) { + ShutdownHook(Path noMatchFile, ClassNameFilter filter) { super(AGENT_THREAD_GROUP, "dd-NoMatchFilter-persist-hook"); this.noMatchFile = noMatchFile; + this.filter = filter; } @Override public void run() { - persistNoMatchFilter(noMatchFile); + persistNoMatchFilter(noMatchFile, filter); } } } diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/outline/IsPublicFilter.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/outline/IsPublicFilter.java deleted file mode 100644 index 026d4beea6b..00000000000 --- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/outline/IsPublicFilter.java +++ /dev/null @@ -1,11 +0,0 @@ -package datadog.trace.agent.tooling.bytebuddy.outline; - -import datadog.trace.agent.tooling.bytebuddy.ClassCodeFilter; -import datadog.trace.api.InstrumenterConfig; - -/** Compact filter that records public types. */ -final class IsPublicFilter extends ClassCodeFilter { - IsPublicFilter() { - super(InstrumenterConfig.get().getResolverVisibilitySize()); - } -} diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/outline/TypeFactory.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/outline/TypeFactory.java index ba9b29438af..162f3d17555 100644 --- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/outline/TypeFactory.java +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/outline/TypeFactory.java @@ -5,6 +5,7 @@ import static datadog.trace.bootstrap.AgentClassLoading.LOCATING_CLASS; import static net.bytebuddy.dynamic.loading.ClassLoadingStrategy.BOOTSTRAP_LOADER; +import datadog.instrument.utils.ClassNameFilter; import datadog.trace.agent.tooling.InstrumenterMetrics; import datadog.trace.agent.tooling.bytebuddy.ClassFileLocators; import datadog.trace.agent.tooling.bytebuddy.TypeInfoCache; @@ -86,7 +87,8 @@ final class TypeFactory { private static final TypeInfoCache fullTypes = new TypeInfoCache<>(InstrumenterConfig.get().getResolverTypePoolSize()); - static final IsPublicFilter isPublicFilter = new IsPublicFilter(); + static final ClassNameFilter isPublicFilter = + new ClassNameFilter(InstrumenterConfig.get().getResolverVisibilitySize()); /** Small local cache to help deduplicate lookups when matching/transforming. */ private final DDCache deferredTypes = DDCaches.newFixedSizeCache(16);