Add capability to attach agent to gradle projects

(cherry picked from commit 75307cc7a104ffa345473bc742ec72b806ff74ba)

IJ-MR-4767

GitOrigin-RevId: 28f128ec895fd0ad128b3d4c13f54ad2bf1e619d
This commit is contained in:
Nikita Nazarov
2020-12-21 10:40:46 +00:00
committed by intellij-monorepo-bot
parent a8aca0359f
commit 339517e2f5
9 changed files with 172 additions and 45 deletions
@@ -14,6 +14,7 @@ import com.intellij.debugger.memory.agent.ui.RetainedSizeDialog;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.xdebugger.XDebuggerManager;
@@ -26,7 +27,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
public class CalculateRetainedSizeAction extends DebuggerTreeAction {
protected final Logger LOG = Logger.getInstance(this.getClass());
protected static final Logger LOG = Logger.getInstance(CalculateRetainedSizeAction.class);
@Override
protected void perform(XValueNodeImpl node, @NotNull String nodeName, AnActionEvent e) {
@@ -46,11 +47,21 @@ public class CalculateRetainedSizeAction extends DebuggerTreeAction {
@Override
public void contextAction(@NotNull SuspendContextImpl suspendContext) {
try {
if (dialog.isDisposed()) {
return;
}
EvaluationContextImpl evaluationContext = new EvaluationContextImpl(suspendContext, suspendContext.getFrameProxy());
MemoryAgent memoryAgent = MemoryAgent.get(debugProcess);
Disposer.register(dialog.getDisposable(), () -> memoryAgent.cancelAction());
MemoryAgentActionResult<Pair<long[], ObjectReference[]>> result = memoryAgent.estimateObjectSize(
evaluationContext, reference, Registry.get("debugger.memory.agent.action.timeout").asInteger()
);
if (dialog.isDisposed()) {
return;
}
if (result.executedSuccessfully()) {
Pair<long[], ObjectReference[]> sizesAndHeldObjects = result.getResult();
dialog.setHeldObjectsAndSizes(
@@ -30,6 +30,8 @@ public interface MemoryAgent {
return MemoryAgentOperations.getAgent(debugProcess);
}
void cancelAction();
@NotNull
MemoryAgentCapabilities capabilities();
@@ -5,11 +5,17 @@ import org.jetbrains.annotations.NotNull;
public class MemoryAgentActionResult<T> {
public enum ErrorCode {
OK,
TIMEOUT;
OK, TIMEOUT, CANCELLED;
public static ErrorCode valueOf(int value) {
return value == 0 ? OK : TIMEOUT;
switch (value) {
case 0:
return OK;
case 1:
return TIMEOUT;
default:
return CANCELLED;
}
}
}
@@ -27,6 +33,6 @@ public class MemoryAgentActionResult<T> {
}
public boolean executedSuccessfully() {
return myErrorCode != ErrorCode.TIMEOUT;
return myErrorCode == ErrorCode.OK;
}
}
@@ -3,32 +3,72 @@ package com.intellij.debugger.memory.agent;
import com.intellij.debugger.engine.evaluation.EvaluateException;
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.io.FileUtil;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.ReferenceType;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
class MemoryAgentImpl implements MemoryAgent {
private enum MemoryAgentActionState {
RUNNING, FINISHED, CANCELLED
}
private static final Logger LOG = Logger.getInstance(MemoryAgentImpl.class);
static final MemoryAgent DISABLED = new MemoryAgentImpl(MemoryAgentCapabilities.DISABLED);
private final String cancellationFileName;
private final MemoryAgentCapabilities myCapabilities;
private MemoryAgentActionState myState;
private File myCancellationFile;
MemoryAgentImpl(@NotNull MemoryAgentCapabilities capabilities) {
cancellationFileName = FileUtil.getTempDirectory() + "/" + "memoryAgentCancellationFile" + new Random().nextInt();
myCapabilities = capabilities;
myState = MemoryAgentActionState.FINISHED;
}
private <T> MemoryAgentActionResult<T> executeOperation(Callable<MemoryAgentActionResult<T>> callable) throws EvaluateException {
if (myState == MemoryAgentActionState.RUNNING) {
throw new EvaluateException("Some action is already running");
}
myState = MemoryAgentActionState.RUNNING;
if (myCancellationFile != null) {
FileUtil.delete(myCancellationFile);
myCancellationFile = null;
}
try {
return callable.call();
}
catch (Exception ex) {
throw new EvaluateException(ex.getMessage());
}
finally {
myState = MemoryAgentActionState.FINISHED;
}
}
@NotNull
@Override
public MemoryAgentActionResult<Pair<long[], ObjectReference[]>> estimateObjectSize(@NotNull EvaluationContextImpl evaluationContext,
@NotNull ObjectReference reference,
long timeoutInMillis) throws EvaluateException {
@NotNull ObjectReference reference,
long timeoutInMillis) throws EvaluateException {
if (!myCapabilities.canEstimateObjectSize()) {
throw new UnsupportedOperationException("Memory agent can't estimate object size");
}
return MemoryAgentOperations.estimateObjectSize(evaluationContext, reference, timeoutInMillis);
return executeOperation(() -> MemoryAgentOperations.estimateObjectSize(evaluationContext, reference, cancellationFileName, timeoutInMillis));
}
@NotNull
@@ -40,7 +80,7 @@ class MemoryAgentImpl implements MemoryAgent {
throw new UnsupportedOperationException("Memory agent can't estimate objects sizes");
}
return MemoryAgentOperations.estimateObjectsSizes(evaluationContext, references, timeoutInMillis);
return executeOperation(() -> MemoryAgentOperations.estimateObjectsSizes(evaluationContext, references, cancellationFileName, timeoutInMillis));
}
@NotNull
@@ -52,7 +92,7 @@ class MemoryAgentImpl implements MemoryAgent {
throw new UnsupportedOperationException("Memory agent can't get shallow size by classes");
}
return MemoryAgentOperations.getShallowSizeByClasses(evaluationContext, classes, timeoutInMillis);
return executeOperation(() -> MemoryAgentOperations.getShallowSizeByClasses(evaluationContext, classes, cancellationFileName, timeoutInMillis));
}
@NotNull
@@ -64,7 +104,7 @@ class MemoryAgentImpl implements MemoryAgent {
throw new UnsupportedOperationException("Memory agent can't get retained size by classes");
}
return MemoryAgentOperations.getRetainedSizeByClasses(evaluationContext, classes, timeoutInMillis);
return executeOperation(() -> MemoryAgentOperations.getRetainedSizeByClasses(evaluationContext, classes, cancellationFileName, timeoutInMillis));
}
@NotNull
@@ -76,7 +116,7 @@ class MemoryAgentImpl implements MemoryAgent {
throw new UnsupportedOperationException("Memory agent can't get shallow and retained size by classes");
}
return MemoryAgentOperations.getShallowAndRetainedSizeByClasses(evaluationContext, classes, timeoutInMillis);
return executeOperation(() -> MemoryAgentOperations.getShallowAndRetainedSizeByClasses(evaluationContext, classes, cancellationFileName, timeoutInMillis));
}
@NotNull
@@ -90,7 +130,20 @@ class MemoryAgentImpl implements MemoryAgent {
throw new UnsupportedOperationException("Memory agent can't provide paths to closest gc roots");
}
return MemoryAgentOperations.findPathsToClosestGCRoots(evaluationContext, reference, pathsNumber, objectsNumber, timeoutInMillis);
return executeOperation(() -> MemoryAgentOperations.findPathsToClosestGCRoots(evaluationContext, reference, pathsNumber, objectsNumber, cancellationFileName, timeoutInMillis));
}
@Override
public void cancelAction() {
if (myState == MemoryAgentActionState.RUNNING) {
try {
myCancellationFile = FileUtil.createTempFile(cancellationFileName, "", true);
myState = MemoryAgentActionState.CANCELLED;
}
catch (IOException ex) {
LOG.error("Couldn't create memory agent cancellation file", ex);
}
}
}
@NotNull
@@ -3,6 +3,8 @@ package com.intellij.debugger.memory.agent;
import com.intellij.debugger.engine.DebugProcessImpl;
import com.intellij.debugger.engine.DebuggerManagerThreadImpl;
import com.intellij.debugger.engine.DebuggerUtils;
import com.intellij.debugger.engine.JVMNameUtil;
import com.intellij.debugger.engine.evaluation.EvaluateException;
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil;
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
@@ -25,17 +27,18 @@ import java.util.List;
final class MemoryAgentOperations {
private static final Key<MemoryAgent> MEMORY_AGENT_KEY = Key.create("MEMORY_AGENT_KEY");
private static final Logger LOG = Logger.getInstance(MemoryAgentOperations.class);
private static LongValue getTimeoutValue(@NotNull EvaluationContextImpl evaluationContext, long timeoutInMillis) {
return evaluationContext.getDebugProcess().getVirtualMachineProxy().mirrorOf(timeoutInMillis);
}
private static final String proxyConstructorSignature = "(Ljava/lang/Object;J)V";
@NotNull
static MemoryAgentActionResult<Pair<long[], ObjectReference[]>> estimateObjectSize(@NotNull EvaluationContextImpl evaluationContext,
@NotNull ObjectReference reference,
@NotNull String cancellationFileName,
long timeoutInMillis) throws EvaluateException {
LongValue timeoutValue = getTimeoutValue(evaluationContext, timeoutInMillis);
Value result = callMethod(evaluationContext, MemoryAgentNames.Methods.ESTIMATE_OBJECT_SIZE, Arrays.asList(reference, timeoutValue));
Value result = callMethod(
evaluationContext,
MemoryAgentNames.Methods.ESTIMATE_OBJECT_SIZE,
cancellationFileName, timeoutInMillis, reference
);
Pair<MemoryAgentActionResult.ErrorCode, Value> errCodeAndResult = ErrorCodeParser.INSTANCE.parse(result);
MemoryAgentActionResult.ErrorCode errCode = errCodeAndResult.getFirst();
Pair<long[], ObjectReference[]> sizesAndObjects;
@@ -55,10 +58,14 @@ final class MemoryAgentOperations {
@NotNull
static MemoryAgentActionResult<long[]> estimateObjectsSizes(@NotNull EvaluationContextImpl evaluationContext,
@NotNull List<ObjectReference> references,
@NotNull String cancellationFileName,
long timeoutInMillis) throws EvaluateException {
LongValue timeoutValue = getTimeoutValue(evaluationContext, timeoutInMillis);
ArrayReference array = wrapWithArray(evaluationContext, references);
Value result = callMethod(evaluationContext, MemoryAgentNames.Methods.ESTIMATE_OBJECTS_SIZE, Arrays.asList(array, timeoutValue));
Value result = callMethod(
evaluationContext,
MemoryAgentNames.Methods.ESTIMATE_OBJECTS_SIZE,
cancellationFileName, timeoutInMillis, array
);
Pair<MemoryAgentActionResult.ErrorCode, Value> errCodeAndResult = ErrorCodeParser.INSTANCE.parse(result);
return new MemoryAgentActionResult<>(
LongArrayParser.INSTANCE.parse(errCodeAndResult.getSecond()).stream().mapToLong(Long::longValue).toArray(),
@@ -69,10 +76,14 @@ final class MemoryAgentOperations {
@NotNull
static MemoryAgentActionResult<long[]> getShallowSizeByClasses(@NotNull EvaluationContextImpl evaluationContext,
@NotNull List<ReferenceType> classes,
@NotNull String cancellationFileName,
long timeoutInMillis) throws EvaluateException {
LongValue timeoutValue = getTimeoutValue(evaluationContext, timeoutInMillis);
ArrayReference array = wrapWithArray(evaluationContext, ContainerUtil.map(classes, ReferenceType::classObject));
Value result = callMethod(evaluationContext, MemoryAgentNames.Methods.GET_SHALLOW_SIZE_BY_CLASSES, Arrays.asList(array, timeoutValue));
Value result = callMethod(
evaluationContext,
MemoryAgentNames.Methods.GET_SHALLOW_SIZE_BY_CLASSES,
cancellationFileName, timeoutInMillis, array
);
Pair<MemoryAgentActionResult.ErrorCode, Value> errCodeAndResult = ErrorCodeParser.INSTANCE.parse(result);
return new MemoryAgentActionResult<>(
LongArrayParser.INSTANCE.parse(errCodeAndResult.getSecond()).stream().mapToLong(Long::longValue).toArray(),
@@ -83,10 +94,14 @@ final class MemoryAgentOperations {
@NotNull
static MemoryAgentActionResult<long[]> getRetainedSizeByClasses(@NotNull EvaluationContextImpl evaluationContext,
@NotNull List<ReferenceType> classes,
@NotNull String cancellationFileName,
long timeoutInMillis) throws EvaluateException {
LongValue timeoutValue = getTimeoutValue(evaluationContext, timeoutInMillis);
ArrayReference array = wrapWithArray(evaluationContext, ContainerUtil.map(classes, ReferenceType::classObject));
Value result = callMethod(evaluationContext, MemoryAgentNames.Methods.GET_RETAINED_SIZE_BY_CLASSES, Arrays.asList(array, timeoutValue));
Value result = callMethod(
evaluationContext,
MemoryAgentNames.Methods.GET_RETAINED_SIZE_BY_CLASSES,
cancellationFileName, timeoutInMillis, array
);
Pair<MemoryAgentActionResult.ErrorCode, Value> errCodeAndResult = ErrorCodeParser.INSTANCE.parse(result);
return new MemoryAgentActionResult<>(
LongArrayParser.INSTANCE.parse(errCodeAndResult.getSecond()).stream().mapToLong(Long::longValue).toArray(),
@@ -97,10 +112,14 @@ final class MemoryAgentOperations {
@NotNull
static MemoryAgentActionResult<Pair<long[], long[]>> getShallowAndRetainedSizeByClasses(@NotNull EvaluationContextImpl evaluationContext,
@NotNull List<ReferenceType> classes,
@NotNull String cancellationFileName,
long timeoutInMillis) throws EvaluateException {
LongValue timeoutValue = getTimeoutValue(evaluationContext, timeoutInMillis);
ArrayReference array = wrapWithArray(evaluationContext, ContainerUtil.map(classes, ReferenceType::classObject));
Value result = callMethod(evaluationContext, MemoryAgentNames.Methods.GET_SHALLOW_AND_RETAINED_SIZE_BY_CLASSES, Arrays.asList(array, timeoutValue));
Value result = callMethod(
evaluationContext,
MemoryAgentNames.Methods.GET_SHALLOW_AND_RETAINED_SIZE_BY_CLASSES,
cancellationFileName, timeoutInMillis, array
);
Pair<MemoryAgentActionResult.ErrorCode, Value> errCodeAndResult = ErrorCodeParser.INSTANCE.parse(result);
Pair<List<Long>, List<Long>> shallowAndRetainedSizes = ShallowAndRetainedSizeParser.INSTANCE.parse(errCodeAndResult.getSecond());
return new MemoryAgentActionResult<>(
@@ -114,15 +133,17 @@ final class MemoryAgentOperations {
@NotNull
static MemoryAgentActionResult<ReferringObjectsInfo> findPathsToClosestGCRoots(@NotNull EvaluationContextImpl evaluationContext,
@NotNull ObjectReference reference, int pathsNumber,
int objectsNumber, long timeoutInMillis) throws EvaluateException {
LongValue timeoutValue = getTimeoutValue(evaluationContext, timeoutInMillis);
@NotNull ObjectReference reference,
int pathsNumber, int objectsNumber,
@NotNull String cancellationFileName,
long timeoutInMillis) throws EvaluateException {
IntegerValue pathsNumberValue = evaluationContext.getDebugProcess().getVirtualMachineProxy().mirrorOf(pathsNumber);
IntegerValue objectsNumberValue = evaluationContext.getDebugProcess().getVirtualMachineProxy().mirrorOf(objectsNumber);
Value result = callMethod(
evaluationContext,
MemoryAgentNames.Methods.FIND_PATHS_TO_CLOSEST_GC_ROOTS,
Arrays.asList(reference, pathsNumberValue, objectsNumberValue, timeoutValue)
cancellationFileName, timeoutInMillis,
reference, pathsNumberValue, objectsNumberValue
);
Pair<MemoryAgentActionResult.ErrorCode, Value> errCodeAndResult = ErrorCodeParser.INSTANCE.parse(result);
@@ -218,11 +239,30 @@ final class MemoryAgentOperations {
return false;
}
@NotNull
private static LongValue getLongValue(@NotNull EvaluationContextImpl evaluationContext, long timeoutInMillis) {
return evaluationContext.getDebugProcess().getVirtualMachineProxy().mirrorOf(timeoutInMillis);
}
@NotNull
private static StringReference getStringReference(@NotNull EvaluationContextImpl evaluationContext, @NotNull String string) {
return evaluationContext.getDebugProcess().getVirtualMachineProxy().mirrorOf(string);
}
private static Value callMethod(@NotNull EvaluationContextImpl evaluationContext,
@NotNull String methodName,
@NotNull List<? extends Value> args) throws EvaluateException {
@NotNull String cancellationFileName,
long timeoutInMillis,
Value... values) throws EvaluateException {
ClassType proxyType = getProxyType(evaluationContext);
return callMethod(evaluationContext, proxyType, methodName, args);
return callMethod(
evaluationContext,
proxyType,
methodName,
getStringReference(evaluationContext, cancellationFileName),
getLongValue(evaluationContext, timeoutInMillis),
Arrays.asList(values)
);
}
@NotNull
@@ -230,6 +270,17 @@ final class MemoryAgentOperations {
@NotNull ClassType proxyType,
@NotNull String methodName,
@NotNull List<? extends Value> args) throws EvaluateException {
return callMethod(evaluationContext, proxyType, methodName, getStringReference(evaluationContext, ""), getLongValue(evaluationContext, -1), args);
}
@NotNull
private static Value callMethod(@NotNull EvaluationContextImpl evaluationContext,
@NotNull ClassType proxyType,
@NotNull String methodName,
@NotNull ObjectReference cancellationFileName,
@NotNull LongValue timeoutInMillis,
@NotNull List<? extends Value> args) throws EvaluateException {
DebuggerManagerThreadImpl.assertIsManagerThread();
long start = System.currentTimeMillis();
List<Method> methods = DebuggerUtilsEx.declaredMethodsByName(proxyType, methodName);
@@ -241,13 +292,16 @@ final class MemoryAgentOperations {
}
Method method = methods.get(0);
if (!method.isStatic()) {
throw EvaluateExceptionUtil.createEvaluateException("Utility method should be static");
}
Value result = evaluationContext
.computeAndKeep(() -> evaluationContext.getDebugProcess().invokeMethod(evaluationContext, proxyType, method, args, true));
.computeAndKeep(() -> {
DebugProcessImpl debugProcess = evaluationContext.getDebugProcess();
Method constructor = DebuggerUtils.findMethod(proxyType, JVMNameUtil.CONSTRUCTOR_NAME, proxyConstructorSignature);
if (constructor == null) {
throw EvaluateExceptionUtil.createEvaluateException("No appropriate constructor found for proxy class");
}
ObjectReference proxyInstance = debugProcess.newInstance(evaluationContext, proxyType, constructor, Arrays.asList(cancellationFileName, timeoutInMillis));
return debugProcess.invokeMethod(evaluationContext, proxyInstance, method, args);
});
LOG.info("Memory agent's method \"" + methodName + "\" took " + (System.currentTimeMillis() - start) + " ms");
return result;
@@ -44,7 +44,7 @@ public class MemoryAgentPathsToClosestGCRootsProvider implements ReferringObject
}
private ReferringObjectsInfo getPathsToGcRoots(@NotNull EvaluationContextImpl evaluationContext,
@NotNull ObjectReference value) throws EvaluateException {
@NotNull ObjectReference value) throws EvaluateException {
MemoryAgent memoryAgent = MemoryAgent.get(evaluationContext.getDebugProcess());
if (!memoryAgent.capabilities().canFindPathsToClosestGcRoots()) {
throw new UnsupportedOperationException();
@@ -146,7 +146,7 @@ object ShallowAndRetainedSizeParser : ResultParser<Pair<List<Long>, List<Long>>>
object SizeAndHeldObjectsParser : ResultParser<Pair<Array<Long>, Array<ObjectReference>>> {
override fun parse(value: Value): Pair<Array<Long>, Array<ObjectReference>> {
if (value !is ArrayReference) throw UnexpectedValueFormatException("Array expected")
if (value.length() < 2) throw UnexpectedValueFormatException("long and array of objects expected")
if (value.length() < 2) throw UnexpectedValueFormatException("array of longs and array of objects expected")
return Pair(
LongArrayParser.parse(value.getValue(0)).toTypedArray(),
ObjectReferencesParser.parse(value.getValue(1)).toTypedArray()
@@ -82,6 +82,7 @@ public class RetainedSizeDialog extends DialogWrapper {
.addToCenter(ScrollPaneFactory.createScrollPane(myTree))
.addToTop(createTopPanel());
if (session != null) {
session.addSessionListener(new XDebugSessionListener() {
@Override
@@ -10,13 +10,13 @@
<orderEntry type="module" module-name="intellij.platform.util" />
<orderEntry type="module-library">
<library name="debugger-memory-agent" type="repository">
<properties maven-id="org.jetbrains.intellij.deps:debugger-memory-agent:1.0.29" />
<properties maven-id="org.jetbrains.intellij.deps:debugger-memory-agent:1.0.30" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/intellij/deps/debugger-memory-agent/1.0.29/debugger-memory-agent-1.0.29.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/intellij/deps/debugger-memory-agent/1.0.30/debugger-memory-agent-1.0.30.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/intellij/deps/debugger-memory-agent/1.0.29/debugger-memory-agent-1.0.29-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/intellij/deps/debugger-memory-agent/1.0.30/debugger-memory-agent-1.0.30-sources.jar!/" />
</SOURCES>
</library>
</orderEntry>