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