Skip to content

Commit 1281df4

Browse files
datadog-bitsdougqh
andcommitted
Add APMLP-509 span memory test
Co-authored-by: dougqh <dougqh@gmail.com>
1 parent 1916c90 commit 1281df4

3 files changed

Lines changed: 125 additions & 5 deletions

File tree

gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ moshi = "1.11.0"
6565

6666
# Testing
6767
assertj = "3.27.7"
68+
jfrunit = "1.0.0.Alpha2"
6869
junit4 = "4.13.2"
6970
junit5 = "5.14.1"
7071
junit-platform = "1.14.1"
@@ -151,6 +152,7 @@ moshi = { module = "com.squareup.moshi:moshi", version.ref = "moshi" }
151152

152153
# Testing
153154
assertj-core = { module = "org.assertj:assertj-core", version.ref = "assertj" }
155+
jfrunit = { module = "org.moditect:jfrunit", version.ref = "jfrunit" }
154156
junit4 = { module = "junit:junit", version.ref = "junit4" }
155157
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit5" }
156158
junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit5" }

internal-api/build.gradle.kts

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,36 @@ tasks.withType<JavaCompile>().configureEach {
1818
configureCompiler(8, JavaVersion.VERSION_1_8, "Need access to sun.misc.SharedSecrets")
1919
}
2020

21-
fun AbstractCompile.configureCompiler(javaVersionInteger: Int, compatibilityVersion: JavaVersion? = null, unsetReleaseFlagReason: String? = null) {
21+
fun AbstractCompile.configureCompiler(
22+
javaVersionInteger: Int,
23+
compatibilityVersion: JavaVersion? = null,
24+
unsetReleaseFlagReason: String? = null,
25+
) {
2226
(project.extra["configureCompiler"] as Closure<*>).call(this, javaVersionInteger, compatibilityVersion, unsetReleaseFlagReason)
2327
}
2428

29+
fun addTestSuite(name: String) {
30+
(project.extra["addTestSuite"] as? Closure<*>)?.call(name)
31+
}
32+
2533
tasks.named<CheckForbiddenApis>("forbiddenApisMain") {
2634
// sun.* are accessible in JDK8, but maybe not accessible when this task is running
2735
failOnMissingClasses = false
2836
}
2937

38+
addTestSuite("memoryTest")
39+
40+
tasks.named<JavaCompile>("compileMemoryTestJava") {
41+
configureCompiler(11, JavaVersion.VERSION_11)
42+
}
43+
44+
tasks.named<Test>("memoryTest") {
45+
javaLauncher =
46+
javaToolchains.launcherFor {
47+
languageVersion = JavaLanguageVersion.of(11)
48+
}
49+
}
50+
3051
val minimumBranchCoverage by extra(0.7)
3152
val minimumInstructionCoverage by extra(0.8)
3253

@@ -234,7 +255,7 @@ val excludedClassesCoverage by extra(
234255
"datadog.trace.bootstrap.instrumentation.api.SpanPostProcessor.NoOpSpanPostProcessor",
235256
"datadog.trace.util.TempLocationManager",
236257
"datadog.trace.util.TempLocationManager.*",
237-
)
258+
),
238259
)
239260

240261
val excludedClassesBranchCoverage by extra(
@@ -247,13 +268,13 @@ val excludedClassesBranchCoverage by extra(
247268
"datadog.trace.util.TempLocationManager.*",
248269
// Branches depend on RUM injector state that cannot be reliably controlled in unit tests
249270
"datadog.trace.api.rum.RumInjectorMetrics",
250-
)
271+
),
251272
)
252273

253274
val excludedClassesInstructionCoverage by extra(
254275
listOf(
255-
"datadog.trace.util.stacktrace.StackWalkerFactory"
256-
)
276+
"datadog.trace.util.stacktrace.StackWalkerFactory",
277+
),
257278
)
258279

259280
dependencies {
@@ -276,6 +297,7 @@ dependencies {
276297
testImplementation("org.junit.vintage:junit-vintage-engine:${libs.versions.junit5.get()}")
277298
testImplementation(libs.commons.math)
278299
testImplementation(libs.bundles.mockito)
300+
add("memoryTestImplementation", libs.jfrunit)
279301
}
280302

281303
jmh {
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package datadog.trace.bootstrap.instrumentation.api;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import java.nio.file.Files;
6+
import java.nio.file.Path;
7+
import java.time.Duration;
8+
import java.util.ArrayList;
9+
import java.util.List;
10+
import jdk.jfr.Recording;
11+
import jdk.jfr.consumer.RecordedEvent;
12+
import jdk.jfr.consumer.RecordedFrame;
13+
import jdk.jfr.consumer.RecordedMethod;
14+
import jdk.jfr.consumer.RecordedStackTrace;
15+
import jdk.jfr.consumer.RecordingFile;
16+
import org.junit.jupiter.api.Test;
17+
import org.junit.jupiter.api.condition.EnabledForJreRange;
18+
import org.junit.jupiter.api.condition.JRE;
19+
import org.moditect.jfrunit.JfrEventTest;
20+
21+
@JfrEventTest
22+
@EnabledForJreRange(min = JRE.JAVA_11)
23+
class AgentTracerSpanCreationMemoryTest {
24+
private static final String INSTRUMENTATION_NAME = "memory-test";
25+
private static final CharSequence SPAN_NAME = "span-creation";
26+
27+
@Test
28+
void startSpanDoesNotAllocateInNoopMode() throws Exception {
29+
warmupSpanCreation();
30+
31+
List<RecordedEvent> events = recordAllocationsWhileCreatingSpans();
32+
long allocationEventsOnStartSpan = events.stream().filter(this::isStartSpanAllocation).count();
33+
34+
assertEquals(0L, allocationEventsOnStartSpan);
35+
}
36+
37+
private static void warmupSpanCreation() {
38+
for (int i = 0; i < 10_000; i++) {
39+
createSpan();
40+
}
41+
}
42+
43+
private static List<RecordedEvent> recordAllocationsWhileCreatingSpans() throws Exception {
44+
try (Recording recording = new Recording()) {
45+
recording.enable("jdk.ObjectAllocationInNewTLAB").withStackTrace().withThreshold(Duration.ZERO);
46+
recording.enable("jdk.ObjectAllocationOutsideTLAB").withStackTrace().withThreshold(Duration.ZERO);
47+
recording.start();
48+
for (int i = 0; i < 25_000; i++) {
49+
createSpan();
50+
}
51+
recording.stop();
52+
return readEvents(recording);
53+
}
54+
}
55+
56+
private static void createSpan() {
57+
AgentSpan span = AgentTracer.startSpan(INSTRUMENTATION_NAME, SPAN_NAME);
58+
span.finish();
59+
}
60+
61+
private static List<RecordedEvent> readEvents(Recording recording) throws Exception {
62+
Path recordingPath = Files.createTempFile("agent-tracer-span-memory-test", ".jfr");
63+
try {
64+
recording.dump(recordingPath);
65+
List<RecordedEvent> events = new ArrayList<>();
66+
try (RecordingFile recordingFile = new RecordingFile(recordingPath)) {
67+
while (recordingFile.hasMoreEvents()) {
68+
events.add(recordingFile.readEvent());
69+
}
70+
}
71+
return events;
72+
} finally {
73+
Files.deleteIfExists(recordingPath);
74+
}
75+
}
76+
77+
private boolean isStartSpanAllocation(RecordedEvent event) {
78+
RecordedStackTrace stackTrace = event.getStackTrace();
79+
if (stackTrace == null) {
80+
return false;
81+
}
82+
83+
for (RecordedFrame frame : stackTrace.getFrames()) {
84+
RecordedMethod method = frame.getMethod();
85+
if (method == null || method.getType() == null) {
86+
continue;
87+
}
88+
89+
if ("datadog.trace.bootstrap.instrumentation.api.AgentTracer".equals(method.getType().getName())
90+
&& "startSpan".equals(method.getName())) {
91+
return true;
92+
}
93+
}
94+
return false;
95+
}
96+
}

0 commit comments

Comments
 (0)