Skip to content

Commit 196140a

Browse files
committed
WIP
1 parent 2ffa640 commit 196140a

File tree

25 files changed

+2348
-77
lines changed

25 files changed

+2348
-77
lines changed
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
package datadog.smoketest
2+
3+
import okhttp3.Request
4+
import java.util.concurrent.atomic.AtomicInteger
5+
import java.util.regex.Pattern
6+
7+
class HttpEndpointTaggingSmokeTest extends AbstractServerSmokeTest {
8+
9+
@Override
10+
ProcessBuilder createProcessBuilder() {
11+
String springBootShadowJar = System.getProperty("datadog.smoketest.springboot.shadowJar.path")
12+
13+
List<String> command = new ArrayList<>()
14+
command.add(javaPath())
15+
command.addAll(defaultJavaProperties)
16+
command.addAll((String[]) [
17+
"-Ddd.writer.type=MultiWriter:TraceStructureWriter:${output.getAbsolutePath()}:includeResource,DDAgentWriter",
18+
"-Ddd.trace.resource.renaming.enabled=true",
19+
"-jar",
20+
springBootShadowJar,
21+
"--server.port=${httpPort}"
22+
])
23+
ProcessBuilder processBuilder = new ProcessBuilder(command)
24+
processBuilder.directory(new File(buildDirectory))
25+
}
26+
27+
@Override
28+
File createTemporaryFile() {
29+
return File.createTempFile("http-endpoint-tagging-trace", "out")
30+
}
31+
32+
@Override
33+
protected Set<String> expectedTraces() {
34+
return [
35+
Pattern.quote("[servlet.request:GET /greeting[spring.handler:IastWebController.greeting]]")
36+
]
37+
}
38+
39+
@Override
40+
protected Set<String> assertTraceCounts(Set<String> expected, Map<String, AtomicInteger> traceCounts) {
41+
List<Pattern> remaining = expected.collect { Pattern.compile(it) }.toList()
42+
for (def i = remaining.size() - 1; i >= 0; i--) {
43+
for (Map.Entry<String, AtomicInteger> entry : traceCounts.entrySet()) {
44+
if (entry.getValue() > 0 && remaining.get(i).matcher(entry.getKey()).matches()) {
45+
remaining.remove(i)
46+
break
47+
}
48+
}
49+
}
50+
return remaining.collect { it.pattern() }.toSet()
51+
}
52+
53+
def "test basic HTTP endpoint tagging"() {
54+
setup:
55+
String url = "http://localhost:${httpPort}/greeting"
56+
def request = new Request.Builder().url(url).get().build()
57+
58+
when:
59+
def response = client.newCall(request).execute()
60+
61+
then:
62+
def responseBodyStr = response.body().string()
63+
responseBodyStr != null
64+
responseBodyStr.contains("Sup Dawg")
65+
response.code() == 200
66+
waitForTraceCount(1)
67+
}
68+
69+
def "test URL parameterization for numeric IDs"() {
70+
setup:
71+
String url = "http://localhost:${httpPort}/users/123"
72+
def request = new Request.Builder().url(url).get().build()
73+
74+
when:
75+
def response = client.newCall(request).execute()
76+
77+
then:
78+
// May return 404 since endpoint doesn't exist, but span should still be created
79+
response.code() in [200, 404]
80+
waitForTraceCount(1)
81+
}
82+
83+
def "test URL parameterization for hex patterns"() {
84+
setup:
85+
String url = "http://localhost:${httpPort}/session/abc123def456"
86+
def request = new Request.Builder().url(url).get().build()
87+
88+
when:
89+
def response = client.newCall(request).execute()
90+
91+
then:
92+
// May return 404 since endpoint doesn't exist, but span should still be created
93+
response.code() in [200, 404]
94+
waitForTraceCount(1)
95+
}
96+
97+
def "test int_id pattern parameterization"() {
98+
setup:
99+
String url = "http://localhost:${httpPort}/api/versions/12.34.56"
100+
def request = new Request.Builder().url(url).get().build()
101+
102+
when:
103+
def response = client.newCall(request).execute()
104+
105+
then:
106+
response.code() in [200, 404]
107+
waitForTraceCount(1)
108+
}
109+
110+
def "test hex_id pattern parameterization"() {
111+
setup:
112+
String url = "http://localhost:${httpPort}/api/tokens/550e8400-e29b-41d4-a716-446655440000"
113+
def request = new Request.Builder().url(url).get().build()
114+
115+
when:
116+
def response = client.newCall(request).execute()
117+
118+
then:
119+
response.code() in [200, 404]
120+
waitForTraceCount(1)
121+
}
122+
123+
def "test str pattern parameterization for long strings"() {
124+
setup:
125+
String url = "http://localhost:${httpPort}/files/very-long-filename-that-exceeds-twenty-characters.pdf"
126+
def request = new Request.Builder().url(url).get().build()
127+
128+
when:
129+
def response = client.newCall(request).execute()
130+
131+
then:
132+
response.code() in [200, 404]
133+
waitForTraceCount(1)
134+
}
135+
136+
def "test str pattern parameterization for special characters"() {
137+
setup:
138+
String url = "http://localhost:${httpPort}/search/query%20with%20spaces"
139+
def request = new Request.Builder().url(url).get().build()
140+
141+
when:
142+
def response = client.newCall(request).execute()
143+
144+
then:
145+
response.code() in [200, 404]
146+
waitForTraceCount(1)
147+
}
148+
149+
def "test mixed URL patterns with multiple segments"() {
150+
setup:
151+
String url = "http://localhost:${httpPort}/api/users/123/orders/abc456def/items/789"
152+
def request = new Request.Builder().url(url).get().build()
153+
154+
when:
155+
def response = client.newCall(request).execute()
156+
157+
then:
158+
response.code() in [200, 404]
159+
waitForTraceCount(1)
160+
}
161+
162+
def "test URL with query parameters"() {
163+
setup:
164+
String url = "http://localhost:${httpPort}/api/users/123?filter=active&limit=10"
165+
def request = new Request.Builder().url(url).get().build()
166+
167+
when:
168+
def response = client.newCall(request).execute()
169+
170+
then:
171+
response.code() in [200, 404]
172+
waitForTraceCount(1)
173+
}
174+
175+
def "test URL with trailing slash"() {
176+
setup:
177+
String url = "http://localhost:${httpPort}/api/users/456/"
178+
def request = new Request.Builder().url(url).get().build()
179+
180+
when:
181+
def response = client.newCall(request).execute()
182+
183+
then:
184+
response.code() in [200, 404]
185+
waitForTraceCount(1)
186+
}
187+
188+
def "test static paths are preserved"() {
189+
setup:
190+
String url = "http://localhost:${httpPort}/health"
191+
def request = new Request.Builder().url(url).get().build()
192+
193+
when:
194+
def response = client.newCall(request).execute()
195+
196+
then:
197+
response.code() in [200, 404]
198+
waitForTraceCount(1)
199+
}
200+
201+
def "test root path handling"() {
202+
setup:
203+
String url = "http://localhost:${httpPort}/"
204+
def request = new Request.Builder().url(url).get().build()
205+
206+
when:
207+
def response = client.newCall(request).execute()
208+
209+
then:
210+
response.code() in [200, 404]
211+
waitForTraceCount(1)
212+
}
213+
214+
def "test pattern precedence - int pattern wins over int_id"() {
215+
setup:
216+
String url = "http://localhost:${httpPort}/api/items/12345"
217+
def request = new Request.Builder().url(url).get().build()
218+
219+
when:
220+
def response = client.newCall(request).execute()
221+
222+
then:
223+
response.code() in [200, 404]
224+
waitForTraceCount(1)
225+
}
226+
227+
def "test pattern precedence - hex pattern wins over hex_id"() {
228+
setup:
229+
String url = "http://localhost:${httpPort}/api/hashes/abc123def"
230+
def request = new Request.Builder().url(url).get().build()
231+
232+
when:
233+
def response = client.newCall(request).execute()
234+
235+
then:
236+
response.code() in [200, 404]
237+
waitForTraceCount(1)
238+
}
239+
240+
def "test edge case - short segments not parameterized"() {
241+
setup:
242+
String url = "http://localhost:${httpPort}/api/x/y"
243+
def request = new Request.Builder().url(url).get().build()
244+
245+
when:
246+
def response = client.newCall(request).execute()
247+
248+
then:
249+
response.code() in [200, 404]
250+
waitForTraceCount(1)
251+
}
252+
}

dd-trace-api/src/main/java/datadog/trace/api/config/TraceInstrumentationConfig.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,5 +196,10 @@ public final class TraceInstrumentationConfig {
196196

197197
public static final String SQS_BODY_PROPAGATION_ENABLED = "trace.sqs.body.propagation.enabled";
198198

199+
public static final String TRACE_RESOURCE_RENAMING_ENABLED = "trace.resource.renaming.enabled";
200+
201+
public static final String TRACE_RESOURCE_RENAMING_ALWAYS_SIMPLIFIED_ENDPOINT =
202+
"trace.resource.renaming.always.simplified.endpoint";
203+
199204
private TraceInstrumentationConfig() {}
200205
}

dd-trace-core/src/jmh/java/datadog/trace/common/metrics/ConflatingMetricsAggregatorBenchmark.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ public class ConflatingMetricsAggregatorBenchmark {
4343
HealthMetrics.NO_OP,
4444
new NullSink(),
4545
2048,
46-
2048);
46+
2048,
47+
false);
4748
private final List<CoreSpan<?>> spans = generateTrace(64);
4849

4950
static List<CoreSpan<?>> generateTrace(int len) {

dd-trace-core/src/main/java/datadog/trace/common/metrics/ConflatingMetricsAggregator.java

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import static datadog.communication.ddagent.DDAgentFeaturesDiscovery.V6_METRICS_ENDPOINT;
44
import static datadog.trace.api.DDTags.BASE_SERVICE;
55
import static datadog.trace.api.Functions.UTF8_ENCODE;
6+
import static datadog.trace.bootstrap.instrumentation.api.Tags.HTTP_ENDPOINT;
7+
import static datadog.trace.bootstrap.instrumentation.api.Tags.HTTP_METHOD;
68
import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND;
79
import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_CLIENT;
810
import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_CONSUMER;
@@ -100,6 +102,7 @@ public final class ConflatingMetricsAggregator implements MetricsAggregator, Eve
100102
private final TimeUnit reportingIntervalTimeUnit;
101103
private final DDAgentFeaturesDiscovery features;
102104
private final HealthMetrics healthMetrics;
105+
private final boolean includeEndpointInMetrics;
103106

104107
private volatile AgentTaskScheduler.Scheduled<?> cancellation;
105108

@@ -120,7 +123,8 @@ public ConflatingMetricsAggregator(
120123
false,
121124
DEFAULT_HEADERS),
122125
config.getTracerMetricsMaxAggregates(),
123-
config.getTracerMetricsMaxPending());
126+
config.getTracerMetricsMaxPending(),
127+
config.isTraceResourceRenamingEnabled());
124128
}
125129

126130
ConflatingMetricsAggregator(
@@ -130,7 +134,8 @@ public ConflatingMetricsAggregator(
130134
HealthMetrics healthMetric,
131135
Sink sink,
132136
int maxAggregates,
133-
int queueSize) {
137+
int queueSize,
138+
boolean includeEndpointInMetrics) {
134139
this(
135140
wellKnownTags,
136141
ignoredResources,
@@ -140,7 +145,8 @@ public ConflatingMetricsAggregator(
140145
maxAggregates,
141146
queueSize,
142147
10,
143-
SECONDS);
148+
SECONDS,
149+
includeEndpointInMetrics);
144150
}
145151

146152
ConflatingMetricsAggregator(
@@ -152,7 +158,8 @@ public ConflatingMetricsAggregator(
152158
int maxAggregates,
153159
int queueSize,
154160
long reportingInterval,
155-
TimeUnit timeUnit) {
161+
TimeUnit timeUnit,
162+
boolean includeEndpointInMetrics) {
156163
this(
157164
ignoredResources,
158165
features,
@@ -162,7 +169,8 @@ public ConflatingMetricsAggregator(
162169
maxAggregates,
163170
queueSize,
164171
reportingInterval,
165-
timeUnit);
172+
timeUnit,
173+
includeEndpointInMetrics);
166174
}
167175

168176
ConflatingMetricsAggregator(
@@ -174,8 +182,10 @@ public ConflatingMetricsAggregator(
174182
int maxAggregates,
175183
int queueSize,
176184
long reportingInterval,
177-
TimeUnit timeUnit) {
185+
TimeUnit timeUnit,
186+
boolean includeEndpointInMetrics) {
178187
this.ignoredResources = ignoredResources;
188+
this.includeEndpointInMetrics = includeEndpointInMetrics;
179189
this.inbox = Queues.mpscArrayQueue(queueSize);
180190
this.batchPool = Queues.spmcArrayQueue(maxAggregates);
181191
this.pending = new ConcurrentHashMap<>(maxAggregates * 4 / 3);
@@ -306,6 +316,16 @@ private boolean spanKindEligible(@Nonnull CharSequence spanKind) {
306316
}
307317

308318
private boolean publish(CoreSpan<?> span, boolean isTopLevel, CharSequence spanKind) {
319+
// Extract HTTP method and endpoint only if the feature is enabled
320+
String httpMethod = null;
321+
String httpEndpoint = null;
322+
if (includeEndpointInMetrics) {
323+
Object httpMethodObj = span.unsafeGetTag(HTTP_METHOD);
324+
httpMethod = httpMethodObj != null ? httpMethodObj.toString() : null;
325+
Object httpEndpointObj = span.unsafeGetTag(HTTP_ENDPOINT);
326+
httpEndpoint = httpEndpointObj != null ? httpEndpointObj.toString() : null;
327+
}
328+
309329
MetricKey newKey =
310330
new MetricKey(
311331
span.getResourceName(),
@@ -317,7 +337,9 @@ private boolean publish(CoreSpan<?> span, boolean isTopLevel, CharSequence spanK
317337
span.getParentId() == 0,
318338
SPAN_KINDS.computeIfAbsent(
319339
spanKind, UTF8BytesString::create), // save repeated utf8 conversions
320-
getPeerTags(span, spanKind.toString()));
340+
getPeerTags(span, spanKind.toString()),
341+
httpMethod,
342+
httpEndpoint);
321343
MetricKey key = keys.putIfAbsent(newKey, newKey);
322344
if (null == key) {
323345
key = newKey;

0 commit comments

Comments
 (0)