From d478253db271e97f5073d43f84194630981f71ad Mon Sep 17 00:00:00 2001 From: "koo.taejin" Date: Mon, 24 Jan 2022 15:49:41 +0900 Subject: [PATCH] [#8472] Improves Coroutines plugin 1. Changes tracing target (Task.runSafely -> resumeWith) = RunSafely is a method that is executed only in case of a specific target, so it has been changed to a general method. 2. Supports tracing threadName 3. Supports tracing cancelling event --- .../resources/profiles/local/pinpoint.config | 29 ++-- .../profiles/release/pinpoint.config | 29 ++-- .../kotlinx/coroutines/CoroutinesIT.java | 139 +++++++++------- .../kotlinx/coroutines/CoroutinesLaunch.kt | 37 ++--- .../test/resources/pinpoint-coroutines.config | 3 +- .../kotlinx/coroutines/CoroutinesConfig.java | 37 ++--- .../coroutines/CoroutinesConstants.java | 9 +- .../CoroutinesMetadataProvider.java | 3 +- .../kotlinx/coroutines/CoroutinesPlugin.java | 152 ++++++++++-------- .../interceptor/CancelledInterceptor.java | 89 ++++++++++ .../CopyAsyncContextInterceptor.java | 78 --------- .../interceptor/DispatchInterceptor.java | 124 ++++---------- .../interceptor/ExecuteTaskInterceptor.java | 68 -------- .../NotifyCancellingInterceptor.java | 89 ++++++++++ .../interceptor/ResumeWithInterceptor.java | 97 +++++++++++ .../ScheduleResumeInterceptor.java | 123 ++++++++++++++ 16 files changed, 653 insertions(+), 453 deletions(-) create mode 100644 plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/interceptor/CancelledInterceptor.java delete mode 100644 plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/interceptor/CopyAsyncContextInterceptor.java delete mode 100644 plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/interceptor/ExecuteTaskInterceptor.java create mode 100644 plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/interceptor/NotifyCancellingInterceptor.java create mode 100644 plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/interceptor/ResumeWithInterceptor.java create mode 100644 plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/interceptor/ScheduleResumeInterceptor.java diff --git a/agent/src/main/resources/profiles/local/pinpoint.config b/agent/src/main/resources/profiles/local/pinpoint.config index 3474c524105f..60e894899588 100644 --- a/agent/src/main/resources/profiles/local/pinpoint.config +++ b/agent/src/main/resources/profiles/local/pinpoint.config @@ -1256,22 +1256,13 @@ profiler.rocketmq.basePackage= # v1.0.1 ~ ########################################################### profiler.kotlin.coroutines.enable=false -# Coroutine Example -# runBlocking(CoroutineName("pinpoint-coroutines")) { -# // has same name("pinpoint-coroutines") with parentContext -# launch { -# } -# // has name("async-pinpoint-coroutines") -# async(CoroutineName("async-pinpoint-coroutines")) { // -# } -# } -# 1. For trace everyone in the above situation -# profiler.kotlin.coroutines.name.include=pinpoint-coroutines,async-pinpoint-coroutines -# 2. For trace only pinpoint-coroutines context -# profiler.kotlin.coroutines.name.include=pinpoint-coroutines -# Note) Trace "async-pinpoint-coroutines" is impossible. This is because the above root connection does not exist. -# -# Only perfect string matching is supported. (If you do not include any value, it will not be tracked.) -# Comma separated list of coroutines name -# eg) profiler.kotlin.coroutines.name.include=CoroutineMyJob1,CoroutineMyJob2 -profiler.kotlin.coroutines.name.include= \ No newline at end of file + +#Trace the name of the thread. +#This is important information to check whether the developer's intention and the behavior of the coroutine match. +#Recommend that you use it in the development environment and not in the production environment. +profiler.kotlin.coroutines.record.threadName=false + +#Track cancellations and the propagation of cancellations. +#This is important information to check whether the developer's intention and the behavior of the coroutine match. +#Recommend that you use it in the development environment and not in the production environment. +profiler.kotlin.coroutines.record.cancel=false diff --git a/agent/src/main/resources/profiles/release/pinpoint.config b/agent/src/main/resources/profiles/release/pinpoint.config index 229439f897bc..403dbe7cd760 100644 --- a/agent/src/main/resources/profiles/release/pinpoint.config +++ b/agent/src/main/resources/profiles/release/pinpoint.config @@ -1279,22 +1279,13 @@ profiler.rocketmq.basePackage= # v1.0.1 ~ ########################################################### profiler.kotlin.coroutines.enable=false -## Coroutine Example -# runBlocking(CoroutineName("pinpoint-coroutines")) { -# // has same name("pinpoint-coroutines") with parentContext -# launch { -# } -# // has name("async-pinpoint-coroutines") -# async(CoroutineName("async-pinpoint-coroutines")) { // -# } -# } -# 1. For trace everyone in the above situation -# profiler.kotlin.coroutines.name.include=pinpoint-coroutines,async-pinpoint-coroutines -# 2. For trace only pinpoint-coroutines context -# profiler.kotlin.coroutines.name.include=pinpoint-coroutines -# Note) Trace "async-pinpoint-coroutines" is impossible. This is because the above root connection does not exist. -# -# Only perfect string matching is supported. (If you do not include any value, it will not be tracked.) -# Comma separated list of coroutines name -# eg) profiler.kotlin.coroutines.name.include=CoroutineMyJob1,CoroutineMyJob2 -profiler.kotlin.coroutines.name.include= \ No newline at end of file + +#Trace the name of the thread. +#This is important information to check whether the developer's intention and the behavior of the coroutine match. +#Recommend that you use it in the development environment and not in the production environment. +profiler.kotlin.coroutines.record.threadName=false + +#Track cancellations and the propagation of cancellations. +#This is important information to check whether the developer's intention and the behavior of the coroutine match. +#Recommend that you use it in the development environment and not in the production environment. +profiler.kotlin.coroutines.record.cancel=false diff --git a/plugins-it/kotlin-coroutines-it/src/test/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/CoroutinesIT.java b/plugins-it/kotlin-coroutines-it/src/test/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/CoroutinesIT.java index 91e6b3619225..f4868961221f 100644 --- a/plugins-it/kotlin-coroutines-it/src/test/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/CoroutinesIT.java +++ b/plugins-it/kotlin-coroutines-it/src/test/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/CoroutinesIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 NAVER Corp. + * Copyright 2022 NAVER Corp. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,9 @@ import com.navercorp.pinpoint.test.plugin.PinpointAgent; import com.navercorp.pinpoint.test.plugin.PinpointConfig; import com.navercorp.pinpoint.test.plugin.PinpointPluginTestSuite; - +import kotlin.coroutines.CoroutineContext; +import kotlinx.coroutines.CoroutineDispatcher; +import kotlinx.coroutines.Dispatchers; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -49,66 +51,109 @@ public class CoroutinesIT { private static final String DISPATCH_METHOD = ".dispatch("; - private static final String RUN_METHOD = ".runSafely("; + private static final String RESUME_WITH_METHOD = ".resumeWith("; + private static final String SCHEDULE_RESUME_METHOD = ".scheduleResumeAfterDelay("; private static final String ASYNC_INVOCATION = "Asynchronous Invocation"; @Test - public void executeOneLaunchBlockTest() { - int minimumExpectedCount = 7; - int launchBlockCount = 1; - int expectedExecutedRunSafelyCount = 2; + public void executeRunBlockingWitoutContext() { + final boolean activeAsync = false; - // This test has 1 ~ 2 executed Async Invocation - // This test has 2 executed runSafely() + // This test has 1 ~ 4 executed Async Invocation + // This test has 4 executed runSafely() CoroutinesLaunch coroutinesLaunch = new CoroutinesLaunch(); - coroutinesLaunch.execute("pinpoint-test"); + coroutinesLaunch.executeWithRunBlocking(); PluginTestVerifier verifier = PluginTestVerifierHolder.getInstance(); List executedMethod = verifier.getExecutedMethod(); AtomicInteger index = new AtomicInteger(); - // dispatch runblocking - Assert.assertTrue(executedMethod.size() >= minimumExpectedCount); - assertFirstDispatch(executedMethod, index); - for (int i = 0; i < launchBlockCount; i++) { - // dispatch launch job - Assert.assertTrue(executedMethod.get(index.getAndIncrement()).contains(DISPATCH_METHOD)); - } + // runBlocking(context) { + Assert.assertTrue(executedMethod.get(index.getAndIncrement()).contains(DISPATCH_METHOD)); + // runBlocking(context) { + assertResumeWith(executedMethod, index, activeAsync); - final String[] executeActualMethods = Arrays.copyOfRange(executedMethod.toArray(new String[0]), index.get(), executedMethod.size()); - Assert.assertTrue(assertExecutedCount(executeActualMethods, RUN_METHOD, expectedExecutedRunSafelyCount)); - Assert.assertTrue(assertExecutedCount(executeActualMethods, ASYNC_INVOCATION, executeActualMethods.length - expectedExecutedRunSafelyCount)); + // val job1 = async(CoroutineName("first")) { + Assert.assertTrue(executedMethod.get(index.getAndIncrement()).contains(DISPATCH_METHOD)); + // val job2 = launch(CoroutineName("second")) { + Assert.assertTrue(executedMethod.get(index.getAndIncrement()).contains(DISPATCH_METHOD)); + + // delay(10L) // job1 + assertResumeWithAndSchedule(executedMethod, index, activeAsync); + + // delay(5L) // job2 + assertResumeWithAndSchedule(executedMethod, index, activeAsync); + + // println("Hello World 1") // job1 + assertResumeWith(executedMethod, index, activeAsync); + // println("Hello World 2") // job2 + assertResumeWith(executedMethod, index, activeAsync); + // println("Hello all of jobs") // rootjob + assertResumeWith(executedMethod, index, activeAsync); } @Test - public void executeTwoLaunchBlockTest() { - int minimumExpectedCount = 10; - int launchBlockCount = 2; - int expectedExecutedRunSafelyCount = 4; - + public void executeRunBlocking() { + final boolean activeAsync = true; - // This test has 1 ~ 4 executed Async Invocation - // This test has 4 executed runSafely() CoroutinesLaunch coroutinesLaunch = new CoroutinesLaunch(); - coroutinesLaunch.execute2("pinpoint-test"); + CoroutineDispatcher dispatcher = Dispatchers.getDefault(); + coroutinesLaunch.executeWithRunBlocking((CoroutineContext) dispatcher); PluginTestVerifier verifier = PluginTestVerifierHolder.getInstance(); + + verifier.awaitTraceCount(17, 10L, 1000L); + List executedMethod = verifier.getExecutedMethod(); AtomicInteger index = new AtomicInteger(); - // dispatch runblocking - Assert.assertTrue(executedMethod.size() >= minimumExpectedCount); - assertFirstDispatch(executedMethod, index); - for (int i = 0; i < launchBlockCount; i++) { - // dispatch launch job - Assert.assertTrue(executedMethod.get(index.getAndIncrement()).contains(DISPATCH_METHOD)); + // runBlocking(context) { + Assert.assertTrue(executedMethod.get(index.getAndIncrement()).contains(DISPATCH_METHOD)); + // runBlocking(context) { + assertResumeWith(executedMethod, index, activeAsync); + + // val job1 = async(CoroutineName("first")) { + Assert.assertTrue(executedMethod.get(index.getAndIncrement()).contains(DISPATCH_METHOD)); + // val job2 = launch(CoroutineName("second")) { + Assert.assertTrue(executedMethod.get(index.getAndIncrement()).contains(DISPATCH_METHOD)); + + // println("Hello all of jobs") // rootjob + assertResumeWith(executedMethod, index, activeAsync); + + // delay(10L) // job1 + assertResumeWithAndSchedule(executedMethod, index, activeAsync); + + // delay(5L) // job2 + assertResumeWithAndSchedule(executedMethod, index, activeAsync); + + // println("Hello World 1") // job1 + assertResumeWith(executedMethod, index, activeAsync); + // println("Hello World 2") // job2 + assertResumeWith(executedMethod, index, activeAsync); + } + + private void assertResumeWithAndSchedule(List executedMethod, AtomicInteger index, boolean activeAsync) { + if (activeAsync) { + Assert.assertTrue(executedMethod.get(index.getAndIncrement()).contains(ASYNC_INVOCATION)); + } + Assert.assertTrue(executedMethod.get(index.getAndIncrement()).contains(RESUME_WITH_METHOD)); + Assert.assertTrue(executedMethod.get(index.getAndIncrement()).contains(SCHEDULE_RESUME_METHOD)); + } + + private void assertResumeWith(List executedMethod, AtomicInteger index, boolean activeAsync) { + if (activeAsync) { + Assert.assertTrue(executedMethod.get(index.getAndIncrement()).contains(ASYNC_INVOCATION)); } + Assert.assertTrue(executedMethod.get(index.getAndIncrement()).contains(RESUME_WITH_METHOD)); + } - final String[] executeActualMethods = Arrays.copyOfRange(executedMethod.toArray(new String[0]), index.get(), executedMethod.size()); - Assert.assertTrue(assertExecutedCount(executeActualMethods, RUN_METHOD, expectedExecutedRunSafelyCount)); - Assert.assertTrue(assertExecutedCount(executeActualMethods, ASYNC_INVOCATION, executeActualMethods.length - expectedExecutedRunSafelyCount)); + private void assertRunblockingDispatch(List executedMethod, AtomicInteger index) { + Assert.assertTrue(executedMethod.get(index.getAndIncrement()).contains(DISPATCH_METHOD)); + Assert.assertTrue(executedMethod.get(index.getAndIncrement()).equals(ASYNC_INVOCATION)); + // run dispatchedContinuation + Assert.assertTrue(executedMethod.get(index.getAndIncrement()).contains(RESUME_WITH_METHOD)); } @@ -116,7 +161,7 @@ private void assertFirstDispatch(List executedMethod, AtomicInteger inde Assert.assertTrue(executedMethod.get(index.getAndIncrement()).contains(DISPATCH_METHOD)); Assert.assertTrue(executedMethod.get(index.getAndIncrement()).equals(ASYNC_INVOCATION)); // run dispatchedContinuation - Assert.assertTrue(executedMethod.get(index.getAndIncrement()).contains(RUN_METHOD)); + Assert.assertTrue(executedMethod.get(index.getAndIncrement()).contains(RESUME_WITH_METHOD)); } private boolean assertExecutedCount(String[] executeActualMethod, String expectedActualMethod, int expectedCount) { @@ -124,22 +169,4 @@ private boolean assertExecutedCount(String[] executeActualMethod, String expecte return count == expectedCount; } - @Test - public void executeCurrentThreadTest() { - int expectedCount = 2; - - // This test has 0 executed Async Invocation - // This test has 0 executed runSafely() - CoroutinesLaunch coroutinesLaunch = new CoroutinesLaunch(); - coroutinesLaunch.executeParentDispatcher("pinpoint-test"); - - // executes 2 times dispatch - PluginTestVerifier verifier = PluginTestVerifierHolder.getInstance(); - - List executedMethod = verifier.getExecutedMethod(); - Assert.assertEquals(expectedCount, executedMethod.size()); - Assert.assertTrue(executedMethod.get(0).contains(DISPATCH_METHOD)); - Assert.assertTrue(executedMethod.get(1).contains(DISPATCH_METHOD)); - } - } diff --git a/plugins-it/kotlin-coroutines-it/src/test/kotlin/com/navercorp/pinpoint/plugin/kotlinx/coroutines/CoroutinesLaunch.kt b/plugins-it/kotlin-coroutines-it/src/test/kotlin/com/navercorp/pinpoint/plugin/kotlinx/coroutines/CoroutinesLaunch.kt index 600cfdbd106a..1589aaef39e3 100644 --- a/plugins-it/kotlin-coroutines-it/src/test/kotlin/com/navercorp/pinpoint/plugin/kotlinx/coroutines/CoroutinesLaunch.kt +++ b/plugins-it/kotlin-coroutines-it/src/test/kotlin/com/navercorp/pinpoint/plugin/kotlinx/coroutines/CoroutinesLaunch.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 NAVER Corp. + * Copyright 2022 NAVER Corp. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,45 +17,28 @@ package com.navercorp.pinpoint.plugin.kotlinx.coroutines import kotlinx.coroutines.* -import java.util.* +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext /** * @author Taejin Koo */ class CoroutinesLaunch { - fun execute(coroutineName: String) { - runBlocking(CoroutineName(coroutineName) + Dispatchers.Default) { - execute0(coroutineName) - } - } - - // Concurrently executes both sections - suspend fun execute0(firstName: String, secondName: String = UUID.randomUUID().toString()) = - coroutineScope { // this: CoroutineScope - val job = async(CoroutineName(firstName)) { + @JvmOverloads + fun executeWithRunBlocking(context: CoroutineContext = EmptyCoroutineContext) { + runBlocking(context) { + val job1 = async(CoroutineName("first")) { delay(10L) println("Hello World 1") } - launch(CoroutineName(secondName)) { + val job2 = launch(CoroutineName("second")) { delay(5L) println("Hello World 2") } - job.join() - println("Hello World") - } - - fun execute2(coroutineName: String) { - runBlocking(CoroutineName(coroutineName) + Dispatchers.Default) { - execute0(coroutineName, coroutineName) + joinAll(job1, job2) + println("Hello all of jobs") } } - fun executeParentDispatcher(coroutineName: String) { - runBlocking(CoroutineName(coroutineName)) { - execute0(coroutineName) - } - } - - } \ No newline at end of file diff --git a/plugins-it/kotlin-coroutines-it/src/test/resources/pinpoint-coroutines.config b/plugins-it/kotlin-coroutines-it/src/test/resources/pinpoint-coroutines.config index e4bac8e1b34b..6252e76f64db 100644 --- a/plugins-it/kotlin-coroutines-it/src/test/resources/pinpoint-coroutines.config +++ b/plugins-it/kotlin-coroutines-it/src/test/resources/pinpoint-coroutines.config @@ -1,2 +1,3 @@ profiler.kotlin.coroutines.enable=true -profiler.kotlin.coroutines.name.include=pinpoint-test \ No newline at end of file +profiler.kotlin.coroutines.record.threadName=false +profiler.kotlin.coroutines.record.cancel=false diff --git a/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/CoroutinesConfig.java b/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/CoroutinesConfig.java index b203a479fe57..8877620dbaaa 100644 --- a/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/CoroutinesConfig.java +++ b/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/CoroutinesConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 NAVER Corp. + * Copyright 2022 NAVER Corp. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,11 +17,6 @@ package com.navercorp.pinpoint.plugin.kotlinx.coroutines; import com.navercorp.pinpoint.bootstrap.config.ProfilerConfig; -import com.navercorp.pinpoint.common.util.StringUtils; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; /** * @author Taejin Koo @@ -29,39 +24,33 @@ public class CoroutinesConfig { private final boolean traceCoroutines; - private final List nameIncludeList; + private final boolean traceCancelEvent; + private final boolean recordThreadName; public CoroutinesConfig(ProfilerConfig config) { this.traceCoroutines = config.readBoolean("profiler.kotlin.coroutines.enable", false); - String nameIncludes = config.readString("profiler.kotlin.coroutines.name.include", ""); - - if (StringUtils.hasLength(nameIncludes)) { - String[] nameIncludeArray = nameIncludes.split(","); - List result = new ArrayList<>(nameIncludeArray.length); - for (String nameInclude : nameIncludeArray) { - if (StringUtils.hasLength(nameInclude)) { - result.add(nameInclude); - } - } - nameIncludeList = Collections.unmodifiableList(result); - } else { - nameIncludeList = Collections.emptyList(); - } + this.traceCancelEvent = config.readBoolean("profiler.kotlin.coroutines.record.cancel", false); + this.recordThreadName = config.readBoolean("profiler.kotlin.coroutines.record.threadName", false); } public boolean isTraceCoroutines() { return traceCoroutines; } - public List getIncludedNameList() { - return nameIncludeList; + public boolean isTraceCancelEvent() { + return traceCancelEvent; + } + + public boolean isRecordThreadName() { + return recordThreadName; } @Override public String toString() { final StringBuilder sb = new StringBuilder("CoroutinesConfig{"); sb.append("traceCoroutines=").append(traceCoroutines); - sb.append(", nameIncludeList=").append(nameIncludeList); + sb.append(", traceCancelEvent=").append(traceCancelEvent); + sb.append(", recordThreadName=").append(recordThreadName); sb.append('}'); return sb.toString(); } diff --git a/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/CoroutinesConstants.java b/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/CoroutinesConstants.java index 7125393e7ea5..a531a45aeda8 100644 --- a/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/CoroutinesConstants.java +++ b/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/CoroutinesConstants.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 NAVER Corp. + * Copyright 2022 NAVER Corp. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,13 @@ package com.navercorp.pinpoint.plugin.kotlinx.coroutines; +import com.navercorp.pinpoint.common.trace.AnnotationKey; +import com.navercorp.pinpoint.common.trace.AnnotationKeyFactory; import com.navercorp.pinpoint.common.trace.ServiceType; import com.navercorp.pinpoint.common.trace.ServiceTypeFactory; +import static com.navercorp.pinpoint.common.trace.AnnotationKeyProperty.VIEW_IN_RECORD_SET; + /** * @author Taejin Koo */ @@ -28,7 +32,6 @@ private CoroutinesConstants() { } public static final ServiceType SERVICE_TYPE = ServiceTypeFactory.of(8901, "KT_COROUTINES"); - - public static final String SCOPE = "KT_COROUTINES_SCOPE"; + public static final AnnotationKey COROUTINE_THREAD_NAME_ANNOTATION_KEY = AnnotationKeyFactory.of(340, "thread.name", VIEW_IN_RECORD_SET); } diff --git a/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/CoroutinesMetadataProvider.java b/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/CoroutinesMetadataProvider.java index 03fdd7ef032d..913ed67a13a3 100644 --- a/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/CoroutinesMetadataProvider.java +++ b/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/CoroutinesMetadataProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 NAVER Corp. + * Copyright 2022 NAVER Corp. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ public class CoroutinesMetadataProvider implements TraceMetadataProvider { @Override public void setup(TraceMetadataSetupContext context) { context.addServiceType(CoroutinesConstants.SERVICE_TYPE); + context.addAnnotationKey(CoroutinesConstants.COROUTINE_THREAD_NAME_ANNOTATION_KEY); } } diff --git a/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/CoroutinesPlugin.java b/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/CoroutinesPlugin.java index 61256e7b65d2..a9d1d4c43564 100644 --- a/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/CoroutinesPlugin.java +++ b/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/CoroutinesPlugin.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 NAVER Corp. + * Copyright 2022 NAVER Corp. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import com.navercorp.pinpoint.bootstrap.instrument.InstrumentException; import com.navercorp.pinpoint.bootstrap.instrument.InstrumentMethod; import com.navercorp.pinpoint.bootstrap.instrument.Instrumentor; +import com.navercorp.pinpoint.bootstrap.instrument.MethodFilters; import com.navercorp.pinpoint.bootstrap.instrument.matcher.Matcher; import com.navercorp.pinpoint.bootstrap.instrument.matcher.Matchers; import com.navercorp.pinpoint.bootstrap.instrument.matcher.operand.SuperClassInternalNameMatcherOperand; @@ -31,16 +32,16 @@ import com.navercorp.pinpoint.bootstrap.logging.PLoggerFactory; import com.navercorp.pinpoint.bootstrap.plugin.ProfilerPlugin; import com.navercorp.pinpoint.bootstrap.plugin.ProfilerPluginSetupContext; -import com.navercorp.pinpoint.common.util.ArrayUtils; -import com.navercorp.pinpoint.plugin.kotlinx.coroutines.interceptor.CopyAsyncContextInterceptor; +import com.navercorp.pinpoint.common.util.VarArgs; +import com.navercorp.pinpoint.plugin.kotlinx.coroutines.interceptor.CancelledInterceptor; import com.navercorp.pinpoint.plugin.kotlinx.coroutines.interceptor.DispatchInterceptor; -import com.navercorp.pinpoint.plugin.kotlinx.coroutines.interceptor.ExecuteTaskInterceptor; +import com.navercorp.pinpoint.plugin.kotlinx.coroutines.interceptor.NotifyCancellingInterceptor; +import com.navercorp.pinpoint.plugin.kotlinx.coroutines.interceptor.ResumeWithInterceptor; +import com.navercorp.pinpoint.plugin.kotlinx.coroutines.interceptor.ScheduleResumeInterceptor; import java.security.ProtectionDomain; import java.util.List; -import static com.navercorp.pinpoint.common.util.VarArgs.va; - /** * @author Taejin Koo */ @@ -49,6 +50,11 @@ public class CoroutinesPlugin implements ProfilerPlugin, MatchableTransformTempl private MatchableTransformTemplate transformTemplate; + @Override + public void setTransformTemplate(MatchableTransformTemplate transformTemplate) { + this.transformTemplate = transformTemplate; + } + @Override public void setup(ProfilerPluginSetupContext context) { final CoroutinesConfig config = new CoroutinesConfig(context.getConfig()); @@ -59,27 +65,53 @@ public void setup(ProfilerPluginSetupContext context) { logger.info("{} disabled", simpleClazzName); return; } - if (config.getIncludedNameList().isEmpty()) { - logger.info("{} could not find any included name.", simpleClazzName); - return; - } logger.info("{} config:{}", simpleClazzName, config); - /** - * 1. Starts coroutine task - * 2. Creates DispatchedContinuation - * 3. Dispatches DispatchedContinuation - * L addCoroutineDispatcherTransformer() - * 4. Creates CancellableContinuation based on DispatchedContinuation - * L propagateAsyncContextTransformer - * 5. Dispatches CancellableContinuation - * 6. Executes task(actual implementation) - * L addExecuteTaskTransformer - */ + /** Coroutines Lifecycle + + Before Run After + + +-----------+ + | Start | -------------+ + +-----------+ | + | | +---------+ + +------------------- | -----| Executor|-------+ + | | +---------+ | + | | | + V V | + +-----------+ +--------------+ +------------+ + | Scheduler | ----> | Continuation | ----> | Dispatcher | + | Ldispatch | + L resumeWith | + Ldispatch | + +-----------+ +--------------+ +------------+ + | | + | V + | +------------+ + + ------------> | Finish | + +------------+ + */ + + addContinuationTransformer(); + addCombinedContextTransformer(); addCoroutineDispatcherTransformer(); - propagateAsyncContextTransformer(); - addExecuteTaskTransformer(); + if (config.isTraceCancelEvent()) { + addJobCancelTransformer(); + } + } + + private void addContinuationTransformer() { + // Matcher matcher = Matchers.newClassBasedMatcher("kotlinx.coroutines.Continuation"); + // transformTemplate.transform(matcher, ContinuationTransform.class); + // + + // It will be excpected that only the below class to be traced will be sufficient, + // because user's coroutine implementation is regenerated based on BaseContinuationImpl. + // However, need to uncomment the above section if it is not being traced. + transformTemplate.transform("kotlin.coroutines.jvm.internal.BaseContinuationImpl", ContinuationTransform.class); + } + + private void addCombinedContextTransformer() { + transformTemplate.transform("kotlin.coroutines.CombinedContext", CombinedContextTransform.class); } private void addCoroutineDispatcherTransformer() { @@ -88,38 +120,19 @@ private void addCoroutineDispatcherTransformer() { transformTemplate.transform(dispatcherMatcher, CoroutineDispatcherTransform.class); } - private void propagateAsyncContextTransformer() { - // For adding AsyncContextAccessor - // > 1.4.0 - transformTemplate.transform("kotlinx.coroutines.internal.DispatchedContinuation", - DispatchedContinuationTransform.class); - // < 1.4.0 - transformTemplate.transform("kotlinx.coroutines.DispatchedContinuation", - DispatchedContinuationTransform.class); - - - // For adding AsyncContextAccessor to CancellableContinuation and propagation AsyncContext from DispatchedTask - transformTemplate.transform("kotlinx.coroutines.CancellableContinuationImpl", - CancellableContinuationTransform.class); + private void addJobCancelTransformer() { + transformTemplate.transform("kotlinx.coroutines.JobSupport", JobCancelTransform.class); } - private void addExecuteTaskTransformer() { - // If below tracing makes problems, you could consider tracing the executeTask of CoroutineScheduler$Worker. - transformTemplate.transform("kotlinx.coroutines.scheduling.CoroutineScheduler", WorkerTransform.class); - } - - public static class CoroutineDispatcherTransform implements TransformCallback { + public static class ContinuationTransform implements TransformCallback { @Override public byte[] doInTransform(Instrumentor instrumentor, ClassLoader classLoader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException { - final CoroutinesConfig config = new CoroutinesConfig(instrumentor.getProfilerConfig()); - - InstrumentClass target = instrumentor.getInstrumentClass(classLoader, className, classfileBuffer); - InstrumentMethod dispatchMethod = target.getDeclaredMethod("dispatch", "kotlin.coroutines.CoroutineContext", "java.lang.Runnable"); - if (dispatchMethod != null) { - dispatchMethod.addScopedInterceptor(DispatchInterceptor.class, va(config), CoroutinesConstants.SCOPE); + List resumeWith = target.getDeclaredMethods(MethodFilters.name("resumeWith")); + for (InstrumentMethod instrumentMethod : resumeWith) { + instrumentMethod.addInterceptor(ResumeWithInterceptor.class, VarArgs.va(CoroutinesConstants.SERVICE_TYPE)); } return target.toBytecode(); @@ -127,7 +140,7 @@ public byte[] doInTransform(Instrumentor instrumentor, ClassLoader classLoader, } - public static class DispatchedContinuationTransform implements TransformCallback { + public static class CombinedContextTransform implements TransformCallback { @Override public byte[] doInTransform(Instrumentor instrumentor, ClassLoader classLoader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException { @@ -139,41 +152,50 @@ public byte[] doInTransform(Instrumentor instrumentor, ClassLoader classLoader, } - public static class WorkerTransform implements TransformCallback { + public static class CoroutineDispatcherTransform implements TransformCallback { @Override public byte[] doInTransform(Instrumentor instrumentor, ClassLoader classLoader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException { InstrumentClass target = instrumentor.getInstrumentClass(classLoader, className, classfileBuffer); - InstrumentMethod runSafelyMethod = target.getDeclaredMethod("runSafely", "kotlinx.coroutines.scheduling.Task"); - if (runSafelyMethod != null) { - runSafelyMethod.addInterceptor(ExecuteTaskInterceptor.class); + + List dispatch = target.getDeclaredMethods(MethodFilters.name("dispatch")); + for (InstrumentMethod instrumentMethod : dispatch) { + instrumentMethod.addInterceptor(DispatchInterceptor.class, VarArgs.va(CoroutinesConstants.SERVICE_TYPE)); + } + + List scheduleResumeAfterDelay = target.getDeclaredMethods(MethodFilters.name("scheduleResumeAfterDelay")); + for (InstrumentMethod instrumentMethod : scheduleResumeAfterDelay) { + instrumentMethod.addInterceptor(ScheduleResumeInterceptor.class, VarArgs.va(CoroutinesConstants.SERVICE_TYPE)); } return target.toBytecode(); } } - public static class CancellableContinuationTransform implements TransformCallback { + public static class JobCancelTransform implements TransformCallback { + @Override public byte[] doInTransform(Instrumentor instrumentor, ClassLoader classLoader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException { InstrumentClass target = instrumentor.getInstrumentClass(classLoader, className, classfileBuffer); - target.addField(AsyncContextAccessor.class); - List declaredConstructors = target.getDeclaredConstructors(); - for (InstrumentMethod declaredConstructor : declaredConstructors) { - String[] parameterTypes = declaredConstructor.getParameterTypes(); - if (ArrayUtils.hasLength(parameterTypes) && "kotlin.coroutines.Continuation".equals(parameterTypes[0])) { - declaredConstructor.addInterceptor(CopyAsyncContextInterceptor.class); - } + List childCancelled = target.getDeclaredMethods(MethodFilters.name("childCancelled")); + for (InstrumentMethod instrumentMethod : childCancelled) { + instrumentMethod.addInterceptor(CancelledInterceptor.class, VarArgs.va(CoroutinesConstants.SERVICE_TYPE)); + } + + List parentCancelled = target.getDeclaredMethods(MethodFilters.name("parentCancelled")); + for (InstrumentMethod instrumentMethod : parentCancelled) { + instrumentMethod.addInterceptor(CancelledInterceptor.class, VarArgs.va(CoroutinesConstants.SERVICE_TYPE)); + } + + List notifyCancelling = target.getDeclaredMethods(MethodFilters.name("notifyCancelling")); + for (InstrumentMethod instrumentMethod : notifyCancelling) { + instrumentMethod.addInterceptor(NotifyCancellingInterceptor.class, VarArgs.va(CoroutinesConstants.SERVICE_TYPE)); } return target.toBytecode(); } - } - @Override - public void setTransformTemplate(MatchableTransformTemplate transformTemplate) { - this.transformTemplate = transformTemplate; } } diff --git a/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/interceptor/CancelledInterceptor.java b/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/interceptor/CancelledInterceptor.java new file mode 100644 index 000000000000..da1f8a75b6a3 --- /dev/null +++ b/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/interceptor/CancelledInterceptor.java @@ -0,0 +1,89 @@ +/* + * Copyright 2022 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.navercorp.pinpoint.plugin.kotlinx.coroutines.interceptor; + +import com.navercorp.pinpoint.bootstrap.context.MethodDescriptor; +import com.navercorp.pinpoint.bootstrap.context.SpanEventRecorder; +import com.navercorp.pinpoint.bootstrap.context.Trace; +import com.navercorp.pinpoint.bootstrap.context.TraceContext; +import com.navercorp.pinpoint.bootstrap.interceptor.AroundInterceptor; +import com.navercorp.pinpoint.bootstrap.interceptor.BasicMethodInterceptor; +import com.navercorp.pinpoint.bootstrap.logging.PLogger; +import com.navercorp.pinpoint.bootstrap.logging.PLoggerFactory; +import com.navercorp.pinpoint.common.trace.ServiceType; +import com.navercorp.pinpoint.common.util.ArrayUtils; + +public class CancelledInterceptor implements AroundInterceptor { + + private final PLogger logger = PLoggerFactory.getLogger(BasicMethodInterceptor.class); + private final boolean isDebug = logger.isDebugEnabled(); + + private final MethodDescriptor descriptor; + private final TraceContext traceContext; + private final ServiceType serviceType; + + public CancelledInterceptor(TraceContext traceContext, MethodDescriptor descriptor, ServiceType serviceType) { + this.descriptor = descriptor; + this.traceContext = traceContext; + this.serviceType = serviceType; + } + + public CancelledInterceptor(TraceContext traceContext, MethodDescriptor descriptor) { + this(traceContext, descriptor, ServiceType.INTERNAL_METHOD); + } + + @Override + public void before(Object target, Object[] args) { + if (isDebug) { + logger.beforeInterceptor(target, args); + } + + Trace trace = traceContext.currentTraceObject(); + if (trace == null) { + return; + } + + final SpanEventRecorder recorder = trace.traceBlockBegin(); + recorder.recordServiceType(serviceType); + } + + @Override + public void after(Object target, Object[] args, Object result, Throwable throwable) { + if (isDebug) { + logger.afterInterceptor(target, args); + } + + Trace trace = traceContext.currentTraceObject(); + if (trace == null) { + return; + } + + try { + final SpanEventRecorder recorder = trace.currentSpanEventRecorder(); + recorder.recordApi(descriptor); + + if (ArrayUtils.getLength(args) == 1) { + Object expectedThrowable = args[0]; + if (expectedThrowable instanceof Throwable) { + recorder.recordException((Throwable) expectedThrowable); + } + } + } finally { + trace.traceBlockEnd(); + } + } +} \ No newline at end of file diff --git a/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/interceptor/CopyAsyncContextInterceptor.java b/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/interceptor/CopyAsyncContextInterceptor.java deleted file mode 100644 index f3b40124f8fc..000000000000 --- a/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/interceptor/CopyAsyncContextInterceptor.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2021 NAVER Corp. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.navercorp.pinpoint.plugin.kotlinx.coroutines.interceptor; - -import com.navercorp.pinpoint.bootstrap.async.AsyncContextAccessor; -import com.navercorp.pinpoint.bootstrap.context.AsyncContext; -import com.navercorp.pinpoint.bootstrap.interceptor.AroundInterceptor; -import com.navercorp.pinpoint.bootstrap.logging.PLogger; -import com.navercorp.pinpoint.bootstrap.logging.PLoggerFactory; -import com.navercorp.pinpoint.common.util.ArrayUtils; - -/** - * @author Taejin Koo - */ -public class CopyAsyncContextInterceptor implements AroundInterceptor { - - private final PLogger logger = PLoggerFactory.getLogger(CopyAsyncContextInterceptor.class); - private final boolean isDebug = logger.isDebugEnabled(); - - @Override - public void before(Object target, Object[] args) { - if (isDebug) { - logger.beforeInterceptor(target, args); - } - } - - @Override - public void after(Object target, Object[] args, Object result, Throwable throwable) { - if (isDebug) { - logger.afterInterceptor(target, args, result, throwable); - } - - if (ArrayUtils.isEmpty(args)) { - return; - } - - AsyncContext originalAsyncContext = getDelegateAsyncContext(args[0]); - if (originalAsyncContext == null) { - return; - } - - setAsyncContext(target, originalAsyncContext); - } - - private AsyncContext getDelegateAsyncContext(Object asyncContextAccessor) { - if (asyncContextAccessor instanceof AsyncContextAccessor) { - return ((AsyncContextAccessor) asyncContextAccessor)._$PINPOINT$_getAsyncContext(); - } - return null; - } - - private void setAsyncContext(Object target, AsyncContext asyncContext) { - if (target instanceof AsyncContextAccessor) { - AsyncContext hasValue = ((AsyncContextAccessor) target)._$PINPOINT$_getAsyncContext(); - if (hasValue == null) { - ((AsyncContextAccessor) target)._$PINPOINT$_setAsyncContext(asyncContext); - } else { - logger.warn("Target already has AsyncContext."); - } - } - } - - -} diff --git a/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/interceptor/DispatchInterceptor.java b/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/interceptor/DispatchInterceptor.java index 8ebf0f043f1c..a58f379ec814 100644 --- a/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/interceptor/DispatchInterceptor.java +++ b/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/interceptor/DispatchInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 NAVER Corp. + * Copyright 2022 NAVER Corp. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,17 +25,10 @@ import com.navercorp.pinpoint.bootstrap.interceptor.AroundInterceptor; import com.navercorp.pinpoint.bootstrap.logging.PLogger; import com.navercorp.pinpoint.bootstrap.logging.PLoggerFactory; +import com.navercorp.pinpoint.common.trace.ServiceType; import com.navercorp.pinpoint.common.util.ArrayUtils; -import com.navercorp.pinpoint.plugin.kotlinx.coroutines.CoroutinesConfig; -import com.navercorp.pinpoint.plugin.kotlinx.coroutines.CoroutinesConstants; - import kotlin.coroutines.Continuation; -import kotlin.coroutines.CoroutineContext; import kotlinx.coroutines.CancellableContinuation; -import kotlinx.coroutines.CoroutineName; - -import java.util.List; -import java.util.Objects; /** * @author Taejin Koo @@ -45,134 +38,81 @@ public class DispatchInterceptor implements AroundInterceptor { private final PLogger logger = PLoggerFactory.getLogger(this.getClass()); private final boolean isDebug = logger.isDebugEnabled(); - private final TraceContext traceContext; private final MethodDescriptor descriptor; - private final List includedNameList; + private final TraceContext traceContext; + private final ServiceType serviceType; - public DispatchInterceptor(TraceContext traceContext, MethodDescriptor descriptor, CoroutinesConfig config) { - this.traceContext = Objects.requireNonNull(traceContext, "traceContext"); - this.descriptor = Objects.requireNonNull(descriptor, "descriptor"); + public DispatchInterceptor(TraceContext traceContext, MethodDescriptor descriptor, ServiceType serviceType) { + this.descriptor = descriptor; + this.traceContext = traceContext; + this.serviceType = serviceType; + } - Objects.requireNonNull(config, "config"); - this.includedNameList = config.getIncludedNameList(); + public DispatchInterceptor(TraceContext traceContext, MethodDescriptor descriptor) { + this(traceContext, descriptor, ServiceType.INTERNAL_METHOD); } @Override public void before(Object target, Object[] args) { if (isDebug) { - logger.beforeInterceptor(target, args); - } - - final Continuation continuation = getContinuation(args); - if (continuation == null) { - return; - } - - if (isCompletedContinuation(continuation)) { - return; + logger.beforeInterceptor(target, descriptor.getClassName(), descriptor.getMethodName(), descriptor.getParameterDescriptor(), args); } - if (!checkSupportCoroutinesName(continuation)) { + Trace trace = traceContext.currentTraceObject(); + if (trace == null) { return; } - final Trace trace = traceContext.currentTraceObject(); - if (trace == null) { + if (isCompletedContinuation(args)) { return; } final SpanEventRecorder recorder = trace.traceBlockBegin(); + recorder.recordServiceType(serviceType); - final AsyncContextAccessor asyncContextAccessor = getAsyncContextAccessor(args); - if (asyncContextAccessor != null) { - // make asynchronous trace-id - final AsyncContext asyncContext = recorder.recordNextAsyncContext(); - asyncContextAccessor._$PINPOINT$_setAsyncContext(asyncContext); + if (ArrayUtils.hasLength(args)) { + if (args[0] instanceof AsyncContextAccessor) { + AsyncContextAccessor accessor = (AsyncContextAccessor) args[0]; + final AsyncContext asyncContext = recorder.recordNextAsyncContext(); + accessor._$PINPOINT$_setAsyncContext(asyncContext); + } } } - private Continuation getContinuation(final Object[] args) { + private boolean isCompletedContinuation(final Object[] args) { if (ArrayUtils.getLength(args) == 2 && args[1] instanceof Continuation) { - return (Continuation) args[1]; - } - return null; - } - - private boolean isCompletedContinuation(final Continuation continuation) { - if (continuation instanceof CancellableContinuation) { - return ((CancellableContinuation) continuation).isCompleted(); - } - return false; - } - - private boolean checkSupportCoroutinesName(final Continuation continuation) { - final CoroutineContext.Key key = CoroutineName.Key; - - final CoroutineContext context = continuation.getContext(); - if (context != null) { - Object element = context.get(key); - if (element instanceof CoroutineName) { - String name = ((CoroutineName) element).getName(); - if (name != null) { - return includedNameList.contains(name); - } + Continuation continuation = (Continuation) args[1]; + if (continuation instanceof CancellableContinuation) { + return ((CancellableContinuation) continuation).isCompleted(); } } - return false; } - private AsyncContextAccessor getAsyncContextAccessor(final Object[] args) { - if (ArrayUtils.getLength(args) != 2) { - if (isDebug) { - logger.debug("Invalid args object. args={}.", args); - } - return null; - } - - if (args[1] instanceof AsyncContextAccessor) { - return (AsyncContextAccessor) args[1]; - } - - if (isDebug) { - logger.debug("Invalid args[1] object. Need metadata accessor({}).", AsyncContextAccessor.class.getName()); - } - - return null; - } - @Override public void after(Object target, Object[] args, Object result, Throwable throwable) { if (isDebug) { - logger.afterInterceptor(target, args, result, throwable); - } - - final Continuation continuation = getContinuation(args); - if (continuation == null) { - return; - } - - if (isCompletedContinuation(continuation)) { - return; + logger.afterInterceptor(target, descriptor.getClassName(), descriptor.getMethodName(), descriptor.getParameterDescriptor(), args, result, throwable); } - if (!checkSupportCoroutinesName(continuation)) { + Trace trace = traceContext.currentTraceObject(); + if (trace == null) { return; } - final Trace trace = traceContext.currentTraceObject(); - if (trace == null) { + if (isCompletedContinuation(args)) { return; } try { final SpanEventRecorder recorder = trace.currentSpanEventRecorder(); recorder.recordApi(descriptor); - recorder.recordServiceType(CoroutinesConstants.SERVICE_TYPE); recorder.recordException(throwable); + recorder.recordServiceType(serviceType); } finally { trace.traceBlockEnd(); } + } } diff --git a/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/interceptor/ExecuteTaskInterceptor.java b/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/interceptor/ExecuteTaskInterceptor.java deleted file mode 100644 index f626c83b0475..000000000000 --- a/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/interceptor/ExecuteTaskInterceptor.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2021 NAVER Corp. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.navercorp.pinpoint.plugin.kotlinx.coroutines.interceptor; - -import com.navercorp.pinpoint.bootstrap.async.AsyncContextAccessor; -import com.navercorp.pinpoint.bootstrap.context.AsyncContext; -import com.navercorp.pinpoint.bootstrap.context.MethodDescriptor; -import com.navercorp.pinpoint.bootstrap.context.SpanEventRecorder; -import com.navercorp.pinpoint.bootstrap.context.TraceContext; -import com.navercorp.pinpoint.bootstrap.interceptor.AsyncContextSpanEventSimpleAroundInterceptor; -import com.navercorp.pinpoint.common.util.ArrayUtils; -import com.navercorp.pinpoint.plugin.kotlinx.coroutines.CoroutinesConstants; - -/** - * @author Taejin Koo - */ -public class ExecuteTaskInterceptor extends AsyncContextSpanEventSimpleAroundInterceptor { - - public ExecuteTaskInterceptor(TraceContext traceContext, MethodDescriptor methodDescriptor) { - super(traceContext, methodDescriptor); - } - - @Override - protected void doInBeforeTrace(SpanEventRecorder recorder, AsyncContext asyncContext, Object target, Object[] args) { - } - - @Override - protected AsyncContext getAsyncContext(Object target, Object[] args) { - if (ArrayUtils.getLength(args) != 1) { - return null; - } - - Object asyncContextAccessor = args[0]; - if (asyncContextAccessor instanceof AsyncContextAccessor) { - AsyncContext asyncContext = ((AsyncContextAccessor) asyncContextAccessor)._$PINPOINT$_getAsyncContext(); - return asyncContext; - } - - return null; - } - - @Override - protected AsyncContext getAsyncContext(Object target, Object[] args, Object result, Throwable throwable) { - return getAsyncContext(target, args); - } - - @Override - protected void doInAfterTrace(SpanEventRecorder recorder, Object target, Object[] args, Object result, Throwable throwable) { - recorder.recordApi(methodDescriptor); - recorder.recordServiceType(CoroutinesConstants.SERVICE_TYPE); - recorder.recordException(throwable); - } - -} diff --git a/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/interceptor/NotifyCancellingInterceptor.java b/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/interceptor/NotifyCancellingInterceptor.java new file mode 100644 index 000000000000..1f48490d9446 --- /dev/null +++ b/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/interceptor/NotifyCancellingInterceptor.java @@ -0,0 +1,89 @@ +/* + * Copyright 2022 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.navercorp.pinpoint.plugin.kotlinx.coroutines.interceptor; + +import com.navercorp.pinpoint.bootstrap.context.MethodDescriptor; +import com.navercorp.pinpoint.bootstrap.context.SpanEventRecorder; +import com.navercorp.pinpoint.bootstrap.context.Trace; +import com.navercorp.pinpoint.bootstrap.context.TraceContext; +import com.navercorp.pinpoint.bootstrap.interceptor.AroundInterceptor; +import com.navercorp.pinpoint.bootstrap.interceptor.BasicMethodInterceptor; +import com.navercorp.pinpoint.bootstrap.logging.PLogger; +import com.navercorp.pinpoint.bootstrap.logging.PLoggerFactory; +import com.navercorp.pinpoint.common.trace.ServiceType; +import com.navercorp.pinpoint.common.util.ArrayUtils; + +public class NotifyCancellingInterceptor implements AroundInterceptor { + + private final PLogger logger = PLoggerFactory.getLogger(BasicMethodInterceptor.class); + private final boolean isDebug = logger.isDebugEnabled(); + + private final MethodDescriptor descriptor; + private final TraceContext traceContext; + private final ServiceType serviceType; + + public NotifyCancellingInterceptor(TraceContext traceContext, MethodDescriptor descriptor, ServiceType serviceType) { + this.descriptor = descriptor; + this.traceContext = traceContext; + this.serviceType = serviceType; + } + + public NotifyCancellingInterceptor(TraceContext traceContext, MethodDescriptor descriptor) { + this(traceContext, descriptor, ServiceType.INTERNAL_METHOD); + } + + @Override + public void before(Object target, Object[] args) { + if (isDebug) { + logger.beforeInterceptor(target, args); + } + + Trace trace = traceContext.currentTraceObject(); + if (trace == null) { + return; + } + + final SpanEventRecorder recorder = trace.traceBlockBegin(); + recorder.recordServiceType(serviceType); + } + + @Override + public void after(Object target, Object[] args, Object result, Throwable throwable) { + if (isDebug) { + logger.afterInterceptor(target, args); + } + + Trace trace = traceContext.currentTraceObject(); + if (trace == null) { + return; + } + + try { + final SpanEventRecorder recorder = trace.currentSpanEventRecorder(); + recorder.recordApi(descriptor); + + if (ArrayUtils.getLength(args) == 2) { + Object expectedThrowable = args[1]; + if (expectedThrowable instanceof Throwable) { + recorder.recordException((Throwable) expectedThrowable); + } + } + } finally { + trace.traceBlockEnd(); + } + } +} \ No newline at end of file diff --git a/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/interceptor/ResumeWithInterceptor.java b/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/interceptor/ResumeWithInterceptor.java new file mode 100644 index 000000000000..f8a27856b9a5 --- /dev/null +++ b/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/interceptor/ResumeWithInterceptor.java @@ -0,0 +1,97 @@ +/* + * Copyright 2022 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.navercorp.pinpoint.plugin.kotlinx.coroutines.interceptor; + +import com.navercorp.pinpoint.bootstrap.async.AsyncContextAccessor; +import com.navercorp.pinpoint.bootstrap.config.ProfilerConfig; +import com.navercorp.pinpoint.bootstrap.context.AsyncContext; +import com.navercorp.pinpoint.bootstrap.context.MethodDescriptor; +import com.navercorp.pinpoint.bootstrap.context.SpanEventRecorder; +import com.navercorp.pinpoint.bootstrap.context.TraceContext; +import com.navercorp.pinpoint.bootstrap.interceptor.AsyncContextSpanEventSimpleAroundInterceptor; +import com.navercorp.pinpoint.common.trace.ServiceType; +import com.navercorp.pinpoint.plugin.kotlinx.coroutines.CoroutinesConfig; +import com.navercorp.pinpoint.plugin.kotlinx.coroutines.CoroutinesConstants; +import kotlin.coroutines.Continuation; +import kotlin.coroutines.CoroutineContext; + +/** + * @author Taejin Koo + */ +public class ResumeWithInterceptor extends AsyncContextSpanEventSimpleAroundInterceptor { + + private final ServiceType serviceType; + + private final boolean recordThreadName; + + public ResumeWithInterceptor(TraceContext traceContext, MethodDescriptor descriptor, ServiceType serviceType) { + super(traceContext, descriptor); + this.serviceType = serviceType; + + ProfilerConfig profilerConfig = traceContext.getProfilerConfig(); + this.recordThreadName = new CoroutinesConfig(profilerConfig).isRecordThreadName(); + } + + public ResumeWithInterceptor(TraceContext traceContext, MethodDescriptor methodDescriptor) { + this(traceContext, methodDescriptor, ServiceType.INTERNAL_METHOD); + } + + @Override + protected AsyncContext getAsyncContext(Object target, Object[] args, Object result, Throwable throwable) { + return getAsyncContext(target); + } + + @Override + protected AsyncContext getAsyncContext(Object target, Object[] args) { + return getAsyncContext(target); + } + + private AsyncContext getAsyncContext(Object object) { + if (object instanceof Continuation) { + CoroutineContext context = ((Continuation) object).getContext(); + if (context instanceof AsyncContextAccessor) { + AsyncContextAccessor accessor = (AsyncContextAccessor) context; + AsyncContext asyncContext = accessor._$PINPOINT$_getAsyncContext(); + + return asyncContext; + } + } + return null; + } + + + @Override + protected void doInBeforeTrace(SpanEventRecorder recorder, AsyncContext asyncContext, Object target, Object[] args) { + } + + @Override + protected void doInAfterTrace(SpanEventRecorder recorder, Object target, Object[] args, Object result, Throwable throwable) { + recorder.recordApi(methodDescriptor); + recorder.recordServiceType(serviceType); + recorder.recordException(throwable); + + if (recordThreadName) { + Thread currentThread = Thread.currentThread(); + if (currentThread != null) { + String threadName = currentThread.getName(); + if (threadName != null) { + recorder.recordAttribute(CoroutinesConstants.COROUTINE_THREAD_NAME_ANNOTATION_KEY, threadName); + } + } + } + } +} diff --git a/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/interceptor/ScheduleResumeInterceptor.java b/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/interceptor/ScheduleResumeInterceptor.java new file mode 100644 index 000000000000..0fb5c7cc5dfc --- /dev/null +++ b/plugins/kotlin-coroutines/src/main/java/com/navercorp/pinpoint/plugin/kotlinx/coroutines/interceptor/ScheduleResumeInterceptor.java @@ -0,0 +1,123 @@ +/* + * Copyright 2022 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.navercorp.pinpoint.plugin.kotlinx.coroutines.interceptor; + +import com.navercorp.pinpoint.bootstrap.async.AsyncContextAccessor; +import com.navercorp.pinpoint.bootstrap.context.AsyncContext; +import com.navercorp.pinpoint.bootstrap.context.MethodDescriptor; +import com.navercorp.pinpoint.bootstrap.context.SpanEventRecorder; +import com.navercorp.pinpoint.bootstrap.context.Trace; +import com.navercorp.pinpoint.bootstrap.context.TraceContext; +import com.navercorp.pinpoint.bootstrap.interceptor.AroundInterceptor; +import com.navercorp.pinpoint.bootstrap.logging.PLogger; +import com.navercorp.pinpoint.bootstrap.logging.PLoggerFactory; +import com.navercorp.pinpoint.common.trace.ServiceType; +import com.navercorp.pinpoint.common.util.ArrayUtils; +import kotlin.coroutines.Continuation; + +/** + * @author Taejin Koo + */ +public class ScheduleResumeInterceptor implements AroundInterceptor { + + private final PLogger logger = PLoggerFactory.getLogger(this.getClass()); + private final boolean isDebug = logger.isDebugEnabled(); + + private final TraceContext traceContext; + private final MethodDescriptor descriptor; + private final ServiceType serviceType; + + public ScheduleResumeInterceptor(TraceContext traceContext, MethodDescriptor descriptor, ServiceType serviceType) { + this.descriptor = descriptor; + this.traceContext = traceContext; + this.serviceType = serviceType; + } + + public ScheduleResumeInterceptor(TraceContext traceContext, MethodDescriptor descriptor) { + this(traceContext, descriptor, ServiceType.INTERNAL_METHOD); + } + + @Override + public void before(Object target, Object[] args) { + if (isDebug) { + logger.beforeInterceptor(target, descriptor.getClassName(), descriptor.getMethodName(), descriptor.getParameterDescriptor(), args); + } + + final Continuation continuation = getContinuation(args); + if (continuation == null) { + return; + } + + final Trace trace = traceContext.currentTraceObject(); + if (trace == null) { + return; + } + + final SpanEventRecorder recorder = trace.traceBlockBegin(); + final AsyncContextAccessor asyncContextAccessor = getAsyncContextAccessor(continuation); + if (asyncContextAccessor != null) { + final AsyncContext asyncContext = recorder.recordNextAsyncContext(); + asyncContextAccessor._$PINPOINT$_setAsyncContext(asyncContext); + } + } + + private Continuation getContinuation(final Object[] args) { + if (ArrayUtils.getLength(args) == 2 && args[1] instanceof Continuation) { + return (Continuation) args[1]; + } + return null; + } + + private AsyncContextAccessor getAsyncContextAccessor(final Continuation continuation) { + if (continuation == null) { + return null; + } + + Object object = continuation.getContext(); + if (object instanceof AsyncContextAccessor) { + return (AsyncContextAccessor) object; + } + return null; + } + + @Override + public void after(Object target, Object[] args, Object result, Throwable throwable) { + if (isDebug) { + logger.afterInterceptor(target, descriptor.getClassName(), descriptor.getMethodName(), descriptor.getParameterDescriptor(), args, result, throwable); + } + + final Continuation continuation = getContinuation(args); + if (continuation == null) { + return; + } + + final Trace trace = traceContext.currentTraceObject(); + if (trace == null) { + return; + } + + try { + final SpanEventRecorder recorder = trace.currentSpanEventRecorder(); + recorder.recordApi(descriptor); + recorder.recordServiceType(serviceType); + recorder.recordException(throwable); + } finally { + trace.traceBlockEnd(); + } + } + +} \ No newline at end of file