mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-04 17:20:55 +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();
|
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) {
|
public static String escapeJsonString(String s) {
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
int length = s.length();
|
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
|
* 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.
|
* 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.JavaValue
|
||||||
import com.intellij.debugger.engine.SuspendContext
|
import com.intellij.debugger.engine.SuspendContext
|
||||||
|
import com.sun.jdi.Location
|
||||||
import com.sun.jdi.ObjectReference
|
import com.sun.jdi.ObjectReference
|
||||||
import com.sun.jdi.ThreadReference
|
import com.sun.jdi.ThreadReference
|
||||||
import org.jetbrains.annotations.ApiStatus
|
import org.jetbrains.annotations.ApiStatus
|
||||||
@@ -27,7 +28,8 @@ open class CoroutineInfoData(
|
|||||||
val lastObservedFrame: ObjectReference?,
|
val lastObservedFrame: ObjectReference?,
|
||||||
val lastObservedThread: ThreadReference?,
|
val lastObservedThread: ThreadReference?,
|
||||||
val debugCoroutineInfoRef: ObjectReference?,
|
val debugCoroutineInfoRef: ObjectReference?,
|
||||||
private val stackFrameProvider: CoroutineStackFramesProvider?
|
private val stackFrameProvider: CoroutineStackFramesProvider?,
|
||||||
|
val lastObservedStackTrace: List<Location> = emptyList()
|
||||||
) {
|
) {
|
||||||
val name: String = name ?: DEFAULT_COROUTINE_NAME
|
val name: String = name ?: DEFAULT_COROUTINE_NAME
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class CoroutineDebugProbesProxy(val suspendContext: SuspendContextImpl) {
|
|||||||
try {
|
try {
|
||||||
val executionContext = suspendContext.executionContext()
|
val executionContext = suspendContext.executionContext()
|
||||||
val coroutineInfos =
|
val coroutineInfos =
|
||||||
CoroutinesInfoFromJsonAndReferencesProvider(executionContext).dumpCoroutinesInfo()
|
CoroutinesInfoFromJsonAndReferencesProvider(executionContext).dumpCoroutinesWithStacktraces()
|
||||||
?: CoroutineLibraryAgent2Proxy.instance(executionContext)?.dumpCoroutinesInfo()
|
?: CoroutineLibraryAgent2Proxy.instance(executionContext)?.dumpCoroutinesInfo()
|
||||||
?: emptyList()
|
?: emptyList()
|
||||||
coroutineInfoCache.ok(coroutineInfos)
|
coroutineInfoCache.ok(coroutineInfos)
|
||||||
|
|||||||
@@ -3,8 +3,10 @@
|
|||||||
package org.jetbrains.kotlin.idea.debugger.coroutine.proxy
|
package org.jetbrains.kotlin.idea.debugger.coroutine.proxy
|
||||||
|
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
|
import com.intellij.rt.debugger.JsonUtils
|
||||||
import com.intellij.rt.debugger.coroutines.CoroutinesDebugHelper
|
import com.intellij.rt.debugger.coroutines.CoroutinesDebugHelper
|
||||||
import com.sun.jdi.ArrayReference
|
import com.sun.jdi.ArrayReference
|
||||||
|
import com.sun.jdi.Location
|
||||||
import com.sun.jdi.ObjectReference
|
import com.sun.jdi.ObjectReference
|
||||||
import com.sun.jdi.StringReference
|
import com.sun.jdi.StringReference
|
||||||
import com.sun.jdi.ThreadReference
|
import com.sun.jdi.ThreadReference
|
||||||
@@ -56,6 +58,40 @@ internal class CoroutinesInfoFromJsonAndReferencesProvider(
|
|||||||
return calculateCoroutineInfoData(coroutinesInfo, coroutineInfoRefs, lastObservedThreadRefs, lastObservedFrameRefs)
|
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? {
|
private fun fallbackToOldMirrorDump(executionContext: DefaultExecutionContext): ArrayReference? {
|
||||||
val debugProbesImpl = DebugProbesImpl.instance(executionContext)
|
val debugProbesImpl = DebugProbesImpl.instance(executionContext)
|
||||||
return if (debugProbesImpl != null && debugProbesImpl.isInstalled && debugProbesImpl.canDumpCoroutinesInfoAsJsonAndReferences()) {
|
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(
|
private data class CoroutineInfoFromJson(
|
||||||
val name: String?,
|
val name: String?,
|
||||||
val id: Long?,
|
val id: Long?,
|
||||||
|
|||||||
@@ -60,10 +60,10 @@ private class CoroutineDumpItem(info: CoroutineInfoData) : MergeableDumpItem {
|
|||||||
|
|
||||||
override val stackTrace: String =
|
override val stackTrace: String =
|
||||||
info.coroutineDescriptor + "\n" +
|
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 {
|
override val interestLevel: Int = when {
|
||||||
info.continuationStackFrames.isEmpty() -> -10
|
info.lastObservedStackTrace.isEmpty() -> -10
|
||||||
else -> stackTrace.count { it == '\n' }
|
else -> stackTrace.count { it == '\n' }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user