[kotlin] Optimize coroutine panel loading time

(cherry picked from commit 29a9c965fef723c6c283662eab1542cabe5d32cb)

IJ-MR-13882

GitOrigin-RevId: ad3ce16c002f0922959d0a27c23ee4e3c2e2ce27
This commit is contained in:
Nikita Nazarov
2021-09-06 22:41:46 +03:00
committed by intellij-monorepo-bot
parent d2e08cb3b4
commit daf8b99c0d
8 changed files with 225 additions and 82 deletions

View File

@@ -27,5 +27,6 @@
<orderEntry type="module" module-name="intellij.platform.core.ui" />
<orderEntry type="module" module-name="intellij.platform.ide.util.io" />
<orderEntry type="module" module-name="intellij.platform.smRunner" />
<orderEntry type="library" name="gson" level="project" />
</component>
</module>

View File

@@ -0,0 +1,127 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.kotlin.idea.debugger.coroutine
import com.google.gson.Gson
import com.sun.jdi.ArrayReference
import com.sun.jdi.ObjectReference
import com.sun.jdi.StringReference
import com.sun.jdi.ThreadReference
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineStackTraceProvider
import org.jetbrains.kotlin.idea.debugger.coroutine.data.LazyCoroutineInfoData
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.CoroutineInfoProvider
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.DebugProbesImpl
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.MirrorOfCoroutineContext
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
class CoroutinesInfoFromJsonAndReferencesProvider(
private val executionContext: DefaultExecutionContext,
private val debugProbesImpl: DebugProbesImpl
) : CoroutineInfoProvider {
private val stackTraceProvider = CoroutineStackTraceProvider(executionContext)
override fun dumpCoroutinesInfo(): List<CoroutineInfoData> {
val array = debugProbesImpl.dumpCoroutinesInfoAsJsonAndReferences(executionContext)
?: return emptyList()
if (array.length() != 4) {
error("The result array of 'dumpCoroutinesInfoAsJSONAndReferences' should be of size 4")
}
val coroutinesInfoAsJsonString = array.getValue(0).safeAs<StringReference>()?.value()
?: error("The first element of the result array must be a string")
val lastObservedThreadRefs = array.getValue(1).safeAs<ArrayReference>()?.toTypedList<ThreadReference?>()
?: error("The second element of the result array must be an array")
val lastObservedFrameRefs = array.getValue(2).safeAs<ArrayReference>()?.toTypedList<ObjectReference>()
?: error("The third element of the result array must be an array")
val coroutineInfoRefs = array.getValue(3).safeAs<ArrayReference>()?.toTypedList<ObjectReference>()
?: error("The fourth element of the result array must be an array")
val coroutinesInfo = Gson().fromJson(coroutinesInfoAsJsonString, Array<CoroutineInfoPack>::class.java)
if (coroutineInfoRefs.size != lastObservedFrameRefs.size ||
lastObservedFrameRefs.size != coroutinesInfo.size ||
coroutinesInfo.size != lastObservedThreadRefs.size) {
error("Arrays must have equal sizes")
}
return calculateCoroutineInfoData(coroutinesInfo, coroutineInfoRefs, lastObservedThreadRefs, lastObservedFrameRefs)
}
private fun calculateCoroutineInfoData(
coroutineInfos: Array<CoroutineInfoPack>,
coroutineInfoRefs: List<ObjectReference>,
lastObservedThreadRefs: List<ThreadReference?>,
lastObservedFrameRefs: List<ObjectReference>
): List<CoroutineInfoData> {
val result = mutableListOf<LazyCoroutineInfoData>()
for ((i, info) in coroutineInfos.withIndex()) {
result.add(
getLazyCoroutineInfoData(
info,
coroutineInfoRefs[i],
lastObservedThreadRefs[i],
lastObservedFrameRefs[i],
stackTraceProvider
)
)
}
return result
}
private fun getLazyCoroutineInfoData(
info: CoroutineInfoPack,
coroutineInfosRef: ObjectReference,
lastObservedThreadRef: ThreadReference?,
lastObservedFrameRef: ObjectReference?,
stackTraceProvider: CoroutineStackTraceProvider
): LazyCoroutineInfoData {
val coroutineContextMirror = MirrorOfCoroutineContext(
info.name,
info.id,
info.dispatcher
)
val coroutineInfoMirror = debugProbesImpl.getCoroutineInfo(
coroutineInfosRef,
executionContext,
coroutineContextMirror,
info.sequenceNumber,
info.state,
lastObservedThreadRef,
lastObservedFrameRef
)
return LazyCoroutineInfoData(coroutineInfoMirror, stackTraceProvider)
}
private data class CoroutineInfoPack(
val name: String?,
val id: Long?,
val dispatcher: String?,
val sequenceNumber: Long?,
val state: String?
)
companion object {
fun instance(executionContext: DefaultExecutionContext, debugProbesImpl: DebugProbesImpl): CoroutinesInfoFromJsonAndReferencesProvider? {
if (debugProbesImpl.canDumpCoroutinesInfoAsJsonAndReferences()) {
return CoroutinesInfoFromJsonAndReferencesProvider(executionContext, debugProbesImpl)
}
return null
}
val log by logger
}
}
private inline fun <reified T> ArrayReference.toTypedList(): List<T> {
val result = mutableListOf<T>()
for (value in values) {
if (value !is T) {
error("Value has type ${value::class.java}, but ${T::class.java} was expected")
}
result.add(value)
}
return result
}

View File

@@ -30,10 +30,9 @@ abstract class CoroutineInfoData(val descriptor: CoroutineDescriptor) {
}
class LazyCoroutineInfoData(
descriptor: CoroutineDescriptor,
private val mirror: MirrorOfCoroutineInfo,
private val stackTraceProvider: CoroutineStackTraceProvider
) : CoroutineInfoData(descriptor) {
) : CoroutineInfoData(CoroutineDescriptor.instance(mirror)) {
val stackFrames: CoroutineStackTraceProvider.CoroutineStackFrames? by lazy {
stackTraceProvider.findStackFrames(mirror)
}

View File

@@ -4,14 +4,14 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.proxy
import com.intellij.debugger.engine.DebuggerManagerThreadImpl
import com.intellij.debugger.engine.SuspendContextImpl
import com.intellij.openapi.util.registry.Registry
import org.jetbrains.kotlin.idea.debugger.coroutine.CoroutinesInfoFromJsonAndReferencesProvider
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoCache
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.DebugProbesImpl
import org.jetbrains.kotlin.idea.debugger.coroutine.util.executionContext
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
class CoroutineDebugProbesProxy(val suspendContext: SuspendContextImpl) {
private val log by logger
/**
* Invokes DebugProbes from debugged process's classpath and returns states of coroutines
* Should be invoked on debugger manager thread
@@ -33,12 +33,20 @@ class CoroutineDebugProbesProxy(val suspendContext: SuspendContextImpl) {
}
private fun findProvider(executionContext: DefaultExecutionContext): CoroutineInfoProvider? {
val agentProxy = CoroutineLibraryAgent2Proxy.instance(executionContext)
if (agentProxy != null)
return agentProxy
if (standaloneCoroutineDebuggerEnabled())
return CoroutineNoLibraryProxy(executionContext)
return null
val debugProbesImpl = DebugProbesImpl.instance(executionContext)
return when {
debugProbesImpl != null && debugProbesImpl.isInstalled ->
CoroutinesInfoFromJsonAndReferencesProvider.instance(executionContext, debugProbesImpl) ?:
CoroutineLibraryAgent2Proxy(executionContext, debugProbesImpl)
standaloneCoroutineDebuggerEnabled() ->
CoroutineNoLibraryProxy(executionContext)
else ->
null
}
}
companion object {
private val log by logger
}
}

View File

@@ -2,47 +2,25 @@
package org.jetbrains.kotlin.idea.debugger.coroutine.proxy
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineDescriptor
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData
import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineStackTraceProvider
import org.jetbrains.kotlin.idea.debugger.coroutine.data.LazyCoroutineInfoData
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.DebugProbesImpl
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
class CoroutineLibraryAgent2Proxy(private val executionContext: DefaultExecutionContext) : CoroutineInfoProvider {
private val debugProbesImpl = DebugProbesImpl.instance(executionContext)
class CoroutineLibraryAgent2Proxy(
private val executionContext: DefaultExecutionContext,
private val debugProbesImpl: DebugProbesImpl
) : CoroutineInfoProvider {
private val stackTraceProvider = CoroutineStackTraceProvider(executionContext)
override fun dumpCoroutinesInfo(): List<CoroutineInfoData> {
val result = debugProbesImpl?.dumpCoroutinesInfo(executionContext) ?: emptyList()
val result = debugProbesImpl.dumpCoroutinesInfo(executionContext)
return result.map {
LazyCoroutineInfoData(
CoroutineDescriptor.instance(it),
it,
stackTraceProvider
)
}
}
fun isInstalled(): Boolean {
return try {
debugProbesImpl?.isInstalled ?: false
} catch (e: Exception) {
log.error("Exception happened while checking agent status.", e)
false
}
}
companion object {
fun instance(executionContext: DefaultExecutionContext): CoroutineLibraryAgent2Proxy? {
val agentProxy = CoroutineLibraryAgent2Proxy(executionContext)
return if (agentProxy.isInstalled())
agentProxy
else
null
}
val log by logger
}
}

View File

@@ -2,10 +2,7 @@
package org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror
import com.sun.jdi.BooleanValue
import com.sun.jdi.LongValue
import com.sun.jdi.ObjectReference
import com.sun.jdi.ThreadReference
import com.sun.jdi.*
import org.jetbrains.kotlin.idea.debugger.coroutine.util.isSubTypeOrSame
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
import org.jetbrains.kotlin.idea.debugger.evaluate.DefaultExecutionContext
@@ -26,6 +23,7 @@ class DebugProbesImpl private constructor(context: DefaultExecutionContext) :
private val enhanceStackTraceWithThreadDumpMethod by MethodMirrorDelegate("enhanceStackTraceWithThreadDump", javaLangListMirror)
private val dumpMethod by MethodMirrorDelegate("dumpCoroutinesInfo", javaLangListMirror, "()Ljava/util/List;")
private val dumpCoroutinesInfoAsJsonAndReferences by MethodDelegate<ArrayReference>("dumpCoroutinesInfoAsJsonAndReferences", "()[Ljava/lang/Object;")
val isInstalled: Boolean by lazy { isInstalled(context) }
@@ -34,8 +32,8 @@ class DebugProbesImpl private constructor(context: DefaultExecutionContext) :
fun isInstalled(context: DefaultExecutionContext): Boolean =
isInstalledInDebugMethod.value(instance, context)?.booleanValue() ?:
isInstalledInCoreMethod.value(instance, context)?.booleanValue()
?: throw IllegalStateException("isInstalledMethod not found")
isInstalledInCoreMethod.value(instance, context)?.booleanValue() ?:
false
fun enhanceStackTraceWithThreadDump(
context: DefaultExecutionContext,
@@ -54,6 +52,32 @@ class DebugProbesImpl private constructor(context: DefaultExecutionContext) :
return referenceList.values.mapNotNull { coroutineInfo.mirror(it, context) }
}
fun canDumpCoroutinesInfoAsJsonAndReferences() =
dumpCoroutinesInfoAsJsonAndReferences.method != null
fun dumpCoroutinesInfoAsJsonAndReferences(executionContext: DefaultExecutionContext) =
dumpCoroutinesInfoAsJsonAndReferences.value(instance, executionContext)
fun getCoroutineInfo(
value: ObjectReference,
context: DefaultExecutionContext,
coroutineContext: MirrorOfCoroutineContext,
sequenceNumber: Long?,
state: String?,
lastObservedThread: ThreadReference?,
lastObservedFrame: ObjectReference?
): MirrorOfCoroutineInfo {
return coroutineInfo.fetchMirror(
value,
context,
coroutineContext,
sequenceNumber,
state,
lastObservedThread,
lastObservedFrame
)
}
fun getCoroutineInfo(value: ObjectReference?, context: DefaultExecutionContext): MirrorOfCoroutineInfo? {
val coroutineOwner = debugProbesCoroutineOwner.mirror(value, context)
return coroutineOwner?.coroutineInfo
@@ -119,9 +143,8 @@ class DebugCoroutineInfoImpl(context: DefaultExecutionContext) :
}
return MirrorOfCoroutineInfo(
value,
sequenceNumber.value(value)?.longValue(),
coroutineContext,
sequenceNumber.value(value)?.longValue(),
state,
lastObservedThread.value(value),
lastObservedFrame.mirror(value, context)?.reference,
@@ -140,7 +163,7 @@ class CoroutineInfo private constructor(
private val contextFieldRef by FieldMirrorDelegate("context", CoroutineContext(context))
private val sequenceNumberField by FieldDelegate<LongValue>("sequenceNumber")
private val creationStackTraceMethod by MethodMirrorDelegate("getCreationStackTrace", JavaUtilAbstractCollection(context))
private val stateMethod by MethodMirrorDelegate<ObjectReference, String>("getState", JavaLangObjectToString(context))
private val stateMethod by MethodDelegate<StringReference>("getState", "()Ljava/lang/String;")
private val lastObservedStackTraceMethod by MethodDelegate<ObjectReference>("lastObservedStackTrace")
private val lastObservedFrameField by FieldDelegate<ObjectReference>("lastObservedFrame")
@@ -162,32 +185,54 @@ class CoroutineInfo private constructor(
}
}
fun fetchMirror(
value: ObjectReference,
context: DefaultExecutionContext,
coroutineContext: MirrorOfCoroutineContext,
sequenceNumber: Long?,
state: String?,
lastObservedThread: ThreadReference?,
lastObservedFrame: ObjectReference?
): MirrorOfCoroutineInfo {
return MirrorOfCoroutineInfo(
coroutineContext,
sequenceNumber,
state,
lastObservedThread,
lastObservedFrame,
getEnhancedStackTraceProvider(value, context),
getCreationStackTraceProvider(value, context)
)
}
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfCoroutineInfo {
val state = stateMethod.mirror(value, context)
val state = stateMethod.value(value, context)
val coroutineContext = contextFieldRef.mirror(value, context)
val sequenceNumber = sequenceNumberField.value(value)?.longValue()
val enhancedStackTraceProvider = {
return MirrorOfCoroutineInfo(
coroutineContext,
sequenceNumber,
state?.value(),
lastObservedThreadField.value(value),
lastObservedFrameField.value(value),
getEnhancedStackTraceProvider(value, context),
getCreationStackTraceProvider(value, context)
)
}
private fun getCreationStackTraceProvider(value: ObjectReference, context: DefaultExecutionContext) =
StackTraceMirrorProvider {
val creationStackTraceMirror = creationStackTraceMethod.mirror(value, context)
creationStackTraceMirror?.values?.mapNotNull { stackTraceElement.mirror(it, context) }
}
private fun getEnhancedStackTraceProvider(value: ObjectReference, context: DefaultExecutionContext) =
StackTraceMirrorProvider {
val lastObservedStackTrace = lastObservedStackTraceMethod.value(value, context)
if (lastObservedStackTrace != null)
debugProbesImplMirror.enhanceStackTraceWithThreadDump(context, value, lastObservedStackTrace)
else
emptyList()
}
val creationStackTraceProvider = {
val creationStackTraceMirror = creationStackTraceMethod.mirror(value, context)
creationStackTraceMirror?.values?.mapNotNull { stackTraceElement.mirror(it, context) }
}
return MirrorOfCoroutineInfo(
value,
sequenceNumber,
coroutineContext,
state,
lastObservedThreadField.value(value),
lastObservedFrameField.value(value),
enhancedStackTraceProvider,
creationStackTraceProvider
)
}
}

View File

@@ -11,16 +11,14 @@ class CoroutineContext(context: DefaultExecutionContext) :
BaseMirror<ObjectReference, MirrorOfCoroutineContext>("kotlin.coroutines.CombinedContext", context) {
private val coroutineNameRef = CoroutineName(context)
private val coroutineIdRef = CoroutineId(context)
private val jobRef = Job(context)
private val dispatcherRef = CoroutineDispatcher(context)
private val getContextElement by MethodDelegate<ObjectReference>("get")
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfCoroutineContext? {
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): MirrorOfCoroutineContext {
val coroutineName = getElementValue(value, context, coroutineNameRef)
val coroutineId = getElementValue(value, context, coroutineIdRef)
val job = getElementValue(value, context, jobRef)
val dispatcher = getElementValue(value, context, dispatcherRef)
return MirrorOfCoroutineContext(value, coroutineName, coroutineId, dispatcher, job)
return MirrorOfCoroutineContext(coroutineName, coroutineId, dispatcher)
}
private fun <T> getElementValue(value: ObjectReference, context: DefaultExecutionContext, keyProvider: ContextKey<T>): T? {
@@ -56,16 +54,6 @@ class CoroutineId(context: DefaultExecutionContext) : ContextKey<Long>("kotlinx.
override fun key() = key.staticValue()
}
class Job(context: DefaultExecutionContext) : ContextKey<ObjectReference>("kotlinx.coroutines.Job\$Key", context) {
private val key by FieldDelegate<ObjectReference>("\$\$INSTANCE")
override fun fetchMirror(value: ObjectReference, context: DefaultExecutionContext): ObjectReference? {
return value
}
override fun key() = key.staticValue()
}
class CoroutineDispatcher(context: DefaultExecutionContext) : ContextKey<String>("kotlinx.coroutines.CoroutineDispatcher", context) {
private val key by FieldDelegate<ObjectReference>("Key")
private val jlm = JavaLangObjectToString(context)

View File

@@ -11,11 +11,9 @@ data class MirrorOfStandaloneCoroutine(
)
data class MirrorOfCoroutineContext(
val that: ObjectReference,
val name: String?,
val id: Long?,
val dispatcher: String?,
val job: ObjectReference?
)
data class MirrorOfCoroutineOwner(val that: ObjectReference, val coroutineInfo: MirrorOfCoroutineInfo?)
@@ -25,9 +23,8 @@ data class MirrorOfDebugProbesImpl(val that: ObjectReference, val instance: Obje
data class MirrorOfWeakReference(val that: ObjectReference, val reference: ObjectReference?)
data class MirrorOfCoroutineInfo(
val that: ObjectReference,
val sequenceNumber: Long?,
val context: MirrorOfCoroutineContext?,
val sequenceNumber: Long?,
val state: String?,
val lastObservedThread: ThreadReference?,
val lastObservedFrame: ObjectReference?,