Skip to content

Commit d5df412

Browse files
authored
Merge branch 'main' into main
2 parents e4eba10 + 3bfa0ef commit d5df412

File tree

74 files changed

+2209
-2606
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+2209
-2606
lines changed

.github/workflows/overhead-benchmark-daily.yml

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,15 @@ jobs:
4242
- name: Copy results back to gh-pages branch
4343
run: rsync -avv benchmark-overhead/results/ gh-pages/benchmark-overhead/results/ && rm -rf benchmark-overhead/results
4444

45+
- name: Use CLA approved bot
46+
run: .github/scripts/use-cla-approved-bot.sh
47+
4548
- name: Commit updated results
46-
uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5 # v9.1.4
47-
with:
48-
add: "benchmark-overhead/results"
49-
cwd: "./gh-pages"
50-
branch: "gh-pages"
51-
message: "update test result data"
52-
author_name: otelbot
53-
author_email: [email protected]
54-
committer_name: otelbot
55-
committer_email: [email protected]
49+
working-directory: ./gh-pages
50+
run: |
51+
git add benchmark-overhead/results
52+
git commit -m "update test result data"
53+
git push
5654
5755
workflow-notification:
5856
permissions:

CONTRIBUTING.md

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,28 @@ See [Running the tests](./docs/contributing/running-tests.md) for more details.
1515

1616
### Snapshot builds
1717

18-
For developers testing code changes before a release is complete, there are
19-
snapshot builds of the `main` branch. They are available from
20-
the Sonatype snapshot repository at `https://central.sonatype.com/repository/maven-snapshots/`
21-
([browse](https://central.sonatype.com/service/rest/repository/browse/maven-snapshots/io/opentelemetry/)).
18+
For developers testing code changes before a release is complete, snapshot builds of the `main`
19+
branch are available from the Sonatype snapshot repository at `https://central.sonatype.com/repository/maven-snapshots/`.
20+
21+
To find the latest snapshot, check the maven metadata (replace `{LATEST_VERSION}` with the current
22+
stable release):
23+
24+
```
25+
https://central.sonatype.com/repository/maven-snapshots/io/opentelemetry/javaagent/opentelemetry-javaagent/{LATEST_VERSION}-SNAPSHOT/maven-metadata.xml
26+
```
27+
28+
Look for the `<timestamp>` and `<buildNumber>` in the XML response, then construct the download URL:
29+
30+
```
31+
https://central.sonatype.com/repository/maven-snapshots/io/opentelemetry/javaagent/opentelemetry-javaagent/{VERSION}-SNAPSHOT/opentelemetry-javaagent-{VERSION}-{TIMESTAMP}-{BUILD_NUMBER}.jar
32+
```
33+
34+
For example, if the metadata shows timestamp `20250925.160708` and build number `56` for version
35+
`2.21.0`, the snapshot JAR URL would be:
36+
37+
```
38+
https://central.sonatype.com/repository/maven-snapshots/io/opentelemetry/javaagent/opentelemetry-javaagent/2.21.0-SNAPSHOT/opentelemetry-javaagent-2.21.0-20250925.160708-56.jar
39+
```
2240

2341
### Building from source
2442

docs/instrumentation-list.yaml

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,33 @@ libraries:
390390
default: ''
391391
telemetry:
392392
- when: default
393+
metrics:
394+
- name: rpc.client.duration
395+
description: The duration of an outbound RPC invocation.
396+
type: HISTOGRAM
397+
unit: ms
398+
attributes:
399+
- name: rpc.method
400+
type: STRING
401+
- name: rpc.service
402+
type: STRING
403+
- name: rpc.system
404+
type: STRING
405+
- name: server.address
406+
type: STRING
407+
- name: server.port
408+
type: LONG
409+
- name: rpc.server.duration
410+
description: The duration of an inbound RPC invocation.
411+
type: HISTOGRAM
412+
unit: ms
413+
attributes:
414+
- name: rpc.method
415+
type: STRING
416+
- name: rpc.service
417+
type: STRING
418+
- name: rpc.system
419+
type: STRING
393420
spans:
394421
- span_kind: CLIENT
395422
attributes:
@@ -6399,13 +6426,6 @@ libraries:
63996426
scope:
64006427
name: io.opentelemetry.runtime-telemetry-java8
64016428
rxjava:
6402-
- name: rxjava-1.0
6403-
source_path: instrumentation/rxjava/rxjava-1.0
6404-
scope:
6405-
name: io.opentelemetry.rxjava-1.0
6406-
target_versions:
6407-
library:
6408-
- io.reactivex:rxjava:1.0.7
64096429
- name: rxjava-2.0
64106430
source_path: instrumentation/rxjava/rxjava-2.0
64116431
scope:
@@ -7977,6 +7997,13 @@ internal:
79777997
source_path: instrumentation/internal/internal-lambda
79787998
scope:
79797999
name: io.opentelemetry.internal-lambda
8000+
- name: rxjava-1.0
8001+
source_path: instrumentation/rxjava/rxjava-1.0
8002+
scope:
8003+
name: io.opentelemetry.rxjava-1.0
8004+
target_versions:
8005+
library:
8006+
- io.reactivex:rxjava:1.0.7
79808007
- name: internal-eclipse-osgi-3.6
79818008
source_path: instrumentation/internal/internal-eclipse-osgi-3.6
79828009
scope:

instrumentation/java-http-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/javahttpclient/HttpClientInstrumentation.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ public static AsyncAdviceScope start(HttpRequest request) {
156156
}
157157

158158
public CompletableFuture<HttpResponse<?>> end(
159-
@Nullable Throwable throwable, @Nullable CompletableFuture<HttpResponse<?>> future) {
159+
@Nullable Throwable throwable, CompletableFuture<HttpResponse<?>> future) {
160160
if (callDepth.decrementAndGet() > 0 || scope == null) {
161161
// async end nested call
162162
return future;
@@ -167,8 +167,8 @@ public CompletableFuture<HttpResponse<?>> end(
167167
instrumenter().end(context, request, null, throwable);
168168
return future;
169169
}
170-
future = future.whenComplete(new ResponseConsumer(instrumenter(), context, request));
171-
return CompletableFutureWrapper.wrap(future, parentContext);
170+
return CompletableFutureWrapper.wrap(future, parentContext)
171+
.whenComplete(new ResponseConsumer(instrumenter(), context, request));
172172
}
173173
}
174174

@@ -182,7 +182,7 @@ public static AsyncAdviceScope methodEnter(
182182
@AssignReturned.ToReturned
183183
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
184184
public static CompletableFuture<HttpResponse<?>> methodExit(
185-
@Advice.Return @Nullable CompletableFuture<HttpResponse<?>> future,
185+
@Advice.Return CompletableFuture<HttpResponse<?>> future,
186186
@Advice.Thrown @Nullable Throwable throwable,
187187
@Advice.Enter @Nullable AsyncAdviceScope scope) {
188188
return scope == null ? future : scope.end(throwable, future);

instrumentation/java-http-client/library/src/main/java/io/opentelemetry/instrumentation/javahttpclient/internal/CompletableFutureWrapper.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,15 @@
1313
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
1414
* any time.
1515
*/
16-
public final class CompletableFutureWrapper {
16+
public final class CompletableFutureWrapper<T> extends CompletableFuture<T> {
17+
private final CompletableFuture<?> future;
1718

18-
private CompletableFutureWrapper() {}
19+
private CompletableFutureWrapper(CompletableFuture<?> future) {
20+
this.future = future;
21+
}
1922

2023
public static <T> CompletableFuture<T> wrap(CompletableFuture<T> future, Context context) {
21-
CompletableFuture<T> result = new CompletableFuture<>();
24+
CompletableFuture<T> result = new CompletableFutureWrapper<>(future);
2225
future.whenComplete(
2326
(T value, Throwable throwable) -> {
2427
try (Scope ignored = context.makeCurrent()) {
@@ -32,4 +35,16 @@ public static <T> CompletableFuture<T> wrap(CompletableFuture<T> future, Context
3235

3336
return result;
3437
}
38+
39+
@Override
40+
public <U> CompletableFuture<U> newIncompleteFuture() {
41+
return new CompletableFutureWrapper<>(future);
42+
}
43+
44+
@Override
45+
public boolean cancel(boolean mayInterruptIfRunning) {
46+
boolean result = super.cancel(mayInterruptIfRunning);
47+
future.cancel(mayInterruptIfRunning);
48+
return result;
49+
}
3550
}

instrumentation/java-http-client/library/src/main/java/io/opentelemetry/instrumentation/javahttpclient/internal/OpenTelemetryHttpClient.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,9 @@ private <T> CompletableFuture<HttpResponse<T>> traceAsync(
142142
instrumenter.end(context, request, null, t);
143143
throw t;
144144
}
145-
future = future.whenComplete(new ResponseConsumer(instrumenter, context, request));
146-
future = CompletableFutureWrapper.wrap(future, parentContext);
145+
future =
146+
CompletableFutureWrapper.wrap(future, parentContext)
147+
.whenComplete(new ResponseConsumer(instrumenter, context, request));
147148
return future;
148149
}
149150
}

instrumentation/java-http-client/testing/src/main/java/io/opentelemetry/instrumentation/javahttpclient/AbstractJavaHttpClientTest.java

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,22 @@
55

66
package io.opentelemetry.instrumentation.javahttpclient;
77

8+
import static io.opentelemetry.api.common.AttributeKey.stringKey;
9+
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
810
import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PROTOCOL_VERSION;
11+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
912

1013
import io.opentelemetry.api.common.AttributeKey;
14+
import io.opentelemetry.api.trace.Span;
15+
import io.opentelemetry.api.trace.SpanKind;
1116
import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpClientTest;
1217
import io.opentelemetry.instrumentation.testing.junit.http.HttpClientResult;
1318
import io.opentelemetry.instrumentation.testing.junit.http.HttpClientTestOptions;
19+
import io.opentelemetry.sdk.trace.data.StatusData;
20+
import io.opentelemetry.semconv.ErrorAttributes;
21+
import io.opentelemetry.semconv.HttpAttributes;
22+
import io.opentelemetry.semconv.ServerAttributes;
23+
import io.opentelemetry.semconv.UrlAttributes;
1424
import java.io.IOException;
1525
import java.net.URI;
1626
import java.net.http.HttpClient;
@@ -19,7 +29,11 @@
1929
import java.util.HashSet;
2030
import java.util.Map;
2131
import java.util.Set;
32+
import java.util.concurrent.CancellationException;
33+
import java.util.concurrent.CompletableFuture;
34+
import java.util.concurrent.TimeUnit;
2235
import org.junit.jupiter.api.BeforeAll;
36+
import org.junit.jupiter.api.Test;
2337

2438
public abstract class AbstractJavaHttpClientTest extends AbstractHttpClientTest<HttpRequest> {
2539

@@ -106,4 +120,76 @@ protected void configure(HttpClientTestOptions.Builder optionsBuilder) {
106120
return attributes;
107121
});
108122
}
123+
124+
@SuppressWarnings("Interruption") // test calls CompletableFuture.cancel with true
125+
@Test
126+
void cancelRequest() throws InterruptedException {
127+
boolean isJdk11 = "11".equals(System.getProperty("java.specification.version"));
128+
String method = "GET";
129+
URI uri = resolveAddress("/long-request");
130+
131+
CompletableFuture<String> future =
132+
testing.runWithSpan(
133+
"parent",
134+
() -> {
135+
HttpRequest request =
136+
HttpRequest.newBuilder()
137+
.uri(uri)
138+
.method(method, HttpRequest.BodyPublishers.noBody())
139+
.header("delay", String.valueOf(TimeUnit.SECONDS.toMillis(5)))
140+
.build();
141+
return client
142+
.sendAsync(request, HttpResponse.BodyHandlers.ofString())
143+
.thenApply(HttpResponse::body)
144+
.whenComplete(
145+
(response, throwable) ->
146+
testing.runWithSpan(
147+
"child",
148+
() -> {
149+
if (throwable != null && throwable.getCause() != null) {
150+
Span.current()
151+
.setAttribute(
152+
"throwable", throwable.getCause().getClass().getName());
153+
}
154+
}))
155+
// this stage is only added to trigger the whenComplete stage when this stage gets
156+
// cancelled
157+
.exceptionally(ex -> "cancelled");
158+
});
159+
160+
// sleep a bit to let the request start
161+
Thread.sleep(1_000);
162+
future.cancel(true);
163+
assertThatThrownBy(future::get).isInstanceOf(CancellationException.class);
164+
165+
testing.waitAndAssertTraces(
166+
trace ->
167+
trace.hasSpansSatisfyingExactly(
168+
span -> span.hasName("parent").hasNoParent(),
169+
span ->
170+
span.hasName("GET")
171+
.hasKind(SpanKind.CLIENT)
172+
.hasParent(trace.getSpan(0))
173+
.hasStatus(StatusData.error())
174+
.hasAttributesSatisfyingExactly(
175+
equalTo(UrlAttributes.URL_FULL, uri.toString()),
176+
equalTo(ServerAttributes.SERVER_ADDRESS, uri.getHost()),
177+
equalTo(ServerAttributes.SERVER_PORT, uri.getPort()),
178+
equalTo(HttpAttributes.HTTP_REQUEST_METHOD, method),
179+
equalTo(
180+
ErrorAttributes.ERROR_TYPE, CancellationException.class.getName())),
181+
span ->
182+
span.hasName("test-http-server")
183+
.hasKind(SpanKind.SERVER)
184+
.hasParent(trace.getSpan(1))
185+
// jdk 11 does not cancel the request on the server side so the request
186+
// succeeds
187+
.hasStatus(isJdk11 ? StatusData.unset() : StatusData.error()),
188+
span ->
189+
span.hasName("child")
190+
.hasParent(trace.getSpan(0))
191+
.hasAttributesSatisfyingExactly(
192+
equalTo(
193+
stringKey("throwable"), CancellationException.class.getName()))));
194+
}
109195
}

instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/ConnectionInstrumentation.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ public static class PrepareAdvice {
6262
@Advice.OnMethodExit(suppress = Throwable.class)
6363
public static void addDbInfo(
6464
@Advice.Argument(0) String sql, @Advice.Return PreparedStatement statement) {
65+
if (JdbcSingletons.isWrapper(statement, PreparedStatement.class)) {
66+
return;
67+
}
68+
6569
JdbcData.preparedStatement.set(statement, sql);
6670
}
6771
}
@@ -105,6 +109,10 @@ public void end(@Nullable Throwable throwable) {
105109
@Advice.OnMethodEnter(suppress = Throwable.class)
106110
public static AdviceScope onEnter(
107111
@Advice.This Connection connection, @Advice.Origin("#m") String methodName) {
112+
if (JdbcSingletons.isWrapper(connection, Connection.class)) {
113+
return null;
114+
}
115+
108116
return AdviceScope.start(connection, methodName);
109117
}
110118

instrumentation/jdbc/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jdbc/JdbcSingletons.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,15 @@
1111
import io.opentelemetry.instrumentation.api.incubator.semconv.net.PeerServiceAttributesExtractor;
1212
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
1313
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
14+
import io.opentelemetry.instrumentation.api.internal.cache.Cache;
1415
import io.opentelemetry.instrumentation.jdbc.internal.DbRequest;
1516
import io.opentelemetry.instrumentation.jdbc.internal.JdbcInstrumenterFactory;
1617
import io.opentelemetry.instrumentation.jdbc.internal.JdbcNetworkAttributesGetter;
1718
import io.opentelemetry.javaagent.bootstrap.internal.AgentCommonConfig;
1819
import io.opentelemetry.javaagent.bootstrap.internal.AgentInstrumentationConfig;
1920
import io.opentelemetry.javaagent.bootstrap.jdbc.DbInfo;
21+
import java.sql.SQLException;
22+
import java.sql.Wrapper;
2023
import java.util.Collections;
2124
import javax.sql.DataSource;
2225

@@ -68,5 +71,31 @@ public static Instrumenter<DataSource, DbInfo> dataSourceInstrumenter() {
6871
return DATASOURCE_INSTRUMENTER;
6972
}
7073

74+
private static final Cache<Class<?>, Boolean> wrapperClassCache = Cache.weak();
75+
76+
/**
77+
* Returns true if the given object is a wrapper and shouldn't be instrumented. We'll instrument
78+
* the underlying object called by the wrapper instead.
79+
*/
80+
public static <T extends Wrapper> boolean isWrapper(T object, Class<T> clazz) {
81+
return wrapperClassCache.computeIfAbsent(
82+
object.getClass(), key -> isWrapperInternal(object, clazz));
83+
}
84+
85+
private static <T extends Wrapper> boolean isWrapperInternal(T object, Class<T> clazz) {
86+
try {
87+
// we are dealing with a wrapper when the object unwraps to a different instance
88+
if (object.isWrapperFor(clazz)) {
89+
T unwrapped = object.unwrap(clazz);
90+
if (object != unwrapped) {
91+
return true;
92+
}
93+
}
94+
} catch (SQLException | AbstractMethodError e) {
95+
// ignore
96+
}
97+
return false;
98+
}
99+
71100
private JdbcSingletons() {}
72101
}

0 commit comments

Comments
 (0)