/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.ddmuilib; import com.android.ddmlib.AllocationInfo; import com.android.ddmlib.Client; import com.android.ddmlib.ClientData; import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.viewers.ILabelProviderListener; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ITableLabelProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.swt.SWT; import org.eclipse.swt.SWTException; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.FormAttachment; import org.eclipse.swt.layout.FormData; import org.eclipse.swt.layout.FormLayout; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Sash; import org.eclipse.swt.widgets.Table; /** * Base class for our information panels. */ public class AllocationPanel extends TablePanel { private final static String PREFS_ALLOC_COL_SIZE = "allocPanel.Col0"; //$NON-NLS-1$ private final static String PREFS_ALLOC_COL_CLASS = "allocPanel.Col1"; //$NON-NLS-1$ private final static String PREFS_ALLOC_COL_THREAD = "allocPanel.Col2"; //$NON-NLS-1$ private final static String PREFS_ALLOC_COL_TRACE_CLASS = "allocPanel.Col3"; //$NON-NLS-1$ private final static String PREFS_ALLOC_COL_TRACE_METHOD = "allocPanel.Col4"; //$NON-NLS-1$ private final static String PREFS_ALLOC_SASH = "allocPanel.sash"; //$NON-NLS-1$ private static final String PREFS_STACK_COL_CLASS = "allocPanel.stack.col0"; //$NON-NLS-1$ private static final String PREFS_STACK_COL_METHOD = "allocPanel.stack.col1"; //$NON-NLS-1$ private static final String PREFS_STACK_COL_FILE = "allocPanel.stack.col2"; //$NON-NLS-1$ private static final String PREFS_STACK_COL_LINE = "allocPanel.stack.col3"; //$NON-NLS-1$ private static final String PREFS_STACK_COL_NATIVE = "allocPanel.stack.col4"; //$NON-NLS-1$ private Composite mAllocationBase; private Table mAllocationTable; private TableViewer mAllocationViewer; private StackTracePanel mStackTracePanel; private Table mStackTraceTable; private Button mEnableButton; private Button mRequestButton; /** * Content Provider to display the allocations of a client. * Expected input is a {@link Client} object, elements used in the table are of type * {@link AllocationInfo}. */ private static class AllocationContentProvider implements IStructuredContentProvider { public Object[] getElements(Object inputElement) { if (inputElement instanceof Client) { AllocationInfo[] allocs = ((Client)inputElement).getClientData().getAllocations(); if (allocs != null) { return allocs; } } return new Object[0]; } public void dispose() { // pass } public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { // pass } } /** * A Label Provider to use with {@link AllocationContentProvider}. It expects the elements to be * of type {@link AllocationInfo}. */ private static class AllocationLabelProvider implements ITableLabelProvider { public Image getColumnImage(Object element, int columnIndex) { return null; } public String getColumnText(Object element, int columnIndex) { if (element instanceof AllocationInfo) { AllocationInfo alloc = (AllocationInfo)element; switch (columnIndex) { case 0: return Integer.toString(alloc.getSize()); case 1: return alloc.getAllocatedClass(); case 2: return Short.toString(alloc.getThreadId()); case 3: StackTraceElement[] traces = alloc.getStackTrace(); if (traces.length > 0) { return traces[0].getClassName(); } break; case 4: traces = alloc.getStackTrace(); if (traces.length > 0) { return traces[0].getMethodName(); } break; } } return null; } public void addListener(ILabelProviderListener listener) { // pass } public void dispose() { // pass } public boolean isLabelProperty(Object element, String property) { // pass return false; } public void removeListener(ILabelProviderListener listener) { // pass } } /** * Create our control(s). */ @Override protected Control createControl(Composite parent) { final IPreferenceStore store = DdmUiPreferences.getStore(); // base composite for selected client with enabled thread update. mAllocationBase = new Composite(parent, SWT.NONE); mAllocationBase.setLayout(new FormLayout()); // table above the sash Composite topParent = new Composite(mAllocationBase, SWT.NONE); topParent.setLayout(new GridLayout(2, false)); mEnableButton = new Button(topParent, SWT.PUSH); mEnableButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { Client current = getCurrentClient(); int status = current.getClientData().getAllocationStatus(); if (status == ClientData.ALLOCATION_TRACKING_ON) { current.enableAllocationTracker(false); } else { current.enableAllocationTracker(true); } current.requestAllocationStatus(); } }); mRequestButton = new Button(topParent, SWT.PUSH); mRequestButton.setText("Get Allocations"); mRequestButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { getCurrentClient().requestAllocationDetails(); } }); setUpButtons(false /* enabled */, ClientData.ALLOCATION_TRACKING_OFF /* trackingStatus */); mAllocationTable = new Table(topParent, SWT.MULTI | SWT.FULL_SELECTION); GridData gridData; mAllocationTable.setLayoutData(gridData = new GridData(GridData.FILL_BOTH)); gridData.horizontalSpan = 2; mAllocationTable.setHeaderVisible(true); mAllocationTable.setLinesVisible(true); TableHelper.createTableColumn( mAllocationTable, "Allocation Size", SWT.RIGHT, "888", //$NON-NLS-1$ PREFS_ALLOC_COL_SIZE, store); TableHelper.createTableColumn( mAllocationTable, "Allocated Class", SWT.LEFT, "Allocated Class", //$NON-NLS-1$ PREFS_ALLOC_COL_CLASS, store); TableHelper.createTableColumn( mAllocationTable, "Thread Id", SWT.LEFT, "999", //$NON-NLS-1$ PREFS_ALLOC_COL_THREAD, store); TableHelper.createTableColumn( mAllocationTable, "Allocated in", SWT.LEFT, "utime", //$NON-NLS-1$ PREFS_ALLOC_COL_TRACE_CLASS, store); TableHelper.createTableColumn( mAllocationTable, "Allocated in", SWT.LEFT, "utime", //$NON-NLS-1$ PREFS_ALLOC_COL_TRACE_METHOD, store); mAllocationViewer = new TableViewer(mAllocationTable); mAllocationViewer.setContentProvider(new AllocationContentProvider()); mAllocationViewer.setLabelProvider(new AllocationLabelProvider()); mAllocationViewer.addSelectionChangedListener(new ISelectionChangedListener() { public void selectionChanged(SelectionChangedEvent event) { AllocationInfo selectedAlloc = getAllocationSelection(event.getSelection()); updateAllocationStackTrace(selectedAlloc); } }); // the separating sash final Sash sash = new Sash(mAllocationBase, SWT.HORIZONTAL); Color darkGray = parent.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY); sash.setBackground(darkGray); // the UI below the sash mStackTracePanel = new StackTracePanel(); mStackTraceTable = mStackTracePanel.createPanel(mAllocationBase, PREFS_STACK_COL_CLASS, PREFS_STACK_COL_METHOD, PREFS_STACK_COL_FILE, PREFS_STACK_COL_LINE, PREFS_STACK_COL_NATIVE, store); // now setup the sash. // form layout data FormData data = new FormData(); data.top = new FormAttachment(0, 0); data.bottom = new FormAttachment(sash, 0); data.left = new FormAttachment(0, 0); data.right = new FormAttachment(100, 0); topParent.setLayoutData(data); final FormData sashData = new FormData(); if (store != null && store.contains(PREFS_ALLOC_SASH)) { sashData.top = new FormAttachment(0, store.getInt(PREFS_ALLOC_SASH)); } else { sashData.top = new FormAttachment(50,0); // 50% across } sashData.left = new FormAttachment(0, 0); sashData.right = new FormAttachment(100, 0); sash.setLayoutData(sashData); data = new FormData(); data.top = new FormAttachment(sash, 0); data.bottom = new FormAttachment(100, 0); data.left = new FormAttachment(0, 0); data.right = new FormAttachment(100, 0); mStackTraceTable.setLayoutData(data); // allow resizes, but cap at minPanelWidth sash.addListener(SWT.Selection, new Listener() { public void handleEvent(Event e) { Rectangle sashRect = sash.getBounds(); Rectangle panelRect = mAllocationBase.getClientArea(); int bottom = panelRect.height - sashRect.height - 100; e.y = Math.max(Math.min(e.y, bottom), 100); if (e.y != sashRect.y) { sashData.top = new FormAttachment(0, e.y); store.setValue(PREFS_ALLOC_SASH, e.y); mAllocationBase.layout(); } } }); return mAllocationBase; } /** * Sets the focus to the proper control inside the panel. */ @Override public void setFocus() { mAllocationTable.setFocus(); } /** * Sent when an existing client information changed. * <p/> * This is sent from a non UI thread. * @param client the updated client. * @param changeMask the bit mask describing the changed properties. It can contain * any of the following values: {@link Client#CHANGE_INFO}, {@link Client#CHANGE_NAME} * {@link Client#CHANGE_DEBUGGER_INTEREST}, {@link Client#CHANGE_THREAD_MODE}, * {@link Client#CHANGE_THREAD_DATA}, {@link Client#CHANGE_HEAP_MODE}, * {@link Client#CHANGE_HEAP_DATA}, {@link Client#CHANGE_NATIVE_HEAP_DATA} * * @see IClientChangeListener#clientChanged(Client, int) */ public void clientChanged(final Client client, int changeMask) { if (client == getCurrentClient()) { if ((changeMask & Client.CHANGE_HEAP_ALLOCATIONS) != 0) { try { mAllocationTable.getDisplay().asyncExec(new Runnable() { public void run() { mAllocationViewer.refresh(); updateAllocationStackCall(); } }); } catch (SWTException e) { // widget is disposed, we do nothing } } else if ((changeMask & Client.CHANGE_HEAP_ALLOCATION_STATUS) != 0) { try { mAllocationTable.getDisplay().asyncExec(new Runnable() { public void run() { setUpButtons(true, client.getClientData().getAllocationStatus()); } }); } catch (SWTException e) { // widget is disposed, we do nothing } } } } /** * Sent when a new device is selected. The new device can be accessed * with {@link #getCurrentDevice()}. */ @Override public void deviceSelected() { // pass } /** * Sent when a new client is selected. The new client can be accessed * with {@link #getCurrentClient()}. */ @Override public void clientSelected() { if (mAllocationTable.isDisposed()) { return; } Client client = getCurrentClient(); mStackTracePanel.setCurrentClient(client); mStackTracePanel.setViewerInput(null); // always empty on client selection change. if (client != null) { setUpButtons(true /* enabled */, client.getClientData().getAllocationStatus()); } else { setUpButtons(false /* enabled */, ClientData.ALLOCATION_TRACKING_OFF /* trackingStatus */); } mAllocationViewer.setInput(client); } /** * Updates the stack call of the currently selected thread. * <p/> * This <b>must</b> be called from the UI thread. */ private void updateAllocationStackCall() { Client client = getCurrentClient(); if (client != null) { // get the current selection in the ThreadTable AllocationInfo selectedAlloc = getAllocationSelection(null); if (selectedAlloc != null) { updateAllocationStackTrace(selectedAlloc); } else { updateAllocationStackTrace(null); } } } /** * updates the stackcall of the specified allocation. If <code>null</code> the UI is emptied * of current data. * @param thread */ private void updateAllocationStackTrace(AllocationInfo alloc) { mStackTracePanel.setViewerInput(alloc); } @Override protected void setTableFocusListener() { addTableToFocusListener(mAllocationTable); addTableToFocusListener(mStackTraceTable); } /** * Returns the current allocation selection or <code>null</code> if none is found. * If a {@link ISelection} object is specified, the first {@link AllocationInfo} from this * selection is returned, otherwise, the <code>ISelection</code> returned by * {@link TableViewer#getSelection()} is used. * @param selection the {@link ISelection} to use, or <code>null</code> */ private AllocationInfo getAllocationSelection(ISelection selection) { if (selection == null) { selection = mAllocationViewer.getSelection(); } if (selection instanceof IStructuredSelection) { IStructuredSelection structuredSelection = (IStructuredSelection)selection; Object object = structuredSelection.getFirstElement(); if (object instanceof AllocationInfo) { return (AllocationInfo)object; } } return null; } /** * * @param enabled * @param trackingStatus */ private void setUpButtons(boolean enabled, int trackingStatus) { if (enabled) { switch (trackingStatus) { case ClientData.ALLOCATION_TRACKING_UNKNOWN: mEnableButton.setText("?"); mEnableButton.setEnabled(false); mRequestButton.setEnabled(false); break; case ClientData.ALLOCATION_TRACKING_OFF: mEnableButton.setText("Start Tracking"); mEnableButton.setEnabled(true); mRequestButton.setEnabled(false); break; case ClientData.ALLOCATION_TRACKING_ON: mEnableButton.setText("Stop Tracking"); mEnableButton.setEnabled(true); mRequestButton.setEnabled(true); break; } } else { mEnableButton.setEnabled(false); mRequestButton.setEnabled(false); mEnableButton.setText("Start Tracking"); } } }