-
Notifications
You must be signed in to change notification settings - Fork 872
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Async @WithSpan Instrumentation for Guava ListenableFuture (#2811)
* Add Guava instrumentation library with AsyncSpanEndStrategy * Enable span strategy in advice * Spotless * Nix attempt at typeInitializer advice, leave TODO comment to revisit * Move async span strategy registration to helper class * Remove use of sameThreadExecutor * Make helper class final and add comment about relying on static initializer
- Loading branch information
Showing
9 changed files
with
297 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
23 changes: 23 additions & 0 deletions
23
...src/main/java/io/opentelemetry/javaagent/instrumentation/guava/InstrumentationHelper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.javaagent.instrumentation.guava; | ||
|
||
import io.opentelemetry.instrumentation.api.tracer.async.AsyncSpanEndStrategies; | ||
import io.opentelemetry.instrumentation.guava.GuavaAsyncSpanEndStrategy; | ||
|
||
public final class InstrumentationHelper { | ||
static { | ||
AsyncSpanEndStrategies.getInstance().registerStrategy(GuavaAsyncSpanEndStrategy.INSTANCE); | ||
} | ||
|
||
/** | ||
* This method is invoked to trigger the runtime system to execute the static initializer block | ||
* ensuring that the {@link GuavaAsyncSpanEndStrategy} is registered exactly once. | ||
*/ | ||
public static void initialize() {} | ||
|
||
private InstrumentationHelper() {} | ||
} |
105 changes: 105 additions & 0 deletions
105
instrumentation/guava-10.0/javaagent/src/test/groovy/GuavaWithSpanInstrumentationTest.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import com.google.common.util.concurrent.Futures | ||
import com.google.common.util.concurrent.SettableFuture | ||
import io.opentelemetry.api.trace.SpanKind | ||
import io.opentelemetry.instrumentation.guava.TracedWithSpan | ||
import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification | ||
|
||
class GuavaWithSpanInstrumentationTest extends AgentInstrumentationSpecification { | ||
|
||
def "should capture span for already done ListenableFuture"() { | ||
setup: | ||
new TracedWithSpan().listenableFuture(Futures.immediateFuture("Value")) | ||
|
||
expect: | ||
assertTraces(1) { | ||
trace(0, 1) { | ||
span(0) { | ||
name "TracedWithSpan.listenableFuture" | ||
kind SpanKind.INTERNAL | ||
hasNoParent() | ||
errored false | ||
attributes { | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
def "should capture span for already failed ListenableFuture"() { | ||
setup: | ||
def error = new IllegalArgumentException("Boom") | ||
new TracedWithSpan().listenableFuture(Futures.immediateFailedFuture(error)) | ||
|
||
expect: | ||
assertTraces(1) { | ||
trace(0, 1) { | ||
span(0) { | ||
name "TracedWithSpan.listenableFuture" | ||
kind SpanKind.INTERNAL | ||
hasNoParent() | ||
errored true | ||
errorEvent(IllegalArgumentException, "Boom") | ||
attributes { | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
def "should capture span for eventually done ListenableFuture"() { | ||
setup: | ||
def future = SettableFuture.<String>create() | ||
new TracedWithSpan().listenableFuture(future) | ||
|
||
expect: | ||
Thread.sleep(500) // sleep a bit just to make sure no span is captured | ||
assertTraces(0) {} | ||
|
||
future.set("Value") | ||
|
||
assertTraces(1) { | ||
trace(0, 1) { | ||
span(0) { | ||
name "TracedWithSpan.listenableFuture" | ||
kind SpanKind.INTERNAL | ||
hasNoParent() | ||
errored false | ||
attributes { | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
def "should capture span for eventually failed ListenableFuture"() { | ||
setup: | ||
def error = new IllegalArgumentException("Boom") | ||
def future = SettableFuture.<String>create() | ||
new TracedWithSpan().listenableFuture(future) | ||
|
||
expect: | ||
Thread.sleep(500) // sleep a bit just to make sure no span is captured | ||
assertTraces(0) {} | ||
|
||
future.setException(error) | ||
|
||
assertTraces(1) { | ||
trace(0, 1) { | ||
span(0) { | ||
name "TracedWithSpan.listenableFuture" | ||
kind SpanKind.INTERNAL | ||
hasNoParent() | ||
errored true | ||
errorEvent(IllegalArgumentException, "Boom") | ||
attributes { | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
...a-10.0/javaagent/src/test/java/io/opentelemetry/instrumentation/guava/TracedWithSpan.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.instrumentation.guava; | ||
|
||
import com.google.common.util.concurrent.ListenableFuture; | ||
import io.opentelemetry.extension.annotations.WithSpan; | ||
|
||
public class TracedWithSpan { | ||
@WithSpan | ||
public ListenableFuture<String> listenableFuture(ListenableFuture<String> future) { | ||
return future; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
apply from: "$rootDir/gradle/instrumentation-library.gradle" | ||
|
||
dependencies { | ||
library group: 'com.google.guava', name: 'guava', version: '10.0' | ||
} |
40 changes: 40 additions & 0 deletions
40
...brary/src/main/java/io/opentelemetry/instrumentation/guava/GuavaAsyncSpanEndStrategy.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.instrumentation.guava; | ||
|
||
import com.google.common.util.concurrent.ListenableFuture; | ||
import io.opentelemetry.context.Context; | ||
import io.opentelemetry.instrumentation.api.tracer.BaseTracer; | ||
import io.opentelemetry.instrumentation.api.tracer.async.AsyncSpanEndStrategy; | ||
|
||
public enum GuavaAsyncSpanEndStrategy implements AsyncSpanEndStrategy { | ||
INSTANCE; | ||
|
||
@Override | ||
public boolean supports(Class<?> returnType) { | ||
return ListenableFuture.class.isAssignableFrom(returnType); | ||
} | ||
|
||
@Override | ||
public Object end(BaseTracer tracer, Context context, Object returnValue) { | ||
ListenableFuture<?> future = (ListenableFuture<?>) returnValue; | ||
if (future.isDone()) { | ||
endSpan(tracer, context, future); | ||
} else { | ||
future.addListener(() -> endSpan(tracer, context, future), Runnable::run); | ||
} | ||
return future; | ||
} | ||
|
||
private void endSpan(BaseTracer tracer, Context context, ListenableFuture<?> future) { | ||
try { | ||
future.get(); | ||
tracer.end(context); | ||
} catch (Throwable exception) { | ||
tracer.endExceptionally(context, exception); | ||
} | ||
} | ||
} |
89 changes: 89 additions & 0 deletions
89
instrumentation/guava-10.0/library/src/test/groovy/GuavaAsyncSpanEndStrategyTest.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import com.google.common.util.concurrent.Futures | ||
import com.google.common.util.concurrent.ListenableFuture | ||
import com.google.common.util.concurrent.SettableFuture | ||
import io.opentelemetry.context.Context | ||
import io.opentelemetry.instrumentation.api.tracer.BaseTracer | ||
import io.opentelemetry.instrumentation.guava.GuavaAsyncSpanEndStrategy | ||
import spock.lang.Specification | ||
|
||
class GuavaAsyncSpanEndStrategyTest extends Specification { | ||
BaseTracer tracer | ||
|
||
Context context | ||
|
||
def underTest = GuavaAsyncSpanEndStrategy.INSTANCE | ||
|
||
void setup() { | ||
tracer = Mock() | ||
context = Mock() | ||
} | ||
|
||
def "ListenableFuture is supported"() { | ||
expect: | ||
underTest.supports(ListenableFuture) | ||
} | ||
|
||
def "SettableFuture is also supported"() { | ||
expect: | ||
underTest.supports(SettableFuture) | ||
} | ||
|
||
def "ends span on already done future"() { | ||
when: | ||
underTest.end(tracer, context, Futures.immediateFuture("Value")) | ||
|
||
then: | ||
1 * tracer.end(context) | ||
} | ||
|
||
def "ends span on already failed future"() { | ||
given: | ||
def exception = new IllegalStateException() | ||
|
||
when: | ||
underTest.end(tracer, context, Futures.immediateFailedFuture(exception)) | ||
|
||
then: | ||
1 * tracer.endExceptionally(context, { it.getCause() == exception }) | ||
} | ||
|
||
def "ends span on eventually done future"() { | ||
given: | ||
def future = SettableFuture.<String>create() | ||
|
||
when: | ||
underTest.end(tracer, context, future) | ||
|
||
then: | ||
0 * tracer._ | ||
|
||
when: | ||
future.set("Value") | ||
|
||
then: | ||
1 * tracer.end(context) | ||
} | ||
|
||
def "ends span on eventually failed future"() { | ||
given: | ||
def future = SettableFuture.<String>create() | ||
def exception = new IllegalStateException() | ||
|
||
when: | ||
underTest.end(tracer, context, future) | ||
|
||
then: | ||
0 * tracer._ | ||
|
||
when: | ||
future.setException(exception) | ||
|
||
then: | ||
1 * tracer.endExceptionally(context, { it.getCause() == exception }) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters