Skip to content
Draft

WIP #10393

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -196,5 +196,10 @@ public final class TraceInstrumentationConfig {

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

public static final String TRACE_RESOURCE_RENAMING_ENABLED = "trace.resource.renaming.enabled";

public static final String TRACE_RESOURCE_RENAMING_ALWAYS_SIMPLIFIED_ENDPOINT =
"trace.resource.renaming.always.simplified.endpoint";

private TraceInstrumentationConfig() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import static datadog.communication.ddagent.DDAgentFeaturesDiscovery.V6_METRICS_ENDPOINT;
import static datadog.trace.api.DDTags.BASE_SERVICE;
import static datadog.trace.api.Functions.UTF8_ENCODE;
import static datadog.trace.bootstrap.instrumentation.api.Tags.HTTP_ENDPOINT;
import static datadog.trace.bootstrap.instrumentation.api.Tags.HTTP_METHOD;
import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND;
import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_CLIENT;
import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_CONSUMER;
Expand Down Expand Up @@ -307,6 +309,12 @@ private boolean spanKindEligible(@Nonnull CharSequence spanKind) {
}

private boolean publish(CoreSpan<?> span, boolean isTopLevel, CharSequence spanKind) {
// Extract HTTP method and endpoint
Object httpMethodObj = span.unsafeGetTag(HTTP_METHOD);
String httpMethod = httpMethodObj != null ? httpMethodObj.toString() : null;
Object httpEndpointObj = span.unsafeGetTag(HTTP_ENDPOINT);
String httpEndpoint = httpEndpointObj != null ? httpEndpointObj.toString() : null;

MetricKey newKey =
new MetricKey(
span.getResourceName(),
Expand All @@ -318,7 +326,9 @@ private boolean publish(CoreSpan<?> span, boolean isTopLevel, CharSequence spanK
span.getParentId() == 0,
SPAN_KINDS.computeIfAbsent(
spanKind, UTF8BytesString::create), // save repeated utf8 conversions
getPeerTags(span, spanKind.toString()));
getPeerTags(span, spanKind.toString()),
httpMethod,
httpEndpoint);
MetricKey key = keys.putIfAbsent(newKey, newKey);
if (null == key) {
key = newKey;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public final class MetricKey {
private final boolean isTraceRoot;
private final UTF8BytesString spanKind;
private final List<UTF8BytesString> peerTags;
private final UTF8BytesString httpMethod;
private final UTF8BytesString httpEndpoint;

public MetricKey(
CharSequence resource,
Expand All @@ -28,7 +30,9 @@ public MetricKey(
boolean synthetics,
boolean isTraceRoot,
CharSequence spanKind,
List<UTF8BytesString> peerTags) {
List<UTF8BytesString> peerTags,
CharSequence httpMethod,
CharSequence httpEndpoint) {
this.resource = null == resource ? EMPTY : UTF8BytesString.create(resource);
this.service = null == service ? EMPTY : UTF8BytesString.create(service);
this.operationName = null == operationName ? EMPTY : UTF8BytesString.create(operationName);
Expand All @@ -38,6 +42,8 @@ public MetricKey(
this.isTraceRoot = isTraceRoot;
this.spanKind = null == spanKind ? EMPTY : UTF8BytesString.create(spanKind);
this.peerTags = peerTags == null ? Collections.emptyList() : peerTags;
this.httpMethod = null == httpMethod ? EMPTY : UTF8BytesString.create(httpMethod);
this.httpEndpoint = null == httpEndpoint ? EMPTY : UTF8BytesString.create(httpEndpoint);

// Unrolled polynomial hashcode to avoid varargs allocation
// and eliminate data dependency between iterations as in Arrays.hashCode.
Expand All @@ -47,13 +53,15 @@ public MetricKey(
// https://richardstartin.github.io/posts/still-true-in-java-9-handwritten-hash-codes-are-faster

this.hash =
-196513505 * Boolean.hashCode(this.isTraceRoot)
+ -1807454463 * this.spanKind.hashCode()
+ 887_503_681 * this.peerTags.hashCode() // possibly unroll here has well.
+ 28_629_151 * this.resource.hashCode()
+ 923_521 * this.service.hashCode()
+ 29791 * this.operationName.hashCode()
+ 961 * this.type.hashCode()
-196_513_505 * Boolean.hashCode(this.isTraceRoot)
+ -1_807_454_463 * this.spanKind.hashCode()
+ 887_503_681 * this.peerTags.hashCode()
+ 28_629_151 * this.httpMethod.hashCode()
+ 923_521 * this.httpEndpoint.hashCode()
+ 29_791 * this.resource.hashCode()
+ 961 * this.service.hashCode()
+ 31 * this.operationName.hashCode()
+ this.type.hashCode()
+ 31 * httpStatusCode
+ (this.synthetics ? 1 : 0);
}
Expand Down Expand Up @@ -94,6 +102,14 @@ public List<UTF8BytesString> getPeerTags() {
return peerTags;
}

public UTF8BytesString getHttpMethod() {
return httpMethod;
}

public UTF8BytesString getHttpEndpoint() {
return httpEndpoint;
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand All @@ -110,7 +126,9 @@ public boolean equals(Object o) {
&& type.equals(metricKey.type)
&& isTraceRoot == metricKey.isTraceRoot
&& spanKind.equals(metricKey.spanKind)
&& peerTags.equals(metricKey.peerTags);
&& peerTags.equals(metricKey.peerTags)
&& httpMethod.equals(metricKey.httpMethod)
&& httpEndpoint.equals(metricKey.httpEndpoint);
}
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public final class SerializingMetricWriter implements MetricWriter {
private static final byte[] IS_TRACE_ROOT = "IsTraceRoot".getBytes(ISO_8859_1);
private static final byte[] SPAN_KIND = "SpanKind".getBytes(ISO_8859_1);
private static final byte[] PEER_TAGS = "PeerTags".getBytes(ISO_8859_1);
private static final byte[] HTTP_METHOD = "HTTPMethod".getBytes(ISO_8859_1);
private static final byte[] HTTP_ENDPOINT = "HTTPEndpoint".getBytes(ISO_8859_1);

// Constant declared here for compile-time folding
public static final int TRISTATE_TRUE = TriState.TRUE.serialValue;
Expand Down Expand Up @@ -104,7 +106,7 @@ public void startBucket(int metricCount, long start, long duration) {

@Override
public void add(MetricKey key, AggregateMetric aggregate) {
writer.startMap(15);
writer.startMap(17);

writer.writeUTF8(NAME);
writer.writeUTF8(key.getOperationName());
Expand Down Expand Up @@ -138,6 +140,12 @@ public void add(MetricKey key, AggregateMetric aggregate) {
writer.writeUTF8(peerTag);
}

writer.writeUTF8(HTTP_METHOD);
writer.writeUTF8(key.getHttpMethod());

writer.writeUTF8(HTTP_ENDPOINT);
writer.writeUTF8(key.getHttpEndpoint());

writer.writeUTF8(HITS);
writer.writeInt(aggregate.getHitCount());

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package datadog.trace.core.endpoint;

import static datadog.trace.bootstrap.instrumentation.api.Tags.HTTP_ENDPOINT;

import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Resolves HTTP endpoints for APM trace metrics by determining whether to use http.route or compute
* http.endpoint from the URL.
*
* <p>This class implements the endpoint inference logic defined in the RFC-1051 for trace resource
* renaming, including route eligibility checks and endpoint computation.
*/
public class EndpointResolver {
private static final Logger log = LoggerFactory.getLogger(EndpointResolver.class);

private final boolean enabled;
private final boolean alwaysSimplifiedEndpoint;

/**
* Creates a new EndpointResolver with the given configuration.
*
* @param enabled whether endpoint inference is enabled
* @param alwaysSimplifiedEndpoint whether to always compute endpoint even when route exists
*/
public EndpointResolver(boolean enabled, boolean alwaysSimplifiedEndpoint) {
this.enabled = enabled;
this.alwaysSimplifiedEndpoint = alwaysSimplifiedEndpoint;
}

/**
* Resolves the endpoint for a span and optionally tags it with http.endpoint.
*
* <p>Resolution logic:
*
* <ol>
* <li>If disabled, return null
* <li>If alwaysSimplifiedEndpoint=true, compute from URL and tag span
* <li>If http.route exists and is eligible, use it (no tagging)
* <li>Otherwise, compute from URL and tag span with http.endpoint
* </ol>
*
* @param unsafeTags unsafe tag map. Using at this point, when the span is finished and there
* should not be anymore external interaction, should be considered safe
* @param httpRoute the http.route tag value (may be null)
* @param httpUrl the http.url tag value (may be null)
* @return the resolved endpoint, or null if resolution fails
*/
@Nullable
public String resolveEndpoint(
java.util.Map<String, Object> unsafeTags,
@Nullable String httpRoute,
@Nullable String httpUrl) {
if (!enabled) {
return null;
}

// If alwaysSimplifiedEndpoint is set, always compute and tag
if (alwaysSimplifiedEndpoint) {
String endpoint = computeEndpoint(httpUrl);
if (endpoint != null) {
unsafeTags.put(HTTP_ENDPOINT, endpoint);
}
return endpoint;
}

// If route exists and is eligible, use it
if (isRouteEligible(httpRoute)) {
return httpRoute;
}

// Compute endpoint from URL and tag the span
String endpoint = computeEndpoint(httpUrl);
if (endpoint != null) {
unsafeTags.put(HTTP_ENDPOINT, endpoint);
}
return endpoint;
}

/**
* Determines if an http.route is eligible for use as an endpoint.
*
* <p>A route is NOT eligible if it is null, empty, or a catch-all wildcard pattern. Catch-all
* patterns (single or double wildcards) indicate instrumentation problems rather than actual
* routes. Regex fallback patterns are considered eligible.
*
* @param route the http.route value to check
* @return true if the route can be used as an endpoint
*/
public static boolean isRouteEligible(@Nullable String route) {
if (route == null || route.isEmpty()) {
return false;
}

// Discard catch-all routes that indicate instrumentation problems
if ("*".equals(route) || "*/*".equals(route)) {
return false;
}

return true;
}

/**
* Computes an endpoint from a URL using the simplification algorithm.
*
* @param url the http.url tag value
* @return the computed endpoint, or null if URL is null/empty
*/
@Nullable
public static String computeEndpoint(@Nullable String url) {
if (url == null || url.isEmpty()) {
return null;
}

try {
return EndpointSimplifier.simplifyUrl(url);
} catch (Exception e) {
log.debug("Failed to compute endpoint from URL: {}", url, e);
return null;
}
}

/**
* Returns whether endpoint inference is enabled.
*
* @return true if enabled
*/
public boolean isEnabled() {
return enabled;
}

/**
* Returns whether simplified endpoint computation is always used.
*
* @return true if alwaysSimplifiedEndpoint is set
*/
public boolean isAlwaysSimplifiedEndpoint() {
return alwaysSimplifiedEndpoint;
}
}
Loading
Loading