mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-16 14:23:28 +07:00
[debugger] Compute last observed stack trace for coroutines in one evaluation with the dump.
* It's faster than computing coroutine stack trace for each coroutine separately. * Coroutine dump will be evaluated together with stack traces even if the vm threads were resumed (by ThreadBlockedMonitor e.g.). * lastObservedStackTrace is not a lazy property of DebugCoroutineInfo, so it's already computed on the library side when the dump is taken. GitOrigin-RevId: 07c41bbe1e2682b431aedb6ffc70ba8331cf1765
This commit is contained in:
committed by
intellij-monorepo-bot
parent
8fcaef61ba
commit
fbd38fbf3b
@@ -23,6 +23,17 @@ public final class JsonUtils {
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
public static String dumpStackTraceElements(List<StackTraceElement> stack) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append("[");
|
||||
for (int i = 0; i < stack.size(); i++) {
|
||||
if (i > 0) result.append(", ");
|
||||
dumpStackTraceElement(result, stack.get(i));
|
||||
}
|
||||
result.append("]");
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
public static String escapeJsonString(String s) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
int length = s.length();
|
||||
|
||||
@@ -166,6 +166,29 @@ public final class CoroutinesDebugHelper {
|
||||
}
|
||||
}
|
||||
|
||||
public static Object[] dumpCoroutinesWithStacktracesAsJson() throws ReflectiveOperationException {
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
try {
|
||||
Class<?> debugProbesImplClass = classLoader.loadClass("kotlinx.coroutines.debug.internal.DebugProbesImpl");
|
||||
Object debugProbesImplInstance = debugProbesImplClass.getField("INSTANCE").get(null);
|
||||
Object[] dump = (Object[])invoke(debugProbesImplInstance, "dumpCoroutinesInfoAsJsonAndReferences");
|
||||
Object[] coroutineInfos = (Object[])dump[3];
|
||||
String[] lastObservedStackTraces = new String[coroutineInfos.length];
|
||||
for (int i = 0; i < coroutineInfos.length; i++) {
|
||||
lastObservedStackTraces[i] = lastObservedStackTrace(coroutineInfos[i]);
|
||||
}
|
||||
dump[3] = lastObservedStackTraces;
|
||||
return dump;
|
||||
} catch (Throwable e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String lastObservedStackTrace(Object debugCoroutineInfo) throws ReflectiveOperationException {
|
||||
List<StackTraceElement> stackTrace = (List<StackTraceElement>)invoke(debugCoroutineInfo, "lastObservedStackTrace");
|
||||
return JsonUtils.dumpStackTraceElements(stackTrace);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method takes the array of {@link kotlinx.coroutines.debug.internal.DebugCoroutineInfo} instances
|
||||
* and for each coroutine finds it's job and the first parent, which corresponds to some coroutine, captured in the dump.
|
||||
|
||||
@@ -4,6 +4,7 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.data
|
||||
|
||||
import com.intellij.debugger.engine.JavaValue
|
||||
import com.intellij.debugger.engine.SuspendContext
|
||||
import com.sun.jdi.Location
|
||||
import com.sun.jdi.ObjectReference
|
||||
import com.sun.jdi.ThreadReference
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
@@ -27,7 +28,8 @@ open class CoroutineInfoData(
|
||||
val lastObservedFrame: ObjectReference?,
|
||||
val lastObservedThread: ThreadReference?,
|
||||
val debugCoroutineInfoRef: ObjectReference?,
|
||||
private val stackFrameProvider: CoroutineStackFramesProvider?
|
||||
private val stackFrameProvider: CoroutineStackFramesProvider?,
|
||||
val lastObservedStackTrace: List<Location> = emptyList()
|
||||
) {
|
||||
val name: String = name ?: DEFAULT_COROUTINE_NAME
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ class CoroutineDebugProbesProxy(val suspendContext: SuspendContextImpl) {
|
||||
try {
|
||||
val executionContext = suspendContext.executionContext()
|
||||
val coroutineInfos =
|
||||
CoroutinesInfoFromJsonAndReferencesProvider(executionContext).dumpCoroutinesInfo()
|
||||
CoroutinesInfoFromJsonAndReferencesProvider(executionContext).dumpCoroutinesWithStacktraces()
|
||||
?: CoroutineLibraryAgent2Proxy.instance(executionContext)?.dumpCoroutinesInfo()
|
||||
?: emptyList()
|
||||
coroutineInfoCache.ok(coroutineInfos)
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
package org.jetbrains.kotlin.idea.debugger.coroutine.proxy
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.intellij.rt.debugger.JsonUtils
|
||||
import com.intellij.rt.debugger.coroutines.CoroutinesDebugHelper
|
||||
import com.sun.jdi.ArrayReference
|
||||
import com.sun.jdi.Location
|
||||
import com.sun.jdi.ObjectReference
|
||||
import com.sun.jdi.StringReference
|
||||
import com.sun.jdi.ThreadReference
|
||||
@@ -56,6 +58,40 @@ internal class CoroutinesInfoFromJsonAndReferencesProvider(
|
||||
return calculateCoroutineInfoData(coroutinesInfo, coroutineInfoRefs, lastObservedThreadRefs, lastObservedFrameRefs)
|
||||
}
|
||||
|
||||
fun dumpCoroutinesWithStacktraces(): List<CoroutineInfoData>? {
|
||||
val array = callMethodFromHelper(CoroutinesDebugHelper::class.java, executionContext, "dumpCoroutinesWithStacktracesAsJson", emptyList(), JsonUtils::class.java.name)
|
||||
|
||||
val arrayValues = (array as? ArrayReference)?.values ?: return null
|
||||
|
||||
if (arrayValues.size != 4) {
|
||||
error("The result array of 'dumpCoroutinesWithStacktracesAsJson' should be of size 4")
|
||||
}
|
||||
|
||||
val coroutinesInfoAsJsonString = arrayValues[0].safeAs<StringReference>()?.value()
|
||||
?: error("The first element of the result array must be a string")
|
||||
val lastObservedThreadRefs = arrayValues[1].safeAs<ArrayReference>()?.toTypedList<ThreadReference?>()
|
||||
?: error("The second element of the result array must be an array")
|
||||
val lastObservedFrameRefs = arrayValues[2].safeAs<ArrayReference>()?.toTypedList<ObjectReference?>()
|
||||
?: error("The third element of the result array must be an array")
|
||||
val lastObservedStackTraceJsons = arrayValues[3].safeAs<ArrayReference>()?.toTypedList<StringReference>()
|
||||
?: error("The fourth element of the result array must be an array")
|
||||
|
||||
val coroutinesInfo = Gson().fromJson(coroutinesInfoAsJsonString, Array<CoroutineInfoFromJson>::class.java)
|
||||
val lastObservedStackTraces: List<List<Location>> = lastObservedStackTraceJsons.map {
|
||||
Gson().fromJson(it.value(), Array<StackTraceElementData>::class.java).map { ste ->
|
||||
findOrCreateLocation(executionContext, ste.stackTraceElement())
|
||||
}
|
||||
}
|
||||
|
||||
if (lastObservedStackTraces.size != lastObservedFrameRefs.size ||
|
||||
lastObservedFrameRefs.size != coroutinesInfo.size ||
|
||||
coroutinesInfo.size != lastObservedThreadRefs.size) {
|
||||
error("Arrays must have equal sizes")
|
||||
}
|
||||
|
||||
return calculateCoroutineInfoDataWithStacktraces(coroutinesInfo, lastObservedThreadRefs, lastObservedFrameRefs, lastObservedStackTraces)
|
||||
}
|
||||
|
||||
private fun fallbackToOldMirrorDump(executionContext: DefaultExecutionContext): ArrayReference? {
|
||||
val debugProbesImpl = DebugProbesImpl.instance(executionContext)
|
||||
return if (debugProbesImpl != null && debugProbesImpl.isInstalled && debugProbesImpl.canDumpCoroutinesInfoAsJsonAndReferences()) {
|
||||
@@ -84,6 +120,27 @@ internal class CoroutinesInfoFromJsonAndReferencesProvider(
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateCoroutineInfoDataWithStacktraces(
|
||||
coroutineInfos: Array<CoroutineInfoFromJson>,
|
||||
lastObservedThreadRefs: List<ThreadReference?>,
|
||||
lastObservedFrameRefs: List<ObjectReference?>,
|
||||
lastObservedStackTraces: List<List<Location>>
|
||||
): List<CoroutineInfoData> {
|
||||
return coroutineInfos.mapIndexed { i, info ->
|
||||
CoroutineInfoData(
|
||||
name = info.name,
|
||||
id = info.sequenceNumber,
|
||||
state = info.state,
|
||||
dispatcher = info.dispatcher,
|
||||
lastObservedFrame = lastObservedFrameRefs[i],
|
||||
lastObservedThread = lastObservedThreadRefs[i],
|
||||
debugCoroutineInfoRef = null,
|
||||
stackFrameProvider = null,
|
||||
lastObservedStackTrace = lastObservedStackTraces[i]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private data class CoroutineInfoFromJson(
|
||||
val name: String?,
|
||||
val id: Long?,
|
||||
|
||||
@@ -60,10 +60,10 @@ private class CoroutineDumpItem(info: CoroutineInfoData) : MergeableDumpItem {
|
||||
|
||||
override val stackTrace: String =
|
||||
info.coroutineDescriptor + "\n" +
|
||||
info.continuationStackFrames.map { it.location }.joinToString(prefix = "\t", separator = "\n") { ThreadDumpAction.renderLocation(it) }
|
||||
info.lastObservedStackTrace.joinToString(prefix = "\t", separator = "\n\t") { ThreadDumpAction.renderLocation(it) }
|
||||
|
||||
override val interestLevel: Int = when {
|
||||
info.continuationStackFrames.isEmpty() -> -10
|
||||
info.lastObservedStackTrace.isEmpty() -> -10
|
||||
else -> stackTrace.count { it == '\n' }
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user