prototype of a call tracer

This commit is contained in:
Egor Ushakov
2018-12-19 19:28:53 +03:00
parent 9a2ea005af
commit e4ec6e933f
10 changed files with 359 additions and 34 deletions
@@ -299,12 +299,16 @@ public class DebugProcessEvents extends DebugProcessImpl {
return null;
}
private static void enableNonSuspendingRequest(EventRequest request, Consumer<Event> handler) {
request.setSuspendPolicy(EventRequest.SUSPEND_NONE);
public static void enableRequestWithHandler(EventRequest request, Consumer<Event> handler) {
request.putProperty(REQUEST_HANDLER, handler);
request.enable();
}
private static void enableNonSuspendingRequest(EventRequest request, Consumer<Event> handler) {
request.setSuspendPolicy(EventRequest.SUSPEND_NONE);
enableRequestWithHandler(request, handler);
}
private void processVMStartEvent(final SuspendContextImpl suspendContext, VMStartEvent event) {
preprocessEvent(suspendContext, event.thread());
@@ -121,43 +121,46 @@ public class RequestManagerImpl extends DebugProcessAdapterImpl implements Reque
}
if (requestor.isClassFiltersEnabled() && !(request instanceof BreakpointRequest) /*no built-in class filters support for breakpoint requests*/ ) {
ClassFilter[] classFilters = requestor.getClassFilters();
if (DebuggerUtilsEx.getEnabledNumber(classFilters) == 1) {
for (final ClassFilter filter : classFilters) {
if (!filter.isEnabled()) {
continue;
}
final JVMName jvmClassName = ReadAction.compute(() -> {
PsiClass psiClass = DebuggerUtils.findClass(filter.getPattern(), myDebugProcess.getProject(), myDebugProcess.getSearchScope());
if (psiClass == null) {
return null;
}
return JVMNameUtil.getJVMQualifiedName(psiClass);
});
String pattern = filter.getPattern();
try {
if (jvmClassName != null) {
pattern = jvmClassName.getName(myDebugProcess);
}
}
catch (EvaluateException ignored) {
}
addClassFilter(request, pattern);
break; // adding more than one inclusion filter does not work, only events that satisfy ALL filters are placed in the event queue.
}
}
for (ClassFilter filter : requestor.getClassExclusionFilters()) {
if (filter.isEnabled()) {
addClassExclusionFilter(request, filter.getPattern());
}
}
addClassFilters(request, requestor.getClassFilters(), requestor.getClassExclusionFilters());
}
registerRequestInternal(requestor, request);
}
public void addClassFilters(EventRequest request, ClassFilter[] classFilters, ClassFilter[] classExclusionFilters) {
if (DebuggerUtilsEx.getEnabledNumber(classFilters) == 1) {
for (final ClassFilter filter : classFilters) {
if (!filter.isEnabled()) {
continue;
}
final JVMName jvmClassName = ReadAction.compute(() -> {
PsiClass psiClass = DebuggerUtils.findClass(filter.getPattern(), myDebugProcess.getProject(), myDebugProcess.getSearchScope());
if (psiClass == null) {
return null;
}
return JVMNameUtil.getJVMQualifiedName(psiClass);
});
String pattern = filter.getPattern();
try {
if (jvmClassName != null) {
pattern = jvmClassName.getName(myDebugProcess);
}
}
catch (EvaluateException ignored) {
}
addClassFilter(request, pattern);
break; // adding more than one inclusion filter does not work, only events that satisfy ALL filters are placed in the event queue.
}
}
for (ClassFilter filter : classExclusionFilters) {
if (filter.isEnabled()) {
addClassExclusionFilter(request, filter.getPattern());
}
}
}
public void registerRequestInternal(final Requestor requestor, final EventRequest request) {
registerRequest(requestor, request);
request.putProperty(REQUESTOR, requestor);
@@ -0,0 +1,61 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.debugger.settings;
import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.openapi.util.Comparing;
import com.intellij.ui.classFilter.ClassFilter;
import com.intellij.util.xmlb.annotations.XCollection;
import org.jetbrains.annotations.NotNull;
@State(name = "TraceSettings", storages = @Storage("debugger.xml"))
public class TraceSettings implements PersistentStateComponent<TraceSettings> {
private ClassFilter[] myClassFilters;
private ClassFilter[] myClassExclusionFilters;
@XCollection(propertyElementName = "class-filters")
public final ClassFilter[] getClassFilters() {
return myClassFilters != null ? myClassFilters : ClassFilter.EMPTY_ARRAY;
}
public final boolean setClassFilters(ClassFilter[] classFilters) {
boolean changed = !filtersEqual(myClassFilters, classFilters);
myClassFilters = classFilters;
return changed;
}
protected static boolean filtersEqual(Object[] a, Object[] b) {
if ((a == null || a.length == 0) && (b == null || b.length == 0)) {
return true;
}
return Comparing.equal(a, b);
}
@XCollection(propertyElementName = "class-exclusion-filters")
public ClassFilter[] getClassExclusionFilters() {
return myClassExclusionFilters != null ? myClassExclusionFilters : ClassFilter.EMPTY_ARRAY;
}
public boolean setClassExclusionFilters(ClassFilter[] classExclusionFilters) {
boolean changed = !filtersEqual(myClassExclusionFilters, classExclusionFilters);
myClassExclusionFilters = classExclusionFilters;
return changed;
}
public static TraceSettings getInstance() {
return ServiceManager.getService(TraceSettings.class);
}
@Override
public void loadState(@NotNull TraceSettings state) {
myClassFilters = state.getClassFilters();
myClassExclusionFilters = state.getClassExclusionFilters();
}
@Override
public TraceSettings getState() {
return this;
}
}
@@ -30,6 +30,7 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.JDOMExternalizerUtil;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiCodeFragment;
import com.intellij.psi.PsiElement;
@@ -282,6 +283,12 @@ public abstract class Breakpoint<P extends JavaBreakpointProperties> implements
private void runAction(EvaluationContextImpl context, LocatableEvent event) {
DebugProcessImpl debugProcess = context.getDebugProcess();
if (getProperties().isTRACING_START() && Registry.is("debugger.call.tracing")) {
CallTracer.get(debugProcess).start(context);
}
if (getProperties().isTRACING_END() && Registry.is("debugger.call.tracing")) {
CallTracer.get(debugProcess).stop();
}
if (isLogEnabled() || isLogExpressionEnabled() || isLogStack()) {
StringBuilder buf = new StringBuilder();
if (myXBreakpoint.isLogMessage()) {
@@ -0,0 +1,114 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.debugger.ui.breakpoints;
import com.intellij.debugger.engine.DebugProcessEvents;
import com.intellij.debugger.engine.DebugProcessImpl;
import com.intellij.debugger.engine.evaluation.EvaluateException;
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
import com.intellij.debugger.impl.DebuggerUtilsEx;
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl;
import com.intellij.debugger.settings.TraceSettings;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.classFilter.ClassFilter;
import com.sun.jdi.IncompatibleThreadStateException;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.MethodEntryEvent;
import com.sun.jdi.request.EventRequest;
import com.sun.jdi.request.EventRequestManager;
import com.sun.jdi.request.MethodEntryRequest;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
/**
* @author egor
*/
public class CallTracer {
private static final Logger LOG = Logger.getInstance(CallTracer.class);
public static final Key<CallTracer> CALL_TRACER_KEY = Key.create("CALL_TRACER");
private final EventRequestManager myRequestManager;
private final DebugProcessImpl myDebugProcess;
private final List<MethodEntryRequest> myEntryRequests = new ArrayList<>(1);
private int myStartIndent = 0;
public CallTracer(DebugProcessImpl debugProcess) {
myDebugProcess = debugProcess;
myRequestManager = debugProcess.getRequestsManager().getVMRequestManager();
}
public void start(EvaluationContextImpl context) {
if (myEntryRequests.isEmpty()) {
myStartIndent = 0;
try {
ThreadReferenceProxyImpl thread = context.getSuspendContext().getThread();
if (thread != null) {
myStartIndent = thread.frameCount();
}
}
catch (EvaluateException e) {
LOG.error(e);
}
TraceSettings traceSettings = TraceSettings.getInstance();
ClassFilter[] classFilters = traceSettings.getClassFilters();
ClassFilter[] exclusionFilters = traceSettings.getClassExclusionFilters();
if (DebuggerUtilsEx.getEnabledNumber(classFilters) == 0) {
addEntryRequest(null, exclusionFilters);
}
else {
for (ClassFilter filter : classFilters) {
if (filter.isEnabled()) {
addEntryRequest(filter, exclusionFilters);
}
}
}
}
}
private void addEntryRequest(ClassFilter filter, ClassFilter[] exclusionFilters) {
MethodEntryRequest request = myRequestManager.createMethodEntryRequest();
request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); // to be able to detect frameCount inside the event handler
myDebugProcess.getRequestsManager()
.addClassFilters(request, filter != null ? new ClassFilter[]{filter} : ClassFilter.EMPTY_ARRAY, exclusionFilters);
myEntryRequests.add(request);
DebugProcessEvents.enableRequestWithHandler(request, this::accept);
}
public void stop() {
if (!myEntryRequests.isEmpty()) {
myEntryRequests.forEach(myRequestManager::deleteEventRequest);
myEntryRequests.clear();
}
}
private void accept(Event event) {
if (event instanceof MethodEntryEvent) {
MethodEntryEvent methodEntryEvent = (MethodEntryEvent)event;
try {
int indent = methodEntryEvent.thread().frameCount() - myStartIndent;
if (indent < 0) {
stop();
return;
}
myDebugProcess.printToConsole("\n" + StringUtil.repeat(" ", indent) + methodEntryEvent.method());
}
catch (IncompatibleThreadStateException e) {
LOG.error(e);
}
}
}
@NotNull
public static CallTracer get(DebugProcessImpl debugProcess) {
CallTracer tracer = debugProcess.getUserData(CALL_TRACER_KEY);
if (tracer == null) {
tracer = new CallTracer(debugProcess);
debugProcess.putUserData(CALL_TRACER_KEY, tracer);
}
return tracer;
}
}
@@ -0,0 +1,95 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.debugger.ui.breakpoints;
import com.intellij.debugger.settings.TraceSettings;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.ui.IdeBorderFactory;
import com.intellij.ui.components.JBCheckBox;
import com.intellij.xdebugger.breakpoints.XLineBreakpoint;
import com.intellij.xdebugger.breakpoints.ui.XBreakpointCustomPropertiesPanel;
import com.intellij.xdebugger.impl.breakpoints.XBreakpointBase;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.java.debugger.breakpoints.properties.JavaLineBreakpointProperties;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class CallTracingPropertiesPanel extends XBreakpointCustomPropertiesPanel<XLineBreakpoint<JavaLineBreakpointProperties>> {
private final Project myProject;
private JBCheckBox myStartTracing;
private JBCheckBox myEndTracing;
public CallTracingPropertiesPanel(Project project) {
myProject = project;
}
@NotNull
@Override
public JComponent getComponent() {
myStartTracing = new JBCheckBox("Start");
myEndTracing = new JBCheckBox("Stop");
JButton filters = new JButton("Filters...");
//DialogUtil.registerMnemonic(myStartTracing);
//DialogUtil.registerMnemonic(myEndTracing);
JPanel _panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 0));
_panel.add(myStartTracing);
_panel.add(myEndTracing);
_panel.add(filters);
_panel.setBorder(IdeBorderFactory.createTitledBorder("Tracing", true));
ActionListener listener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JCheckBox toUncheck = null;
if (myStartTracing.isSelected() && myEndTracing.isSelected()) {
Object source = e.getSource();
if (myStartTracing.equals(source)) {
toUncheck = myEndTracing;
}
else if (myEndTracing.equals(source)) {
toUncheck = myStartTracing;
}
if (toUncheck != null) {
toUncheck.setSelected(false);
}
}
}
};
myStartTracing.addActionListener(listener);
myEndTracing.addActionListener(listener);
filters.addActionListener(e -> {
EditClassFiltersDialog dialog = new EditClassFiltersDialog(myProject);
TraceSettings traceSettings = TraceSettings.getInstance();
dialog.setFilters(traceSettings.getClassFilters(), traceSettings.getClassExclusionFilters());
dialog.setTitle("Tracing Class Filters");
dialog.show();
if (dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
traceSettings.setClassFilters(dialog.getFilters());
traceSettings.setClassExclusionFilters(dialog.getExclusionFilters());
}
});
return _panel;
}
@Override
public void loadFrom(@NotNull XLineBreakpoint<JavaLineBreakpointProperties> breakpoint) {
myStartTracing.setSelected(breakpoint.getProperties().isTRACING_START());
myEndTracing.setSelected(breakpoint.getProperties().isTRACING_END());
}
@Override
public void saveTo(@NotNull XLineBreakpoint<JavaLineBreakpointProperties> breakpoint) {
boolean changed = breakpoint.getProperties().setTRACING_START(myStartTracing.isSelected());
changed = breakpoint.getProperties().setTRACING_END(myEndTracing.isSelected()) || changed;
if (changed) {
((XBreakpointBase)breakpoint).fireBreakpointChanged();
}
}
}
@@ -10,6 +10,7 @@ import com.intellij.icons.AllIcons;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
@@ -18,6 +19,7 @@ import com.intellij.xdebugger.XDebuggerUtil;
import com.intellij.xdebugger.XSourcePosition;
import com.intellij.xdebugger.breakpoints.XBreakpoint;
import com.intellij.xdebugger.breakpoints.XLineBreakpoint;
import com.intellij.xdebugger.breakpoints.ui.XBreakpointCustomPropertiesPanel;
import com.intellij.xdebugger.breakpoints.ui.XBreakpointGroupingRule;
import com.intellij.xdebugger.impl.XSourcePositionImpl;
import com.intellij.xdebugger.impl.breakpoints.XLineBreakpointImpl;
@@ -309,4 +311,13 @@ public class JavaLineBreakpointType extends JavaLineBreakpointTypeBase<JavaLineB
return true;
});
}
@Nullable
@Override
public XBreakpointCustomPropertiesPanel<XLineBreakpoint<JavaLineBreakpointProperties>> createCustomPropertiesPanel(@NotNull Project project) {
if (Registry.is("debugger.call.tracing")) {
return new CallTracingPropertiesPanel(project);
}
return null;
}
}
@@ -29,6 +29,9 @@ public class JavaBreakpointProperties<T extends JavaBreakpointProperties> extend
private ClassFilter[] myCallerFilters;
private ClassFilter[] myCallerExclusionFilters;
private boolean TRACING_START = false;
private boolean TRACING_END = false;
@XCollection(propertyElementName = "instance-filters")
public InstanceFilter[] getInstanceFilters() {
return myInstanceFilters != null ? myInstanceFilters : InstanceFilter.EMPTY_ARRAY;
@@ -100,6 +103,9 @@ public class JavaBreakpointProperties<T extends JavaBreakpointProperties> extend
setCALLER_FILTERS_ENABLED(state.isCALLER_FILTERS_ENABLED());
myCallerFilters = state.getCallerFilters();
myCallerExclusionFilters = state.getCallerExclusionFilters();
setTRACING_START(state.isTRACING_START());
setTRACING_END(state.isTRACING_END());
}
@OptionTag("count-filter-enabled")
@@ -178,4 +184,26 @@ public class JavaBreakpointProperties<T extends JavaBreakpointProperties> extend
myCallerExclusionFilters = callerExclusionFilters;
return changed;
}
@OptionTag("tracing-start")
public boolean isTRACING_START() {
return TRACING_START;
}
public boolean setTRACING_START(boolean TRACING_START) {
boolean changed = this.TRACING_START != TRACING_START;
this.TRACING_START = TRACING_START;
return changed;
}
@OptionTag("tracing-end")
public boolean isTRACING_END() {
return TRACING_END;
}
public boolean setTRACING_END(boolean TRACING_END) {
boolean changed = this.TRACING_END != TRACING_END;
this.TRACING_END = TRACING_END;
return changed;
}
}
@@ -430,6 +430,7 @@ debugger.attach.to.process.action=true
debugger.sa.jdwp.debug=false
debugger.evaluate.single.threaded.timeout=1000
debugger.evaluate.single.threaded.timeout.description=Number of milliseconds to evaluate resuming only the current thread, then resume all threads
debugger.call.tracing=false
execution.java.always.debug=false
execution.java.always.debug.description=Always run java processes with the debug agent
@@ -151,6 +151,7 @@
<applicationService serviceImplementation="com.intellij.debugger.settings.DebuggerSettings"/>
<projectService serviceImplementation="com.intellij.debugger.settings.DebuggerProjectSettings"/>
<applicationService serviceImplementation="com.intellij.debugger.settings.ThreadsViewSettings"/>
<applicationService serviceImplementation="com.intellij.debugger.settings.TraceSettings"/>
<applicationService serviceInterface="com.intellij.packaging.elements.PackagingElementFactory"
serviceImplementation="com.intellij.packaging.impl.elements.PackagingElementFactoryImpl"/>