Skip to content

Commit 599be45

Browse files
committed
feat(profiling): Add OLTP/P format validation tests
1 parent 0519083 commit 599be45

File tree

14 files changed

+967
-465
lines changed

14 files changed

+967
-465
lines changed

dd-java-agent/agent-profiling/profiling-otel/src/main/java/com/datadog/profiling/otel/JfrToOtlpConverter.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,6 @@ private void handleExecutionSample(ExecutionSample event, Control ctl) {
288288
if (event == null) {
289289
return;
290290
}
291-
System.out.println("===> event: " + event);
292291
JfrStackTrace st = event.stackTrace();
293292
int stackIndex = convertStackTrace(st);
294293
int linkIndex = extractLinkIndex(event.spanId(), event.localRootSpanId());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
package com.datadog.profiling.otel;
2+
3+
import static org.junit.jupiter.api.Assertions.*;
4+
5+
import com.datadog.profiling.otel.proto.dictionary.*;
6+
import java.lang.reflect.Field;
7+
import org.junit.jupiter.api.Test;
8+
9+
/**
10+
* Tests for verifying deduplication behavior in JFR to OTLP conversion.
11+
*
12+
* <p>These tests use reflection to access internal dictionary tables and verify that:
13+
*
14+
* <ul>
15+
* <li>Identical stacktraces are deduplicated correctly
16+
* <li>Dictionary tables (String, Function, Location, Stack) work as expected
17+
* <li>Large-scale conversions handle deduplication efficiently
18+
* </ul>
19+
*/
20+
class JfrToOtlpConverterDeduplicationTest {
21+
22+
@Test
23+
void verifyStacktraceDeduplication() throws Exception {
24+
JfrToOtlpConverter converter = new JfrToOtlpConverter();
25+
26+
// Access internal dictionary tables via reflection
27+
StringTable strings = getDictionaryTable(converter, "stringTable", StringTable.class);
28+
FunctionTable functions = getDictionaryTable(converter, "functionTable", FunctionTable.class);
29+
LocationTable locations = getDictionaryTable(converter, "locationTable", LocationTable.class);
30+
StackTable stacks = getDictionaryTable(converter, "stackTable", StackTable.class);
31+
32+
// Simulate multiple samples with identical stacks
33+
// Stack A: method1 -> method2 -> method3
34+
int className1 = strings.intern("com.example.Class1");
35+
int methodName1 = strings.intern("method1");
36+
int func1 = functions.intern(className1, methodName1, 0, 10);
37+
int loc1 = locations.intern(0, 0x1000, func1, 10, 0);
38+
39+
int className2 = strings.intern("com.example.Class2");
40+
int methodName2 = strings.intern("method2");
41+
int func2 = functions.intern(className2, methodName2, 0, 20);
42+
int loc2 = locations.intern(0, 0x2000, func2, 20, 0);
43+
44+
int className3 = strings.intern("com.example.Class3");
45+
int methodName3 = strings.intern("method3");
46+
int func3 = functions.intern(className3, methodName3, 0, 30);
47+
int loc3 = locations.intern(0, 0x3000, func3, 30, 0);
48+
49+
int[] stackA = new int[] {loc1, loc2, loc3};
50+
51+
// Intern stack A multiple times - should get same index
52+
int stackIndex1 = stacks.intern(stackA);
53+
int stackIndex2 = stacks.intern(stackA);
54+
int stackIndex3 = stacks.intern(new int[] {loc1, loc2, loc3}); // New array, same content
55+
56+
// All should reference the same stack index
57+
assertEquals(stackIndex1, stackIndex2, "Identical stacks should deduplicate");
58+
assertEquals(stackIndex1, stackIndex3, "Stacks with same content should deduplicate");
59+
60+
// Create different stack B: method4 -> method5
61+
int className4 = strings.intern("com.example.Class4");
62+
int methodName4 = strings.intern("method4");
63+
int func4 = functions.intern(className4, methodName4, 0, 40);
64+
int loc4 = locations.intern(0, 0x4000, func4, 40, 0);
65+
66+
int className5 = strings.intern("com.example.Class5");
67+
int methodName5 = strings.intern("method5");
68+
int func5 = functions.intern(className5, methodName5, 0, 50);
69+
int loc5 = locations.intern(0, 0x5000, func5, 50, 0);
70+
71+
int[] stackB = new int[] {loc4, loc5};
72+
int stackIndexB = stacks.intern(stackB);
73+
74+
// Stack B should have different index
75+
assertNotEquals(stackIndex1, stackIndexB, "Different stacks should have different indices");
76+
77+
// Verify stack table size - should have 3 entries: index 0 (null), Stack A, Stack B
78+
assertEquals(3, stacks.size(), "Should have 3 stacks: null, A, B");
79+
80+
// Verify string deduplication - repeated interns return same index
81+
int className1Again = strings.intern("com.example.Class1");
82+
assertEquals(className1, className1Again, "Duplicate strings should deduplicate");
83+
}
84+
85+
@Test
86+
void verifyDictionaryTableDeduplication() throws Exception {
87+
JfrToOtlpConverter converter = new JfrToOtlpConverter();
88+
89+
// Access internal dictionary tables via reflection
90+
StringTable strings = getDictionaryTable(converter, "stringTable", StringTable.class);
91+
FunctionTable functions = getDictionaryTable(converter, "functionTable", FunctionTable.class);
92+
LocationTable locations = getDictionaryTable(converter, "locationTable", LocationTable.class);
93+
94+
// Verify string deduplication
95+
int str1 = strings.intern("test.Class");
96+
int str2 = strings.intern("test.Class"); // Duplicate
97+
int str3 = strings.intern("other.Class"); // Different
98+
99+
assertEquals(str1, str2, "Duplicate strings should return same index");
100+
assertNotEquals(str1, str3, "Different strings should return different index");
101+
102+
// Initial size: 1 (index 0 for null/empty) + 2 unique strings = 3
103+
assertEquals(3, strings.size(), "StringTable should have 3 entries");
104+
105+
// Verify function deduplication
106+
int method1 = strings.intern("method");
107+
int func1 = functions.intern(str1, method1, 0, 100);
108+
int func2 = functions.intern(str1, method1, 0, 100); // Duplicate
109+
int func3 = functions.intern(str3, method1, 0, 200); // Different class
110+
111+
assertEquals(func1, func2, "Duplicate functions should return same index");
112+
assertNotEquals(func1, func3, "Different functions should return different index");
113+
114+
// Function table size: 1 (index 0) + 2 unique functions = 3
115+
assertEquals(3, functions.size(), "FunctionTable should have 3 entries");
116+
117+
// Verify location deduplication
118+
int loc1 = locations.intern(0, 0x1000, func1, 10, 0);
119+
int loc2 = locations.intern(0, 0x1000, func1, 10, 0); // Duplicate
120+
int loc3 = locations.intern(0, 0x2000, func1, 20, 0); // Different address
121+
122+
assertEquals(loc1, loc2, "Duplicate locations should return same index");
123+
assertNotEquals(loc1, loc3, "Different locations should return different index");
124+
125+
// Location table size: 1 (index 0) + 2 unique locations = 3
126+
assertEquals(3, locations.size(), "LocationTable should have 3 entries");
127+
}
128+
129+
@Test
130+
void verifyLargeScaleDeduplication() throws Exception {
131+
JfrToOtlpConverter converter = new JfrToOtlpConverter();
132+
133+
// Access internal dictionary tables via reflection
134+
StringTable strings = getDictionaryTable(converter, "stringTable", StringTable.class);
135+
FunctionTable functions = getDictionaryTable(converter, "functionTable", FunctionTable.class);
136+
LocationTable locations = getDictionaryTable(converter, "locationTable", LocationTable.class);
137+
StackTable stacks = getDictionaryTable(converter, "stackTable", StackTable.class);
138+
139+
// Create 10 unique stacks
140+
int[][] uniqueStacks = new int[10][];
141+
for (int i = 0; i < 10; i++) {
142+
int className = strings.intern("com.example.Class" + i);
143+
int methodName = strings.intern("method" + i);
144+
int func = functions.intern(className, methodName, 0, i * 10);
145+
int loc = locations.intern(0, 0x1000 + (i * 0x100), func, i * 10, 0);
146+
uniqueStacks[i] = new int[] {loc};
147+
}
148+
149+
// Intern each unique stack 100 times
150+
int[] stackIndices = new int[10];
151+
for (int i = 0; i < 10; i++) {
152+
int firstIndex = stacks.intern(uniqueStacks[i]);
153+
stackIndices[i] = firstIndex;
154+
155+
// Intern same stack 99 more times
156+
for (int repeat = 0; repeat < 99; repeat++) {
157+
int repeatIndex = stacks.intern(uniqueStacks[i]);
158+
assertEquals(
159+
firstIndex, repeatIndex, "Stack " + i + " repeat " + repeat + " should deduplicate");
160+
}
161+
}
162+
163+
// Verify all 10 stacks have unique indices
164+
for (int i = 0; i < 10; i++) {
165+
for (int j = i + 1; j < 10; j++) {
166+
assertNotEquals(
167+
stackIndices[i],
168+
stackIndices[j],
169+
"Stack " + i + " and " + j + " should have different indices");
170+
}
171+
}
172+
173+
// Stack table should have 11 entries: index 0 (null) + 10 unique stacks
174+
assertEquals(11, stacks.size(), "StackTable should have 11 entries after 1000 interns");
175+
176+
// Verify string table has expected number of entries
177+
// Initial: 1 (index 0) + 10 class names + 10 method names = 21
178+
assertEquals(21, strings.size(), "StringTable should have 21 entries");
179+
180+
// Verify function table has expected number of entries
181+
// Initial: 1 (index 0) + 10 unique functions = 11
182+
assertEquals(11, functions.size(), "FunctionTable should have 11 entries");
183+
184+
// Verify location table has expected number of entries
185+
// Initial: 1 (index 0) + 10 unique locations = 11
186+
assertEquals(11, locations.size(), "LocationTable should have 11 entries");
187+
}
188+
189+
@Test
190+
void verifyLinkTableDeduplication() throws Exception {
191+
JfrToOtlpConverter converter = new JfrToOtlpConverter();
192+
193+
// Access link table via reflection
194+
LinkTable links = getDictionaryTable(converter, "linkTable", LinkTable.class);
195+
196+
// Create trace links
197+
int link1 = links.intern(123L, 456L);
198+
int link2 = links.intern(123L, 456L); // Duplicate
199+
int link3 = links.intern(789L, 101112L); // Different
200+
201+
assertEquals(link1, link2, "Duplicate links should return same index");
202+
assertNotEquals(link1, link3, "Different links should return different index");
203+
204+
// Link table size: 1 (index 0) + 2 unique links = 3
205+
assertEquals(3, links.size(), "LinkTable should have 3 entries");
206+
}
207+
208+
// Helper method to access private dictionary table fields using reflection
209+
@SuppressWarnings("unchecked")
210+
private <T> T getDictionaryTable(JfrToOtlpConverter converter, String fieldName, Class<T> type)
211+
throws Exception {
212+
Field field = JfrToOtlpConverter.class.getDeclaredField(fieldName);
213+
field.setAccessible(true);
214+
return (T) field.get(converter);
215+
}
216+
}

0 commit comments

Comments
 (0)