Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
d2cc4ec
Experimenting with introducing CoreSpanBuilder reuse
dougqh Sep 12, 2025
d83ff16
Adding protection against reusing a CoreSpanBuilder that's still in-use
dougqh Sep 15, 2025
045e4c9
spotless
dougqh Sep 15, 2025
95e6f17
Adding configuration option to control reuse of SpanBuilders
dougqh Sep 15, 2025
55b56e7
Adding explanatory comments
dougqh Sep 15, 2025
8de4b30
Enabling by default
dougqh Sep 16, 2025
8adfe8f
Merge branch 'master' into dougqh/spanbuilder-pooling
dougqh Sep 16, 2025
5763a31
Adding reuse tests
dougqh Sep 16, 2025
353afa2
Merge branch 'dougqh/spanbuilder-pooling' of github.com:DataDog/dd-tr…
dougqh Sep 16, 2025
23b03b8
spotless
dougqh Sep 16, 2025
b588b7a
Merge branch 'master' into dougqh/spanbuilder-pooling
dougqh Sep 16, 2025
68a6f03
Changed the API to be safe for atypical usage
dougqh Sep 16, 2025
ec730ec
Merge branch 'dougqh/spanbuilder-pooling' of github.com:DataDog/dd-tr…
dougqh Sep 16, 2025
2d22b41
Fleshing out tests
dougqh Sep 17, 2025
55bd500
spotless
dougqh Sep 17, 2025
2258db5
Merge branch 'master' into dougqh/spanbuilder-pooling
dougqh Sep 17, 2025
079d1f5
Improving single threaded performance
dougqh Sep 17, 2025
a55095e
spotless
dougqh Sep 17, 2025
26482bf
Merge branch 'dougqh/spanbuilder-pooling' of github.com:DataDog/dd-tr…
dougqh Sep 17, 2025
e5161ef
Fixing test that renaming didn't update properly
dougqh Sep 18, 2025
40ba13d
Adding benchmarks for span creation
dougqh Sep 18, 2025
70eccff
spotless
dougqh Sep 18, 2025
5b594a2
Adding clarifying comments & more tests
dougqh Sep 18, 2025
65fa1d7
spotless
dougqh Sep 18, 2025
136329e
tweaking comments
dougqh Sep 18, 2025
2e7da54
More comments
dougqh Sep 18, 2025
98d5603
More comment clean-up
dougqh Sep 18, 2025
ff9acbe
Addressing review feedback
dougqh Sep 19, 2025
4475e42
Adding overload to just check major version
dougqh Oct 7, 2025
4d2ec09
Adding ThreadUtils
dougqh Oct 7, 2025
aea9d3b
Adding isVirtualThread check to reuseSingleSpanBuilder
dougqh Oct 7, 2025
8bb8f29
Merge branch 'master' into dougqh/spanbuilder-pooling
dougqh Oct 7, 2025
6ed469b
Addressing review comments - reduced visibility
dougqh Oct 7, 2025
e26959d
Merge branch 'master' into dougqh/spanbuilder-pooling
dougqh Oct 7, 2025
66f62fb
Update dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java
dougqh Oct 14, 2025
94970a4
Update internal-api/src/main/java/datadog/trace/bootstrap/instrumenta…
dougqh Oct 14, 2025
388a288
Merge branch 'master' into dougqh/spanbuilder-pooling
dougqh Oct 14, 2025
86c03d9
Update dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java
dougqh Oct 14, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ public boolean is(int major, int minor, int update) {
return this.major == major && this.minor == minor && this.update == update;
}

public boolean isAtLeast(int major) {
return isAtLeast(major, 0, 0);
}

public boolean isAtLeast(int major, int minor, int update) {
return isAtLeast(this.major, this.minor, this.update, major, minor, update);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package datadog.environment;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;

/**
* Helper class for working with Threads
*
* <p>Uses feature detection and provides static helpers to work with different versions of Java
*
* <p>This class is designed to use MethodHandles that constant propagate to minimize the overhead
*/
public final class ThreadUtils {
static final MethodHandle H_IS_VIRTUAL = lookupIsVirtual();
static final MethodHandle H_ID = lookupId();

private ThreadUtils() {}

/** Provides the best id available for the Thread Uses threadId on 19+; getId on older JVMs */
public static final long threadId(Thread thread) {
try {
return (long) H_ID.invoke(thread);
} catch (Throwable t) {
return 0L;
}
}

/** Indicates whether virtual threads are supported on this JVM */
public static final boolean supportsVirtualThreads() {
return (H_IS_VIRTUAL != null);
}

/** Indicates if the current thread is a virtual thread */
public static final boolean isCurrentThreadVirtual() {
// H_IS_VIRTUAL will constant propagate -- then dead code eliminate -- and inline as needed
try {
return (H_IS_VIRTUAL != null) && (boolean) H_IS_VIRTUAL.invoke(Thread.currentThread());
} catch (Throwable t) {
return false;
}
}

/** Indicates if the provided thread is a virtual thread */
public static final boolean isVirtual(Thread thread) {
// H_IS_VIRTUAL will constant propagate -- then dead code eliminate -- and inline as needed
try {
return (H_IS_VIRTUAL != null) && (boolean) H_IS_VIRTUAL.invoke(thread);
} catch (Throwable t) {
return false;
}
}

private static final MethodHandle lookupIsVirtual() {
try {
return MethodHandles.lookup()
.findVirtual(Thread.class, "isVirtual", MethodType.methodType(boolean.class));
} catch (NoSuchMethodException | IllegalAccessException e) {
return null;
}
}

private static final MethodHandle lookupId() {
MethodHandle threadIdHandle = lookupThreadId();
return threadIdHandle != null ? threadIdHandle : lookupGetId();
}

private static final MethodHandle lookupThreadId() {
try {
return MethodHandles.lookup()
.findVirtual(Thread.class, "threadId", MethodType.methodType(long.class));
} catch (NoSuchMethodException | IllegalAccessException e) {
return null;
}
}

private static final MethodHandle lookupGetId() {
try {
return MethodHandles.lookup()
.findVirtual(Thread.class, "getId", MethodType.methodType(long.class));
} catch (NoSuchMethodException | IllegalAccessException e) {
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package datadog.environment;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.jupiter.api.Test;

public class ThreadUtilsTest {
@Test
public void threadId() throws InterruptedException {
Thread thread = new Thread("foo");
thread.start();
try {
// always works on Thread's where getId isn't overridden by child class
assertEquals(thread.getId(), ThreadUtils.threadId(thread));
} finally {
thread.join();
}
}

@Test
public void supportsVirtualThreads() {
assertEquals(
JavaVersion.getRuntimeVersion().isAtLeast(21), ThreadUtils.supportsVirtualThreads());
}

@Test
public void isVirtualThread_false() throws InterruptedException {
Thread thread = new Thread("foo");
thread.start();
try {
assertFalse(ThreadUtils.isVirtual(thread));
} finally {
thread.join();
}
}

@Test
public void isCurrentThreadVirtual_false() throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
assertFalse(executor.submit(() -> ThreadUtils.isCurrentThreadVirtual()).get());
} finally {
executor.shutdown();
}
}

@Test
public void isVirtualThread_true() throws InterruptedException {
if (!ThreadUtils.supportsVirtualThreads()) return;

Thread vThread = startVirtualThread(() -> {});
try {
assertTrue(ThreadUtils.isVirtual(vThread));
} finally {
vThread.join();
}
}

@Test
public void isCurrentThreadVirtual_true() throws InterruptedException {
if (!ThreadUtils.supportsVirtualThreads()) return;

AtomicBoolean result = new AtomicBoolean();

Thread vThread =
startVirtualThread(
() -> {
result.set(ThreadUtils.isCurrentThreadVirtual());
});

vThread.join();
assertTrue(result.get());
}

/*
* Should only be called on JVMs that support virtual threads
*/
static final Thread startVirtualThread(Runnable runnable) {
MethodHandle h_startVThread;
try {
h_startVThread =
MethodHandles.lookup()
.findStatic(
Thread.class,
"startVirtualThread",
MethodType.methodType(Runnable.class, Thread.class));
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new IllegalStateException(e);
}

try {
return (Thread) h_startVThread.invoke(runnable);
} catch (Throwable e) {
throw new IllegalStateException(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ public final class GeneralConfig {
public static final String OPTIMIZED_MAP_ENABLED = "optimized.map.enabled";
public static final String TAG_NAME_UTF8_CACHE_SIZE = "tag.name.utf8.cache.size";
public static final String TAG_VALUE_UTF8_CACHE_SIZE = "tag.value.utf8.cache.size";
public static final String SPAN_BUILDER_REUSE_ENABLED = "span.builder.reuse.enabled";
Copy link
Contributor Author

@dougqh dougqh Sep 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this is best class to be placing these in. Or if this is the proper namespace. dd.trace... might be better

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree dd.trace prefix fits better

public static final String STACK_TRACE_LENGTH_LIMIT = "stack.trace.length.limit";

public static final String SSI_INJECTION_ENABLED = "injection.enabled";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package datadog.trace.core;

import static java.util.concurrent.TimeUnit.MICROSECONDS;

import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;

/**
* Benchmark of key operations of the CoreTracer
*
* <p>NOTE: This is a multi-threaded benchmark; single threaded benchmarks don't accurately reflect
* some of the optimizations.
*
* <p>Use -t 1, if you'd like to do a single threaded run
*/
@State(Scope.Benchmark)
@Warmup(iterations = 3)
@Measurement(iterations = 5)
@BenchmarkMode(Mode.Throughput)
@Threads(8)
@OutputTimeUnit(MICROSECONDS)
@Fork(value = 1)
public class CoreTracerBenchmark {
static final CoreTracer TRACER = CoreTracer.builder().build();

@Benchmark
public AgentSpan startSpan() {
return TRACER.startSpan("foo", "bar");
}

@Benchmark
public AgentSpan buildSpan() {
return TRACER.buildSpan("foo", "bar").start();
}

@Benchmark
public AgentSpan singleSpanBuilder() {
return TRACER.singleSpanBuilder("foo", "bar").start();
}
}
Loading
Loading