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.AllocationInfo.AllocationSorter; 22 import com.android.ddmlib.AllocationInfo.SortMode; 23 import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener; 24 import com.android.ddmlib.ClientData.AllocationTrackingStatus; 25 26 import org.eclipse.jface.preference.IPreferenceStore; 27 import org.eclipse.jface.viewers.ILabelProviderListener; 28 import org.eclipse.jface.viewers.ISelection; 29 import org.eclipse.jface.viewers.ISelectionChangedListener; 30 import org.eclipse.jface.viewers.IStructuredContentProvider; 31 import org.eclipse.jface.viewers.IStructuredSelection; 32 import org.eclipse.jface.viewers.ITableLabelProvider; 33 import org.eclipse.jface.viewers.SelectionChangedEvent; 34 import org.eclipse.jface.viewers.TableViewer; 35 import org.eclipse.jface.viewers.Viewer; 36 import org.eclipse.swt.SWT; 37 import org.eclipse.swt.SWTException; 38 import org.eclipse.swt.events.ModifyEvent; 39 import org.eclipse.swt.events.ModifyListener; 40 import org.eclipse.swt.events.SelectionAdapter; 41 import org.eclipse.swt.events.SelectionEvent; 42 import org.eclipse.swt.graphics.Color; 43 import org.eclipse.swt.graphics.Image; 44 import org.eclipse.swt.graphics.Rectangle; 45 import org.eclipse.swt.layout.FormAttachment; 46 import org.eclipse.swt.layout.FormData; 47 import org.eclipse.swt.layout.FormLayout; 48 import org.eclipse.swt.layout.GridData; 49 import org.eclipse.swt.layout.GridLayout; 50 import org.eclipse.swt.widgets.Button; 51 import org.eclipse.swt.widgets.Label; 52 import org.eclipse.swt.widgets.Composite; 53 import org.eclipse.swt.widgets.Control; 54 import org.eclipse.swt.widgets.Display; 55 import org.eclipse.swt.widgets.Event; 56 import org.eclipse.swt.widgets.Listener; 57 import org.eclipse.swt.widgets.Sash; 58 import org.eclipse.swt.widgets.Table; 59 import org.eclipse.swt.widgets.TableColumn; 60 import org.eclipse.swt.widgets.Text; 61 62 import java.util.ArrayList; 63 import java.util.Arrays; 64 65 /** 66 * Base class for our information panels. 67 */ 68 public class AllocationPanel extends TablePanel { 69 70 private final static String PREFS_ALLOC_COL_NUMBER = "allocPanel.Col00"; //$NON-NLS-1$ 71 private final static String PREFS_ALLOC_COL_SIZE = "allocPanel.Col0"; //$NON-NLS-1$ 72 private final static String PREFS_ALLOC_COL_CLASS = "allocPanel.Col1"; //$NON-NLS-1$ 73 private final static String PREFS_ALLOC_COL_THREAD = "allocPanel.Col2"; //$NON-NLS-1$ 74 private final static String PREFS_ALLOC_COL_TRACE_CLASS = "allocPanel.Col3"; //$NON-NLS-1$ 75 private final static String PREFS_ALLOC_COL_TRACE_METHOD = "allocPanel.Col4"; //$NON-NLS-1$ 76 77 private final static String PREFS_ALLOC_SASH = "allocPanel.sash"; //$NON-NLS-1$ 78 79 private static final String PREFS_STACK_COL_CLASS = "allocPanel.stack.col0"; //$NON-NLS-1$ 80 private static final String PREFS_STACK_COL_METHOD = "allocPanel.stack.col1"; //$NON-NLS-1$ 81 private static final String PREFS_STACK_COL_FILE = "allocPanel.stack.col2"; //$NON-NLS-1$ 82 private static final String PREFS_STACK_COL_LINE = "allocPanel.stack.col3"; //$NON-NLS-1$ 83 private static final String PREFS_STACK_COL_NATIVE = "allocPanel.stack.col4"; //$NON-NLS-1$ 84 85 private Composite mAllocationBase; 86 private Table mAllocationTable; 87 private TableViewer mAllocationViewer; 88 89 private StackTracePanel mStackTracePanel; 90 private Table mStackTraceTable; 91 private Button mEnableButton; 92 private Button mRequestButton; 93 private Button mTraceFilterCheck; 94 95 private final AllocationSorter mSorter = new AllocationSorter(); 96 private TableColumn mSortColumn; 97 private Image mSortUpImg; 98 private Image mSortDownImg; 99 private String mFilterText = null; 100 101 /** 102 * Content Provider to display the allocations of a client. 103 * Expected input is a {@link Client} object, elements used in the table are of type 104 * {@link AllocationInfo}. 105 */ 106 private class AllocationContentProvider implements IStructuredContentProvider { 107 public Object[] getElements(Object inputElement) { 108 if (inputElement instanceof Client) { 109 AllocationInfo[] allocs = ((Client)inputElement).getClientData().getAllocations(); 110 if (allocs != null) { 111 if (mFilterText != null && mFilterText.length() > 0) { 112 allocs = getFilteredAllocations(allocs, mFilterText); 113 } 114 Arrays.sort(allocs, mSorter); 115 return allocs; 116 } 117 } 118 119 return new Object[0]; 120 } 121 122 public void dispose() { 123 // pass 124 } 125 126 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { 127 // pass 128 } 129 } 130 131 /** 132 * A Label Provider to use with {@link AllocationContentProvider}. It expects the elements to be 133 * of type {@link AllocationInfo}. 134 */ 135 private static class AllocationLabelProvider implements ITableLabelProvider { 136 137 public Image getColumnImage(Object element, int columnIndex) { 138 return null; 139 } 140 141 public String getColumnText(Object element, int columnIndex) { 142 if (element instanceof AllocationInfo) { 143 AllocationInfo alloc = (AllocationInfo)element; 144 switch (columnIndex) { 145 case 0: 146 return Integer.toString(alloc.getAllocNumber()); 147 case 1: 148 return Integer.toString(alloc.getSize()); 149 case 2: 150 return alloc.getAllocatedClass(); 151 case 3: 152 return Short.toString(alloc.getThreadId()); 153 case 4: 154 return alloc.getFirstTraceClassName(); 155 case 5: 156 return alloc.getFirstTraceMethodName(); 157 } 158 } 159 160 return null; 161 } 162 163 public void addListener(ILabelProviderListener listener) { 164 // pass 165 } 166 167 public void dispose() { 168 // pass 169 } 170 171 public boolean isLabelProperty(Object element, String property) { 172 // pass 173 return false; 174 } 175 176 public void removeListener(ILabelProviderListener listener) { 177 // pass 178 } 179 } 180 181 /** 182 * Create our control(s). 183 */ 184 @Override 185 protected Control createControl(Composite parent) { 186 final IPreferenceStore store = DdmUiPreferences.getStore(); 187 188 Display display = parent.getDisplay(); 189 190 // get some images 191 mSortUpImg = ImageLoader.getDdmUiLibLoader().loadImage("sort_up.png", display); 192 mSortDownImg = ImageLoader.getDdmUiLibLoader().loadImage("sort_down.png", display); 193 194 // base composite for selected client with enabled thread update. 195 mAllocationBase = new Composite(parent, SWT.NONE); 196 mAllocationBase.setLayout(new FormLayout()); 197 198 // table above the sash 199 Composite topParent = new Composite(mAllocationBase, SWT.NONE); 200 topParent.setLayout(new GridLayout(6, false)); 201 202 mEnableButton = new Button(topParent, SWT.PUSH); 203 mEnableButton.addSelectionListener(new SelectionAdapter() { 204 @Override 205 public void widgetSelected(SelectionEvent e) { 206 Client current = getCurrentClient(); 207 AllocationTrackingStatus status = current.getClientData().getAllocationStatus(); 208 if (status == AllocationTrackingStatus.ON) { 209 current.enableAllocationTracker(false); 210 } else { 211 current.enableAllocationTracker(true); 212 } 213 current.requestAllocationStatus(); 214 } 215 }); 216 217 mRequestButton = new Button(topParent, SWT.PUSH); 218 mRequestButton.setText("Get Allocations"); 219 mRequestButton.addSelectionListener(new SelectionAdapter() { 220 @Override 221 public void widgetSelected(SelectionEvent e) { 222 getCurrentClient().requestAllocationDetails(); 223 } 224 }); 225 226 setUpButtons(false /* enabled */, AllocationTrackingStatus.OFF); 227 228 GridData gridData; 229 230 Composite spacer = new Composite(topParent, SWT.NONE); 231 spacer.setLayoutData(gridData = new GridData(GridData.FILL_HORIZONTAL)); 232 233 new Label(topParent, SWT.NONE).setText("Filter:"); 234 235 final Text filterText = new Text(topParent, SWT.BORDER); 236 filterText.setLayoutData(gridData = new GridData(GridData.FILL_HORIZONTAL)); 237 gridData.widthHint = 200; 238 239 filterText.addModifyListener(new ModifyListener() { 240 public void modifyText(ModifyEvent arg0) { 241 mFilterText = filterText.getText().trim(); 242 mAllocationViewer.refresh(); 243 } 244 }); 245 246 mTraceFilterCheck = new Button(topParent, SWT.CHECK); 247 mTraceFilterCheck.setText("Inc. trace"); 248 mTraceFilterCheck.addSelectionListener(new SelectionAdapter() { 249 @Override 250 public void widgetSelected(SelectionEvent arg0) { 251 mAllocationViewer.refresh(); 252 } 253 }); 254 255 mAllocationTable = new Table(topParent, SWT.MULTI | SWT.FULL_SELECTION); 256 mAllocationTable.setLayoutData(gridData = new GridData(GridData.FILL_BOTH)); 257 gridData.horizontalSpan = 6; 258 mAllocationTable.setHeaderVisible(true); 259 mAllocationTable.setLinesVisible(true); 260 261 final TableColumn numberCol = TableHelper.createTableColumn( 262 mAllocationTable, 263 "Alloc Order", 264 SWT.RIGHT, 265 "Alloc Order", //$NON-NLS-1$ 266 PREFS_ALLOC_COL_NUMBER, store); 267 numberCol.addSelectionListener(new SelectionAdapter() { 268 @Override 269 public void widgetSelected(SelectionEvent arg0) { 270 setSortColumn(numberCol, SortMode.NUMBER); 271 } 272 }); 273 274 final TableColumn sizeCol = TableHelper.createTableColumn( 275 mAllocationTable, 276 "Allocation Size", 277 SWT.RIGHT, 278 "888", //$NON-NLS-1$ 279 PREFS_ALLOC_COL_SIZE, store); 280 sizeCol.addSelectionListener(new SelectionAdapter() { 281 @Override 282 public void widgetSelected(SelectionEvent arg0) { 283 setSortColumn(sizeCol, SortMode.SIZE); 284 } 285 }); 286 287 final TableColumn classCol = TableHelper.createTableColumn( 288 mAllocationTable, 289 "Allocated Class", 290 SWT.LEFT, 291 "Allocated Class", //$NON-NLS-1$ 292 PREFS_ALLOC_COL_CLASS, store); 293 classCol.addSelectionListener(new SelectionAdapter() { 294 @Override 295 public void widgetSelected(SelectionEvent arg0) { 296 setSortColumn(classCol, SortMode.CLASS); 297 } 298 }); 299 300 final TableColumn threadCol = TableHelper.createTableColumn( 301 mAllocationTable, 302 "Thread Id", 303 SWT.LEFT, 304 "999", //$NON-NLS-1$ 305 PREFS_ALLOC_COL_THREAD, store); 306 threadCol.addSelectionListener(new SelectionAdapter() { 307 @Override 308 public void widgetSelected(SelectionEvent arg0) { 309 setSortColumn(threadCol, SortMode.THREAD); 310 } 311 }); 312 313 final TableColumn inClassCol = TableHelper.createTableColumn( 314 mAllocationTable, 315 "Allocated in", 316 SWT.LEFT, 317 "utime", //$NON-NLS-1$ 318 PREFS_ALLOC_COL_TRACE_CLASS, store); 319 inClassCol.addSelectionListener(new SelectionAdapter() { 320 @Override 321 public void widgetSelected(SelectionEvent arg0) { 322 setSortColumn(inClassCol, SortMode.IN_CLASS); 323 } 324 }); 325 326 final TableColumn inMethodCol = TableHelper.createTableColumn( 327 mAllocationTable, 328 "Allocated in", 329 SWT.LEFT, 330 "utime", //$NON-NLS-1$ 331 PREFS_ALLOC_COL_TRACE_METHOD, store); 332 inMethodCol.addSelectionListener(new SelectionAdapter() { 333 @Override 334 public void widgetSelected(SelectionEvent arg0) { 335 setSortColumn(inMethodCol, SortMode.IN_METHOD); 336 } 337 }); 338 339 // init the default sort colum 340 switch (mSorter.getSortMode()) { 341 case SIZE: 342 mSortColumn = sizeCol; 343 break; 344 case CLASS: 345 mSortColumn = classCol; 346 break; 347 case THREAD: 348 mSortColumn = threadCol; 349 break; 350 case IN_CLASS: 351 mSortColumn = inClassCol; 352 break; 353 case IN_METHOD: 354 mSortColumn = inMethodCol; 355 break; 356 } 357 358 mSortColumn.setImage(mSorter.isDescending() ? mSortDownImg : mSortUpImg); 359 360 mAllocationViewer = new TableViewer(mAllocationTable); 361 mAllocationViewer.setContentProvider(new AllocationContentProvider()); 362 mAllocationViewer.setLabelProvider(new AllocationLabelProvider()); 363 364 mAllocationViewer.addSelectionChangedListener(new ISelectionChangedListener() { 365 public void selectionChanged(SelectionChangedEvent event) { 366 AllocationInfo selectedAlloc = getAllocationSelection(event.getSelection()); 367 updateAllocationStackTrace(selectedAlloc); 368 } 369 }); 370 371 // the separating sash 372 final Sash sash = new Sash(mAllocationBase, SWT.HORIZONTAL); 373 Color darkGray = parent.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY); 374 sash.setBackground(darkGray); 375 376 // the UI below the sash 377 mStackTracePanel = new StackTracePanel(); 378 mStackTraceTable = mStackTracePanel.createPanel(mAllocationBase, 379 PREFS_STACK_COL_CLASS, 380 PREFS_STACK_COL_METHOD, 381 PREFS_STACK_COL_FILE, 382 PREFS_STACK_COL_LINE, 383 PREFS_STACK_COL_NATIVE, 384 store); 385 386 // now setup the sash. 387 // form layout data 388 FormData data = new FormData(); 389 data.top = new FormAttachment(0, 0); 390 data.bottom = new FormAttachment(sash, 0); 391 data.left = new FormAttachment(0, 0); 392 data.right = new FormAttachment(100, 0); 393 topParent.setLayoutData(data); 394 395 final FormData sashData = new FormData(); 396 if (store != null && store.contains(PREFS_ALLOC_SASH)) { 397 sashData.top = new FormAttachment(0, store.getInt(PREFS_ALLOC_SASH)); 398 } else { 399 sashData.top = new FormAttachment(50,0); // 50% across 400 } 401 sashData.left = new FormAttachment(0, 0); 402 sashData.right = new FormAttachment(100, 0); 403 sash.setLayoutData(sashData); 404 405 data = new FormData(); 406 data.top = new FormAttachment(sash, 0); 407 data.bottom = new FormAttachment(100, 0); 408 data.left = new FormAttachment(0, 0); 409 data.right = new FormAttachment(100, 0); 410 mStackTraceTable.setLayoutData(data); 411 412 // allow resizes, but cap at minPanelWidth 413 sash.addListener(SWT.Selection, new Listener() { 414 public void handleEvent(Event e) { 415 Rectangle sashRect = sash.getBounds(); 416 Rectangle panelRect = mAllocationBase.getClientArea(); 417 int bottom = panelRect.height - sashRect.height - 100; 418 e.y = Math.max(Math.min(e.y, bottom), 100); 419 if (e.y != sashRect.y) { 420 sashData.top = new FormAttachment(0, e.y); 421 store.setValue(PREFS_ALLOC_SASH, e.y); 422 mAllocationBase.layout(); 423 } 424 } 425 }); 426 427 return mAllocationBase; 428 } 429 430 @Override 431 public void dispose() { 432 mSortUpImg.dispose(); 433 mSortDownImg.dispose(); 434 super.dispose(); 435 } 436 437 /** 438 * Sets the focus to the proper control inside the panel. 439 */ 440 @Override 441 public void setFocus() { 442 mAllocationTable.setFocus(); 443 } 444 445 /** 446 * Sent when an existing client information changed. 447 * <p/> 448 * This is sent from a non UI thread. 449 * @param client the updated client. 450 * @param changeMask the bit mask describing the changed properties. It can contain 451 * any of the following values: {@link Client#CHANGE_INFO}, {@link Client#CHANGE_NAME} 452 * {@link Client#CHANGE_DEBUGGER_STATUS}, {@link Client#CHANGE_THREAD_MODE}, 453 * {@link Client#CHANGE_THREAD_DATA}, {@link Client#CHANGE_HEAP_MODE}, 454 * {@link Client#CHANGE_HEAP_DATA}, {@link Client#CHANGE_NATIVE_HEAP_DATA} 455 * 456 * @see IClientChangeListener#clientChanged(Client, int) 457 */ 458 public void clientChanged(final Client client, int changeMask) { 459 if (client == getCurrentClient()) { 460 if ((changeMask & Client.CHANGE_HEAP_ALLOCATIONS) != 0) { 461 try { 462 mAllocationTable.getDisplay().asyncExec(new Runnable() { 463 public void run() { 464 mAllocationViewer.refresh(); 465 updateAllocationStackCall(); 466 } 467 }); 468 } catch (SWTException e) { 469 // widget is disposed, we do nothing 470 } 471 } else if ((changeMask & Client.CHANGE_HEAP_ALLOCATION_STATUS) != 0) { 472 try { 473 mAllocationTable.getDisplay().asyncExec(new Runnable() { 474 public void run() { 475 setUpButtons(true, client.getClientData().getAllocationStatus()); 476 } 477 }); 478 } catch (SWTException e) { 479 // widget is disposed, we do nothing 480 } 481 } 482 } 483 } 484 485 /** 486 * Sent when a new device is selected. The new device can be accessed 487 * with {@link #getCurrentDevice()}. 488 */ 489 @Override 490 public void deviceSelected() { 491 // pass 492 } 493 494 /** 495 * Sent when a new client is selected. The new client can be accessed 496 * with {@link #getCurrentClient()}. 497 */ 498 @Override 499 public void clientSelected() { 500 if (mAllocationTable.isDisposed()) { 501 return; 502 } 503 504 Client client = getCurrentClient(); 505 506 mStackTracePanel.setCurrentClient(client); 507 mStackTracePanel.setViewerInput(null); // always empty on client selection change. 508 509 if (client != null) { 510 setUpButtons(true /* enabled */, client.getClientData().getAllocationStatus()); 511 } else { 512 setUpButtons(false /* enabled */, AllocationTrackingStatus.OFF); 513 } 514 515 mAllocationViewer.setInput(client); 516 } 517 518 /** 519 * Updates the stack call of the currently selected thread. 520 * <p/> 521 * This <b>must</b> be called from the UI thread. 522 */ 523 private void updateAllocationStackCall() { 524 Client client = getCurrentClient(); 525 if (client != null) { 526 // get the current selection in the ThreadTable 527 AllocationInfo selectedAlloc = getAllocationSelection(null); 528 529 if (selectedAlloc != null) { 530 updateAllocationStackTrace(selectedAlloc); 531 } else { 532 updateAllocationStackTrace(null); 533 } 534 } 535 } 536 537 /** 538 * updates the stackcall of the specified allocation. If <code>null</code> the UI is emptied 539 * of current data. 540 * @param thread 541 */ 542 private void updateAllocationStackTrace(AllocationInfo alloc) { 543 mStackTracePanel.setViewerInput(alloc); 544 } 545 546 @Override 547 protected void setTableFocusListener() { 548 addTableToFocusListener(mAllocationTable); 549 addTableToFocusListener(mStackTraceTable); 550 } 551 552 /** 553 * Returns the current allocation selection or <code>null</code> if none is found. 554 * If a {@link ISelection} object is specified, the first {@link AllocationInfo} from this 555 * selection is returned, otherwise, the <code>ISelection</code> returned by 556 * {@link TableViewer#getSelection()} is used. 557 * @param selection the {@link ISelection} to use, or <code>null</code> 558 */ 559 private AllocationInfo getAllocationSelection(ISelection selection) { 560 if (selection == null) { 561 selection = mAllocationViewer.getSelection(); 562 } 563 564 if (selection instanceof IStructuredSelection) { 565 IStructuredSelection structuredSelection = (IStructuredSelection)selection; 566 Object object = structuredSelection.getFirstElement(); 567 if (object instanceof AllocationInfo) { 568 return (AllocationInfo)object; 569 } 570 } 571 572 return null; 573 } 574 575 /** 576 * 577 * @param enabled 578 * @param trackingStatus 579 */ 580 private void setUpButtons(boolean enabled, AllocationTrackingStatus trackingStatus) { 581 if (enabled) { 582 switch (trackingStatus) { 583 case UNKNOWN: 584 mEnableButton.setText("?"); 585 mEnableButton.setEnabled(false); 586 mRequestButton.setEnabled(false); 587 break; 588 case OFF: 589 mEnableButton.setText("Start Tracking"); 590 mEnableButton.setEnabled(true); 591 mRequestButton.setEnabled(false); 592 break; 593 case ON: 594 mEnableButton.setText("Stop Tracking"); 595 mEnableButton.setEnabled(true); 596 mRequestButton.setEnabled(true); 597 break; 598 } 599 } else { 600 mEnableButton.setEnabled(false); 601 mRequestButton.setEnabled(false); 602 mEnableButton.setText("Start Tracking"); 603 } 604 } 605 606 private void setSortColumn(final TableColumn column, SortMode sortMode) { 607 // set the new sort mode 608 mSorter.setSortMode(sortMode); 609 610 mAllocationTable.setRedraw(false); 611 612 // remove image from previous sort colum 613 if (mSortColumn != column) { 614 mSortColumn.setImage(null); 615 } 616 617 mSortColumn = column; 618 if (mSorter.isDescending()) { 619 mSortColumn.setImage(mSortDownImg); 620 } else { 621 mSortColumn.setImage(mSortUpImg); 622 } 623 624 mAllocationTable.setRedraw(true); 625 mAllocationViewer.refresh(); 626 } 627 628 private AllocationInfo[] getFilteredAllocations(AllocationInfo[] allocations, 629 String filterText) { 630 ArrayList<AllocationInfo> results = new ArrayList<AllocationInfo>(); 631 632 filterText = filterText.toLowerCase(); 633 boolean fullTrace = mTraceFilterCheck.getSelection(); 634 635 for (AllocationInfo info : allocations) { 636 if (info.filter(filterText, fullTrace)) { 637 results.add(info); 638 } 639 } 640 641 return results.toArray(new AllocationInfo[results.size()]); 642 } 643 644 } 645 646