1 /* 2 * Copyright (C) 2007 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.Client; 20 import com.android.ddmlib.ClientData; 21 import com.android.ddmlib.Log; 22 import com.android.ddmlib.NativeAllocationInfo; 23 import com.android.ddmlib.NativeLibraryMapInfo; 24 import com.android.ddmlib.NativeStackCallInfo; 25 import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener; 26 import com.android.ddmlib.HeapSegment.HeapSegmentElement; 27 import com.android.ddmuilib.annotation.WorkerThread; 28 29 import org.eclipse.jface.preference.IPreferenceStore; 30 import org.eclipse.swt.SWT; 31 import org.eclipse.swt.SWTException; 32 import org.eclipse.swt.custom.StackLayout; 33 import org.eclipse.swt.events.SelectionAdapter; 34 import org.eclipse.swt.events.SelectionEvent; 35 import org.eclipse.swt.graphics.Image; 36 import org.eclipse.swt.graphics.ImageData; 37 import org.eclipse.swt.graphics.PaletteData; 38 import org.eclipse.swt.graphics.RGB; 39 import org.eclipse.swt.graphics.Rectangle; 40 import org.eclipse.swt.layout.FormAttachment; 41 import org.eclipse.swt.layout.FormData; 42 import org.eclipse.swt.layout.FormLayout; 43 import org.eclipse.swt.layout.GridData; 44 import org.eclipse.swt.layout.GridLayout; 45 import org.eclipse.swt.widgets.Button; 46 import org.eclipse.swt.widgets.Combo; 47 import org.eclipse.swt.widgets.Composite; 48 import org.eclipse.swt.widgets.Control; 49 import org.eclipse.swt.widgets.Display; 50 import org.eclipse.swt.widgets.Event; 51 import org.eclipse.swt.widgets.FileDialog; 52 import org.eclipse.swt.widgets.Label; 53 import org.eclipse.swt.widgets.Listener; 54 import org.eclipse.swt.widgets.Sash; 55 import org.eclipse.swt.widgets.Table; 56 import org.eclipse.swt.widgets.TableItem; 57 58 import java.io.BufferedWriter; 59 import java.io.FileWriter; 60 import java.io.IOException; 61 import java.io.PrintWriter; 62 import java.text.DecimalFormat; 63 import java.text.NumberFormat; 64 import java.util.ArrayList; 65 import java.util.Collections; 66 import java.util.Comparator; 67 import java.util.HashMap; 68 import java.util.Iterator; 69 70 /** 71 * Panel with native heap information. 72 */ 73 public final class NativeHeapPanel extends BaseHeapPanel { 74 75 /** color palette and map legend. NATIVE is the last enum is a 0 based enum list, so we need 76 * Native+1 at least. We also need 2 more entries for free area and expansion area. */ 77 private static final int NUM_PALETTE_ENTRIES = HeapSegmentElement.KIND_NATIVE+2 +1; 78 private static final String[] mMapLegend = new String[NUM_PALETTE_ENTRIES]; 79 private static final PaletteData mMapPalette = createPalette(); 80 81 private static final int ALLOC_DISPLAY_ALL = 0; 82 private static final int ALLOC_DISPLAY_PRE_ZYGOTE = 1; 83 private static final int ALLOC_DISPLAY_POST_ZYGOTE = 2; 84 85 private Display mDisplay; 86 87 private Composite mBase; 88 89 private Label mUpdateStatus; 90 91 /** combo giving choice of what to display: all, pre-zygote, post-zygote */ 92 private Combo mAllocDisplayCombo; 93 94 private Button mFullUpdateButton; 95 96 // see CreateControl() 97 //private Button mDiffUpdateButton; 98 99 private Combo mDisplayModeCombo; 100 101 /** stack composite for mode (1-2) & 3 */ 102 private Composite mTopStackComposite; 103 104 private StackLayout mTopStackLayout; 105 106 /** stack composite for mode 1 & 2 */ 107 private Composite mAllocationStackComposite; 108 109 private StackLayout mAllocationStackLayout; 110 111 /** top level container for mode 1 & 2 */ 112 private Composite mTableModeControl; 113 114 /** top level object for the allocation mode */ 115 private Control mAllocationModeTop; 116 117 /** top level for the library mode */ 118 private Control mLibraryModeTopControl; 119 120 /** composite for page UI and total memory display */ 121 private Composite mPageUIComposite; 122 123 private Label mTotalMemoryLabel; 124 125 private Label mPageLabel; 126 127 private Button mPageNextButton; 128 129 private Button mPagePreviousButton; 130 131 private Table mAllocationTable; 132 133 private Table mLibraryTable; 134 135 private Table mLibraryAllocationTable; 136 137 private Table mDetailTable; 138 139 private Label mImage; 140 141 private int mAllocDisplayMode = ALLOC_DISPLAY_ALL; 142 143 /** 144 * pointer to current stackcall thread computation in order to quit it if 145 * required (new update requested) 146 */ 147 private StackCallThread mStackCallThread; 148 149 /** Current Library Allocation table fill thread. killed if selection changes */ 150 private FillTableThread mFillTableThread; 151 152 /** 153 * current client data. Used to access the malloc info when switching pages 154 * or selecting allocation to show stack call 155 */ 156 private ClientData mClientData; 157 158 /** 159 * client data from a previous display. used when asking for an "update & diff" 160 */ 161 private ClientData mBackUpClientData; 162 163 /** list of NativeAllocationInfo objects filled with the list from ClientData */ 164 private final ArrayList<NativeAllocationInfo> mAllocations = 165 new ArrayList<NativeAllocationInfo>(); 166 167 /** list of the {@link NativeAllocationInfo} being displayed based on the selection 168 * of {@link #mAllocDisplayCombo}. 169 */ 170 private final ArrayList<NativeAllocationInfo> mDisplayedAllocations = 171 new ArrayList<NativeAllocationInfo>(); 172 173 /** list of NativeAllocationInfo object kept as backup when doing an "update & diff" */ 174 private final ArrayList<NativeAllocationInfo> mBackUpAllocations = 175 new ArrayList<NativeAllocationInfo>(); 176 177 /** back up of the total memory, used when doing an "update & diff" */ 178 private int mBackUpTotalMemory; 179 180 private int mCurrentPage = 0; 181 182 private int mPageCount = 0; 183 184 /** 185 * list of allocation per Library. This is created from the list of 186 * NativeAllocationInfo objects that is stored in the ClientData object. Since we 187 * don't keep this list around, it is recomputed everytime the client 188 * changes. 189 */ 190 private final ArrayList<LibraryAllocations> mLibraryAllocations = 191 new ArrayList<LibraryAllocations>(); 192 193 /* args to setUpdateStatus() */ 194 private static final int NOT_SELECTED = 0; 195 196 private static final int NOT_ENABLED = 1; 197 198 private static final int ENABLED = 2; 199 200 private static final int DISPLAY_PER_PAGE = 20; 201 202 private static final String PREFS_ALLOCATION_SASH = "NHallocSash"; //$NON-NLS-1$ 203 private static final String PREFS_LIBRARY_SASH = "NHlibrarySash"; //$NON-NLS-1$ 204 private static final String PREFS_DETAIL_ADDRESS = "NHdetailAddress"; //$NON-NLS-1$ 205 private static final String PREFS_DETAIL_LIBRARY = "NHdetailLibrary"; //$NON-NLS-1$ 206 private static final String PREFS_DETAIL_METHOD = "NHdetailMethod"; //$NON-NLS-1$ 207 private static final String PREFS_DETAIL_FILE = "NHdetailFile"; //$NON-NLS-1$ 208 private static final String PREFS_DETAIL_LINE = "NHdetailLine"; //$NON-NLS-1$ 209 private static final String PREFS_ALLOC_TOTAL = "NHallocTotal"; //$NON-NLS-1$ 210 private static final String PREFS_ALLOC_COUNT = "NHallocCount"; //$NON-NLS-1$ 211 private static final String PREFS_ALLOC_SIZE = "NHallocSize"; //$NON-NLS-1$ 212 private static final String PREFS_ALLOC_LIBRARY = "NHallocLib"; //$NON-NLS-1$ 213 private static final String PREFS_ALLOC_METHOD = "NHallocMethod"; //$NON-NLS-1$ 214 private static final String PREFS_ALLOC_FILE = "NHallocFile"; //$NON-NLS-1$ 215 private static final String PREFS_LIB_LIBRARY = "NHlibLibrary"; //$NON-NLS-1$ 216 private static final String PREFS_LIB_SIZE = "NHlibSize"; //$NON-NLS-1$ 217 private static final String PREFS_LIB_COUNT = "NHlibCount"; //$NON-NLS-1$ 218 private static final String PREFS_LIBALLOC_TOTAL = "NHlibAllocTotal"; //$NON-NLS-1$ 219 private static final String PREFS_LIBALLOC_COUNT = "NHlibAllocCount"; //$NON-NLS-1$ 220 private static final String PREFS_LIBALLOC_SIZE = "NHlibAllocSize"; //$NON-NLS-1$ 221 private static final String PREFS_LIBALLOC_METHOD = "NHlibAllocMethod"; //$NON-NLS-1$ 222 223 /** static formatter object to format all numbers as #,### */ 224 private static DecimalFormat sFormatter; 225 static { 226 sFormatter = (DecimalFormat)NumberFormat.getInstance(); 227 if (sFormatter != null) 228 sFormatter = new DecimalFormat("#,###"); 229 else 230 sFormatter.applyPattern("#,###"); 231 } 232 233 234 /** 235 * caching mechanism to avoid recomputing the backtrace for a particular 236 * address several times. 237 */ 238 private HashMap<Long, NativeStackCallInfo> mSourceCache = 239 new HashMap<Long,NativeStackCallInfo>(); 240 private long mTotalSize; 241 private Button mSaveButton; 242 private Button mSymbolsButton; 243 244 /** 245 * thread class to convert the address call into method, file and line 246 * number in the background. 247 */ 248 private class StackCallThread extends BackgroundThread { 249 private ClientData mClientData; 250 StackCallThread(ClientData cd)251 public StackCallThread(ClientData cd) { 252 mClientData = cd; 253 } 254 getClientData()255 public ClientData getClientData() { 256 return mClientData; 257 } 258 259 @Override run()260 public void run() { 261 // loop through all the NativeAllocationInfo and init them 262 Iterator<NativeAllocationInfo> iter = mAllocations.iterator(); 263 int total = mAllocations.size(); 264 int count = 0; 265 while (iter.hasNext()) { 266 267 if (isQuitting()) 268 return; 269 270 NativeAllocationInfo info = iter.next(); 271 if (info.isStackCallResolved() == false) { 272 final Long[] list = info.getStackCallAddresses(); 273 final int size = list.length; 274 275 ArrayList<NativeStackCallInfo> resolvedStackCall = 276 new ArrayList<NativeStackCallInfo>(); 277 278 for (int i = 0; i < size; i++) { 279 long addr = list[i]; 280 281 // first check if the addr has already been converted. 282 NativeStackCallInfo source = mSourceCache.get(addr); 283 284 // if not we convert it 285 if (source == null) { 286 source = sourceForAddr(addr); 287 mSourceCache.put(addr, source); 288 } 289 290 resolvedStackCall.add(source); 291 } 292 293 info.setResolvedStackCall(resolvedStackCall); 294 } 295 // after every DISPLAY_PER_PAGE we ask for a ui refresh, unless 296 // we reach total, since we also do it after the loop 297 // (only an issue in case we have a perfect number of page) 298 count++; 299 if ((count % DISPLAY_PER_PAGE) == 0 && count != total) { 300 if (updateNHAllocationStackCalls(mClientData, count) == false) { 301 // looks like the app is quitting, so we just 302 // stopped the thread 303 return; 304 } 305 } 306 } 307 308 updateNHAllocationStackCalls(mClientData, count); 309 } 310 sourceForAddr(long addr)311 private NativeStackCallInfo sourceForAddr(long addr) { 312 NativeLibraryMapInfo library = getLibraryFor(addr); 313 314 if (library != null) { 315 316 Addr2Line process = Addr2Line.getProcess(library.getLibraryName()); 317 if (process != null) { 318 // remove the base of the library address 319 long value = addr - library.getStartAddress(); 320 NativeStackCallInfo info = process.getAddress(value); 321 if (info != null) { 322 return info; 323 } 324 } 325 } 326 327 return new NativeStackCallInfo(library != null ? library.getLibraryName() : null, 328 Long.toHexString(addr), ""); 329 } 330 getLibraryFor(long addr)331 private NativeLibraryMapInfo getLibraryFor(long addr) { 332 Iterator<NativeLibraryMapInfo> it = mClientData.getNativeLibraryMapInfo(); 333 334 while (it.hasNext()) { 335 NativeLibraryMapInfo info = it.next(); 336 337 if (info.isWithinLibrary(addr)) { 338 return info; 339 } 340 } 341 342 Log.d("ddm-nativeheap", "Failed finding Library for " + Long.toHexString(addr)); 343 return null; 344 } 345 346 /** 347 * update the Native Heap panel with the amount of allocation for which the 348 * stack call has been computed. This is called from a non UI thread, but 349 * will be executed in the UI thread. 350 * 351 * @param count the amount of allocation 352 * @return false if the display was disposed and the update couldn't happen 353 */ updateNHAllocationStackCalls(final ClientData clientData, final int count)354 private boolean updateNHAllocationStackCalls(final ClientData clientData, final int count) { 355 if (mDisplay.isDisposed() == false) { 356 mDisplay.asyncExec(new Runnable() { 357 public void run() { 358 updateAllocationStackCalls(clientData, count); 359 } 360 }); 361 return true; 362 } 363 return false; 364 } 365 } 366 367 private class FillTableThread extends BackgroundThread { 368 private LibraryAllocations mLibAlloc; 369 370 private int mMax; 371 FillTableThread(LibraryAllocations liballoc, int m)372 public FillTableThread(LibraryAllocations liballoc, int m) { 373 mLibAlloc = liballoc; 374 mMax = m; 375 } 376 377 @Override run()378 public void run() { 379 for (int i = mMax; i > 0 && isQuitting() == false; i -= 10) { 380 updateNHLibraryAllocationTable(mLibAlloc, mMax - i, mMax - i + 10); 381 } 382 } 383 384 /** 385 * updates the library allocation table in the Native Heap panel. This is 386 * called from a non UI thread, but will be executed in the UI thread. 387 * 388 * @param liballoc the current library allocation object being displayed 389 * @param start start index of items that need to be displayed 390 * @param end end index of the items that need to be displayed 391 */ updateNHLibraryAllocationTable(final LibraryAllocations libAlloc, final int start, final int end)392 private void updateNHLibraryAllocationTable(final LibraryAllocations libAlloc, 393 final int start, final int end) { 394 if (mDisplay.isDisposed() == false) { 395 mDisplay.asyncExec(new Runnable() { 396 public void run() { 397 updateLibraryAllocationTable(libAlloc, start, end); 398 } 399 }); 400 } 401 402 } 403 } 404 405 /** class to aggregate allocations per library */ 406 public static class LibraryAllocations { 407 private String mLibrary; 408 409 private final ArrayList<NativeAllocationInfo> mLibAllocations = 410 new ArrayList<NativeAllocationInfo>(); 411 412 private int mSize; 413 414 private int mCount; 415 416 /** construct the aggregate object for a library */ LibraryAllocations(final String lib)417 public LibraryAllocations(final String lib) { 418 mLibrary = lib; 419 } 420 421 /** get the library name */ getLibrary()422 public String getLibrary() { 423 return mLibrary; 424 } 425 426 /** add a NativeAllocationInfo object to this aggregate object */ addAllocation(NativeAllocationInfo info)427 public void addAllocation(NativeAllocationInfo info) { 428 mLibAllocations.add(info); 429 } 430 431 /** get an iterator on the NativeAllocationInfo objects */ getAllocations()432 public Iterator<NativeAllocationInfo> getAllocations() { 433 return mLibAllocations.iterator(); 434 } 435 436 /** get a NativeAllocationInfo object by index */ getAllocation(int index)437 public NativeAllocationInfo getAllocation(int index) { 438 return mLibAllocations.get(index); 439 } 440 441 /** returns the NativeAllocationInfo object count */ getAllocationSize()442 public int getAllocationSize() { 443 return mLibAllocations.size(); 444 } 445 446 /** returns the total allocation size */ getSize()447 public int getSize() { 448 return mSize; 449 } 450 451 /** returns the number of allocations */ getCount()452 public int getCount() { 453 return mCount; 454 } 455 456 /** 457 * compute the allocation count and size for allocation objects added 458 * through <code>addAllocation()</code>, and sort the objects by 459 * total allocation size. 460 */ computeAllocationSizeAndCount()461 public void computeAllocationSizeAndCount() { 462 mSize = 0; 463 mCount = 0; 464 for (NativeAllocationInfo info : mLibAllocations) { 465 mCount += info.getAllocationCount(); 466 mSize += info.getAllocationCount() * info.getSize(); 467 } 468 Collections.sort(mLibAllocations, new Comparator<NativeAllocationInfo>() { 469 public int compare(NativeAllocationInfo o1, NativeAllocationInfo o2) { 470 return o2.getAllocationCount() * o2.getSize() - 471 o1.getAllocationCount() * o1.getSize(); 472 } 473 }); 474 } 475 } 476 477 /** 478 * Create our control(s). 479 */ 480 @Override createControl(Composite parent)481 protected Control createControl(Composite parent) { 482 483 mDisplay = parent.getDisplay(); 484 485 mBase = new Composite(parent, SWT.NONE); 486 GridLayout gl = new GridLayout(1, false); 487 gl.horizontalSpacing = 0; 488 gl.verticalSpacing = 0; 489 mBase.setLayout(gl); 490 mBase.setLayoutData(new GridData(GridData.FILL_BOTH)); 491 492 // composite for <update btn> <status> 493 Composite tmp = new Composite(mBase, SWT.NONE); 494 tmp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 495 tmp.setLayout(gl = new GridLayout(2, false)); 496 gl.marginWidth = gl.marginHeight = 0; 497 498 mFullUpdateButton = new Button(tmp, SWT.NONE); 499 mFullUpdateButton.setText("Full Update"); 500 mFullUpdateButton.addSelectionListener(new SelectionAdapter() { 501 @Override 502 public void widgetSelected(SelectionEvent e) { 503 mBackUpClientData = null; 504 mDisplayModeCombo.setEnabled(false); 505 mSaveButton.setEnabled(false); 506 emptyTables(); 507 // if we already have a stack call computation for this 508 // client 509 // we stop it 510 if (mStackCallThread != null && 511 mStackCallThread.getClientData() == mClientData) { 512 mStackCallThread.quit(); 513 mStackCallThread = null; 514 } 515 mLibraryAllocations.clear(); 516 Client client = getCurrentClient(); 517 if (client != null) { 518 client.requestNativeHeapInformation(); 519 } 520 } 521 }); 522 523 mUpdateStatus = new Label(tmp, SWT.NONE); 524 mUpdateStatus.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 525 526 // top layout for the combos and oter controls on the right. 527 Composite top_layout = new Composite(mBase, SWT.NONE); 528 top_layout.setLayout(gl = new GridLayout(4, false)); 529 gl.marginWidth = gl.marginHeight = 0; 530 531 new Label(top_layout, SWT.NONE).setText("Show:"); 532 533 mAllocDisplayCombo = new Combo(top_layout, SWT.DROP_DOWN | SWT.READ_ONLY); 534 mAllocDisplayCombo.setLayoutData(new GridData( 535 GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL)); 536 mAllocDisplayCombo.add("All Allocations"); 537 mAllocDisplayCombo.add("Pre-Zygote Allocations"); 538 mAllocDisplayCombo.add("Zygote Child Allocations (Z)"); 539 mAllocDisplayCombo.addSelectionListener(new SelectionAdapter() { 540 @Override 541 public void widgetSelected(SelectionEvent e) { 542 onAllocDisplayChange(); 543 } 544 }); 545 mAllocDisplayCombo.select(0); 546 547 // separator 548 Label separator = new Label(top_layout, SWT.SEPARATOR | SWT.VERTICAL); 549 GridData gd; 550 separator.setLayoutData(gd = new GridData( 551 GridData.VERTICAL_ALIGN_FILL | GridData.GRAB_VERTICAL)); 552 gd.heightHint = 0; 553 gd.verticalSpan = 2; 554 555 mSaveButton = new Button(top_layout, SWT.PUSH); 556 mSaveButton.setText("Save..."); 557 mSaveButton.setEnabled(false); 558 mSaveButton.addSelectionListener(new SelectionAdapter() { 559 @Override 560 public void widgetSelected(SelectionEvent e) { 561 FileDialog fileDialog = new FileDialog(mBase.getShell(), SWT.SAVE); 562 563 fileDialog.setText("Save Allocations"); 564 fileDialog.setFileName("allocations.txt"); 565 566 String fileName = fileDialog.open(); 567 if (fileName != null) { 568 saveAllocations(fileName); 569 } 570 } 571 }); 572 573 /* 574 * TODO: either fix the diff mechanism or remove it altogether. 575 mDiffUpdateButton = new Button(top_layout, SWT.NONE); 576 mDiffUpdateButton.setText("Update && Diff"); 577 mDiffUpdateButton.addSelectionListener(new SelectionAdapter() { 578 @Override 579 public void widgetSelected(SelectionEvent e) { 580 // since this is an update and diff, we need to store the 581 // current list 582 // of mallocs 583 mBackUpAllocations.clear(); 584 mBackUpAllocations.addAll(mAllocations); 585 mBackUpClientData = mClientData; 586 mBackUpTotalMemory = mClientData.getTotalNativeMemory(); 587 588 mDisplayModeCombo.setEnabled(false); 589 emptyTables(); 590 // if we already have a stack call computation for this 591 // client 592 // we stop it 593 if (mStackCallThread != null && 594 mStackCallThread.getClientData() == mClientData) { 595 mStackCallThread.quit(); 596 mStackCallThread = null; 597 } 598 mLibraryAllocations.clear(); 599 Client client = getCurrentClient(); 600 if (client != null) { 601 client.requestNativeHeapInformation(); 602 } 603 } 604 }); 605 */ 606 607 Label l = new Label(top_layout, SWT.NONE); 608 l.setText("Display:"); 609 610 mDisplayModeCombo = new Combo(top_layout, SWT.DROP_DOWN | SWT.READ_ONLY); 611 mDisplayModeCombo.setLayoutData(new GridData( 612 GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL)); 613 mDisplayModeCombo.setItems(new String[] { "Allocation List", "By Libraries" }); 614 mDisplayModeCombo.select(0); 615 mDisplayModeCombo.addSelectionListener(new SelectionAdapter() { 616 @Override 617 public void widgetSelected(SelectionEvent e) { 618 switchDisplayMode(); 619 } 620 }); 621 mDisplayModeCombo.setEnabled(false); 622 623 mSymbolsButton = new Button(top_layout, SWT.PUSH); 624 mSymbolsButton.setText("Load Symbols"); 625 mSymbolsButton.setEnabled(false); 626 627 628 // create a composite that will contains the actual content composites, 629 // in stack mode layout. 630 // This top level composite contains 2 other composites. 631 // * one for both Allocations and Libraries mode 632 // * one for flat mode (which is gone for now) 633 634 mTopStackComposite = new Composite(mBase, SWT.NONE); 635 mTopStackComposite.setLayout(mTopStackLayout = new StackLayout()); 636 mTopStackComposite.setLayoutData(new GridData(GridData.FILL_BOTH)); 637 638 // create 1st and 2nd modes 639 createTableDisplay(mTopStackComposite); 640 641 mTopStackLayout.topControl = mTableModeControl; 642 mTopStackComposite.layout(); 643 644 setUpdateStatus(NOT_SELECTED); 645 646 // Work in progress 647 // TODO add image display of native heap. 648 //mImage = new Label(mBase, SWT.NONE); 649 650 mBase.pack(); 651 652 return mBase; 653 } 654 655 /** 656 * Sets the focus to the proper control inside the panel. 657 */ 658 @Override setFocus()659 public void setFocus() { 660 // TODO 661 } 662 663 664 /** 665 * Sent when an existing client information changed. 666 * <p/> 667 * This is sent from a non UI thread. 668 * @param client the updated client. 669 * @param changeMask the bit mask describing the changed properties. It can contain 670 * any of the following values: {@link Client#CHANGE_INFO}, {@link Client#CHANGE_NAME} 671 * {@link Client#CHANGE_DEBUGGER_STATUS}, {@link Client#CHANGE_THREAD_MODE}, 672 * {@link Client#CHANGE_THREAD_DATA}, {@link Client#CHANGE_HEAP_MODE}, 673 * {@link Client#CHANGE_HEAP_DATA}, {@link Client#CHANGE_NATIVE_HEAP_DATA} 674 * 675 * @see IClientChangeListener#clientChanged(Client, int) 676 */ clientChanged(final Client client, int changeMask)677 public void clientChanged(final Client client, int changeMask) { 678 if (client == getCurrentClient()) { 679 if ((changeMask & Client.CHANGE_NATIVE_HEAP_DATA) == Client.CHANGE_NATIVE_HEAP_DATA) { 680 if (mBase.isDisposed()) 681 return; 682 683 mBase.getDisplay().asyncExec(new Runnable() { 684 public void run() { 685 clientSelected(); 686 } 687 }); 688 } 689 } 690 } 691 692 /** 693 * Sent when a new device is selected. The new device can be accessed 694 * with {@link #getCurrentDevice()}. 695 */ 696 @Override deviceSelected()697 public void deviceSelected() { 698 // pass 699 } 700 701 /** 702 * Sent when a new client is selected. The new client can be accessed 703 * with {@link #getCurrentClient()}. 704 */ 705 @Override clientSelected()706 public void clientSelected() { 707 if (mBase.isDisposed()) 708 return; 709 710 Client client = getCurrentClient(); 711 712 mDisplayModeCombo.setEnabled(false); 713 emptyTables(); 714 715 Log.d("ddms", "NativeHeapPanel: changed " + client); 716 717 if (client != null) { 718 ClientData cd = client.getClientData(); 719 mClientData = cd; 720 721 // if (cd.getShowHeapUpdates()) 722 setUpdateStatus(ENABLED); 723 // else 724 // setUpdateStatus(NOT_ENABLED); 725 726 initAllocationDisplay(); 727 728 //renderBitmap(cd); 729 } else { 730 mClientData = null; 731 setUpdateStatus(NOT_SELECTED); 732 } 733 734 mBase.pack(); 735 } 736 737 /** 738 * Update the UI with the newly compute stack calls, unless the UI switched 739 * to a different client. 740 * 741 * @param cd the ClientData for which the stack call are being computed. 742 * @param count the current count of allocations for which the stack calls 743 * have been computed. 744 */ 745 @WorkerThread updateAllocationStackCalls(ClientData cd, int count)746 public void updateAllocationStackCalls(ClientData cd, int count) { 747 // we have to check that the panel still shows the same clientdata than 748 // the thread is computing for. 749 if (cd == mClientData) { 750 751 int total = mAllocations.size(); 752 753 if (count == total) { 754 // we're done: do something 755 mDisplayModeCombo.setEnabled(true); 756 mSaveButton.setEnabled(true); 757 758 mStackCallThread = null; 759 } else { 760 // work in progress, update the progress bar. 761 // mUiThread.setStatusLine("Computing stack call: " + count 762 // + "/" + total); 763 } 764 765 // FIXME: attempt to only update when needed. 766 // Because the number of pages is not related to mAllocations.size() anymore 767 // due to pre-zygote/post-zygote display, update all the time. 768 // At some point we should remove the pages anyway, since it's getting computed 769 // really fast now. 770 // if ((mCurrentPage + 1) * DISPLAY_PER_PAGE == count 771 // || (count == total && mCurrentPage == mPageCount - 1)) { 772 try { 773 // get the current selection of the allocation 774 int index = mAllocationTable.getSelectionIndex(); 775 NativeAllocationInfo info = null; 776 777 if (index != -1) { 778 info = (NativeAllocationInfo)mAllocationTable.getItem(index).getData(); 779 } 780 781 // empty the table 782 emptyTables(); 783 784 // fill it again 785 fillAllocationTable(); 786 787 // reselect 788 mAllocationTable.setSelection(index); 789 790 // display detail table if needed 791 if (info != null) { 792 fillDetailTable(info); 793 } 794 } catch (SWTException e) { 795 if (mAllocationTable.isDisposed()) { 796 // looks like the table is disposed. Let's ignore it. 797 } else { 798 throw e; 799 } 800 } 801 802 } else { 803 // old client still running. doesn't really matter. 804 } 805 } 806 807 @Override setTableFocusListener()808 protected void setTableFocusListener() { 809 addTableToFocusListener(mAllocationTable); 810 addTableToFocusListener(mLibraryTable); 811 addTableToFocusListener(mLibraryAllocationTable); 812 addTableToFocusListener(mDetailTable); 813 } 814 onAllocDisplayChange()815 protected void onAllocDisplayChange() { 816 mAllocDisplayMode = mAllocDisplayCombo.getSelectionIndex(); 817 818 // create the new list 819 updateAllocDisplayList(); 820 821 updateTotalMemoryDisplay(); 822 823 // reset the ui. 824 mCurrentPage = 0; 825 updatePageUI(); 826 switchDisplayMode(); 827 } 828 updateAllocDisplayList()829 private void updateAllocDisplayList() { 830 mTotalSize = 0; 831 mDisplayedAllocations.clear(); 832 for (NativeAllocationInfo info : mAllocations) { 833 if (mAllocDisplayMode == ALLOC_DISPLAY_ALL || 834 (mAllocDisplayMode == ALLOC_DISPLAY_PRE_ZYGOTE ^ info.isZygoteChild())) { 835 mDisplayedAllocations.add(info); 836 mTotalSize += info.getSize() * info.getAllocationCount(); 837 } else { 838 // skip this item 839 continue; 840 } 841 } 842 843 int count = mDisplayedAllocations.size(); 844 845 mPageCount = count / DISPLAY_PER_PAGE; 846 847 // need to add a page for the rest of the div 848 if ((count % DISPLAY_PER_PAGE) > 0) { 849 mPageCount++; 850 } 851 } 852 updateTotalMemoryDisplay()853 private void updateTotalMemoryDisplay() { 854 switch (mAllocDisplayMode) { 855 case ALLOC_DISPLAY_ALL: 856 mTotalMemoryLabel.setText(String.format("Total Memory: %1$s Bytes", 857 sFormatter.format(mTotalSize))); 858 break; 859 case ALLOC_DISPLAY_PRE_ZYGOTE: 860 mTotalMemoryLabel.setText(String.format("Zygote Memory: %1$s Bytes", 861 sFormatter.format(mTotalSize))); 862 break; 863 case ALLOC_DISPLAY_POST_ZYGOTE: 864 mTotalMemoryLabel.setText(String.format("Post-zygote Memory: %1$s Bytes", 865 sFormatter.format(mTotalSize))); 866 break; 867 } 868 } 869 870 switchDisplayMode()871 private void switchDisplayMode() { 872 switch (mDisplayModeCombo.getSelectionIndex()) { 873 case 0: {// allocations 874 mTopStackLayout.topControl = mTableModeControl; 875 mAllocationStackLayout.topControl = mAllocationModeTop; 876 mAllocationStackComposite.layout(); 877 mTopStackComposite.layout(); 878 emptyTables(); 879 fillAllocationTable(); 880 } 881 break; 882 case 1: {// libraries 883 mTopStackLayout.topControl = mTableModeControl; 884 mAllocationStackLayout.topControl = mLibraryModeTopControl; 885 mAllocationStackComposite.layout(); 886 mTopStackComposite.layout(); 887 emptyTables(); 888 fillLibraryTable(); 889 } 890 break; 891 } 892 } 893 initAllocationDisplay()894 private void initAllocationDisplay() { 895 mAllocations.clear(); 896 mAllocations.addAll(mClientData.getNativeAllocationList()); 897 898 updateAllocDisplayList(); 899 900 // if we have a previous clientdata and it matches the current one. we 901 // do a diff between the new list and the old one. 902 if (mBackUpClientData != null && mBackUpClientData == mClientData) { 903 904 ArrayList<NativeAllocationInfo> add = new ArrayList<NativeAllocationInfo>(); 905 906 // we go through the list of NativeAllocationInfo in the new list and check if 907 // there's one with the same exact data (size, allocation, count and 908 // stackcall addresses) in the old list. 909 // if we don't find any, we add it to the "add" list 910 for (NativeAllocationInfo mi : mAllocations) { 911 boolean found = false; 912 for (NativeAllocationInfo old_mi : mBackUpAllocations) { 913 if (mi.equals(old_mi)) { 914 found = true; 915 break; 916 } 917 } 918 if (found == false) { 919 add.add(mi); 920 } 921 } 922 923 // put the result in mAllocations 924 mAllocations.clear(); 925 mAllocations.addAll(add); 926 927 // display the difference in memory usage. This is computed 928 // calculating the memory usage of the objects in mAllocations. 929 int count = 0; 930 for (NativeAllocationInfo allocInfo : mAllocations) { 931 count += allocInfo.getSize() * allocInfo.getAllocationCount(); 932 } 933 934 mTotalMemoryLabel.setText(String.format("Memory Difference: %1$s Bytes", 935 sFormatter.format(count))); 936 } 937 else { 938 // display the full memory usage 939 updateTotalMemoryDisplay(); 940 //mDiffUpdateButton.setEnabled(mClientData.getTotalNativeMemory() > 0); 941 } 942 mTotalMemoryLabel.pack(); 943 944 // update the page ui 945 mDisplayModeCombo.select(0); 946 947 mLibraryAllocations.clear(); 948 949 // reset to first page 950 mCurrentPage = 0; 951 952 // update the label 953 updatePageUI(); 954 955 // now fill the allocation Table with the current page 956 switchDisplayMode(); 957 958 // start the thread to compute the stack calls 959 if (mAllocations.size() > 0) { 960 mStackCallThread = new StackCallThread(mClientData); 961 mStackCallThread.start(); 962 } 963 } 964 updatePageUI()965 private void updatePageUI() { 966 967 // set the label and pack to update the layout, otherwise 968 // the label will be cut off if the new size is bigger 969 if (mPageCount == 0) { 970 mPageLabel.setText("0 of 0 allocations."); 971 } else { 972 StringBuffer buffer = new StringBuffer(); 973 // get our starting index 974 int start = (mCurrentPage * DISPLAY_PER_PAGE) + 1; 975 // end index, taking into account the last page can be half full 976 int count = mDisplayedAllocations.size(); 977 int end = Math.min(start + DISPLAY_PER_PAGE - 1, count); 978 buffer.append(sFormatter.format(start)); 979 buffer.append(" - "); 980 buffer.append(sFormatter.format(end)); 981 buffer.append(" of "); 982 buffer.append(sFormatter.format(count)); 983 buffer.append(" allocations."); 984 mPageLabel.setText(buffer.toString()); 985 } 986 987 // handle the button enabled state. 988 mPagePreviousButton.setEnabled(mCurrentPage > 0); 989 // reminder: mCurrentPage starts at 0. 990 mPageNextButton.setEnabled(mCurrentPage < mPageCount - 1); 991 992 mPageLabel.pack(); 993 mPageUIComposite.pack(); 994 995 } 996 997 private void fillAllocationTable() { 998 // get the count 999 int count = mDisplayedAllocations.size(); 1000 1001 // get our starting index 1002 int start = mCurrentPage * DISPLAY_PER_PAGE; 1003 1004 // loop for DISPLAY_PER_PAGE or till we reach count 1005 int end = start + DISPLAY_PER_PAGE; 1006 1007 for (int i = start; i < end && i < count; i++) { 1008 NativeAllocationInfo info = mDisplayedAllocations.get(i); 1009 1010 TableItem item = null; 1011 1012 if (mAllocDisplayMode == ALLOC_DISPLAY_ALL) { 1013 item = new TableItem(mAllocationTable, SWT.NONE); 1014 item.setText(0, (info.isZygoteChild() ? "Z " : "") + 1015 sFormatter.format(info.getSize() * info.getAllocationCount())); 1016 item.setText(1, sFormatter.format(info.getAllocationCount())); 1017 item.setText(2, sFormatter.format(info.getSize())); 1018 } else if (mAllocDisplayMode == ALLOC_DISPLAY_PRE_ZYGOTE ^ info.isZygoteChild()) { 1019 item = new TableItem(mAllocationTable, SWT.NONE); 1020 item.setText(0, sFormatter.format(info.getSize() * info.getAllocationCount())); 1021 item.setText(1, sFormatter.format(info.getAllocationCount())); 1022 item.setText(2, sFormatter.format(info.getSize())); 1023 } else { 1024 // skip this item 1025 continue; 1026 } 1027 1028 item.setData(info); 1029 1030 NativeStackCallInfo bti = info.getRelevantStackCallInfo(); 1031 if (bti != null) { 1032 String lib = bti.getLibraryName(); 1033 String method = bti.getMethodName(); 1034 String source = bti.getSourceFile(); 1035 if (lib != null) 1036 item.setText(3, lib); 1037 if (method != null) 1038 item.setText(4, method); 1039 if (source != null) 1040 item.setText(5, source); 1041 } 1042 } 1043 } 1044 1045 private void fillLibraryTable() { 1046 // fill the library table 1047 sortAllocationsPerLibrary(); 1048 1049 for (LibraryAllocations liballoc : mLibraryAllocations) { 1050 if (liballoc != null) { 1051 TableItem item = new TableItem(mLibraryTable, SWT.NONE); 1052 String lib = liballoc.getLibrary(); 1053 item.setText(0, lib != null ? lib : ""); 1054 item.setText(1, sFormatter.format(liballoc.getSize())); 1055 item.setText(2, sFormatter.format(liballoc.getCount())); 1056 } 1057 } 1058 } 1059 1060 private void fillLibraryAllocationTable() { 1061 mLibraryAllocationTable.removeAll(); 1062 mDetailTable.removeAll(); 1063 int index = mLibraryTable.getSelectionIndex(); 1064 if (index != -1) { 1065 LibraryAllocations liballoc = mLibraryAllocations.get(index); 1066 // start a thread that will fill table 10 at a time to keep the ui 1067 // responsive, but first we kill the previous one if there was one 1068 if (mFillTableThread != null) { 1069 mFillTableThread.quit(); 1070 } 1071 mFillTableThread = new FillTableThread(liballoc, 1072 liballoc.getAllocationSize()); 1073 mFillTableThread.start(); 1074 } 1075 } 1076 1077 public void updateLibraryAllocationTable(LibraryAllocations liballoc, 1078 int start, int end) { 1079 try { 1080 if (mLibraryTable.isDisposed() == false) { 1081 int index = mLibraryTable.getSelectionIndex(); 1082 if (index != -1) { 1083 LibraryAllocations newliballoc = mLibraryAllocations.get( 1084 index); 1085 if (newliballoc == liballoc) { 1086 int count = liballoc.getAllocationSize(); 1087 for (int i = start; i < end && i < count; i++) { 1088 NativeAllocationInfo info = liballoc.getAllocation(i); 1089 1090 TableItem item = new TableItem( 1091 mLibraryAllocationTable, SWT.NONE); 1092 item.setText(0, sFormatter.format( 1093 info.getSize() * info.getAllocationCount())); 1094 item.setText(1, sFormatter.format(info.getAllocationCount())); 1095 item.setText(2, sFormatter.format(info.getSize())); 1096 1097 NativeStackCallInfo stackCallInfo = info.getRelevantStackCallInfo(); 1098 if (stackCallInfo != null) { 1099 item.setText(3, stackCallInfo.getMethodName()); 1100 } 1101 } 1102 } else { 1103 // we should quit the thread 1104 if (mFillTableThread != null) { 1105 mFillTableThread.quit(); 1106 mFillTableThread = null; 1107 } 1108 } 1109 } 1110 } 1111 } catch (SWTException e) { 1112 Log.e("ddms", "error when updating the library allocation table"); 1113 } 1114 } 1115 1116 private void fillDetailTable(final NativeAllocationInfo mi) { 1117 mDetailTable.removeAll(); 1118 mDetailTable.setRedraw(false); 1119 1120 try { 1121 // populate the detail Table with the back trace 1122 Long[] addresses = mi.getStackCallAddresses(); 1123 NativeStackCallInfo[] resolvedStackCall = mi.getResolvedStackCall(); 1124 1125 if (resolvedStackCall == null) { 1126 return; 1127 } 1128 1129 for (int i = 0 ; i < resolvedStackCall.length ; i++) { 1130 if (addresses[i] == null || addresses[i].longValue() == 0) { 1131 continue; 1132 } 1133 1134 long addr = addresses[i].longValue(); 1135 NativeStackCallInfo source = resolvedStackCall[i]; 1136 1137 TableItem item = new TableItem(mDetailTable, SWT.NONE); 1138 item.setText(0, String.format("%08x", addr)); //$NON-NLS-1$ 1139 1140 String libraryName = source.getLibraryName(); 1141 String methodName = source.getMethodName(); 1142 String sourceFile = source.getSourceFile(); 1143 int lineNumber = source.getLineNumber(); 1144 1145 if (libraryName != null) 1146 item.setText(1, libraryName); 1147 if (methodName != null) 1148 item.setText(2, methodName); 1149 if (sourceFile != null) 1150 item.setText(3, sourceFile); 1151 if (lineNumber != -1) 1152 item.setText(4, Integer.toString(lineNumber)); 1153 } 1154 } finally { 1155 mDetailTable.setRedraw(true); 1156 } 1157 } 1158 1159 /* 1160 * Are updates enabled? 1161 */ 1162 private void setUpdateStatus(int status) { 1163 switch (status) { 1164 case NOT_SELECTED: 1165 mUpdateStatus.setText("Select a client to see heap info"); 1166 mAllocDisplayCombo.setEnabled(false); 1167 mFullUpdateButton.setEnabled(false); 1168 //mDiffUpdateButton.setEnabled(false); 1169 break; 1170 case NOT_ENABLED: 1171 mUpdateStatus.setText("Heap updates are " + "NOT ENABLED for this client"); 1172 mAllocDisplayCombo.setEnabled(false); 1173 mFullUpdateButton.setEnabled(false); 1174 //mDiffUpdateButton.setEnabled(false); 1175 break; 1176 case ENABLED: 1177 mUpdateStatus.setText("Press 'Full Update' to retrieve " + "latest data"); 1178 mAllocDisplayCombo.setEnabled(true); 1179 mFullUpdateButton.setEnabled(true); 1180 //mDiffUpdateButton.setEnabled(true); 1181 break; 1182 default: 1183 throw new RuntimeException(); 1184 } 1185 1186 mUpdateStatus.pack(); 1187 } 1188 1189 /** 1190 * Create the Table display. This includes a "detail" Table in the bottom 1191 * half and 2 modes in the top half: allocation Table and 1192 * library+allocations Tables. 1193 * 1194 * @param base the top parent to create the display into 1195 */ 1196 private void createTableDisplay(Composite base) { 1197 final int minPanelWidth = 60; 1198 1199 final IPreferenceStore prefs = DdmUiPreferences.getStore(); 1200 1201 // top level composite for mode 1 & 2 1202 mTableModeControl = new Composite(base, SWT.NONE); 1203 GridLayout gl = new GridLayout(1, false); 1204 gl.marginLeft = gl.marginRight = gl.marginTop = gl.marginBottom = 0; 1205 mTableModeControl.setLayout(gl); 1206 mTableModeControl.setLayoutData(new GridData(GridData.FILL_BOTH)); 1207 1208 mTotalMemoryLabel = new Label(mTableModeControl, SWT.NONE); 1209 mTotalMemoryLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 1210 mTotalMemoryLabel.setText("Total Memory: 0 Bytes"); 1211 1212 // the top half of these modes is dynamic 1213 1214 final Composite sash_composite = new Composite(mTableModeControl, 1215 SWT.NONE); 1216 sash_composite.setLayout(new FormLayout()); 1217 sash_composite.setLayoutData(new GridData(GridData.FILL_BOTH)); 1218 1219 // create the stacked composite 1220 mAllocationStackComposite = new Composite(sash_composite, SWT.NONE); 1221 mAllocationStackLayout = new StackLayout(); 1222 mAllocationStackComposite.setLayout(mAllocationStackLayout); 1223 mAllocationStackComposite.setLayoutData(new GridData( 1224 GridData.FILL_BOTH)); 1225 1226 // create the top half for mode 1 1227 createAllocationTopHalf(mAllocationStackComposite); 1228 1229 // create the top half for mode 2 1230 createLibraryTopHalf(mAllocationStackComposite); 1231 1232 final Sash sash = new Sash(sash_composite, SWT.HORIZONTAL); 1233 1234 // bottom half of these modes is the same: detail table 1235 createDetailTable(sash_composite); 1236 1237 // init value for stack 1238 mAllocationStackLayout.topControl = mAllocationModeTop; 1239 1240 // form layout data 1241 FormData data = new FormData(); 1242 data.top = new FormAttachment(mTotalMemoryLabel, 0); 1243 data.bottom = new FormAttachment(sash, 0); 1244 data.left = new FormAttachment(0, 0); 1245 data.right = new FormAttachment(100, 0); 1246 mAllocationStackComposite.setLayoutData(data); 1247 1248 final FormData sashData = new FormData(); 1249 if (prefs != null && prefs.contains(PREFS_ALLOCATION_SASH)) { 1250 sashData.top = new FormAttachment(0, 1251 prefs.getInt(PREFS_ALLOCATION_SASH)); 1252 } else { 1253 sashData.top = new FormAttachment(50, 0); // 50% across 1254 } 1255 sashData.left = new FormAttachment(0, 0); 1256 sashData.right = new FormAttachment(100, 0); 1257 sash.setLayoutData(sashData); 1258 1259 data = new FormData(); 1260 data.top = new FormAttachment(sash, 0); 1261 data.bottom = new FormAttachment(100, 0); 1262 data.left = new FormAttachment(0, 0); 1263 data.right = new FormAttachment(100, 0); 1264 mDetailTable.setLayoutData(data); 1265 1266 // allow resizes, but cap at minPanelWidth 1267 sash.addListener(SWT.Selection, new Listener() { 1268 public void handleEvent(Event e) { 1269 Rectangle sashRect = sash.getBounds(); 1270 Rectangle panelRect = sash_composite.getClientArea(); 1271 int bottom = panelRect.height - sashRect.height - minPanelWidth; 1272 e.y = Math.max(Math.min(e.y, bottom), minPanelWidth); 1273 if (e.y != sashRect.y) { 1274 sashData.top = new FormAttachment(0, e.y); 1275 prefs.setValue(PREFS_ALLOCATION_SASH, e.y); 1276 sash_composite.layout(); 1277 } 1278 } 1279 }); 1280 } 1281 1282 private void createDetailTable(Composite base) { 1283 1284 final IPreferenceStore prefs = DdmUiPreferences.getStore(); 1285 1286 mDetailTable = new Table(base, SWT.MULTI | SWT.FULL_SELECTION); 1287 mDetailTable.setLayoutData(new GridData(GridData.FILL_BOTH)); 1288 mDetailTable.setHeaderVisible(true); 1289 mDetailTable.setLinesVisible(true); 1290 1291 TableHelper.createTableColumn(mDetailTable, "Address", SWT.RIGHT, 1292 "00000000", PREFS_DETAIL_ADDRESS, prefs); //$NON-NLS-1$ 1293 TableHelper.createTableColumn(mDetailTable, "Library", SWT.LEFT, 1294 "abcdefghijklmnopqrst", PREFS_DETAIL_LIBRARY, prefs); //$NON-NLS-1$ 1295 TableHelper.createTableColumn(mDetailTable, "Method", SWT.LEFT, 1296 "abcdefghijklmnopqrst", PREFS_DETAIL_METHOD, prefs); //$NON-NLS-1$ 1297 TableHelper.createTableColumn(mDetailTable, "File", SWT.LEFT, 1298 "abcdefghijklmnopqrstuvwxyz", PREFS_DETAIL_FILE, prefs); //$NON-NLS-1$ 1299 TableHelper.createTableColumn(mDetailTable, "Line", SWT.RIGHT, 1300 "9,999", PREFS_DETAIL_LINE, prefs); //$NON-NLS-1$ 1301 } 1302 1303 private void createAllocationTopHalf(Composite b) { 1304 final IPreferenceStore prefs = DdmUiPreferences.getStore(); 1305 1306 Composite base = new Composite(b, SWT.NONE); 1307 mAllocationModeTop = base; 1308 GridLayout gl = new GridLayout(1, false); 1309 gl.marginLeft = gl.marginRight = gl.marginTop = gl.marginBottom = 0; 1310 gl.verticalSpacing = 0; 1311 base.setLayout(gl); 1312 base.setLayoutData(new GridData(GridData.FILL_BOTH)); 1313 1314 // horizontal layout for memory total and pages UI 1315 mPageUIComposite = new Composite(base, SWT.NONE); 1316 mPageUIComposite.setLayoutData(new GridData( 1317 GridData.HORIZONTAL_ALIGN_BEGINNING)); 1318 gl = new GridLayout(3, false); 1319 gl.marginLeft = gl.marginRight = gl.marginTop = gl.marginBottom = 0; 1320 gl.horizontalSpacing = 0; 1321 mPageUIComposite.setLayout(gl); 1322 1323 // Page UI 1324 mPagePreviousButton = new Button(mPageUIComposite, SWT.NONE); 1325 mPagePreviousButton.setText("<"); 1326 mPagePreviousButton.addSelectionListener(new SelectionAdapter() { 1327 @Override 1328 public void widgetSelected(SelectionEvent e) { 1329 mCurrentPage--; 1330 updatePageUI(); 1331 emptyTables(); 1332 fillAllocationTable(); 1333 } 1334 }); 1335 1336 mPageNextButton = new Button(mPageUIComposite, SWT.NONE); 1337 mPageNextButton.setText(">"); 1338 mPageNextButton.addSelectionListener(new SelectionAdapter() { 1339 @Override 1340 public void widgetSelected(SelectionEvent e) { 1341 mCurrentPage++; 1342 updatePageUI(); 1343 emptyTables(); 1344 fillAllocationTable(); 1345 } 1346 }); 1347 1348 mPageLabel = new Label(mPageUIComposite, SWT.NONE); 1349 mPageLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 1350 1351 updatePageUI(); 1352 1353 mAllocationTable = new Table(base, SWT.MULTI | SWT.FULL_SELECTION); 1354 mAllocationTable.setLayoutData(new GridData(GridData.FILL_BOTH)); 1355 mAllocationTable.setHeaderVisible(true); 1356 mAllocationTable.setLinesVisible(true); 1357 1358 TableHelper.createTableColumn(mAllocationTable, "Total", SWT.RIGHT, 1359 "9,999,999", PREFS_ALLOC_TOTAL, prefs); //$NON-NLS-1$ 1360 TableHelper.createTableColumn(mAllocationTable, "Count", SWT.RIGHT, 1361 "9,999", PREFS_ALLOC_COUNT, prefs); //$NON-NLS-1$ 1362 TableHelper.createTableColumn(mAllocationTable, "Size", SWT.RIGHT, 1363 "999,999", PREFS_ALLOC_SIZE, prefs); //$NON-NLS-1$ 1364 TableHelper.createTableColumn(mAllocationTable, "Library", SWT.LEFT, 1365 "abcdefghijklmnopqrst", PREFS_ALLOC_LIBRARY, prefs); //$NON-NLS-1$ 1366 TableHelper.createTableColumn(mAllocationTable, "Method", SWT.LEFT, 1367 "abcdefghijklmnopqrst", PREFS_ALLOC_METHOD, prefs); //$NON-NLS-1$ 1368 TableHelper.createTableColumn(mAllocationTable, "File", SWT.LEFT, 1369 "abcdefghijklmnopqrstuvwxyz", PREFS_ALLOC_FILE, prefs); //$NON-NLS-1$ 1370 1371 mAllocationTable.addSelectionListener(new SelectionAdapter() { 1372 @Override 1373 public void widgetSelected(SelectionEvent e) { 1374 // get the selection index 1375 int index = mAllocationTable.getSelectionIndex(); 1376 TableItem item = mAllocationTable.getItem(index); 1377 if (item != null && item.getData() instanceof NativeAllocationInfo) { 1378 fillDetailTable((NativeAllocationInfo)item.getData()); 1379 } 1380 } 1381 }); 1382 } 1383 1384 private void createLibraryTopHalf(Composite base) { 1385 final int minPanelWidth = 60; 1386 1387 final IPreferenceStore prefs = DdmUiPreferences.getStore(); 1388 1389 // create a composite that'll contain 2 tables horizontally 1390 final Composite top = new Composite(base, SWT.NONE); 1391 mLibraryModeTopControl = top; 1392 top.setLayout(new FormLayout()); 1393 top.setLayoutData(new GridData(GridData.FILL_BOTH)); 1394 1395 // first table: library 1396 mLibraryTable = new Table(top, SWT.MULTI | SWT.FULL_SELECTION); 1397 mLibraryTable.setLayoutData(new GridData(GridData.FILL_BOTH)); 1398 mLibraryTable.setHeaderVisible(true); 1399 mLibraryTable.setLinesVisible(true); 1400 1401 TableHelper.createTableColumn(mLibraryTable, "Library", SWT.LEFT, 1402 "abcdefghijklmnopqrstuvwxyz", PREFS_LIB_LIBRARY, prefs); //$NON-NLS-1$ 1403 TableHelper.createTableColumn(mLibraryTable, "Size", SWT.RIGHT, 1404 "9,999,999", PREFS_LIB_SIZE, prefs); //$NON-NLS-1$ 1405 TableHelper.createTableColumn(mLibraryTable, "Count", SWT.RIGHT, 1406 "9,999", PREFS_LIB_COUNT, prefs); //$NON-NLS-1$ 1407 1408 mLibraryTable.addSelectionListener(new SelectionAdapter() { 1409 @Override 1410 public void widgetSelected(SelectionEvent e) { 1411 fillLibraryAllocationTable(); 1412 } 1413 }); 1414 1415 final Sash sash = new Sash(top, SWT.VERTICAL); 1416 1417 // 2nd table: allocation per library 1418 mLibraryAllocationTable = new Table(top, SWT.MULTI | SWT.FULL_SELECTION); 1419 mLibraryAllocationTable.setLayoutData(new GridData(GridData.FILL_BOTH)); 1420 mLibraryAllocationTable.setHeaderVisible(true); 1421 mLibraryAllocationTable.setLinesVisible(true); 1422 1423 TableHelper.createTableColumn(mLibraryAllocationTable, "Total", 1424 SWT.RIGHT, "9,999,999", PREFS_LIBALLOC_TOTAL, prefs); //$NON-NLS-1$ 1425 TableHelper.createTableColumn(mLibraryAllocationTable, "Count", 1426 SWT.RIGHT, "9,999", PREFS_LIBALLOC_COUNT, prefs); //$NON-NLS-1$ 1427 TableHelper.createTableColumn(mLibraryAllocationTable, "Size", 1428 SWT.RIGHT, "999,999", PREFS_LIBALLOC_SIZE, prefs); //$NON-NLS-1$ 1429 TableHelper.createTableColumn(mLibraryAllocationTable, "Method", 1430 SWT.LEFT, "abcdefghijklmnopqrst", PREFS_LIBALLOC_METHOD, prefs); //$NON-NLS-1$ 1431 1432 mLibraryAllocationTable.addSelectionListener(new SelectionAdapter() { 1433 @Override 1434 public void widgetSelected(SelectionEvent e) { 1435 // get the index of the selection in the library table 1436 int index1 = mLibraryTable.getSelectionIndex(); 1437 // get the index in the library allocation table 1438 int index2 = mLibraryAllocationTable.getSelectionIndex(); 1439 // get the MallocInfo object 1440 LibraryAllocations liballoc = mLibraryAllocations.get(index1); 1441 NativeAllocationInfo info = liballoc.getAllocation(index2); 1442 fillDetailTable(info); 1443 } 1444 }); 1445 1446 // form layout data 1447 FormData data = new FormData(); 1448 data.top = new FormAttachment(0, 0); 1449 data.bottom = new FormAttachment(100, 0); 1450 data.left = new FormAttachment(0, 0); 1451 data.right = new FormAttachment(sash, 0); 1452 mLibraryTable.setLayoutData(data); 1453 1454 final FormData sashData = new FormData(); 1455 if (prefs != null && prefs.contains(PREFS_LIBRARY_SASH)) { 1456 sashData.left = new FormAttachment(0, 1457 prefs.getInt(PREFS_LIBRARY_SASH)); 1458 } else { 1459 sashData.left = new FormAttachment(50, 0); 1460 } 1461 sashData.bottom = new FormAttachment(100, 0); 1462 sashData.top = new FormAttachment(0, 0); // 50% across 1463 sash.setLayoutData(sashData); 1464 1465 data = new FormData(); 1466 data.top = new FormAttachment(0, 0); 1467 data.bottom = new FormAttachment(100, 0); 1468 data.left = new FormAttachment(sash, 0); 1469 data.right = new FormAttachment(100, 0); 1470 mLibraryAllocationTable.setLayoutData(data); 1471 1472 // allow resizes, but cap at minPanelWidth 1473 sash.addListener(SWT.Selection, new Listener() { 1474 public void handleEvent(Event e) { 1475 Rectangle sashRect = sash.getBounds(); 1476 Rectangle panelRect = top.getClientArea(); 1477 int right = panelRect.width - sashRect.width - minPanelWidth; 1478 e.x = Math.max(Math.min(e.x, right), minPanelWidth); 1479 if (e.x != sashRect.x) { 1480 sashData.left = new FormAttachment(0, e.x); 1481 prefs.setValue(PREFS_LIBRARY_SASH, e.y); 1482 top.layout(); 1483 } 1484 } 1485 }); 1486 } 1487 1488 private void emptyTables() { 1489 mAllocationTable.removeAll(); 1490 mLibraryTable.removeAll(); 1491 mLibraryAllocationTable.removeAll(); 1492 mDetailTable.removeAll(); 1493 } 1494 1495 private void sortAllocationsPerLibrary() { 1496 if (mClientData != null) { 1497 mLibraryAllocations.clear(); 1498 1499 // create a hash map of LibraryAllocations to access aggregate 1500 // objects already created 1501 HashMap<String, LibraryAllocations> libcache = 1502 new HashMap<String, LibraryAllocations>(); 1503 1504 // get the allocation count 1505 int count = mDisplayedAllocations.size(); 1506 for (int i = 0; i < count; i++) { 1507 NativeAllocationInfo allocInfo = mDisplayedAllocations.get(i); 1508 1509 NativeStackCallInfo stackCallInfo = allocInfo.getRelevantStackCallInfo(); 1510 if (stackCallInfo != null) { 1511 String libraryName = stackCallInfo.getLibraryName(); 1512 LibraryAllocations liballoc = libcache.get(libraryName); 1513 if (liballoc == null) { 1514 // didn't find a library allocation object already 1515 // created so we create one 1516 liballoc = new LibraryAllocations(libraryName); 1517 // add it to the cache 1518 libcache.put(libraryName, liballoc); 1519 // add it to the list 1520 mLibraryAllocations.add(liballoc); 1521 } 1522 // add the MallocInfo object to it. 1523 liballoc.addAllocation(allocInfo); 1524 } 1525 } 1526 // now that the list is created, we need to compute the size and 1527 // sort it by size. This will also sort the MallocInfo objects 1528 // inside each LibraryAllocation objects. 1529 for (LibraryAllocations liballoc : mLibraryAllocations) { 1530 liballoc.computeAllocationSizeAndCount(); 1531 } 1532 1533 // now we sort it 1534 Collections.sort(mLibraryAllocations, 1535 new Comparator<LibraryAllocations>() { 1536 public int compare(LibraryAllocations o1, 1537 LibraryAllocations o2) { 1538 return o2.getSize() - o1.getSize(); 1539 } 1540 }); 1541 } 1542 } 1543 1544 private void renderBitmap(ClientData cd) { 1545 byte[] pixData; 1546 1547 // Atomically get and clear the heap data. 1548 synchronized (cd) { 1549 if (serializeHeapData(cd.getVmHeapData()) == false) { 1550 // no change, we return. 1551 return; 1552 } 1553 1554 pixData = getSerializedData(); 1555 1556 ImageData id = createLinearHeapImage(pixData, 200, mMapPalette); 1557 Image image = new Image(mBase.getDisplay(), id); 1558 mImage.setImage(image); 1559 mImage.pack(true); 1560 } 1561 } 1562 1563 /* 1564 * Create color palette for map. Set up titles for legend. 1565 */ 1566 private static PaletteData createPalette() { 1567 RGB colors[] = new RGB[NUM_PALETTE_ENTRIES]; 1568 colors[0] 1569 = new RGB(192, 192, 192); // non-heap pixels are gray 1570 mMapLegend[0] 1571 = "(heap expansion area)"; 1572 1573 colors[1] 1574 = new RGB(0, 0, 0); // free chunks are black 1575 mMapLegend[1] 1576 = "free"; 1577 1578 colors[HeapSegmentElement.KIND_OBJECT + 2] 1579 = new RGB(0, 0, 255); // objects are blue 1580 mMapLegend[HeapSegmentElement.KIND_OBJECT + 2] 1581 = "data object"; 1582 1583 colors[HeapSegmentElement.KIND_CLASS_OBJECT + 2] 1584 = new RGB(0, 255, 0); // class objects are green 1585 mMapLegend[HeapSegmentElement.KIND_CLASS_OBJECT + 2] 1586 = "class object"; 1587 1588 colors[HeapSegmentElement.KIND_ARRAY_1 + 2] 1589 = new RGB(255, 0, 0); // byte/bool arrays are red 1590 mMapLegend[HeapSegmentElement.KIND_ARRAY_1 + 2] 1591 = "1-byte array (byte[], boolean[])"; 1592 1593 colors[HeapSegmentElement.KIND_ARRAY_2 + 2] 1594 = new RGB(255, 128, 0); // short/char arrays are orange 1595 mMapLegend[HeapSegmentElement.KIND_ARRAY_2 + 2] 1596 = "2-byte array (short[], char[])"; 1597 1598 colors[HeapSegmentElement.KIND_ARRAY_4 + 2] 1599 = new RGB(255, 255, 0); // obj/int/float arrays are yellow 1600 mMapLegend[HeapSegmentElement.KIND_ARRAY_4 + 2] 1601 = "4-byte array (object[], int[], float[])"; 1602 1603 colors[HeapSegmentElement.KIND_ARRAY_8 + 2] 1604 = new RGB(255, 128, 128); // long/double arrays are pink 1605 mMapLegend[HeapSegmentElement.KIND_ARRAY_8 + 2] 1606 = "8-byte array (long[], double[])"; 1607 1608 colors[HeapSegmentElement.KIND_UNKNOWN + 2] 1609 = new RGB(255, 0, 255); // unknown objects are cyan 1610 mMapLegend[HeapSegmentElement.KIND_UNKNOWN + 2] 1611 = "unknown object"; 1612 1613 colors[HeapSegmentElement.KIND_NATIVE + 2] 1614 = new RGB(64, 64, 64); // native objects are dark gray 1615 mMapLegend[HeapSegmentElement.KIND_NATIVE + 2] 1616 = "non-Java object"; 1617 1618 return new PaletteData(colors); 1619 } 1620 1621 private void saveAllocations(String fileName) { 1622 try { 1623 PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(fileName))); 1624 1625 for (NativeAllocationInfo alloc : mAllocations) { 1626 out.println(alloc.toString()); 1627 } 1628 out.close(); 1629 } catch (IOException e) { 1630 Log.e("Native", e); 1631 } 1632 } 1633 } 1634