/******************************************************************************* * 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(); } }