Skip to content

Commit ce5244b

Browse files
authored
Merge branch 'master' into andrea.marziali/tibco
2 parents bce66f7 + c1f2818 commit ce5244b

File tree

29 files changed

+1119
-20
lines changed

29 files changed

+1119
-20
lines changed

.gitlab/benchmarks/bp-runner.fail-on-breach.yml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
# Auto-generated SLO Thresholds
2+
# Generated: 2026-03-31
3+
#
4+
# Generation Strategy: tight
5+
# Formula: CI_bound / (1 ± T) (T = 5.0%)
6+
#
7+
# SLO Checking:
8+
# - BREACH: 90% CI boundary crosses threshold
9+
# - WARNING: 90% CI boundary crosses warning_threshold (constant)
10+
#
11+
# DO NOT EDIT MANUALLY - Regenerate using:
12+
# benchmark_analyzer generate slos --help
13+
#
114
# Thresholds set based on guidance in https://datadoghq.atlassian.net/wiki/x/LgI1LgE#How-to-choose-thresholds-for-pre-release-gates%3F
215

316
experiments:
@@ -18,7 +31,7 @@ experiments:
1831
# https://benchmarking.us1.prod.dog/trends?projectId=4&branch=master&trendsTab=per_scenario&scenario=normal_operation%2Fonly-tracing&trendsType=scenario
1932
- name: normal_operation/only-tracing
2033
thresholds:
21-
- agg_http_req_duration_p50 < 2.6 ms
34+
- agg_http_req_duration_p50 < 2.128 ms
2235
- agg_http_req_duration_p99 < 8.5 ms
2336
# https://benchmarking.us1.prod.dog/trends?projectId=4&branch=master&trendsTab=per_scenario&scenario=normal_operation%2Fotel-latest&trendsType=scenario
2437
- name: normal_operation/otel-latest
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
package datadog.trace.bootstrap.instrumentation.ffm;
2+
3+
import datadog.context.ContextScope;
4+
import datadog.trace.api.InstrumenterConfig;
5+
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
6+
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
7+
import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString;
8+
import datadog.trace.bootstrap.instrumentation.decorator.BaseDecorator;
9+
import java.lang.invoke.MethodHandle;
10+
import java.lang.invoke.MethodHandles;
11+
import java.lang.invoke.MethodType;
12+
import java.util.Set;
13+
import org.slf4j.Logger;
14+
import org.slf4j.LoggerFactory;
15+
16+
public final class FFMNativeMethodDecorator extends BaseDecorator {
17+
private static final Logger LOGGER = LoggerFactory.getLogger(FFMNativeMethodDecorator.class);
18+
private static final CharSequence TRACE_FFM = UTF8BytesString.create("trace-ffm");
19+
private static final CharSequence OPERATION_NAME = UTF8BytesString.create("trace.native");
20+
21+
private static final MethodHandle START_SPAN_MH =
22+
safeFindStatic(
23+
"startSpan",
24+
MethodType.methodType(ContextScope.class, CharSequence.class, boolean.class));
25+
private static final MethodHandle END_SPAN_MH =
26+
safeFindStatic(
27+
"endSpan",
28+
MethodType.methodType(Object.class, Throwable.class, ContextScope.class, Object.class));
29+
30+
public static final FFMNativeMethodDecorator DECORATE = new FFMNativeMethodDecorator();
31+
32+
private static MethodHandle safeFindStatic(String name, MethodType methodType) {
33+
try {
34+
return MethodHandles.lookup().findStatic(FFMNativeMethodDecorator.class, name, methodType);
35+
} catch (Throwable t) {
36+
LOGGER.debug("Cannot find method {} in NativeMethodHandleWrapper", name, t);
37+
return null;
38+
}
39+
}
40+
41+
public static MethodHandle wrap(
42+
final MethodHandle original, final String libraryName, final String methodName) {
43+
if (START_SPAN_MH == null || END_SPAN_MH == null) {
44+
return original;
45+
}
46+
try {
47+
MethodType originalType = original.type();
48+
boolean isVoid = originalType.returnType() == void.class;
49+
50+
// We need the ContextScope to be visible to the finally block.
51+
// Easiest way is to artificially prepend it to the target signature.
52+
// The added parameter is ignored by the original handle.
53+
// originalWithScope: (ContextScope, args...) -> R
54+
MethodHandle originalWithScope = MethodHandles.dropArguments(original, 0, ContextScope.class);
55+
56+
/*
57+
* Build the cleanup handle used by MethodHandles.tryFinally.
58+
*
59+
* tryFinally has a strict calling convention:
60+
* - void target -> cleanup(Throwable, ContextScope, args...)
61+
* - non-void -> cleanup(Throwable, R, ContextScope, args...)
62+
*
63+
* END_SPAN_MH is (Throwable, ContextScope, Object) -> Object,
64+
* so we need to reshape it to match what tryFinally expects.
65+
*/
66+
MethodHandle cleanup;
67+
68+
if (isVoid) {
69+
// No return value: bind `null` as the result argument.
70+
MethodHandle endWithNull = MethodHandles.insertArguments(END_SPAN_MH, 2, (Object) null);
71+
72+
// Make it accept the original arguments even though they are unused.
73+
MethodHandle endDropped =
74+
MethodHandles.dropArguments(endWithNull, 2, originalType.parameterList());
75+
76+
// tryFinally requires void return for void targets.
77+
cleanup = endDropped.asType(endDropped.type().changeReturnType(void.class));
78+
79+
} else {
80+
/*
81+
* Non-void case:
82+
* tryFinally will call cleanup as:
83+
* (Throwable, returnValue, ContextScope, args...)
84+
*
85+
* END_SPAN_MH expects:
86+
* (Throwable, ContextScope, result)
87+
*
88+
* So we first permute parameters to swap returnValue and ContextScope.
89+
*/
90+
MethodHandle endPermuted =
91+
MethodHandles.permuteArguments(
92+
END_SPAN_MH,
93+
MethodType.methodType(
94+
Object.class, Throwable.class, Object.class, ContextScope.class),
95+
0,
96+
2,
97+
1);
98+
99+
// Accept original arguments (unused) after the required ones.
100+
MethodHandle endDropped =
101+
MethodHandles.dropArguments(endPermuted, 3, originalType.parameterList());
102+
103+
// Adapt return and result parameter types to match the original signature.
104+
MethodType cleanupType =
105+
endDropped
106+
.type()
107+
.changeParameterType(1, originalType.returnType())
108+
.changeReturnType(originalType.returnType());
109+
110+
cleanup = endDropped.asType(cleanupType);
111+
}
112+
113+
// Wrap the original in try/finally semantics.
114+
// Resulting handle:
115+
// (ContextScope, args...) -> R
116+
MethodHandle withFinally = MethodHandles.tryFinally(originalWithScope, cleanup);
117+
118+
// Precompute span metadata so we don't redo the lookup per invocation.
119+
final CharSequence resourceName = resourceNameFor(libraryName, methodName);
120+
final boolean methodMeasured = isMethodMeasured(libraryName, methodName);
121+
122+
// Bind both arguments to startSpan.
123+
// After binding: () -> ContextScope
124+
MethodHandle boundStart =
125+
MethodHandles.insertArguments(START_SPAN_MH, 0, resourceName, methodMeasured);
126+
127+
// Make it look like it takes the same arguments as the original,
128+
// even though they are ignored.
129+
// (args...) -> ContextScope
130+
MethodHandle startCombiner =
131+
MethodHandles.dropArguments(boundStart, 0, originalType.parameterList());
132+
133+
/*
134+
* foldArguments wires it all together:
135+
*
136+
* scope = startCombiner(args...)
137+
* return withFinally(scope, args...)
138+
*
139+
* Final shape matches the original:
140+
* (args...) -> R
141+
*/
142+
return MethodHandles.foldArguments(withFinally, startCombiner);
143+
144+
} catch (Throwable t) {
145+
LOGGER.debug(
146+
"Cannot wrap method handle for library {} and method {}", libraryName, methodName, t);
147+
return original;
148+
}
149+
}
150+
151+
@SuppressWarnings("unused")
152+
public static ContextScope startSpan(CharSequence resourceName, boolean methodMeasured) {
153+
AgentSpan span = AgentTracer.startSpan(TRACE_FFM.toString(), OPERATION_NAME);
154+
DECORATE.afterStart(span);
155+
span.setResourceName(resourceName);
156+
if (methodMeasured) {
157+
span.setMeasured(true);
158+
}
159+
return AgentTracer.activateSpan(span);
160+
}
161+
162+
@SuppressWarnings("unused")
163+
public static Object endSpan(Throwable t, ContextScope scope, Object result) {
164+
try {
165+
if (scope != null) {
166+
final AgentSpan span = AgentSpan.fromContext(scope.context());
167+
scope.close();
168+
169+
if (span != null) {
170+
if (t != null) {
171+
DECORATE.onError(span, t);
172+
span.addThrowable(t);
173+
}
174+
span.finish();
175+
}
176+
}
177+
} catch (Throwable ignored) {
178+
179+
}
180+
return result;
181+
}
182+
183+
public static boolean isMethodTraced(final String library, final String method) {
184+
return matches(InstrumenterConfig.get().getTraceNativeMethods().get(library), method);
185+
}
186+
187+
public static boolean isMethodMeasured(final String library, final String method) {
188+
return matches(InstrumenterConfig.get().getMeasureNativeMethods().get(library), method);
189+
}
190+
191+
public static CharSequence resourceNameFor(final String library, final String method) {
192+
return UTF8BytesString.create(library + "." + method);
193+
}
194+
195+
private static boolean matches(final Set<String> allows, final String method) {
196+
if (allows == null) {
197+
return false;
198+
}
199+
return allows.contains(method) || allows.contains("*");
200+
}
201+
202+
@Override
203+
protected String[] instrumentationNames() {
204+
return new String[] {TRACE_FFM.toString()};
205+
}
206+
207+
@Override
208+
protected CharSequence spanType() {
209+
return null;
210+
}
211+
212+
@Override
213+
protected CharSequence component() {
214+
return TRACE_FFM;
215+
}
216+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package datadog.trace.bootstrap.instrumentation.ffm;
2+
3+
import datadog.trace.api.Pair;
4+
import java.io.File;
5+
import java.util.Locale;
6+
import java.util.concurrent.ConcurrentHashMap;
7+
8+
public final class NativeLibraryHelper {
9+
// this map is unlimited. However, the number of entries depends on the configured methods we want
10+
// to trace.
11+
private static final ConcurrentHashMap<Long, Pair<String, String>> SYMBOLS_MAP =
12+
new ConcurrentHashMap<>();
13+
14+
private NativeLibraryHelper() {}
15+
16+
public static void onSymbolLookup(
17+
final String libraryName, final String symbol, final long address) {
18+
if (libraryName != null && !libraryName.isEmpty()) {
19+
if (FFMNativeMethodDecorator.isMethodTraced(libraryName, symbol)) {
20+
SYMBOLS_MAP.put(address, Pair.of(libraryName, symbol));
21+
}
22+
}
23+
}
24+
25+
public static Pair<String, String> reverseResolveLibraryAndSymbol(long address) {
26+
return SYMBOLS_MAP.get(address);
27+
}
28+
29+
public static String extractLibraryName(String fullPath) {
30+
String libraryName = new File(fullPath).getName().toLowerCase(Locale.ROOT);
31+
int dot = libraryName.lastIndexOf('.');
32+
libraryName = (dot > 0) ? libraryName.substring(0, dot) : libraryName;
33+
return libraryName;
34+
}
35+
}

dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/context/FieldBackedContextInjector.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ public ClassVisitor wrap(
106106
final MethodList<?> methods,
107107
final int writerFlags,
108108
final int readerFlags) {
109-
return new ClassVisitor(Opcodes.ASM8, classVisitor) {
109+
return new ClassVisitor(Opcodes.ASM9, classVisitor) {
110110

111111
private final boolean frames =
112112
implementationContext.getClassFileVersion().isAtLeast(ClassFileVersion.JAVA_V6);

dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/context/FieldBackedContextRequestRewriter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public ClassVisitor wrap(
8484
final MethodList<?> methods,
8585
final int writerFlags,
8686
final int readerFlags) {
87-
return new ClassVisitor(Opcodes.ASM8, classVisitor) {
87+
return new ClassVisitor(Opcodes.ASM9, classVisitor) {
8888
@Override
8989
public MethodVisitor visitMethod(
9090
final int access,

dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@
4444
1 io.opentelemetry.javaagent.*
4545
1 java.*
4646
0 java.lang.ClassLoader
47+
0 jdk.internal.foreign.abi.AbstractLinker
48+
0 jdk.internal.loader.NativeLibrary
49+
0 jdk.internal.loader.RawNativeLibraries$*
50+
0 jdk.internal.loader.NativeLibraries$*
4751
# allow exception profiling instrumentation
4852
0 java.lang.Exception
4953
0 java.lang.Error
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
plugins {
2+
id 'idea'
3+
}
4+
5+
muzzle {
6+
pass {
7+
coreJdk('25')
8+
}
9+
}
10+
11+
apply from: "$rootDir/gradle/java.gradle"
12+
apply from: "$rootDir/gradle/slf4j-simple.gradle"
13+
14+
tracerJava {
15+
addSourceSetFor(JavaVersion.VERSION_25)
16+
}
17+
18+
testJvmConstraints {
19+
minJavaVersion = JavaVersion.VERSION_25
20+
}
21+
22+
idea {
23+
module {
24+
jdkName = '25'
25+
}
26+
}
27+
28+
29+
tasks.named("compileMain_java25Java", JavaCompile) {
30+
configureCompiler(it, 25, JavaVersion.VERSION_1_8)
31+
}
32+
33+
tasks.named("compileTestGroovy", GroovyCompile) {
34+
configureCompiler(it, 25)
35+
}
36+
dependencies {
37+
implementation project(':dd-java-agent:instrumentation:datadog:tracing:trace-annotation')
38+
}

0 commit comments

Comments
 (0)