Skip to content

Commit

Permalink
Persist NoMatchFilter
Browse files Browse the repository at this point in the history
  • Loading branch information
mcculls committed Apr 20, 2023
1 parent be39176 commit f068ff0
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
package datadog.trace.agent.tooling.bytebuddy.memoize;

import static datadog.trace.util.AgentThreadFactory.AGENT_THREAD_GROUP;

import datadog.trace.api.Config;
import datadog.trace.api.DDTraceApiInfo;
import datadog.trace.api.InstrumenterConfig;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Compact filter that records the hash and short 'class-code' for uninteresting types.
Expand All @@ -12,8 +28,11 @@
* which would otherwise make the no-match filter overly large.
*/
final class NoMatchFilter {
private static final Logger log = LoggerFactory.getLogger(NoMatchFilter.class);

private static final int MAX_CAPACITY = 1 << 16;
private static final int MIN_CAPACITY = 1 << 8;
private static final int MAX_HASH_ATTEMPTS = 3;

private static final long[] slots;
private static final int slotMask;
Expand All @@ -25,9 +44,16 @@ final class NoMatchFilter {
} 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];

// seed filter from previously collected results?
Path noMatchFile = discoverNoMatchFile();
if (null != noMatchFile) {
seedNoMatchFilter(noMatchFile);
}
}

public static boolean contains(String name) {
Expand All @@ -38,7 +64,7 @@ public static boolean contains(String name) {
return false;
} else if ((int) value == hash) {
return (int) (value >>> 32) == classCode(name);
} else if (i == 3) {
} else if (i == MAX_HASH_ATTEMPTS) {
return false;
}
h = rehash(h);
Expand All @@ -52,8 +78,8 @@ public static void add(String name) {
index = slotMask & h;
if (slots[index] == 0) {
break;
} else if (i == 3) {
index = slotMask & hash;
} else if (i == MAX_HASH_ATTEMPTS) {
index = slotMask & hash; // overwrite original slot
break;
}
h = rehash(h);
Expand Down Expand Up @@ -81,4 +107,98 @@ private static int classCode(String name) {
private static int rehash(int oldHash) {
return Integer.reverseBytes(oldHash * 0x9e3775cd) * 0x9e3775cd;
}

static Path discoverNoMatchFile() {
String cacheDir = InstrumenterConfig.get().getResolverCacheDir();
if (null == cacheDir) {
return null;
}

// use different file for each tracer + service combination
String filterKey =
DDTraceApiInfo.VERSION
+ "/"
+ Config.get().getServiceName()
+ "/"
+ Config.get().getVersion();

String noMatchFilterName =
UUID.nameUUIDFromBytes(filterKey.getBytes(StandardCharsets.UTF_8)) + "-nomatch.filter";

return Paths.get(cacheDir, noMatchFilterName);
}

static 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());
}
}
}
}

static void persistNoMatchFilter(Path noMatchFile) {
log.debug("Persisting NoMatchFilter to {}", noMatchFile);
try (DataOutputStream out =
new DataOutputStream(new BufferedOutputStream(Files.newOutputStream(noMatchFile)))) {
out.writeUTF("dd-java-agent");
out.writeUTF(DDTraceApiInfo.VERSION);
out.writeUTF("NoMatchFilter");
out.writeInt(slots.length);
for (long slot : slots) {
out.writeLong(slot);
}
} catch (IOException e) {
if (log.isDebugEnabled()) {
log.info("Unable to persist NoMatchFilter to {}", noMatchFile, e);
} else {
log.info("Unable to persist NoMatchFilter to {}: {}", noMatchFile, e.getMessage());
}
}
}

static void expectVersion(DataInputStream in, String version) throws IOException {
if (!version.equals(in.readUTF())) {
throw new IOException("version mismatch");
}
}

static class ShutdownHook extends Thread {
private final Path noMatchFile;

ShutdownHook(Path noMatchFile) {
super(AGENT_THREAD_GROUP, "dd-NoMatchFilter-persist-hook");
this.noMatchFile = noMatchFile;
}

@Override
public void run() {
persistNoMatchFilter(noMatchFile);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ public final class TraceInstrumentationConfig {
"spring-data.repository.interface.resource-name";

public static final String RESOLVER_CACHE_CONFIG = "resolver.cache.config";
public static final String RESOLVER_CACHE_DIR = "resolver.cache.dir";
public static final String RESOLVER_USE_LOADCLASS = "resolver.use.loadclass";
public static final String RESOLVER_RESET_INTERVAL = "resolver.reset.interval";
public static final String RESOLVER_NAMES_ARE_UNIQUE = "resolver.names.are.unique";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import static datadog.trace.api.config.TraceInstrumentationConfig.LOGS_MDC_TAGS_INJECTION_ENABLED;
import static datadog.trace.api.config.TraceInstrumentationConfig.MEASURE_METHODS;
import static datadog.trace.api.config.TraceInstrumentationConfig.RESOLVER_CACHE_CONFIG;
import static datadog.trace.api.config.TraceInstrumentationConfig.RESOLVER_CACHE_DIR;
import static datadog.trace.api.config.TraceInstrumentationConfig.RESOLVER_NAMES_ARE_UNIQUE;
import static datadog.trace.api.config.TraceInstrumentationConfig.RESOLVER_RESET_INTERVAL;
import static datadog.trace.api.config.TraceInstrumentationConfig.RESOLVER_USE_LOADCLASS;
Expand Down Expand Up @@ -93,6 +94,7 @@ public class InstrumenterConfig {
private final List<String> excludedCodeSources;

private final ResolverCacheConfig resolverCacheConfig;
private final String resolverCacheDir;
private final boolean resolverNamesAreUnique;
private final boolean resolverUseLoadClass;
private final int resolverResetInterval;
Expand Down Expand Up @@ -170,6 +172,7 @@ private InstrumenterConfig() {
resolverCacheConfig =
configProvider.getEnum(
RESOLVER_CACHE_CONFIG, ResolverCacheConfig.class, ResolverCacheConfig.DEFAULT);
resolverCacheDir = configProvider.getString(RESOLVER_CACHE_DIR);
resolverNamesAreUnique = configProvider.getBoolean(RESOLVER_NAMES_ARE_UNIQUE, true);
resolverUseLoadClass = configProvider.getBoolean(RESOLVER_USE_LOADCLASS, true);
resolverResetInterval =
Expand Down Expand Up @@ -315,6 +318,10 @@ public int getResolverTypePoolSize() {
return resolverCacheConfig.typePoolSize();
}

public String getResolverCacheDir() {
return resolverCacheDir;
}

public boolean isResolverNamesAreUnique() {
return resolverNamesAreUnique;
}
Expand Down Expand Up @@ -420,6 +427,8 @@ public String toString() {
+ excludedCodeSources
+ ", resolverCacheConfig="
+ resolverCacheConfig
+ ", resolverCacheDir="
+ resolverCacheDir
+ ", resolverNamesAreUnique="
+ resolverNamesAreUnique
+ ", resolverUseLoadClass="
Expand Down

0 comments on commit f068ff0

Please sign in to comment.