From ac2e8e1a19d97d3542fa9e32220176b4b550bbaf Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Tue, 30 Apr 2024 09:34:55 +0300 Subject: [PATCH] Add async operation end strategy for kotlin coroutines flow (#11168) --- ...strumentation.muzzle-generation.gradle.kts | 1 + .../javaagent/gradle.properties | 1 - .../javaagent/build.gradle.kts | 5 +- .../KotlinCoroutinesInstrumentation.java | 0 ...KotlinCoroutinesInstrumentationHelper.java | 0 ...KotlinCoroutinesInstrumentationModule.java | 0 .../AnnotationInstrumentationHelper.java | 0 .../AnnotationInstrumentationModule.java | 0 .../AnnotationSingletons.java | 2 +- .../ExpandFramesClassVisitor.java | 0 ...otlinCoroutinesIgnoredTypesConfigurer.java | 0 .../MethodRequest.java | 0 .../MethodRequestCodeAttributesGetter.java | 0 .../SpanAttributeUtil.java | 0 .../WithSpanInstrumentation.java | 0 .../ClazzWithDefaultConstructorArguments.kt | 0 .../KotlinCoroutinesInstrumentationTest.kt | 132 ----------- .../javaagent-kotlin/build.gradle.kts | 23 ++ .../kotlinxcoroutines/flow/FlowUtil.kt | 17 ++ .../javaagent/build.gradle.kts | 48 ++++ .../flow/AbstractFlowInstrumentation.java | 38 +++ .../flow/FlowInstrumentationHelper.java | 44 ++++ ...inCoroutinesFlowInstrumentationModule.java | 26 +++ .../KotlinCoroutines13InstrumentationTest.kt | 218 ++++++++++++++++++ .../flow/FlowWithSpanTest.kt | 66 ++++++ settings.gradle.kts | 4 +- 26 files changed, 487 insertions(+), 138 deletions(-) delete mode 100644 instrumentation/kotlinx-coroutines/javaagent/gradle.properties rename instrumentation/kotlinx-coroutines/{ => kotlinx-coroutines-1.0}/javaagent/build.gradle.kts (93%) rename instrumentation/kotlinx-coroutines/{ => kotlinx-coroutines-1.0}/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/KotlinCoroutinesInstrumentation.java (100%) rename instrumentation/kotlinx-coroutines/{ => kotlinx-coroutines-1.0}/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/KotlinCoroutinesInstrumentationHelper.java (100%) rename instrumentation/kotlinx-coroutines/{ => kotlinx-coroutines-1.0}/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/KotlinCoroutinesInstrumentationModule.java (100%) rename instrumentation/kotlinx-coroutines/{ => kotlinx-coroutines-1.0}/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/AnnotationInstrumentationHelper.java (100%) rename instrumentation/kotlinx-coroutines/{ => kotlinx-coroutines-1.0}/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/AnnotationInstrumentationModule.java (100%) rename instrumentation/kotlinx-coroutines/{ => kotlinx-coroutines-1.0}/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/AnnotationSingletons.java (97%) rename instrumentation/kotlinx-coroutines/{ => kotlinx-coroutines-1.0}/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/ExpandFramesClassVisitor.java (100%) rename instrumentation/kotlinx-coroutines/{ => kotlinx-coroutines-1.0}/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/KotlinCoroutinesIgnoredTypesConfigurer.java (100%) rename instrumentation/kotlinx-coroutines/{ => kotlinx-coroutines-1.0}/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/MethodRequest.java (100%) rename instrumentation/kotlinx-coroutines/{ => kotlinx-coroutines-1.0}/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/MethodRequestCodeAttributesGetter.java (100%) rename instrumentation/kotlinx-coroutines/{ => kotlinx-coroutines-1.0}/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/SpanAttributeUtil.java (100%) rename instrumentation/kotlinx-coroutines/{ => kotlinx-coroutines-1.0}/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/WithSpanInstrumentation.java (100%) rename instrumentation/kotlinx-coroutines/{ => kotlinx-coroutines-1.0}/javaagent/src/test/kotlin/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/ClazzWithDefaultConstructorArguments.kt (100%) rename instrumentation/kotlinx-coroutines/{ => kotlinx-coroutines-1.0}/javaagent/src/test/kotlin/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/KotlinCoroutinesInstrumentationTest.kt (81%) create mode 100644 instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent-kotlin/build.gradle.kts create mode 100644 instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent-kotlin/src/main/kotlin/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/flow/FlowUtil.kt create mode 100644 instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent/build.gradle.kts create mode 100644 instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/flow/AbstractFlowInstrumentation.java create mode 100644 instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/flow/FlowInstrumentationHelper.java create mode 100644 instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/flow/KotlinCoroutinesFlowInstrumentationModule.java create mode 100644 instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent/src/test/kotlin/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/KotlinCoroutines13InstrumentationTest.kt create mode 100644 instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent/src/test/kotlin/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/flow/FlowWithSpanTest.kt diff --git a/gradle-plugins/src/main/kotlin/io.opentelemetry.instrumentation.muzzle-generation.gradle.kts b/gradle-plugins/src/main/kotlin/io.opentelemetry.instrumentation.muzzle-generation.gradle.kts index 634ff0c1a954..fc5fa78fb3bc 100644 --- a/gradle-plugins/src/main/kotlin/io.opentelemetry.instrumentation.muzzle-generation.gradle.kts +++ b/gradle-plugins/src/main/kotlin/io.opentelemetry.instrumentation.muzzle-generation.gradle.kts @@ -67,6 +67,7 @@ fun createLanguageTask( classFileVersion = ClassFileVersion.JAVA_V8 var transformationClassPath = inputClasspath val compileTask = compileTaskProvider.get() + // this does not work for kotlin as compile task does not extend AbstractCompile if (compileTask is AbstractCompile) { val classesDirectory = compileTask.destinationDirectory.asFile.get() val rawClassesDirectory: File = File(classesDirectory.parent, "${classesDirectory.name}raw") diff --git a/instrumentation/kotlinx-coroutines/javaagent/gradle.properties b/instrumentation/kotlinx-coroutines/javaagent/gradle.properties deleted file mode 100644 index 0d6aa7b61fbc..000000000000 --- a/instrumentation/kotlinx-coroutines/javaagent/gradle.properties +++ /dev/null @@ -1 +0,0 @@ -kotlin.stdlib.default.dependency=false diff --git a/instrumentation/kotlinx-coroutines/javaagent/build.gradle.kts b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/build.gradle.kts similarity index 93% rename from instrumentation/kotlinx-coroutines/javaagent/build.gradle.kts rename to instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/build.gradle.kts index b92e9ed14d3d..4283ed7acb5d 100644 --- a/instrumentation/kotlinx-coroutines/javaagent/build.gradle.kts +++ b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/build.gradle.kts @@ -40,9 +40,8 @@ dependencies { testImplementation(project(":instrumentation:reactor:reactor-3.1:library")) testImplementation(project(":instrumentation-annotations")) - // Use first version with flow support since we have tests for it. - testLibrary("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0") - testLibrary("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:1.3.0") + testLibrary("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.0") + testLibrary("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:1.0.0") } tasks { diff --git a/instrumentation/kotlinx-coroutines/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/KotlinCoroutinesInstrumentation.java b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/KotlinCoroutinesInstrumentation.java similarity index 100% rename from instrumentation/kotlinx-coroutines/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/KotlinCoroutinesInstrumentation.java rename to instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/KotlinCoroutinesInstrumentation.java diff --git a/instrumentation/kotlinx-coroutines/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/KotlinCoroutinesInstrumentationHelper.java b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/KotlinCoroutinesInstrumentationHelper.java similarity index 100% rename from instrumentation/kotlinx-coroutines/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/KotlinCoroutinesInstrumentationHelper.java rename to instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/KotlinCoroutinesInstrumentationHelper.java diff --git a/instrumentation/kotlinx-coroutines/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/KotlinCoroutinesInstrumentationModule.java b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/KotlinCoroutinesInstrumentationModule.java similarity index 100% rename from instrumentation/kotlinx-coroutines/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/KotlinCoroutinesInstrumentationModule.java rename to instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/KotlinCoroutinesInstrumentationModule.java diff --git a/instrumentation/kotlinx-coroutines/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/AnnotationInstrumentationHelper.java b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/AnnotationInstrumentationHelper.java similarity index 100% rename from instrumentation/kotlinx-coroutines/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/AnnotationInstrumentationHelper.java rename to instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/AnnotationInstrumentationHelper.java diff --git a/instrumentation/kotlinx-coroutines/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/AnnotationInstrumentationModule.java b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/AnnotationInstrumentationModule.java similarity index 100% rename from instrumentation/kotlinx-coroutines/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/AnnotationInstrumentationModule.java rename to instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/AnnotationInstrumentationModule.java diff --git a/instrumentation/kotlinx-coroutines/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/AnnotationSingletons.java b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/AnnotationSingletons.java similarity index 97% rename from instrumentation/kotlinx-coroutines/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/AnnotationSingletons.java rename to instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/AnnotationSingletons.java index b4db24f6ccc0..134b1a3ac309 100644 --- a/instrumentation/kotlinx-coroutines/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/AnnotationSingletons.java +++ b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/AnnotationSingletons.java @@ -12,7 +12,7 @@ public final class AnnotationSingletons { - private static final String INSTRUMENTATION_NAME = "io.opentelemetry.kotlinx-coroutines"; + private static final String INSTRUMENTATION_NAME = "io.opentelemetry.kotlinx-coroutines-1.0"; private static final Instrumenter INSTRUMENTER = createInstrumenter(); diff --git a/instrumentation/kotlinx-coroutines/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/ExpandFramesClassVisitor.java b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/ExpandFramesClassVisitor.java similarity index 100% rename from instrumentation/kotlinx-coroutines/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/ExpandFramesClassVisitor.java rename to instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/ExpandFramesClassVisitor.java diff --git a/instrumentation/kotlinx-coroutines/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/KotlinCoroutinesIgnoredTypesConfigurer.java b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/KotlinCoroutinesIgnoredTypesConfigurer.java similarity index 100% rename from instrumentation/kotlinx-coroutines/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/KotlinCoroutinesIgnoredTypesConfigurer.java rename to instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/KotlinCoroutinesIgnoredTypesConfigurer.java diff --git a/instrumentation/kotlinx-coroutines/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/MethodRequest.java b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/MethodRequest.java similarity index 100% rename from instrumentation/kotlinx-coroutines/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/MethodRequest.java rename to instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/MethodRequest.java diff --git a/instrumentation/kotlinx-coroutines/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/MethodRequestCodeAttributesGetter.java b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/MethodRequestCodeAttributesGetter.java similarity index 100% rename from instrumentation/kotlinx-coroutines/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/MethodRequestCodeAttributesGetter.java rename to instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/MethodRequestCodeAttributesGetter.java diff --git a/instrumentation/kotlinx-coroutines/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/SpanAttributeUtil.java b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/SpanAttributeUtil.java similarity index 100% rename from instrumentation/kotlinx-coroutines/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/SpanAttributeUtil.java rename to instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/SpanAttributeUtil.java diff --git a/instrumentation/kotlinx-coroutines/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/WithSpanInstrumentation.java b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/WithSpanInstrumentation.java similarity index 100% rename from instrumentation/kotlinx-coroutines/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/WithSpanInstrumentation.java rename to instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/instrumentationannotations/WithSpanInstrumentation.java diff --git a/instrumentation/kotlinx-coroutines/javaagent/src/test/kotlin/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/ClazzWithDefaultConstructorArguments.kt b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/src/test/kotlin/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/ClazzWithDefaultConstructorArguments.kt similarity index 100% rename from instrumentation/kotlinx-coroutines/javaagent/src/test/kotlin/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/ClazzWithDefaultConstructorArguments.kt rename to instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/src/test/kotlin/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/ClazzWithDefaultConstructorArguments.kt diff --git a/instrumentation/kotlinx-coroutines/javaagent/src/test/kotlin/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/KotlinCoroutinesInstrumentationTest.kt b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/src/test/kotlin/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/KotlinCoroutinesInstrumentationTest.kt similarity index 81% rename from instrumentation/kotlinx-coroutines/javaagent/src/test/kotlin/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/KotlinCoroutinesInstrumentationTest.kt rename to instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/src/test/kotlin/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/KotlinCoroutinesInstrumentationTest.kt index 3be67e1077de..2bdcd1106c60 100644 --- a/instrumentation/kotlinx-coroutines/javaagent/src/test/kotlin/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/KotlinCoroutinesInstrumentationTest.kt +++ b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-1.0/javaagent/src/test/kotlin/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/KotlinCoroutinesInstrumentationTest.kt @@ -14,7 +14,6 @@ import io.opentelemetry.extension.kotlin.asContextElement import io.opentelemetry.extension.kotlin.getOpenTelemetryContext import io.opentelemetry.instrumentation.annotations.SpanAttribute import io.opentelemetry.instrumentation.annotations.WithSpan -import io.opentelemetry.instrumentation.reactor.v3_1.ContextPropagationOperator import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension import io.opentelemetry.instrumentation.testing.util.TelemetryDataUtil.orderByRootSpanName import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo @@ -31,16 +30,9 @@ import kotlinx.coroutines.ThreadContextElement import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.channels.produce import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.consumeAsFlow -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.reactive.awaitSingle -import kotlinx.coroutines.reactive.collect -import kotlinx.coroutines.reactor.ReactorContext -import kotlinx.coroutines.reactor.flux import kotlinx.coroutines.reactor.mono import kotlinx.coroutines.runBlocking import kotlinx.coroutines.selects.select @@ -84,58 +76,6 @@ class KotlinCoroutinesInstrumentationTest { val tracer = testing.openTelemetry.getTracer("test") - @ParameterizedTest - @ArgumentsSource(DispatchersSource::class) - fun `traced across channels`(dispatcher: DispatcherWrapper) { - runTest(dispatcher) { - val producer = produce { - repeat(3) { - tracedChild("produce_$it") - send(it) - } - } - - producer.consumeAsFlow().onEach { - tracedChild("consume_$it") - }.collect() - } - - testing.waitAndAssertTraces( - { trace -> - trace.hasSpansSatisfyingExactlyInAnyOrder( - { - it.hasName("parent") - .hasNoParent() - }, - { - it.hasName("produce_0") - .hasParent(trace.getSpan(0)) - }, - { - it.hasName("consume_0") - .hasParent(trace.getSpan(0)) - }, - { - it.hasName("produce_1") - .hasParent(trace.getSpan(0)) - }, - { - it.hasName("consume_1") - .hasParent(trace.getSpan(0)) - }, - { - it.hasName("produce_2") - .hasParent(trace.getSpan(0)) - }, - { - it.hasName("consume_2") - .hasParent(trace.getSpan(0)) - }, - ) - }, - ) - } - @ParameterizedTest @ArgumentsSource(DispatchersSource::class) fun `cancellation prevents trace`(dispatcher: DispatcherWrapper) { @@ -388,78 +328,6 @@ class KotlinCoroutinesInstrumentationTest { ) } - @ParameterizedTest - @ArgumentsSource(DispatchersSource::class) - fun `traced mono with context propagation operator`(dispatcherWrapper: DispatcherWrapper) { - runTest(dispatcherWrapper) { - val currentContext = Context.current() - // clear current context to ensure that ContextPropagationOperator is used for context propagation - withContext(Context.root().asContextElement()) { - val mono = mono(dispatcherWrapper.dispatcher) { - // extract context from reactor and propagate it into coroutine - val reactorContext = coroutineContext[ReactorContext.Key]?.context - val otelContext = ContextPropagationOperator.getOpenTelemetryContext(reactorContext, Context.current()) - withContext(otelContext.asContextElement()) { - tracedChild("child") - } - } - ContextPropagationOperator.runWithContext(mono, currentContext).awaitSingle() - } - } - - testing.waitAndAssertTraces( - { trace -> - trace.hasSpansSatisfyingExactly( - { - it.hasName("parent") - .hasNoParent() - }, - { - it.hasName("child") - .hasParent(trace.getSpan(0)) - }, - ) - }, - ) - } - - @ParameterizedTest - @ArgumentsSource(DispatchersSource::class) - fun `traced flux`(dispatcherWrapper: DispatcherWrapper) { - runTest(dispatcherWrapper) { - flux(dispatcherWrapper.dispatcher) { - repeat(3) { - tracedChild("child_$it") - send(it) - } - }.collect { - } - } - - testing.waitAndAssertTraces( - { trace -> - trace.hasSpansSatisfyingExactly( - { - it.hasName("parent") - .hasNoParent() - }, - { - it.hasName("child_0") - .hasParent(trace.getSpan(0)) - }, - { - it.hasName("child_1") - .hasParent(trace.getSpan(0)) - }, - { - it.hasName("child_2") - .hasParent(trace.getSpan(0)) - }, - ) - }, - ) - } - private val animalKey: ContextKey = ContextKey.named("animal") @ParameterizedTest diff --git a/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent-kotlin/build.gradle.kts b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent-kotlin/build.gradle.kts new file mode 100644 index 000000000000..4d0fa01bb998 --- /dev/null +++ b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent-kotlin/build.gradle.kts @@ -0,0 +1,23 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +// We are using a separate module for kotlin source instead of placing them in +// instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent because muzzle +// generation plugin currently doesn't handle kotlin sources correctly. +plugins { + id("org.jetbrains.kotlin.jvm") + id("otel.java-conventions") +} + +dependencies { + compileOnly("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0") + compileOnly("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + compileOnly(project(":instrumentation-api")) +} + +tasks { + withType(KotlinCompile::class).configureEach { + kotlinOptions { + jvmTarget = "1.8" + } + } +} diff --git a/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent-kotlin/src/main/kotlin/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/flow/FlowUtil.kt b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent-kotlin/src/main/kotlin/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/flow/FlowUtil.kt new file mode 100644 index 000000000000..e9b57c7de9b8 --- /dev/null +++ b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent-kotlin/src/main/kotlin/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/flow/FlowUtil.kt @@ -0,0 +1,17 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.kotlinxcoroutines.flow + +import io.opentelemetry.context.Context +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.onCompletion + +fun onComplete(flow: Flow<*>, instrumenter: Instrumenter, context: Context, request: REQUEST): Flow<*> { + return flow.onCompletion { cause: Throwable? -> + instrumenter.end(context, request, null, cause) + } +} diff --git a/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent/build.gradle.kts b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent/build.gradle.kts new file mode 100644 index 000000000000..8f58876d07d9 --- /dev/null +++ b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent/build.gradle.kts @@ -0,0 +1,48 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + id("org.jetbrains.kotlin.jvm") + id("otel.javaagent-instrumentation") +} + +muzzle { + pass { + group.set("org.jetbrains.kotlinx") + module.set("kotlinx-coroutines-core") + versions.set("[1.3.0,1.3.8)") + } + // 1.3.9 (and beyond?) have changed how artifact names are resolved due to multiplatform variants + pass { + group.set("org.jetbrains.kotlinx") + module.set("kotlinx-coroutines-core-jvm") + versions.set("[1.3.9,)") + } +} + +dependencies { + library("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0") + compileOnly(project(":instrumentation-annotations-support")) + implementation(project(":instrumentation:kotlinx-coroutines:kotlinx-coroutines-flow-1.3:javaagent-kotlin")) + + testInstrumentation(project(":instrumentation:kotlinx-coroutines:kotlinx-coroutines-1.0:javaagent")) + testInstrumentation(project(":instrumentation:opentelemetry-extension-kotlin-1.0:javaagent")) + testInstrumentation(project(":instrumentation:reactor:reactor-3.1:javaagent")) + + testImplementation("io.opentelemetry:opentelemetry-extension-kotlin") + testImplementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + testImplementation(project(":instrumentation:reactor:reactor-3.1:library")) + testImplementation(project(":instrumentation-annotations")) + + testLibrary("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:1.3.0") +} + +tasks { + withType(KotlinCompile::class).configureEach { + kotlinOptions { + jvmTarget = "1.8" + } + } + withType().configureEach { + jvmArgs("-Dio.opentelemetry.javaagent.shaded.io.opentelemetry.context.enableStrictContext=false") + } +} diff --git a/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/flow/AbstractFlowInstrumentation.java b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/flow/AbstractFlowInstrumentation.java new file mode 100644 index 000000000000..4fa1d097c7a5 --- /dev/null +++ b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/flow/AbstractFlowInstrumentation.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.kotlinxcoroutines.flow; + +import static net.bytebuddy.matcher.ElementMatchers.isConstructor; +import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; + +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class AbstractFlowInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher typeMatcher() { + return namedOneOf("kotlinx.coroutines.flow.AbstractFlow", "kotlinx.coroutines.flow.SafeFlow"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isConstructor(), this.getClass().getName() + "$ConstructorAdvice"); + } + + @SuppressWarnings("unused") + public static class ConstructorAdvice { + + @Advice.OnMethodEnter + public static void enter() { + FlowInstrumentationHelper.initialize(); + } + } +} diff --git a/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/flow/FlowInstrumentationHelper.java b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/flow/FlowInstrumentationHelper.java new file mode 100644 index 000000000000..02102afe01d0 --- /dev/null +++ b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/flow/FlowInstrumentationHelper.java @@ -0,0 +1,44 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.kotlinxcoroutines.flow; + +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.annotation.support.async.AsyncOperationEndStrategies; +import io.opentelemetry.instrumentation.api.annotation.support.async.AsyncOperationEndStrategy; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import kotlinx.coroutines.flow.Flow; + +public final class FlowInstrumentationHelper { + private static final FlowAsyncOperationEndStrategy asyncOperationEndStrategy = + new FlowAsyncOperationEndStrategy(); + + static { + AsyncOperationEndStrategies.instance().registerStrategy(asyncOperationEndStrategy); + } + + public static void initialize() {} + + private FlowInstrumentationHelper() {} + + private static final class FlowAsyncOperationEndStrategy implements AsyncOperationEndStrategy { + + @Override + public boolean supports(Class returnType) { + return Flow.class.isAssignableFrom(returnType); + } + + @Override + public Object end( + Instrumenter instrumenter, + Context context, + REQUEST request, + Object asyncValue, + Class responseType) { + Flow flow = (Flow) asyncValue; + return FlowUtilKt.onComplete(flow, instrumenter, context, request); + } + } +} diff --git a/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/flow/KotlinCoroutinesFlowInstrumentationModule.java b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/flow/KotlinCoroutinesFlowInstrumentationModule.java new file mode 100644 index 000000000000..d280f231f430 --- /dev/null +++ b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/flow/KotlinCoroutinesFlowInstrumentationModule.java @@ -0,0 +1,26 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.kotlinxcoroutines.flow; + +import static java.util.Collections.singletonList; + +import com.google.auto.service.AutoService; +import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import java.util.List; + +@AutoService(InstrumentationModule.class) +public class KotlinCoroutinesFlowInstrumentationModule extends InstrumentationModule { + + public KotlinCoroutinesFlowInstrumentationModule() { + super("kotlinx-coroutines", "kotlinx-coroutines-flow"); + } + + @Override + public List typeInstrumentations() { + return singletonList(new AbstractFlowInstrumentation()); + } +} diff --git a/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent/src/test/kotlin/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/KotlinCoroutines13InstrumentationTest.kt b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent/src/test/kotlin/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/KotlinCoroutines13InstrumentationTest.kt new file mode 100644 index 000000000000..ed6fc90b61a4 --- /dev/null +++ b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent/src/test/kotlin/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/KotlinCoroutines13InstrumentationTest.kt @@ -0,0 +1,218 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.kotlinxcoroutines + +import io.opentelemetry.context.Context +import io.opentelemetry.extension.kotlin.asContextElement +import io.opentelemetry.instrumentation.reactor.v3_1.ContextPropagationOperator +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.channels.produce +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.consumeAsFlow +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.reactive.awaitSingle +import kotlinx.coroutines.reactive.collect +import kotlinx.coroutines.reactor.ReactorContext +import kotlinx.coroutines.reactor.flux +import kotlinx.coroutines.reactor.mono +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.api.extension.RegisterExtension +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.Arguments.arguments +import org.junit.jupiter.params.provider.ArgumentsProvider +import org.junit.jupiter.params.provider.ArgumentsSource +import java.util.concurrent.Executors +import java.util.stream.Stream + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@ExperimentalCoroutinesApi +class KotlinCoroutines13InstrumentationTest { + + companion object { + val threadPool = Executors.newFixedThreadPool(2) + val singleThread = Executors.newSingleThreadExecutor() + } + + @AfterAll + fun shutdown() { + threadPool.shutdown() + singleThread.shutdown() + } + + @RegisterExtension + val testing = AgentInstrumentationExtension.create() + + val tracer = testing.openTelemetry.getTracer("test") + + @ParameterizedTest + @ArgumentsSource(DispatchersSource::class) + fun `traced across channels`(dispatcher: DispatcherWrapper) { + runTest(dispatcher) { + val producer = produce { + repeat(3) { + tracedChild("produce_$it") + send(it) + } + } + + producer.consumeAsFlow().onEach { + tracedChild("consume_$it") + }.collect() + } + + testing.waitAndAssertTraces( + { trace -> + trace.hasSpansSatisfyingExactlyInAnyOrder( + { + it.hasName("parent") + .hasNoParent() + }, + { + it.hasName("produce_0") + .hasParent(trace.getSpan(0)) + }, + { + it.hasName("consume_0") + .hasParent(trace.getSpan(0)) + }, + { + it.hasName("produce_1") + .hasParent(trace.getSpan(0)) + }, + { + it.hasName("consume_1") + .hasParent(trace.getSpan(0)) + }, + { + it.hasName("produce_2") + .hasParent(trace.getSpan(0)) + }, + { + it.hasName("consume_2") + .hasParent(trace.getSpan(0)) + }, + ) + }, + ) + } + + @ParameterizedTest + @ArgumentsSource(DispatchersSource::class) + fun `traced mono with context propagation operator`(dispatcherWrapper: DispatcherWrapper) { + runTest(dispatcherWrapper) { + val currentContext = Context.current() + // clear current context to ensure that ContextPropagationOperator is used for context propagation + withContext(Context.root().asContextElement()) { + val mono = mono(dispatcherWrapper.dispatcher) { + // extract context from reactor and propagate it into coroutine + val reactorContext = coroutineContext[ReactorContext.Key]?.context + val otelContext = ContextPropagationOperator.getOpenTelemetryContext(reactorContext, Context.current()) + withContext(otelContext.asContextElement()) { + tracedChild("child") + } + } + ContextPropagationOperator.runWithContext(mono, currentContext).awaitSingle() + } + } + + testing.waitAndAssertTraces( + { trace -> + trace.hasSpansSatisfyingExactly( + { + it.hasName("parent") + .hasNoParent() + }, + { + it.hasName("child") + .hasParent(trace.getSpan(0)) + }, + ) + }, + ) + } + + @ParameterizedTest + @ArgumentsSource(DispatchersSource::class) + fun `traced flux`(dispatcherWrapper: DispatcherWrapper) { + runTest(dispatcherWrapper) { + flux(dispatcherWrapper.dispatcher) { + repeat(3) { + tracedChild("child_$it") + send(it) + } + }.collect { + } + } + + testing.waitAndAssertTraces( + { trace -> + trace.hasSpansSatisfyingExactly( + { + it.hasName("parent") + .hasNoParent() + }, + { + it.hasName("child_0") + .hasParent(trace.getSpan(0)) + }, + { + it.hasName("child_1") + .hasParent(trace.getSpan(0)) + }, + { + it.hasName("child_2") + .hasParent(trace.getSpan(0)) + }, + ) + }, + ) + } + + private fun tracedChild(opName: String) { + tracer.spanBuilder(opName).startSpan().end() + } + + private fun runTest(dispatcherWrapper: DispatcherWrapper, block: suspend CoroutineScope.() -> T): T { + return runTest(dispatcherWrapper.dispatcher, block) + } + + private fun runTest(dispatcher: CoroutineDispatcher, block: suspend CoroutineScope.() -> T): T { + val parentSpan = tracer.spanBuilder("parent").startSpan() + val parentScope = parentSpan.makeCurrent() + try { + return runBlocking(dispatcher, block = block) + } finally { + parentSpan.end() + parentScope.close() + } + } + + class DispatchersSource : ArgumentsProvider { + override fun provideArguments(context: ExtensionContext?): Stream = Stream.of( + // Wrap dispatchers since it seems that ParameterizedTest tries to automatically close + // Closeable arguments with no way to avoid it. + arguments(DispatcherWrapper(Dispatchers.Default)), + arguments(DispatcherWrapper(Dispatchers.IO)), + arguments(DispatcherWrapper(Dispatchers.Unconfined)), + arguments(DispatcherWrapper(threadPool.asCoroutineDispatcher())), + arguments(DispatcherWrapper(singleThread.asCoroutineDispatcher())), + ) + } + + class DispatcherWrapper(val dispatcher: CoroutineDispatcher) { + override fun toString(): String = dispatcher.toString() + } +} diff --git a/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent/src/test/kotlin/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/flow/FlowWithSpanTest.kt b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent/src/test/kotlin/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/flow/FlowWithSpanTest.kt new file mode 100644 index 000000000000..d99e5dd5125b --- /dev/null +++ b/instrumentation/kotlinx-coroutines/kotlinx-coroutines-flow-1.3/javaagent/src/test/kotlin/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/flow/FlowWithSpanTest.kt @@ -0,0 +1,66 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.kotlinxcoroutines.flow + +import io.opentelemetry.instrumentation.annotations.WithSpan +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension +import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo +import io.opentelemetry.semconv.incubating.CodeIncubatingAttributes +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.count +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.runBlocking +import org.assertj.core.api.Condition +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.extension.RegisterExtension +import java.time.Clock +import java.util.concurrent.TimeUnit + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@ExperimentalCoroutinesApi +class FlowWithSpanTest { + + @RegisterExtension + val testing = AgentInstrumentationExtension.create() + + @Test + fun `test method returning Flow with WithSpan annotation`() { + var flowStartTime: Long = 0 + runBlocking { + val flow = simple() + val now = Clock.systemUTC().instant() + flowStartTime = TimeUnit.SECONDS.toNanos(now.epochSecond) + now.nano + flow.count() + } + + testing.waitAndAssertTraces( + { trace -> + trace.hasSpansSatisfyingExactly( + { + it.hasName("FlowWithSpanTest.simple") + .hasNoParent() + .hasAttributesSatisfyingExactly( + equalTo(CodeIncubatingAttributes.CODE_NAMESPACE, this.javaClass.name), + equalTo(CodeIncubatingAttributes.CODE_FUNCTION, "simple") + ) + .has(Condition({ spanData -> spanData.endEpochNanos > flowStartTime }, "end time after $flowStartTime")) + } + ) + } + ) + } + + @WithSpan + fun simple(): Flow = flow { + for (i in 1..3) { + delay(100) + emit(i) + } + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index d088aa831819..bada0c1d1ac4 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -354,7 +354,9 @@ include(":instrumentation:kafka:kafka-clients:kafka-clients-0.11:testing") include(":instrumentation:kafka:kafka-clients:kafka-clients-2.6:library") include(":instrumentation:kafka:kafka-clients:kafka-clients-common:library") include(":instrumentation:kafka:kafka-streams-0.11:javaagent") -include(":instrumentation:kotlinx-coroutines:javaagent") +include(":instrumentation:kotlinx-coroutines:kotlinx-coroutines-1.0:javaagent") +include(":instrumentation:kotlinx-coroutines:kotlinx-coroutines-flow-1.3:javaagent") +include(":instrumentation:kotlinx-coroutines:kotlinx-coroutines-flow-1.3:javaagent-kotlin") include(":instrumentation:ktor:ktor-1.0:library") include(":instrumentation:ktor:ktor-2.0:javaagent") include(":instrumentation:ktor:ktor-2.0:library")