Skip to content
Draft
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
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ moshi = "1.11.0"

# Testing
assertj = "3.27.7"
jfrunit = "1.0.0.Alpha2"
junit4 = "4.13.2"
junit5 = "5.14.1"
junit-platform = "1.14.1"
Expand Down Expand Up @@ -151,6 +152,7 @@ moshi = { module = "com.squareup.moshi:moshi", version.ref = "moshi" }

# Testing
assertj-core = { module = "org.assertj:assertj-core", version.ref = "assertj" }
jfrunit = { module = "org.moditect:jfrunit", version.ref = "jfrunit" }
junit4 = { module = "junit:junit", version.ref = "junit4" }
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit5" }
junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit5" }
Expand Down
32 changes: 27 additions & 5 deletions internal-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,36 @@ tasks.withType<JavaCompile>().configureEach {
configureCompiler(8, JavaVersion.VERSION_1_8, "Need access to sun.misc.SharedSecrets")
}

fun AbstractCompile.configureCompiler(javaVersionInteger: Int, compatibilityVersion: JavaVersion? = null, unsetReleaseFlagReason: String? = null) {
fun AbstractCompile.configureCompiler(
javaVersionInteger: Int,
compatibilityVersion: JavaVersion? = null,
unsetReleaseFlagReason: String? = null,
) {
(project.extra["configureCompiler"] as Closure<*>).call(this, javaVersionInteger, compatibilityVersion, unsetReleaseFlagReason)
}

fun addTestSuite(name: String) {
(project.extra["addTestSuite"] as? Closure<*>)?.call(name)
}

tasks.named<CheckForbiddenApis>("forbiddenApisMain") {
// sun.* are accessible in JDK8, but maybe not accessible when this task is running
failOnMissingClasses = false
}

addTestSuite("memoryTest")

tasks.named<JavaCompile>("compileMemoryTestJava") {
configureCompiler(11, JavaVersion.VERSION_11)
}

tasks.named<Test>("memoryTest") {
javaLauncher =
javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(11)
}
}

val minimumBranchCoverage by extra(0.7)
val minimumInstructionCoverage by extra(0.8)

Expand Down Expand Up @@ -234,7 +255,7 @@ val excludedClassesCoverage by extra(
"datadog.trace.bootstrap.instrumentation.api.SpanPostProcessor.NoOpSpanPostProcessor",
"datadog.trace.util.TempLocationManager",
"datadog.trace.util.TempLocationManager.*",
)
),
)

val excludedClassesBranchCoverage by extra(
Expand All @@ -247,13 +268,13 @@ val excludedClassesBranchCoverage by extra(
"datadog.trace.util.TempLocationManager.*",
// Branches depend on RUM injector state that cannot be reliably controlled in unit tests
"datadog.trace.api.rum.RumInjectorMetrics",
)
),
)

val excludedClassesInstructionCoverage by extra(
listOf(
"datadog.trace.util.stacktrace.StackWalkerFactory"
)
"datadog.trace.util.stacktrace.StackWalkerFactory",
),
)

dependencies {
Expand All @@ -276,6 +297,7 @@ dependencies {
testImplementation("org.junit.vintage:junit-vintage-engine:${libs.versions.junit5.get()}")
testImplementation(libs.commons.math)
testImplementation(libs.bundles.mockito)
add("memoryTestImplementation", libs.jfrunit)
}

jmh {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package datadog.trace.bootstrap.instrumentation.api;

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

import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import jdk.jfr.Recording;
import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.consumer.RecordedFrame;
import jdk.jfr.consumer.RecordedMethod;
import jdk.jfr.consumer.RecordedStackTrace;
import jdk.jfr.consumer.RecordingFile;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledForJreRange;
import org.junit.jupiter.api.condition.JRE;
import org.moditect.jfrunit.JfrEventTest;

@JfrEventTest
@EnabledForJreRange(min = JRE.JAVA_11)
class AgentTracerSpanCreationMemoryTest {
private static final String INSTRUMENTATION_NAME = "memory-test";
private static final CharSequence SPAN_NAME = "span-creation";
private static final CharSequence CHILD_SPAN_NAME = "span-creation-child";

@Test
void startSpanDoesNotAllocateInNoopMode() throws Exception {
warmupSpanCreation();

List<RecordedEvent> events = recordAllocationsWhileCreatingSpans();
long allocationEventsOnStartSpan = events.stream().filter(this::isStartSpanAllocation).count();

assertEquals(0L, allocationEventsOnStartSpan);
}

private static void warmupSpanCreation() {
for (int i = 0; i < 10_000; i++) {
createSpan();
}
}

private static List<RecordedEvent> recordAllocationsWhileCreatingSpans() throws Exception {
try (Recording recording = new Recording()) {
recording.enable("jdk.ObjectAllocationInNewTLAB").withStackTrace().withThreshold(Duration.ZERO);
recording.enable("jdk.ObjectAllocationOutsideTLAB").withStackTrace().withThreshold(Duration.ZERO);
recording.start();
for (int i = 0; i < 25_000; i++) {
createSpan();
}
recording.stop();
return readEvents(recording);
}
}

private static void createSpan() {
AgentSpan span = AgentTracer.startSpan(INSTRUMENTATION_NAME, SPAN_NAME);
try (AgentScope scope = AgentTracer.activateSpan(span)) {
AgentSpan childSpan = AgentTracer.startSpan(INSTRUMENTATION_NAME, CHILD_SPAN_NAME);
try (AgentScope childScope = AgentTracer.activateSpan(childSpan)) {
} finally {
childSpan.finish();
}
} finally {
span.finish();
}
}

private static List<RecordedEvent> readEvents(Recording recording) throws Exception {
Path recordingPath = Files.createTempFile("agent-tracer-span-memory-test", ".jfr");
try {
recording.dump(recordingPath);
List<RecordedEvent> events = new ArrayList<>();
try (RecordingFile recordingFile = new RecordingFile(recordingPath)) {
while (recordingFile.hasMoreEvents()) {
events.add(recordingFile.readEvent());
}
}
return events;
} finally {
Files.deleteIfExists(recordingPath);
}
}

private boolean isStartSpanAllocation(RecordedEvent event) {
RecordedStackTrace stackTrace = event.getStackTrace();
if (stackTrace == null) {
return false;
}

for (RecordedFrame frame : stackTrace.getFrames()) {
RecordedMethod method = frame.getMethod();
if (method == null || method.getType() == null) {
continue;
}

if ("datadog.trace.bootstrap.instrumentation.api.AgentTracer".equals(method.getType().getName())
&& "startSpan".equals(method.getName())) {
return true;
}
}
return false;
}
}
Loading