Skip to content

Commit 949ee8c

Browse files
authored
Extract line information as injectible lines (#9850)
For SymDB we add injectible lines into method scope to provide information about executable line of code where we can put a line probe. We are using the LineNumberTable of each method, sort and make ranges about them.
1 parent 81ec538 commit 949ee8c

File tree

5 files changed

+182
-16
lines changed

5 files changed

+182
-16
lines changed

dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/Scope.java

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@
44
import java.util.List;
55

66
public class Scope {
7+
8+
public static class LineRange {
9+
final int start;
10+
final int end;
11+
12+
public LineRange(int start, int end) {
13+
this.start = start;
14+
this.end = end;
15+
}
16+
}
17+
718
@Json(name = "scope_type")
819
private final ScopeType scopeType;
920

@@ -16,6 +27,12 @@ public class Scope {
1627
@Json(name = "end_line")
1728
private final int endLine;
1829

30+
@Json(name = "has_injectible_lines")
31+
private final boolean hasInjectibleLines;
32+
33+
@Json(name = "injectible_lines")
34+
private final List<LineRange> injectibleLines;
35+
1936
private final String name;
2037

2138
@Json(name = "language_specifics")
@@ -30,6 +47,8 @@ public Scope(
3047
int startLine,
3148
int endLine,
3249
String name,
50+
boolean hasInjectibleLines,
51+
List<LineRange> injectibleLines,
3352
LanguageSpecifics languageSpecifics,
3453
List<Symbol> symbols,
3554
List<Scope> scopes) {
@@ -38,6 +57,8 @@ public Scope(
3857
this.startLine = startLine;
3958
this.endLine = endLine;
4059
this.name = name;
60+
this.hasInjectibleLines = hasInjectibleLines;
61+
this.injectibleLines = injectibleLines;
4162
this.languageSpecifics = languageSpecifics;
4263
this.symbols = symbols;
4364
this.scopes = scopes;
@@ -63,6 +84,14 @@ public String getName() {
6384
return name;
6485
}
6586

87+
public boolean hasInjectibleLines() {
88+
return hasInjectibleLines;
89+
}
90+
91+
public List<LineRange> getInjectibleLines() {
92+
return injectibleLines;
93+
}
94+
6695
public LanguageSpecifics getLanguageSpecifics() {
6796
return languageSpecifics;
6897
}
@@ -110,6 +139,8 @@ public static class Builder {
110139
private final int startLine;
111140
private final int endLine;
112141
private String name;
142+
private boolean hasInjectibleLines;
143+
private List<LineRange> injectibleLines;
113144
private LanguageSpecifics languageSpecifics;
114145
private List<Symbol> symbols;
115146
private List<Scope> scopes;
@@ -126,6 +157,16 @@ public Builder name(String name) {
126157
return this;
127158
}
128159

160+
public Builder hasInjectibleLines(boolean hasInjectibleLines) {
161+
this.hasInjectibleLines = hasInjectibleLines;
162+
return this;
163+
}
164+
165+
public Builder injectibleLines(List<LineRange> injectibleLines) {
166+
this.injectibleLines = injectibleLines;
167+
return this;
168+
}
169+
129170
public Builder languageSpecifics(LanguageSpecifics languageSpecifics) {
130171
this.languageSpecifics = languageSpecifics;
131172
return this;
@@ -143,7 +184,16 @@ public Builder scopes(List<Scope> scopes) {
143184

144185
public Scope build() {
145186
return new Scope(
146-
scopeType, sourceFile, startLine, endLine, name, languageSpecifics, symbols, scopes);
187+
scopeType,
188+
sourceFile,
189+
startLine,
190+
endLine,
191+
name,
192+
hasInjectibleLines,
193+
injectibleLines,
194+
languageSpecifics,
195+
symbols,
196+
scopes);
147197
}
148198
}
149199
}

dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymbolExtractor.java

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@
1010
import java.util.Collection;
1111
import java.util.Collections;
1212
import java.util.HashMap;
13+
import java.util.HashSet;
1314
import java.util.LinkedHashMap;
1415
import java.util.List;
1516
import java.util.Map;
17+
import java.util.Set;
1618
import java.util.stream.Collectors;
1719
import org.objectweb.asm.ClassReader;
1820
import org.objectweb.asm.Label;
@@ -124,6 +126,8 @@ private static List<Scope> extractMethods(ClassNode classNode, String sourceFile
124126
.name(method.name)
125127
.scopes(varScopes)
126128
.symbols(methodSymbols)
129+
.hasInjectibleLines(!methodLineInfo.ranges.isEmpty())
130+
.injectibleLines(methodLineInfo.ranges)
127131
.languageSpecifics(methodSpecifics)
128132
.build();
129133
methodScopes.add(methodScope);
@@ -431,26 +435,47 @@ private static Scope maxScope(Scope scope1, Scope scope2) {
431435
: scope1;
432436
}
433437

434-
private static int getFirstLine(MethodNode methodNode) {
435-
AbstractInsnNode node = methodNode.instructions.getFirst();
436-
while (node != null) {
437-
if (node.getType() == AbstractInsnNode.LINE) {
438-
LineNumberNode lineNumberNode = (LineNumberNode) node;
439-
return lineNumberNode.line;
438+
static List<Scope.LineRange> buildRanges(List<Integer> sortedLineNo) {
439+
if (sortedLineNo.isEmpty()) {
440+
return Collections.emptyList();
441+
}
442+
List<Scope.LineRange> ranges = new ArrayList<>();
443+
int start = sortedLineNo.get(0);
444+
int previous = start;
445+
int i = 1;
446+
outer:
447+
while (i < sortedLineNo.size()) {
448+
int currentLineNo = sortedLineNo.get(i);
449+
while (currentLineNo == previous + 1) {
450+
i++;
451+
previous++;
452+
if (i < sortedLineNo.size()) {
453+
currentLineNo = sortedLineNo.get(i);
454+
} else {
455+
break outer;
456+
}
440457
}
441-
node = node.getNext();
458+
ranges.add(new Scope.LineRange(start, previous));
459+
start = currentLineNo;
460+
previous = start;
461+
i++;
442462
}
443-
return 0;
463+
ranges.add(new Scope.LineRange(start, previous));
464+
return ranges;
444465
}
445466

446467
private static MethodLineInfo extractMethodLineInfo(MethodNode methodNode) {
447468
Map<Label, Integer> map = new HashMap<>();
448-
int startLine = getFirstLine(methodNode);
449-
int maxLine = startLine;
469+
List<Integer> lineNo = new ArrayList<>();
470+
Set<Integer> dedupSet = new HashSet<>();
450471
AbstractInsnNode node = methodNode.instructions.getFirst();
472+
int maxLine = 0;
451473
while (node != null) {
452474
if (node.getType() == AbstractInsnNode.LINE) {
453475
LineNumberNode lineNumberNode = (LineNumberNode) node;
476+
if (dedupSet.add(lineNumberNode.line)) {
477+
lineNo.add(lineNumberNode.line);
478+
}
454479
maxLine = Math.max(lineNumberNode.line, maxLine);
455480
}
456481
if (node.getType() == AbstractInsnNode.LABEL) {
@@ -461,7 +486,10 @@ private static MethodLineInfo extractMethodLineInfo(MethodNode methodNode) {
461486
}
462487
node = node.getNext();
463488
}
464-
return new MethodLineInfo(startLine, maxLine, map);
489+
lineNo.sort(Integer::compareTo);
490+
int startLine = lineNo.isEmpty() ? 0 : lineNo.get(0);
491+
List<Scope.LineRange> ranges = buildRanges(lineNo);
492+
return new MethodLineInfo(startLine, maxLine, map, ranges);
465493
}
466494

467495
private static ClassNode parseClassFile(byte[] classfileBuffer) {
@@ -475,11 +503,15 @@ public static class MethodLineInfo {
475503
final int start;
476504
final int end;
477505
final Map<Label, Integer> lineMap;
506+
final List<Scope.LineRange> ranges;
478507

479-
public MethodLineInfo(int start, int end, Map<Label, Integer> lineMap) {
508+
public MethodLineInfo(
509+
int start, int end, Map<Label, Integer> lineMap, List<Scope.LineRange> ranges) {
480510
this.start = start;
481511
this.end = end;
482512
this.lineMap = lineMap;
513+
514+
this.ranges = ranges;
483515
}
484516
}
485517
}

dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/sink/SymbolSinkTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public void testSimpleFlush() {
4040
assertEquals("file", symbolContent.getPartName());
4141
assertEquals("file.json", symbolContent.getFileName());
4242
assertEquals(
43-
"{\"language\":\"JAVA\",\"scopes\":[{\"end_line\":0,\"scope_type\":\"JAR\",\"start_line\":0}],\"service\":\"service1\"}",
43+
"{\"language\":\"JAVA\",\"scopes\":[{\"end_line\":0,\"has_injectible_lines\":false,\"scope_type\":\"JAR\",\"start_line\":0}],\"service\":\"service1\"}",
4444
new String(symbolContent.getContent()));
4545
}
4646

@@ -109,12 +109,12 @@ public void splitByJarScopes() {
109109
}
110110

111111
@Test
112-
public void splitTootManyJarScopes() {
112+
public void splitTooManyJarScopes() {
113113
SymbolUploaderMock symbolUploaderMock = new SymbolUploaderMock();
114114
Config config = mock(Config.class);
115115
when(config.getServiceName()).thenReturn("service1");
116116
when(config.isSymbolDatabaseCompressed()).thenReturn(false);
117-
SymbolSink symbolSink = new SymbolSink(config, symbolUploaderMock, 2048);
117+
SymbolSink symbolSink = new SymbolSink(config, symbolUploaderMock, 4096);
118118
final int NUM_JAR_SCOPES = 21;
119119
for (int i = 0; i < NUM_JAR_SCOPES; i++) {
120120
symbolSink.addScope(

0 commit comments

Comments
 (0)