Skip to content

Commit bee9080

Browse files
jbachorikclaude
andcommitted
Add comparison test to validate cleanup effectiveness
New test compares memory growth WITH vs WITHOUT cleanup by managing profiler instances manually. Runs same workload twice with different flags (no-method-cleanup vs method-cleanup) and validates at least 20% memory savings from cleanup mechanism. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 6e7729f commit bee9080

1 file changed

Lines changed: 137 additions & 0 deletions

File tree

ddprof-test/src/test/java/com/datadoghq/profiler/memleak/GetLineNumberTableLeakTest.java

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,143 @@ public void testMethodMapCleanupDuringContinuousProfile() throws Exception {
346346
+ "Classes allowed to unload naturally for optimal memory usage");
347347
}
348348

349+
/**
350+
* Comparison test that validates cleanup effectiveness by running the same workload
351+
* twice: once without cleanup (no-method-cleanup) and once with cleanup (default).
352+
* This provides empirical evidence that the cleanup mechanism actually prevents
353+
* unbounded growth.
354+
*/
355+
@Test
356+
public void testCleanupEffectivenessComparison() throws Exception {
357+
// Verify NMT is enabled
358+
Assumptions.assumeTrue(
359+
NativeMemoryTracking.isEnabled(), "Test requires -XX:NativeMemoryTracking=detail");
360+
361+
// Stop the default profiler from AbstractProfilerTest
362+
// We need to manage our own profiler instances for this comparison
363+
stopProfiler();
364+
365+
final int iterations = 8; // Fewer iterations but enough to see difference
366+
final int classesPerIteration = 50; // 250 methods per iteration
367+
368+
// ===== Phase 1: WITHOUT cleanup (no-method-cleanup) =====
369+
System.out.println("\n=== Phase 1: WITHOUT cleanup (no-method-cleanup) ===");
370+
371+
NativeMemoryTracking.NMTSnapshot beforeNoCleanup = NativeMemoryTracking.takeSnapshot();
372+
373+
Path noCleanupFile = tempFile("no-cleanup");
374+
profiler.execute(
375+
"start," + getProfilerCommand() + ",jfr,no-method-cleanup,file=" + noCleanupFile);
376+
377+
Thread.sleep(500); // Stabilize
378+
NativeMemoryTracking.NMTSnapshot afterStartNoCleanup =
379+
NativeMemoryTracking.takeSnapshot();
380+
381+
// Run workload without cleanup
382+
for (int iter = 0; iter < iterations; iter++) {
383+
for (int i = 0; i < classesPerIteration; i++) {
384+
Class<?>[] transientClasses = generateUniqueMethodCalls();
385+
for (Class<?> clazz : transientClasses) {
386+
invokeClassMethods(clazz);
387+
}
388+
}
389+
390+
profiler.dump(noCleanupFile);
391+
Thread.sleep(100);
392+
393+
if ((iter + 1) % 3 == 0) {
394+
System.gc();
395+
Thread.sleep(50);
396+
}
397+
}
398+
399+
NativeMemoryTracking.NMTSnapshot afterNoCleanup = NativeMemoryTracking.takeSnapshot();
400+
long growthNoCleanup =
401+
afterNoCleanup.internalReservedKB - afterStartNoCleanup.internalReservedKB;
402+
System.out.println(
403+
String.format(
404+
"WITHOUT cleanup: Internal memory growth = +%d KB\n"
405+
+ "Check TEST_LOG: MethodMap should grow unbounded (no cleanup logs)",
406+
growthNoCleanup));
407+
408+
profiler.stop();
409+
Thread.sleep(500); // Allow cleanup
410+
411+
// ===== Phase 2: WITH cleanup (default) =====
412+
System.out.println("\n=== Phase 2: WITH cleanup (default) ===");
413+
414+
// Reset class counter to generate same classes
415+
classCounter = 0;
416+
417+
NativeMemoryTracking.NMTSnapshot beforeWithCleanup = NativeMemoryTracking.takeSnapshot();
418+
419+
Path withCleanupFile = tempFile("with-cleanup");
420+
profiler.execute(
421+
"start," + getProfilerCommand() + ",jfr,method-cleanup,file=" + withCleanupFile);
422+
423+
Thread.sleep(500); // Stabilize
424+
NativeMemoryTracking.NMTSnapshot afterStartWithCleanup =
425+
NativeMemoryTracking.takeSnapshot();
426+
427+
// Run same workload with cleanup
428+
for (int iter = 0; iter < iterations; iter++) {
429+
for (int i = 0; i < classesPerIteration; i++) {
430+
Class<?>[] transientClasses = generateUniqueMethodCalls();
431+
for (Class<?> clazz : transientClasses) {
432+
invokeClassMethods(clazz);
433+
}
434+
}
435+
436+
profiler.dump(withCleanupFile);
437+
Thread.sleep(100);
438+
439+
if ((iter + 1) % 3 == 0) {
440+
System.gc();
441+
Thread.sleep(50);
442+
}
443+
}
444+
445+
NativeMemoryTracking.NMTSnapshot afterWithCleanup = NativeMemoryTracking.takeSnapshot();
446+
long growthWithCleanup =
447+
afterWithCleanup.internalReservedKB - afterStartWithCleanup.internalReservedKB;
448+
System.out.println(
449+
String.format(
450+
"WITH cleanup: Internal memory growth = +%d KB\n"
451+
+ "Check TEST_LOG: MethodMap should stay bounded, cleanup logs should appear",
452+
growthWithCleanup));
453+
454+
profiler.stop();
455+
456+
// ===== Comparison =====
457+
System.out.println("\n=== Comparison ===");
458+
System.out.println(
459+
String.format(
460+
"WITHOUT cleanup: +%d KB\n" + "WITH cleanup: +%d KB\n" + "Savings: %d KB (%.1f%%)",
461+
growthNoCleanup,
462+
growthWithCleanup,
463+
growthNoCleanup - growthWithCleanup,
464+
100.0 * (growthNoCleanup - growthWithCleanup) / growthNoCleanup));
465+
466+
// Assert that cleanup actually reduces memory growth
467+
// We expect at least 20% savings from cleanup
468+
long expectedMinSavings = (long) (growthNoCleanup * 0.20);
469+
long actualSavings = growthNoCleanup - growthWithCleanup;
470+
471+
if (actualSavings < expectedMinSavings) {
472+
fail(
473+
String.format(
474+
"Cleanup not effective enough!\n"
475+
+ "Expected at least 20%% savings (>= %d KB)\n"
476+
+ "Actual savings: %d KB (%.1f%%)\n"
477+
+ "This suggests cleanup is not working as intended",
478+
expectedMinSavings,
479+
actualSavings,
480+
100.0 * actualSavings / growthNoCleanup));
481+
}
482+
483+
System.out.println("Result: Cleanup effectiveness validated - significant memory savings observed");
484+
}
485+
349486
private Path tempFile(String name) throws IOException {
350487
Path dir = Paths.get("/tmp/recordings");
351488
Files.createDirectories(dir);

0 commit comments

Comments
 (0)