Skip to content

Commit 7e08161

Browse files
dougqhdevflow.devflow-routing-intake
andauthored
Check the DBM comment region in place, dropping the duplicate-comment substring (#11737)
Check the DBM comment region in place, dropping the extractCommentContent substring SQLCommenter.hasDDComment materialized sql.substring(commentStart, commentEnd) on every duplicate-comment check just to feed containsTraceComment. Add a SharedDBCommenter.containsTraceComment(sql, from, to) range overload that scans the comment body in place, and a Strings.regionContains primitive it (and the String delegate) build on -- no per-call substring. The String overload now delegates to the range form, so Mongo behavior is unchanged. regionContains is the allocation-free, copy-free primitive; the natural-reading SubSequence.contains layer can delegate to it later. Boundary semantics unit- tested on Strings.regionContains; DB needle behavior on the overloads; existing SQLCommenter/SharedDBCommenter suites unchanged and green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Read the comment-region check as a SubSequence view (the followable idiom) Add SubSequence.contains (delegating to the Strings.regionContains primitive) and rewrite containsTraceComment(sql, from, to) as SubSequence.of(sql, from, to).contains(...) -- a 1-to-1 substitution for what you'd idiomatically write on a substring, with no copy. EA elides the view in the transient consumption; even if it doesn't, the string copy is still avoided. Couples this branch to the SubSequence base (#10640); regionContains stays as the allocation-free primitive the view delegates to. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Add SQLCommenterDuplicateCommentBenchmark (substring 140 -> 0 B/op) Isolates the duplicate-comment guard (dbType=null skips the first-word scan; already-DD-commented SQL makes inject return early). The extractCommentContent substring allocated 140 B/op; the in-place range/view scan is EA-elided (~0). @threads(8), @fork(2), -prof gc; numbers in the class Javadoc. Throughput is flat-to-slightly-up but within @fork(2) noise -- this path is scan-CPU-dominated, so the win is the allocation, not throughput. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Refresh SQLCommenterDuplicateCommentBenchmark results to JDK 17 @fork(5) zulu-17 @fork(5), -prof gc: 23.5M -> 26.2M ops/s (~1.1x), 140 -> ~0 B/op. Headline win is the allocation; @fork(5) tightens the earlier bimodal spread. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Merge remote-tracking branch 'origin/master' into dougqh/dbcommenter-scan-overload # Conflicts: # internal-api/src/main/java/datadog/trace/util/SubSequence.java # internal-api/src/test/java/datadog/trace/util/SubSequenceTest.java Merge branch 'master' into dougqh/dbcommenter-scan-overload Merge branch 'master' into dougqh/dbcommenter-scan-overload Co-authored-by: devflow.devflow-routing-intake <devflow.devflow-routing-intake@kubernetes.us1.ddbuild.io>
1 parent fbb45e7 commit 7e08161

8 files changed

Lines changed: 239 additions & 19 deletions

File tree

dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/dbm/SharedDBCommenter.java

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import datadog.trace.api.internal.VisibleForTesting;
88
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
99
import datadog.trace.bootstrap.instrumentation.api.Tags;
10+
import datadog.trace.util.SubSequence;
1011
import java.io.UnsupportedEncodingException;
1112
import java.net.URLEncoder;
1213
import java.nio.charset.StandardCharsets;
@@ -53,19 +54,29 @@ public class SharedDBCommenter {
5354
private static volatile boolean staticPrefixComputed = false;
5455
private static volatile String staticPrefix;
5556

56-
// Used by SQLCommenter and MongoCommentInjector to avoid duplicate comment injection.
57-
// Note: the contains-chain could still be done "better" (a single scan), but the per-call
58-
// "KEY + =" concatenation -- the allocating part -- is now hoisted to the *_EQ constants above.
57+
// Used by SQLCommenter and MongoCommentInjector to avoid duplicate comment injection. Mongo
58+
// passes the already-extracted comment body; SQLCommenter uses the range overload to check it
59+
// in place. Both run the same nine "<key>=" needle checks.
5960
public static boolean containsTraceComment(String commentContent) {
60-
return commentContent.contains(PARENT_SERVICE_EQ)
61-
|| commentContent.contains(DATABASE_SERVICE_EQ)
62-
|| commentContent.contains(DD_HOSTNAME_EQ)
63-
|| commentContent.contains(DD_DB_NAME_EQ)
64-
|| commentContent.contains(DD_PEER_SERVICE_EQ)
65-
|| commentContent.contains(DD_ENV_EQ)
66-
|| commentContent.contains(DD_VERSION_EQ)
67-
|| commentContent.contains(TRACEPARENT_EQ)
68-
|| commentContent.contains(DD_SERVICE_HASH_EQ);
61+
return containsTraceComment(commentContent, 0, commentContent.length());
62+
}
63+
64+
/**
65+
* Range overload: true if {@code sql} contains a trace-comment needle fully within {@code [from,
66+
* to)} -- checks the comment body in place, with no substring allocation of the region.
67+
*/
68+
public static boolean containsTraceComment(String sql, int from, int to) {
69+
// Zero-copy view of the comment body; reads like ordinary String.contains, no substring.
70+
SubSequence comment = SubSequence.of(sql, from, to);
71+
return comment.contains(PARENT_SERVICE_EQ)
72+
|| comment.contains(DATABASE_SERVICE_EQ)
73+
|| comment.contains(DD_HOSTNAME_EQ)
74+
|| comment.contains(DD_DB_NAME_EQ)
75+
|| comment.contains(DD_PEER_SERVICE_EQ)
76+
|| comment.contains(DD_ENV_EQ)
77+
|| comment.contains(DD_VERSION_EQ)
78+
|| comment.contains(TRACEPARENT_EQ)
79+
|| comment.contains(DD_SERVICE_HASH_EQ);
6980
}
7081

7182
// Build database comment content without comment delimiters such as /* */
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package datadog.trace.bootstrap.instrumentation.dbm;
2+
3+
import static datadog.trace.bootstrap.instrumentation.dbm.SharedDBCommenter.containsTraceComment;
4+
import static org.junit.jupiter.api.Assertions.assertFalse;
5+
import static org.junit.jupiter.api.Assertions.assertTrue;
6+
7+
import org.junit.jupiter.api.Test;
8+
9+
/**
10+
* DB-level behavior of {@link SharedDBCommenter#containsTraceComment}: the nine "<key>=" needle
11+
* set, the {@code String} delegate, and the range overload checking the comment body in place. (The
12+
* {@code [from, to)} boundary semantics are unit-tested on {@code Strings.regionContains}.)
13+
*/
14+
class SharedDBCommenterContainsTraceCommentTest {
15+
16+
@Test
17+
void delegate_wholeString() {
18+
assertTrue(containsTraceComment("ddps='svc',dde='test'"));
19+
assertFalse(containsTraceComment("just a plain comment"));
20+
assertFalse(containsTraceComment(""));
21+
}
22+
23+
@Test
24+
void range_needleInsideCommentBody() {
25+
String sql = "SELECT 1 /*ddps='svc',dde='test'*/";
26+
int from = sql.indexOf("/*") + 2;
27+
int to = sql.indexOf("*/");
28+
assertTrue(containsTraceComment(sql, from, to));
29+
}
30+
31+
@Test
32+
void range_nonDdCommentBody() {
33+
String sql = "SELECT 1 /* just a customer comment */";
34+
int from = sql.indexOf("/*") + 2;
35+
int to = sql.indexOf("*/");
36+
assertFalse(containsTraceComment(sql, from, to));
37+
}
38+
39+
@Test
40+
void range_ddNeedleOutsideCommentRegionNotMatched() {
41+
// The DD needle sits in the statement body, not the comment region we pass -- a whole-string
42+
// contains would false-positive; the range check must scope to [from, to).
43+
String sql = "ddps='x' /* clean */";
44+
int from = sql.indexOf("/*") + 2;
45+
int to = sql.indexOf("*/");
46+
assertFalse(containsTraceComment(sql, from, to));
47+
}
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package datadog.trace.instrumentation.jdbc;
2+
3+
import org.openjdk.jmh.annotations.Benchmark;
4+
import org.openjdk.jmh.annotations.Fork;
5+
import org.openjdk.jmh.annotations.Measurement;
6+
import org.openjdk.jmh.annotations.Scope;
7+
import org.openjdk.jmh.annotations.State;
8+
import org.openjdk.jmh.annotations.Threads;
9+
import org.openjdk.jmh.annotations.Warmup;
10+
11+
/**
12+
* Benchmark for the duplicate-comment guard in {@link SQLCommenter#inject} -- the {@code
13+
* hasDDComment} path that avoids double-commenting an already-instrumented statement.
14+
*
15+
* <p><b>What we're measuring.</b> The guard used to materialize {@code sql.substring(commentStart,
16+
* commentEnd)} (the comment body) just to scan it for trace-comment needles. (B) checks the comment
17+
* region in place via {@code SharedDBCommenter.containsTraceComment(sql, from, to)} -- no
18+
* substring.
19+
*
20+
* <p><b>Isolation.</b> The substring only happens when the SQL already carries a comment in the
21+
* checked position; for a DD comment {@code inject} then returns early. Passing {@code dbType=null}
22+
* skips the first-word scan (benchmarked separately for the {@code getFirstWord} change), so over
23+
* already-DD-commented SQL the <i>only</i> allocation left in {@code inject} is the substring (B)
24+
* removes. Run at {@code @Threads(8)} with {@code -prof gc}.
25+
*
26+
* <pre>
27+
* ./gradlew :dd-java-agent:instrumentation:jdbc:jmh # add -prof gc
28+
* </pre>
29+
*
30+
* <p><b>Results</b> (JDK 17, MacBook M-series, {@code @Threads(8)}, {@code @Fork(5)}, {@code -prof
31+
* gc}):
32+
*
33+
* <pre>
34+
* throughput gc.alloc.rate.norm
35+
* before (substring) 23.5M ± 1.1M ops/s 140 B/op
36+
* after (range/view) 26.2M ± 1.5M ops/s ~0 B/op (10^-5)
37+
* </pre>
38+
*
39+
* The extractCommentContent substring (140 B/op) is gone -- the in-place range scan and the
40+
* SubSequence view it flows through are both EA-elided. The allocation delta is exact and
41+
* fork-stable; that's the win. At {@code @Fork(5)} the spread tightens and a small throughput
42+
* uplift (~1.1x) resolves -- but this path is dominated by the nine indexOf scans (CPU the
43+
* alloc-removal doesn't touch), so the headline win is the allocation, a small cut that compounds
44+
* across comment-bearing injects, not a per-call throughput jump.
45+
*/
46+
@Fork(5)
47+
@Warmup(iterations = 2)
48+
@Measurement(iterations = 5)
49+
@Threads(8)
50+
public class SQLCommenterDuplicateCommentBenchmark {
51+
52+
// Already-DD-commented SQL (append style, comment at the end). First needle hits at different
53+
// depths: ddps first (cheap), traceparent-only (scans 8 before the match).
54+
static final String[] SQL = {
55+
"SELECT * FROM foo /*ddps='svc',dde='test',dddbs='mydb',ddh='h',dddb='n',traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-00'*/",
56+
"SELECT * FROM bar WHERE id = 42 /*traceparent='00-00000000000000007fffffffffffffff-000000024cb016ea-01'*/",
57+
};
58+
59+
/** Per-thread cursor so threads don't contend on a shared index under {@code @Threads(8)}. */
60+
@State(Scope.Thread)
61+
public static class Cursor {
62+
int index = 0;
63+
64+
String next() {
65+
int i = index;
66+
index = (i + 1) % SQL.length;
67+
return SQL[i];
68+
}
69+
}
70+
71+
@Benchmark
72+
public boolean alreadyCommented(Cursor cursor) {
73+
// dbType=null skips the first-word scan; the DD comment makes inject return early after the
74+
// duplicate-comment check -- the path (B) optimizes. Returns the input sql (no new String).
75+
return SQLCommenter.inject(cursor.next(), "mydb", null, "h", "n", null, true) != null;
76+
}
77+
}

dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/SQLCommenter.java

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -123,11 +123,6 @@ private static boolean hasDDComment(String sql, boolean appendComment) {
123123
return false;
124124
}
125125

126-
String commentContent = extractCommentContent(sql, appendComment);
127-
return SharedDBCommenter.containsTraceComment(commentContent);
128-
}
129-
130-
private static String extractCommentContent(String sql, boolean appendComment) {
131126
int startIdx;
132127
int endIdx;
133128
if (appendComment) {
@@ -138,9 +133,10 @@ private static String extractCommentContent(String sql, boolean appendComment) {
138133
endIdx = sql.indexOf(CLOSE_COMMENT);
139134
}
140135
if (startIdx != -1 && endIdx != -1 && endIdx > startIdx) {
141-
return sql.substring(startIdx + OPEN_COMMENT_LEN, endIdx);
136+
// Check the comment body in place -- no substring of the comment region.
137+
return SharedDBCommenter.containsTraceComment(sql, startIdx + OPEN_COMMENT_LEN, endIdx);
142138
}
143-
return "";
139+
return false;
144140
}
145141

146142
/**

internal-api/src/main/java/datadog/trace/util/Strings.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,4 +340,17 @@ public SubSequence next() {
340340
return subSeq;
341341
}
342342
}
343+
344+
/**
345+
* True if {@code needle} occurs fully within {@code s[beginIndex, endIndex)} -- a range-limited,
346+
* allocation-free alternative to {@code s.substring(beginIndex, endIndex).contains(needle)}.
347+
*
348+
* <p>{@code indexOf} returns the earliest occurrence at or after {@code beginIndex}; if that one
349+
* overshoots {@code endIndex} there is no earlier full occurrence in range, so the bound check is
350+
* exact.
351+
*/
352+
public static boolean regionContains(String s, int beginIndex, int endIndex, String needle) {
353+
int idx = s.indexOf(needle, beginIndex);
354+
return idx >= 0 && idx + needle.length() <= endIndex;
355+
}
343356
}

internal-api/src/main/java/datadog/trace/util/SubSequence.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,14 @@ public final boolean equals(CharSequence that) {
109109
return true;
110110
}
111111

112+
/**
113+
* True if this sub-sequence contains {@code needle} -- the zero-copy equivalent of {@code
114+
* toString().contains(needle)}, with no substring materialized.
115+
*/
116+
public final boolean contains(String needle) {
117+
return Strings.regionContains(this.str, this.beginIndex, this.endIndex, needle);
118+
}
119+
112120
/** Case-insensitive content comparison; mirrors {@link String#equalsIgnoreCase(String)}. */
113121
public final boolean equalsIgnoreCase(CharSequence that) {
114122
int len = this.length();
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package datadog.trace.util;
2+
3+
import static datadog.trace.util.Strings.regionContains;
4+
import static org.junit.jupiter.api.Assertions.assertFalse;
5+
import static org.junit.jupiter.api.Assertions.assertTrue;
6+
7+
import org.junit.jupiter.api.Test;
8+
9+
/** Boundary semantics of {@link Strings#regionContains(String, int, int, String)}. */
10+
class StringsRegionContainsTest {
11+
12+
// "abXYZcd": a0 b1 X2 Y3 Z4 c5 d6 -> "XYZ" spans [2,5).
13+
private static final String S = "abXYZcd";
14+
15+
@Test
16+
void foundFullyInside() {
17+
assertTrue(regionContains(S, 0, S.length(), "XYZ"));
18+
}
19+
20+
@Test
21+
void notPresent() {
22+
assertFalse(regionContains(S, 0, S.length(), "QQ"));
23+
}
24+
25+
@Test
26+
void exactFit() {
27+
// idx == 2, idx + len == 5 == endIndex -> included.
28+
assertTrue(regionContains(S, 2, 5, "XYZ"));
29+
}
30+
31+
@Test
32+
void straddlingEndIndexExcluded() {
33+
// endIndex == 4 cuts off the trailing 'Z' -> not fully inside.
34+
assertFalse(regionContains(S, 2, 4, "XYZ"));
35+
}
36+
37+
@Test
38+
void occurrenceBeforeBeginIndexExcluded() {
39+
// beginIndex == 3 starts past the needle's first char -> no occurrence at/after beginIndex.
40+
assertFalse(regionContains(S, 3, S.length(), "XYZ"));
41+
}
42+
43+
@Test
44+
void emptyRegion() {
45+
assertFalse(regionContains(S, 2, 2, "XYZ"));
46+
}
47+
48+
@Test
49+
void matchesWholeStringContains() {
50+
assertTrue(regionContains("hello", 0, 5, "ll"));
51+
assertFalse(regionContains("hello", 0, 5, "z"));
52+
}
53+
}

internal-api/src/test/java/datadog/trace/util/SubSequenceTest.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,20 @@ public void appendToBuilder() {
110110
assertEquals(expectedStr, builder1.toString());
111111
}
112112

113+
@Test
114+
public void contains() {
115+
// "/*ddps='svc',dde='x'*/ rest" -- the comment body "ddps='svc',dde='x'" spans [2, 20).
116+
String s = "/*ddps='svc',dde='x'*/ rest";
117+
SubSequence comment = SubSequence.of(s, 2, 20);
118+
assertTrue(comment.contains("ddps="));
119+
assertTrue(comment.contains("dde="));
120+
assertFalse(comment.contains("ddh="));
121+
122+
// View-relative: a needle present in the backing string but outside this view is not found.
123+
SubSequence dde = SubSequence.of(s, 13, 20); // "dde='x'"
124+
assertFalse(dde.contains("ddps=")); // ddps= is before this view's range
125+
}
126+
113127
@Test
114128
public void equalsIgnoreCase() {
115129
SubSequence call = SubSequence.of("xx CALL yy", 3, 7); // "CALL"

0 commit comments

Comments
 (0)