/*******************************************************************************
* Copyright (c) 2013, 2014 Mentor Graphics and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dmitry Kozlov (Mentor Graphics) - Trace control view enhancements (Bug 390827)
*******************************************************************************/
package org.eclipse.cdt.dsf.gdb.internal.ui.tracepoints;
import java.util.Hashtable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.DsfRunnable;
import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants;
import org.eclipse.cdt.dsf.concurrent.ImmediateDataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.ImmediateRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.Query;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.debug.service.IRunControl.ISuspendedDMEvent;
import org.eclipse.cdt.dsf.gdb.internal.ui.GdbUIPlugin;
import org.eclipse.cdt.dsf.gdb.internal.ui.tracepoints.TraceControlView.FailedTraceVariableCreationException;
import org.eclipse.cdt.dsf.gdb.service.GDBTraceControl_7_2.TraceRecordSelectedChangedEvent;
import org.eclipse.cdt.dsf.gdb.service.IGDBTraceControl;
import org.eclipse.cdt.dsf.gdb.service.IGDBTraceControl.ITraceRecordDMContext;
import org.eclipse.cdt.dsf.gdb.service.IGDBTraceControl.ITraceRecordSelectedChangedDMEvent;
import org.eclipse.cdt.dsf.gdb.service.IGDBTraceControl.ITraceStatusDMData;
import org.eclipse.cdt.dsf.gdb.service.IGDBTraceControl.ITraceStatusDMData2;
import org.eclipse.cdt.dsf.gdb.service.IGDBTraceControl.ITraceTargetDMContext;
import org.eclipse.cdt.dsf.gdb.service.IGDBTraceControl.ITraceVariableDMData;
import org.eclipse.cdt.dsf.gdb.service.IGDBTraceControl.ITracingStartedDMEvent;
import org.eclipse.cdt.dsf.gdb.service.IGDBTraceControl.ITracingStoppedDMEvent;
import org.eclipse.cdt.dsf.gdb.service.IGDBTraceControl.ITracingSupportedChangeDMEvent;
import org.eclipse.cdt.dsf.gdb.service.IGDBTraceControl2;
import org.eclipse.cdt.dsf.service.DsfServiceEventHandler;
import org.eclipse.cdt.dsf.service.DsfServicesTracker;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.dsf.ui.viewmodel.datamodel.IDMVMContext;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.debug.ui.contexts.DebugContextEvent;
import org.eclipse.debug.ui.contexts.IDebugContextListener;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IWorkbenchWindow;
/**
* This class is a bridge between the TraceControl view and the TraceControl service.
* It performs the necessary requests to the service on behalf of the view.
* Those request must be done on the DSF Executor thread.
* Note that this class will have a single instance which will deal with
* all DSF debug sessions at the same time.
*/
public class TraceControlModel {
private String fDebugSessionId;
private DsfServicesTracker fServicesTracker;
private volatile IGDBTraceControl fGDBTraceControl;
private volatile ITraceTargetDMContext fTargetContext;
private TraceControlView fTraceControlView;
private IDebugContextListener fDebugContextListener = new IDebugContextListener() {
@Override
public void debugContextChanged(DebugContextEvent event) {
if ((event.getFlags() & DebugContextEvent.ACTIVATED) != 0) {
updateDebugContext();
}
}
};
TraceControlModel(TraceControlView view) {
fTraceControlView = view;
IWorkbenchWindow window = fTraceControlView.getSite().getWorkbenchWindow();
DebugUITools.getDebugContextManager().getContextService(window).addDebugContextListener(fDebugContextListener);
updateDebugContext();
}
protected void updateContent() {
if (getSession() == null) {
notifyUI(TracepointsMessages.TraceControlView_trace_status_no_debug_session);
return;
}
if (fTargetContext == null || fGDBTraceControl == null) {
notifyUI(TracepointsMessages.TraceControlView_trace_status_not_supported);
return;
}
getSession().getExecutor().execute(
new DsfRunnable() {
@Override
public void run() {
if (fTargetContext != null && fGDBTraceControl != null) {
fGDBTraceControl.getTraceStatus(
fTargetContext, new DataRequestMonitor<ITraceStatusDMData>(getSession().getExecutor(), null) {
@Override
protected void handleCompleted() {
if (isSuccess() && getData() != null) {
notifyUI((ITraceStatusDMData2)getData());
} else {
notifyUI((ITraceStatusDMData2)null);
}
}
});
} else {
notifyUI((ITraceStatusDMData2)null);
}
}
});
}
public void init() {
if (fDebugSessionId != null) {
debugSessionChanged();
} else {
updateDebugContext();
}
}
public void dispose() {
IWorkbenchWindow window = fTraceControlView.getSite().getWorkbenchWindow();
DebugUITools.getDebugContextManager().getContextService(window).removeDebugContextListener(fDebugContextListener);
setDebugContext(null);
}
protected void updateDebugContext() {
IAdaptable debugContext = DebugUITools.getDebugContext();
if (debugContext instanceof IDMVMContext) {
setDebugContext((IDMVMContext)debugContext);
} else {
setDebugContext(null);
}
}
protected void setDebugContext(IDMVMContext vmContext) {
if (vmContext != null) {
IDMContext dmContext = vmContext.getDMContext();
String sessionId = dmContext.getSessionId();
fTargetContext = DMContexts.getAncestorOfType(dmContext, ITraceTargetDMContext.class);
if (!sessionId.equals(fDebugSessionId)) {
if (getSession() != null) {
try {
final DsfSession session = getSession();
session.getExecutor().execute(new DsfRunnable() {
@Override
public void run() {
session.removeServiceEventListener(TraceControlModel.this);
}
});
} catch (RejectedExecutionException e) {
// Session is shut down.
}
}
fDebugSessionId = sessionId;
if (fServicesTracker != null) {
fServicesTracker.dispose();
}
fServicesTracker = new DsfServicesTracker(GdbUIPlugin.getBundleContext(), sessionId);
getGDBTraceControl();
debugSessionChanged();
}
} else if (fDebugSessionId != null) {
if (getSession() != null) {
try {
final DsfSession session = getSession();
session.getExecutor().execute(new DsfRunnable() {
@Override
public void run() {
session.removeServiceEventListener(TraceControlModel.this);
}
});
} catch (RejectedExecutionException e) {
// Session is shut down.
}
}
fDebugSessionId = null;
fTargetContext = null;
if (fServicesTracker != null) {
fServicesTracker.dispose();
fServicesTracker = null;
}
debugSessionChanged();
}
}
private void debugSessionChanged() {
if (getSession() != null) {
try {
final DsfSession session = getSession();
session.getExecutor().execute(new DsfRunnable() {
@Override
public void run() {
session.addServiceEventListener(TraceControlModel.this, null);
}
});
} catch (RejectedExecutionException e) {
// Session is shut down.
}
}
updateContent();
}
public void exitVisualizationMode() {
if (getSession() == null) {
return;
}
getSession().getExecutor().execute(
new DsfRunnable() {
@Override
public void run() {
if (fTargetContext != null && fGDBTraceControl != null) {
if (fGDBTraceControl instanceof IGDBTraceControl2) {
((IGDBTraceControl2)fGDBTraceControl).stopTraceVisualization(fTargetContext, new ImmediateRequestMonitor());
} else {
// Legacy way of stopping visualization of trace data
ITraceRecordDMContext emptyDmc = fGDBTraceControl.createTraceRecordContext(fTargetContext, "-1"); //$NON-NLS-1$
fGDBTraceControl.selectTraceRecord(emptyDmc, new ImmediateRequestMonitor());
}
}
}
});
}
/**
* Get the list of trace variables from the backend.
*
* @return null when the list cannot be obtained.
*/
public ITraceVariableDMData[] getTraceVarList() {
if (getSession() == null) {
return null;
}
Query<ITraceVariableDMData[]> query = new Query<ITraceVariableDMData[]>() {
@Override
protected void execute(final DataRequestMonitor<ITraceVariableDMData[]> rm) {
if (fTargetContext != null && fGDBTraceControl != null) {
fGDBTraceControl.getTraceVariables(fTargetContext,
new DataRequestMonitor<ITraceVariableDMData[]>(getSession().getExecutor(), rm) {
@Override
protected void handleCompleted() {
if (isSuccess()) {
rm.setData(getData());
} else {
rm.setData(null);
}
rm.done();
};
});
} else {
rm.setData(null);
rm.done();
}
}
};
try {
getSession().getExecutor().execute(query);
return query.get(1, TimeUnit.SECONDS);
} catch (InterruptedException exc) {
} catch (ExecutionException exc) {
} catch (TimeoutException e) {
}
return null;
}
/**
* Create a new trace variable in the backend.
*
* @throws FailedTraceVariableCreationException when the creation fails. The exception
* will contain the error message to display to the user.
*/
public void createVariable(final String name, final String value) throws FailedTraceVariableCreationException {
if (getSession() == null) {
throw new TraceControlView.FailedTraceVariableCreationException(TracepointsMessages.TraceControlView_create_variable_error);
}
Query<String> query = new Query<String>() {
@Override
protected void execute(final DataRequestMonitor<String> rm) {
if (fTargetContext != null && fGDBTraceControl != null) {
fGDBTraceControl.createTraceVariable(fTargetContext, name, value,
new RequestMonitor(getSession().getExecutor(), rm) {
@Override
protected void handleFailure() {
String message = TracepointsMessages.TraceControlView_create_variable_error;
Throwable t = getStatus().getException();
if (t != null) {
message = t.getMessage();
}
FailedTraceVariableCreationException e =
new FailedTraceVariableCreationException(message);
rm.setStatus(new Status(IStatus.ERROR, GdbUIPlugin.PLUGIN_ID, IDsfStatusConstants.INVALID_STATE, "Backend error", e)); //$NON-NLS-1$
rm.done();
};
});
} else {
FailedTraceVariableCreationException e =
new FailedTraceVariableCreationException(TracepointsMessages.TraceControlView_trace_variable_tracing_unavailable);
rm.setStatus(new Status(IStatus.ERROR, GdbUIPlugin.PLUGIN_ID, IDsfStatusConstants.INVALID_STATE, "Tracing unavailable", e)); //$NON-NLS-1$
rm.done();
}
}
};
try {
getSession().getExecutor().execute(query);
query.get();
} catch (InterruptedException e) {
// Session terminated
} catch (ExecutionException e) {
Throwable t = e.getCause();
if (t instanceof CoreException) {
t = ((CoreException)t).getStatus().getException();
if (t instanceof FailedTraceVariableCreationException) {
throw (FailedTraceVariableCreationException)t;
}
}
throw new FailedTraceVariableCreationException(TracepointsMessages.TraceControlView_create_variable_error);
}
}
public void setCurrentTraceRecord(final String traceRecordId) {
if (getSession() == null) {
return;
}
getSession().getExecutor().execute(
new DsfRunnable() {
@Override
public void run() {
if (fTargetContext != null && fGDBTraceControl != null) {
fGDBTraceControl.getCurrentTraceRecordContext(
fTargetContext,
new ImmediateDataRequestMonitor<ITraceRecordDMContext>() {
@Override
protected void handleSuccess() {
final ITraceRecordDMContext previousDmc = getData();
ITraceRecordDMContext nextRecord = fGDBTraceControl.createTraceRecordContext(fTargetContext, traceRecordId);
// Must send the event right away to tell the services we are starting visualization
// If we don't, the services won't behave accordingly soon enough
// Bug 347514
getSession().dispatchEvent(new TraceRecordSelectedChangedEvent(nextRecord), new Hashtable<String, String>());
fGDBTraceControl.selectTraceRecord(nextRecord, new ImmediateRequestMonitor() {
@Override
protected void handleError() {
// If we weren't able to select the next record, we must notify that we are still on the previous one
// since we have already sent a TraceRecordSelectedChangedEvent early, but it didn't happen.
getSession().dispatchEvent(new TraceRecordSelectedChangedEvent(previousDmc), new Hashtable<String, String>());
}
});
};
});
}
}
});
}
public void setCircularBuffer(final boolean useCircularBuffer) {
if (getSession() == null) {
return;
}
getSession().getExecutor().execute(
new DsfRunnable() {
@Override
public void run() {
if (fTargetContext != null && fGDBTraceControl != null && fGDBTraceControl instanceof IGDBTraceControl2) {
((IGDBTraceControl2)fGDBTraceControl).setCircularTraceBuffer(fTargetContext, useCircularBuffer, new ImmediateRequestMonitor());
}
}
});
}
public void setDisconnectedTracing(final boolean disconnected) {
if (getSession() == null) {
return;
}
getSession().getExecutor().execute(
new DsfRunnable() {
@Override
public void run() {
if (fTargetContext != null && fGDBTraceControl != null && fGDBTraceControl instanceof IGDBTraceControl2) {
((IGDBTraceControl2)fGDBTraceControl).setDisconnectedTracing(fTargetContext, disconnected, new ImmediateRequestMonitor());
}
}
});
}
public void setTraceNotes(final String notes) {
if (getSession() == null) {
return;
}
getSession().getExecutor().execute(
new DsfRunnable() {
@Override
public void run() {
if (fTargetContext != null && fGDBTraceControl != null && fGDBTraceControl instanceof IGDBTraceControl2) {
((IGDBTraceControl2)fGDBTraceControl).setTraceNotes(fTargetContext, notes, new ImmediateRequestMonitor());
}
}
});
}
private void getGDBTraceControl() {
if (getSession() == null) {
fGDBTraceControl = null;
return;
}
getSession().getExecutor().execute(
new DsfRunnable() {
@Override
public void run() {
fGDBTraceControl = getService(IGDBTraceControl.class);
}
});
}
private DsfSession getSession() {
return DsfSession.getSession(fDebugSessionId);
}
private <V> V getService(Class<V> serviceClass) {
if (fServicesTracker != null) {
return fServicesTracker.getService(serviceClass);
}
return null;
}
private void notifyUI(final ITraceStatusDMData2 data) {
final TraceControlView v = fTraceControlView;
if (v != null) {
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
if (v != null) {
v.fLastRefreshTime = System.currentTimeMillis();
v.updateUI(data);
}
}
});
}
}
private void notifyUI(final String message) {
final TraceControlView v = fTraceControlView;
if (v != null) {
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
if (v != null) {
v.updateUI(message);
}
}
});
}
}
/*
* When tracing starts, we know the status has changed
*/
@DsfServiceEventHandler
public void handleEvent(ITracingStartedDMEvent event) {
updateContent();
}
/*
* When tracing stops, we know the status has changed
*/
@DsfServiceEventHandler
public void handleEvent(ITracingStoppedDMEvent event) {
updateContent();
}
@DsfServiceEventHandler
public void handleEvent(ITraceRecordSelectedChangedDMEvent event) {
updateContent();
}
/*
* Since something suspended, might as well refresh our status
* to show the latest.
*/
@DsfServiceEventHandler
public void handleEvent(ISuspendedDMEvent event) {
updateContent();
}
/*
* Tracing support has changed, update view
*/
@DsfServiceEventHandler
public void handleEvent(ITracingSupportedChangeDMEvent event) {
updateContent();
}
}