Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions dd-java-agent/agent-crashtracking/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,18 @@ dependencies {
implementation project(':internal-api')
implementation project(':products:metrics:metrics-lib')
implementation project(':utils:container-utils')
implementation project(':utils:queue-utils')
implementation project(':utils:version-utils')
implementation project(path: ':dd-java-agent:ddprof-lib', configuration: 'shadow')

implementation libs.okhttp
implementation libs.moshi

testImplementation libs.bundles.junit5
testImplementation libs.junit.jupiter
testImplementation libs.bundles.mockito
testImplementation libs.jackson.databind
testImplementation libs.testcontainers
testImplementation group: 'com.squareup.okhttp3', name: 'mockwebserver', version: libs.versions.okhttp.legacy.get()
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package datadog.crashtracking.buildid;

import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;

import datadog.common.queue.Queues;
import datadog.trace.util.AgentTaskScheduler;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;
import org.jctools.queues.MessagePassingQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class BuildIdCollector {
static final Logger LOGGER = LoggerFactory.getLogger(BuildIdCollector.class);
static final BuildInfo EMPTY = new BuildInfo(null, null, null);

private final Map<String, BuildInfo> libraryBuildInfo = new ConcurrentHashMap<>();
private final Set<String> processed = new HashSet<>();
private final AtomicBoolean collecting = new AtomicBoolean(false);
private final MessagePassingQueue<Path> workQueue = Queues.spscArrayQueue(Short.MAX_VALUE);
private final CountDownLatch latch = new CountDownLatch(1);

class Collector implements Runnable {
private final BuildIdExtractor extractor = BuildIdExtractor.create();
private final long deadline;

Collector(long timeout, TimeUnit unit) {
this.deadline = unit.toNanos(timeout) + System.nanoTime();
}

@Override
public void run() {
while (System.nanoTime() <= deadline) {
final Path path = workQueue.poll();
if (path == null) {
if (!collecting.get()) {
break;
}
LockSupport.parkNanos(MILLISECONDS.toNanos(50));
continue;
}
final String fileName = path.getFileName().toString();
LOGGER.debug("Resolving build id for {} against {}", fileName, path);
final String buildId = extractor.extractBuildId(path);
if (buildId != null) {
LOGGER.debug("Found build id {} for library {}", buildId, fileName);
libraryBuildInfo.put(
fileName, new BuildInfo(buildId, extractor.buildIdType(), extractor.fileType()));
}
}
latch.countDown();
}
}

public void addUnprocessedLibrary(String filename) {
if (!collecting.get()) {
libraryBuildInfo.putIfAbsent(filename, EMPTY);
}
}

public void resolveBuildId(Path path) {
if (collecting.compareAndSet(false, true)) {
AgentTaskScheduler.get().execute(new Collector(5, SECONDS));
}
final String filename = path.getFileName().toString();
if (!processed.add(filename)) {
return;
}
if (libraryBuildInfo.remove(filename) == null) {
// the library is not present in the collected ones part of the stackframe
LOGGER.debug(
"Skipping build id resolution for {} as it was not added to unprocessed", filename);

} else {
workQueue.offer(path);
}
}

public void awaitCollectionDone(final int timeoutSeconds) {
if (!collecting.compareAndSet(true, false)) {
return;
}
try {
if (!latch.await(timeoutSeconds, SECONDS)) {
LOGGER.warn("Build id collection incomplete.");
}
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
LOGGER.warn("Interrupted while waiting for build id collection to finish");
}
}

public BuildInfo getBuildInfo(String filename) {
return libraryBuildInfo.get(filename);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package datadog.crashtracking.buildid;

import datadog.environment.OperatingSystem;
import java.nio.file.Path;

/**
* Interface for extracting build IDs from native library binaries. Build IDs help identify exact
* library versions for symbolization of native stack traces.
*/
public interface BuildIdExtractor {
/**
* Extracts build ID from a binary file.
*
* @param file Path to the library file
* @return Build ID as hex string, or null if not found or on error
*/
String extractBuildId(Path file);

/**
* @return the file type this extractor operates for.
*/
BuildInfo.FileType fileType();

/**
* @return the build id type this extractor is able to provide.
*/
BuildInfo.BuildIdType buildIdType();

/**
* Factory method that returns appropriate extractor for the platform.
*
* @return Platform-specific build ID extractor
*/
static BuildIdExtractor create() {
if (OperatingSystem.isLinux()) {
return new ElfBuildIdExtractor();
} else if (OperatingSystem.isWindows()) {
return new PeBuildIdExtractor();
}
return new NoOpBuildIdExtractor();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package datadog.crashtracking.buildid;

public class BuildInfo {
public enum BuildIdType {
SHA1, // ELF
PE // WIN
}

public enum FileType {
ELF,
PE,
}

public final String buildId;
public final BuildIdType buildIdType;
public final FileType fileType;

public BuildInfo(final String buildId, final BuildIdType buildIdType, final FileType fileType) {
this.buildId = buildId;
this.buildIdType = buildIdType;
this.fileType = fileType;
}
}
Loading