1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ddmuilib; 18 19 import com.android.ddmlib.AllocationInfo; 20 import com.android.ddmlib.Client; 21 import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener; 22 import com.android.ddmlib.ClientData.AllocationTrackingStatus; 23 24 import org.eclipse.jface.preference.IPreferenceStore; 25 import org.eclipse.jface.viewers.ILabelProviderListener; 26 import org.eclipse.jface.viewers.ISelection; 27 import org.eclipse.jface.viewers.ISelectionChangedListener; 28 import org.eclipse.jface.viewers.IStructuredContentProvider; 29 import org.eclipse.jface.viewers.IStructuredSelection; 30 import org.eclipse.jface.viewers.ITableLabelProvider; 31 import org.eclipse.jface.viewers.SelectionChangedEvent; 32 import org.eclipse.jface.viewers.TableViewer; 33 import org.eclipse.jface.viewers.Viewer; 34 import org.eclipse.swt.SWT; 35 import org.eclipse.swt.SWTException; 36 import org.eclipse.swt.events.SelectionAdapter; 37 import org.eclipse.swt.events.SelectionEvent; 38 import org.eclipse.swt.graphics.Color; 39 import org.eclipse.swt.graphics.Image; 40 import org.eclipse.swt.graphics.Rectangle; 41 import org.eclipse.swt.layout.FormAttachment; 42 import org.eclipse.swt.layout.FormData; 43 import org.eclipse.swt.layout.FormLayout; 44 import org.eclipse.swt.layout.GridData; 45 import org.eclipse.swt.layout.GridLayout; 46 import org.eclipse.swt.widgets.Button; 47 import org.eclipse.swt.widgets.Composite; 48 import org.eclipse.swt.widgets.Control; 49 import org.eclipse.swt.widgets.Event; 50 import org.eclipse.swt.widgets.Listener; 51 import org.eclipse.swt.widgets.Sash; 52 import org.eclipse.swt.widgets.Table; 53 54 /** 55 * Base class for our information panels. 56 */ 57 public class AllocationPanel extends TablePanel { 58 59 private final static String PREFS_ALLOC_COL_SIZE = "allocPanel.Col0"; //$NON-NLS-1$ 60 private final static String PREFS_ALLOC_COL_CLASS = "allocPanel.Col1"; //$NON-NLS-1$ 61 private final static String PREFS_ALLOC_COL_THREAD = "allocPanel.Col2"; //$NON-NLS-1$ 62 private final static String PREFS_ALLOC_COL_TRACE_CLASS = "allocPanel.Col3"; //$NON-NLS-1$ 63 private final static String PREFS_ALLOC_COL_TRACE_METHOD = "allocPanel.Col4"; //$NON-NLS-1$ 64 65 private final static String PREFS_ALLOC_SASH = "allocPanel.sash"; //$NON-NLS-1$ 66 67 private static final String PREFS_STACK_COL_CLASS = "allocPanel.stack.col0"; //$NON-NLS-1$ 68 private static final String PREFS_STACK_COL_METHOD = "allocPanel.stack.col1"; //$NON-NLS-1$ 69 private static final String PREFS_STACK_COL_FILE = "allocPanel.stack.col2"; //$NON-NLS-1$ 70 private static final String PREFS_STACK_COL_LINE = "allocPanel.stack.col3"; //$NON-NLS-1$ 71 private static final String PREFS_STACK_COL_NATIVE = "allocPanel.stack.col4"; //$NON-NLS-1$ 72 73 private Composite mAllocationBase; 74 private Table mAllocationTable; 75 private TableViewer mAllocationViewer; 76 77 private StackTracePanel mStackTracePanel; 78 private Table mStackTraceTable; 79 private Button mEnableButton; 80 private Button mRequestButton; 81 82 /** 83 * Content Provider to display the allocations of a client. 84 * Expected input is a {@link Client} object, elements used in the table are of type 85 * {@link AllocationInfo}. 86 */ 87 private static class AllocationContentProvider implements IStructuredContentProvider { getElements(Object inputElement)88 public Object[] getElements(Object inputElement) { 89 if (inputElement instanceof Client) { 90 AllocationInfo[] allocs = ((Client)inputElement).getClientData().getAllocations(); 91 if (allocs != null) { 92 return allocs; 93 } 94 } 95 96 return new Object[0]; 97 } 98 dispose()99 public void dispose() { 100 // pass 101 } 102 inputChanged(Viewer viewer, Object oldInput, Object newInput)103 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { 104 // pass 105 } 106 } 107 108 /** 109 * A Label Provider to use with {@link AllocationContentProvider}. It expects the elements to be 110 * of type {@link AllocationInfo}. 111 */ 112 private static class AllocationLabelProvider implements ITableLabelProvider { 113 getColumnImage(Object element, int columnIndex)114 public Image getColumnImage(Object element, int columnIndex) { 115 return null; 116 } 117 getColumnText(Object element, int columnIndex)118 public String getColumnText(Object element, int columnIndex) { 119 if (element instanceof AllocationInfo) { 120 AllocationInfo alloc = (AllocationInfo)element; 121 switch (columnIndex) { 122 case 0: 123 return Integer.toString(alloc.getSize()); 124 case 1: 125 return alloc.getAllocatedClass(); 126 case 2: 127 return Short.toString(alloc.getThreadId()); 128 case 3: 129 StackTraceElement[] traces = alloc.getStackTrace(); 130 if (traces.length > 0) { 131 return traces[0].getClassName(); 132 } 133 break; 134 case 4: 135 traces = alloc.getStackTrace(); 136 if (traces.length > 0) { 137 return traces[0].getMethodName(); 138 } 139 break; 140 } 141 } 142 143 return null; 144 } 145 addListener(ILabelProviderListener listener)146 public void addListener(ILabelProviderListener listener) { 147 // pass 148 } 149 dispose()150 public void dispose() { 151 // pass 152 } 153 isLabelProperty(Object element, String property)154 public boolean isLabelProperty(Object element, String property) { 155 // pass 156 return false; 157 } 158 removeListener(ILabelProviderListener listener)159 public void removeListener(ILabelProviderListener listener) { 160 // pass 161 } 162 } 163 164 /** 165 * Create our control(s). 166 */ 167 @Override createControl(Composite parent)168 protected Control createControl(Composite parent) { 169 final IPreferenceStore store = DdmUiPreferences.getStore(); 170 171 // base composite for selected client with enabled thread update. 172 mAllocationBase = new Composite(parent, SWT.NONE); 173 mAllocationBase.setLayout(new FormLayout()); 174 175 // table above the sash 176 Composite topParent = new Composite(mAllocationBase, SWT.NONE); 177 topParent.setLayout(new GridLayout(2, false)); 178 179 mEnableButton = new Button(topParent, SWT.PUSH); 180 mEnableButton.addSelectionListener(new SelectionAdapter() { 181 @Override 182 public void widgetSelected(SelectionEvent e) { 183 Client current = getCurrentClient(); 184 AllocationTrackingStatus status = current.getClientData().getAllocationStatus(); 185 if (status == AllocationTrackingStatus.ON) { 186 current.enableAllocationTracker(false); 187 } else { 188 current.enableAllocationTracker(true); 189 } 190 current.requestAllocationStatus(); 191 } 192 }); 193 194 mRequestButton = new Button(topParent, SWT.PUSH); 195 mRequestButton.setText("Get Allocations"); 196 mRequestButton.addSelectionListener(new SelectionAdapter() { 197 @Override 198 public void widgetSelected(SelectionEvent e) { 199 getCurrentClient().requestAllocationDetails(); 200 } 201 }); 202 203 setUpButtons(false /* enabled */, AllocationTrackingStatus.OFF); 204 205 mAllocationTable = new Table(topParent, SWT.MULTI | SWT.FULL_SELECTION); 206 GridData gridData; 207 mAllocationTable.setLayoutData(gridData = new GridData(GridData.FILL_BOTH)); 208 gridData.horizontalSpan = 2; 209 mAllocationTable.setHeaderVisible(true); 210 mAllocationTable.setLinesVisible(true); 211 212 TableHelper.createTableColumn( 213 mAllocationTable, 214 "Allocation Size", 215 SWT.RIGHT, 216 "888", //$NON-NLS-1$ 217 PREFS_ALLOC_COL_SIZE, store); 218 219 TableHelper.createTableColumn( 220 mAllocationTable, 221 "Allocated Class", 222 SWT.LEFT, 223 "Allocated Class", //$NON-NLS-1$ 224 PREFS_ALLOC_COL_CLASS, store); 225 226 TableHelper.createTableColumn( 227 mAllocationTable, 228 "Thread Id", 229 SWT.LEFT, 230 "999", //$NON-NLS-1$ 231 PREFS_ALLOC_COL_THREAD, store); 232 233 TableHelper.createTableColumn( 234 mAllocationTable, 235 "Allocated in", 236 SWT.LEFT, 237 "utime", //$NON-NLS-1$ 238 PREFS_ALLOC_COL_TRACE_CLASS, store); 239 240 TableHelper.createTableColumn( 241 mAllocationTable, 242 "Allocated in", 243 SWT.LEFT, 244 "utime", //$NON-NLS-1$ 245 PREFS_ALLOC_COL_TRACE_METHOD, store); 246 247 mAllocationViewer = new TableViewer(mAllocationTable); 248 mAllocationViewer.setContentProvider(new AllocationContentProvider()); 249 mAllocationViewer.setLabelProvider(new AllocationLabelProvider()); 250 251 mAllocationViewer.addSelectionChangedListener(new ISelectionChangedListener() { 252 public void selectionChanged(SelectionChangedEvent event) { 253 AllocationInfo selectedAlloc = getAllocationSelection(event.getSelection()); 254 updateAllocationStackTrace(selectedAlloc); 255 } 256 }); 257 258 // the separating sash 259 final Sash sash = new Sash(mAllocationBase, SWT.HORIZONTAL); 260 Color darkGray = parent.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY); 261 sash.setBackground(darkGray); 262 263 // the UI below the sash 264 mStackTracePanel = new StackTracePanel(); 265 mStackTraceTable = mStackTracePanel.createPanel(mAllocationBase, 266 PREFS_STACK_COL_CLASS, 267 PREFS_STACK_COL_METHOD, 268 PREFS_STACK_COL_FILE, 269 PREFS_STACK_COL_LINE, 270 PREFS_STACK_COL_NATIVE, 271 store); 272 273 // now setup the sash. 274 // form layout data 275 FormData data = new FormData(); 276 data.top = new FormAttachment(0, 0); 277 data.bottom = new FormAttachment(sash, 0); 278 data.left = new FormAttachment(0, 0); 279 data.right = new FormAttachment(100, 0); 280 topParent.setLayoutData(data); 281 282 final FormData sashData = new FormData(); 283 if (store != null && store.contains(PREFS_ALLOC_SASH)) { 284 sashData.top = new FormAttachment(0, store.getInt(PREFS_ALLOC_SASH)); 285 } else { 286 sashData.top = new FormAttachment(50,0); // 50% across 287 } 288 sashData.left = new FormAttachment(0, 0); 289 sashData.right = new FormAttachment(100, 0); 290 sash.setLayoutData(sashData); 291 292 data = new FormData(); 293 data.top = new FormAttachment(sash, 0); 294 data.bottom = new FormAttachment(100, 0); 295 data.left = new FormAttachment(0, 0); 296 data.right = new FormAttachment(100, 0); 297 mStackTraceTable.setLayoutData(data); 298 299 // allow resizes, but cap at minPanelWidth 300 sash.addListener(SWT.Selection, new Listener() { 301 public void handleEvent(Event e) { 302 Rectangle sashRect = sash.getBounds(); 303 Rectangle panelRect = mAllocationBase.getClientArea(); 304 int bottom = panelRect.height - sashRect.height - 100; 305 e.y = Math.max(Math.min(e.y, bottom), 100); 306 if (e.y != sashRect.y) { 307 sashData.top = new FormAttachment(0, e.y); 308 store.setValue(PREFS_ALLOC_SASH, e.y); 309 mAllocationBase.layout(); 310 } 311 } 312 }); 313 314 return mAllocationBase; 315 } 316 317 /** 318 * Sets the focus to the proper control inside the panel. 319 */ 320 @Override setFocus()321 public void setFocus() { 322 mAllocationTable.setFocus(); 323 } 324 325 /** 326 * Sent when an existing client information changed. 327 * <p/> 328 * This is sent from a non UI thread. 329 * @param client the updated client. 330 * @param changeMask the bit mask describing the changed properties. It can contain 331 * any of the following values: {@link Client#CHANGE_INFO}, {@link Client#CHANGE_NAME} 332 * {@link Client#CHANGE_DEBUGGER_STATUS}, {@link Client#CHANGE_THREAD_MODE}, 333 * {@link Client#CHANGE_THREAD_DATA}, {@link Client#CHANGE_HEAP_MODE}, 334 * {@link Client#CHANGE_HEAP_DATA}, {@link Client#CHANGE_NATIVE_HEAP_DATA} 335 * 336 * @see IClientChangeListener#clientChanged(Client, int) 337 */ clientChanged(final Client client, int changeMask)338 public void clientChanged(final Client client, int changeMask) { 339 if (client == getCurrentClient()) { 340 if ((changeMask & Client.CHANGE_HEAP_ALLOCATIONS) != 0) { 341 try { 342 mAllocationTable.getDisplay().asyncExec(new Runnable() { 343 public void run() { 344 mAllocationViewer.refresh(); 345 updateAllocationStackCall(); 346 } 347 }); 348 } catch (SWTException e) { 349 // widget is disposed, we do nothing 350 } 351 } else if ((changeMask & Client.CHANGE_HEAP_ALLOCATION_STATUS) != 0) { 352 try { 353 mAllocationTable.getDisplay().asyncExec(new Runnable() { 354 public void run() { 355 setUpButtons(true, client.getClientData().getAllocationStatus()); 356 } 357 }); 358 } catch (SWTException e) { 359 // widget is disposed, we do nothing 360 } 361 } 362 } 363 } 364 365 /** 366 * Sent when a new device is selected. The new device can be accessed 367 * with {@link #getCurrentDevice()}. 368 */ 369 @Override deviceSelected()370 public void deviceSelected() { 371 // pass 372 } 373 374 /** 375 * Sent when a new client is selected. The new client can be accessed 376 * with {@link #getCurrentClient()}. 377 */ 378 @Override clientSelected()379 public void clientSelected() { 380 if (mAllocationTable.isDisposed()) { 381 return; 382 } 383 384 Client client = getCurrentClient(); 385 386 mStackTracePanel.setCurrentClient(client); 387 mStackTracePanel.setViewerInput(null); // always empty on client selection change. 388 389 if (client != null) { 390 setUpButtons(true /* enabled */, client.getClientData().getAllocationStatus()); 391 } else { 392 setUpButtons(false /* enabled */, AllocationTrackingStatus.OFF); 393 } 394 395 mAllocationViewer.setInput(client); 396 } 397 398 /** 399 * Updates the stack call of the currently selected thread. 400 * <p/> 401 * This <b>must</b> be called from the UI thread. 402 */ updateAllocationStackCall()403 private void updateAllocationStackCall() { 404 Client client = getCurrentClient(); 405 if (client != null) { 406 // get the current selection in the ThreadTable 407 AllocationInfo selectedAlloc = getAllocationSelection(null); 408 409 if (selectedAlloc != null) { 410 updateAllocationStackTrace(selectedAlloc); 411 } else { 412 updateAllocationStackTrace(null); 413 } 414 } 415 } 416 417 /** 418 * updates the stackcall of the specified allocation. If <code>null</code> the UI is emptied 419 * of current data. 420 * @param thread 421 */ updateAllocationStackTrace(AllocationInfo alloc)422 private void updateAllocationStackTrace(AllocationInfo alloc) { 423 mStackTracePanel.setViewerInput(alloc); 424 } 425 426 @Override setTableFocusListener()427 protected void setTableFocusListener() { 428 addTableToFocusListener(mAllocationTable); 429 addTableToFocusListener(mStackTraceTable); 430 } 431 432 /** 433 * Returns the current allocation selection or <code>null</code> if none is found. 434 * If a {@link ISelection} object is specified, the first {@link AllocationInfo} from this 435 * selection is returned, otherwise, the <code>ISelection</code> returned by 436 * {@link TableViewer#getSelection()} is used. 437 * @param selection the {@link ISelection} to use, or <code>null</code> 438 */ getAllocationSelection(ISelection selection)439 private AllocationInfo getAllocationSelection(ISelection selection) { 440 if (selection == null) { 441 selection = mAllocationViewer.getSelection(); 442 } 443 444 if (selection instanceof IStructuredSelection) { 445 IStructuredSelection structuredSelection = (IStructuredSelection)selection; 446 Object object = structuredSelection.getFirstElement(); 447 if (object instanceof AllocationInfo) { 448 return (AllocationInfo)object; 449 } 450 } 451 452 return null; 453 } 454 455 /** 456 * 457 * @param enabled 458 * @param trackingStatus 459 */ setUpButtons(boolean enabled, AllocationTrackingStatus trackingStatus)460 private void setUpButtons(boolean enabled, AllocationTrackingStatus trackingStatus) { 461 if (enabled) { 462 switch (trackingStatus) { 463 case UNKNOWN: 464 mEnableButton.setText("?"); 465 mEnableButton.setEnabled(false); 466 mRequestButton.setEnabled(false); 467 break; 468 case OFF: 469 mEnableButton.setText("Start Tracking"); 470 mEnableButton.setEnabled(true); 471 mRequestButton.setEnabled(false); 472 break; 473 case ON: 474 mEnableButton.setText("Stop Tracking"); 475 mEnableButton.setEnabled(true); 476 mRequestButton.setEnabled(true); 477 break; 478 } 479 } else { 480 mEnableButton.setEnabled(false); 481 mRequestButton.setEnabled(false); 482 mEnableButton.setText("Start Tracking"); 483 } 484 } 485 } 486 487