/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.ddmuilib; import com.android.ddmlib.Client; import com.android.ddmlib.ClientData; import com.android.ddmlib.Log; import com.android.ddmlib.NativeAllocationInfo; import com.android.ddmlib.NativeLibraryMapInfo; import com.android.ddmlib.NativeStackCallInfo; import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener; import com.android.ddmlib.HeapSegment.HeapSegmentElement; import com.android.ddmuilib.annotation.WorkerThread; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.swt.SWT; import org.eclipse.swt.SWTException; import org.eclipse.swt.custom.StackLayout; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.PaletteData; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.FormAttachment; import org.eclipse.swt.layout.FormData; import org.eclipse.swt.layout.FormLayout; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.FileDialog; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Sash; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableItem; import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; /** * Panel with native heap information. */ public final class NativeHeapPanel extends BaseHeapPanel { /** color palette and map legend. NATIVE is the last enum is a 0 based enum list, so we need * Native+1 at least. We also need 2 more entries for free area and expansion area. */ private static final int NUM_PALETTE_ENTRIES = HeapSegmentElement.KIND_NATIVE+2 +1; private static final String[] mMapLegend = new String[NUM_PALETTE_ENTRIES]; private static final PaletteData mMapPalette = createPalette(); private static final int ALLOC_DISPLAY_ALL = 0; private static final int ALLOC_DISPLAY_PRE_ZYGOTE = 1; private static final int ALLOC_DISPLAY_POST_ZYGOTE = 2; private Display mDisplay; private Composite mBase; private Label mUpdateStatus; /** combo giving choice of what to display: all, pre-zygote, post-zygote */ private Combo mAllocDisplayCombo; private Button mFullUpdateButton; // see CreateControl() //private Button mDiffUpdateButton; private Combo mDisplayModeCombo; /** stack composite for mode (1-2) & 3 */ private Composite mTopStackComposite; private StackLayout mTopStackLayout; /** stack composite for mode 1 & 2 */ private Composite mAllocationStackComposite; private StackLayout mAllocationStackLayout; /** top level container for mode 1 & 2 */ private Composite mTableModeControl; /** top level object for the allocation mode */ private Control mAllocationModeTop; /** top level for the library mode */ private Control mLibraryModeTopControl; /** composite for page UI and total memory display */ private Composite mPageUIComposite; private Label mTotalMemoryLabel; private Label mPageLabel; private Button mPageNextButton; private Button mPagePreviousButton; private Table mAllocationTable; private Table mLibraryTable; private Table mLibraryAllocationTable; private Table mDetailTable; private Label mImage; private int mAllocDisplayMode = ALLOC_DISPLAY_ALL; /** * pointer to current stackcall thread computation in order to quit it if * required (new update requested) */ private StackCallThread mStackCallThread; /** Current Library Allocation table fill thread. killed if selection changes */ private FillTableThread mFillTableThread; /** * current client data. Used to access the malloc info when switching pages * or selecting allocation to show stack call */ private ClientData mClientData; /** * client data from a previous display. used when asking for an "update & diff" */ private ClientData mBackUpClientData; /** list of NativeAllocationInfo objects filled with the list from ClientData */ private final ArrayList<NativeAllocationInfo> mAllocations = new ArrayList<NativeAllocationInfo>(); /** list of the {@link NativeAllocationInfo} being displayed based on the selection * of {@link #mAllocDisplayCombo}. */ private final ArrayList<NativeAllocationInfo> mDisplayedAllocations = new ArrayList<NativeAllocationInfo>(); /** list of NativeAllocationInfo object kept as backup when doing an "update & diff" */ private final ArrayList<NativeAllocationInfo> mBackUpAllocations = new ArrayList<NativeAllocationInfo>(); /** back up of the total memory, used when doing an "update & diff" */ private int mBackUpTotalMemory; private int mCurrentPage = 0; private int mPageCount = 0; /** * list of allocation per Library. This is created from the list of * NativeAllocationInfo objects that is stored in the ClientData object. Since we * don't keep this list around, it is recomputed everytime the client * changes. */ private final ArrayList<LibraryAllocations> mLibraryAllocations = new ArrayList<LibraryAllocations>(); /* args to setUpdateStatus() */ private static final int NOT_SELECTED = 0; private static final int NOT_ENABLED = 1; private static final int ENABLED = 2; private static final int DISPLAY_PER_PAGE = 20; private static final String PREFS_ALLOCATION_SASH = "NHallocSash"; //$NON-NLS-1$ private static final String PREFS_LIBRARY_SASH = "NHlibrarySash"; //$NON-NLS-1$ private static final String PREFS_DETAIL_ADDRESS = "NHdetailAddress"; //$NON-NLS-1$ private static final String PREFS_DETAIL_LIBRARY = "NHdetailLibrary"; //$NON-NLS-1$ private static final String PREFS_DETAIL_METHOD = "NHdetailMethod"; //$NON-NLS-1$ private static final String PREFS_DETAIL_FILE = "NHdetailFile"; //$NON-NLS-1$ private static final String PREFS_DETAIL_LINE = "NHdetailLine"; //$NON-NLS-1$ private static final String PREFS_ALLOC_TOTAL = "NHallocTotal"; //$NON-NLS-1$ private static final String PREFS_ALLOC_COUNT = "NHallocCount"; //$NON-NLS-1$ private static final String PREFS_ALLOC_SIZE = "NHallocSize"; //$NON-NLS-1$ private static final String PREFS_ALLOC_LIBRARY = "NHallocLib"; //$NON-NLS-1$ private static final String PREFS_ALLOC_METHOD = "NHallocMethod"; //$NON-NLS-1$ private static final String PREFS_ALLOC_FILE = "NHallocFile"; //$NON-NLS-1$ private static final String PREFS_LIB_LIBRARY = "NHlibLibrary"; //$NON-NLS-1$ private static final String PREFS_LIB_SIZE = "NHlibSize"; //$NON-NLS-1$ private static final String PREFS_LIB_COUNT = "NHlibCount"; //$NON-NLS-1$ private static final String PREFS_LIBALLOC_TOTAL = "NHlibAllocTotal"; //$NON-NLS-1$ private static final String PREFS_LIBALLOC_COUNT = "NHlibAllocCount"; //$NON-NLS-1$ private static final String PREFS_LIBALLOC_SIZE = "NHlibAllocSize"; //$NON-NLS-1$ private static final String PREFS_LIBALLOC_METHOD = "NHlibAllocMethod"; //$NON-NLS-1$ /** static formatter object to format all numbers as #,### */ private static DecimalFormat sFormatter; static { sFormatter = (DecimalFormat)NumberFormat.getInstance(); if (sFormatter != null) sFormatter = new DecimalFormat("#,###"); else sFormatter.applyPattern("#,###"); } /** * caching mechanism to avoid recomputing the backtrace for a particular * address several times. */ private HashMap<Long, NativeStackCallInfo> mSourceCache = new HashMap<Long,NativeStackCallInfo>(); private long mTotalSize; private Button mSaveButton; private Button mSymbolsButton; /** * thread class to convert the address call into method, file and line * number in the background. */ private class StackCallThread extends BackgroundThread { private ClientData mClientData; public StackCallThread(ClientData cd) { mClientData = cd; } public ClientData getClientData() { return mClientData; } @Override public void run() { // loop through all the NativeAllocationInfo and init them Iterator<NativeAllocationInfo> iter = mAllocations.iterator(); int total = mAllocations.size(); int count = 0; while (iter.hasNext()) { if (isQuitting()) return; NativeAllocationInfo info = iter.next(); if (info.isStackCallResolved() == false) { final Long[] list = info.getStackCallAddresses(); final int size = list.length; ArrayList<NativeStackCallInfo> resolvedStackCall = new ArrayList<NativeStackCallInfo>(); for (int i = 0; i < size; i++) { long addr = list[i]; // first check if the addr has already been converted. NativeStackCallInfo source = mSourceCache.get(addr); // if not we convert it if (source == null) { source = sourceForAddr(addr); mSourceCache.put(addr, source); } resolvedStackCall.add(source); } info.setResolvedStackCall(resolvedStackCall); } // after every DISPLAY_PER_PAGE we ask for a ui refresh, unless // we reach total, since we also do it after the loop // (only an issue in case we have a perfect number of page) count++; if ((count % DISPLAY_PER_PAGE) == 0 && count != total) { if (updateNHAllocationStackCalls(mClientData, count) == false) { // looks like the app is quitting, so we just // stopped the thread return; } } } updateNHAllocationStackCalls(mClientData, count); } private NativeStackCallInfo sourceForAddr(long addr) { NativeLibraryMapInfo library = getLibraryFor(addr); if (library != null) { Addr2Line process = Addr2Line.getProcess(library.getLibraryName()); if (process != null) { // remove the base of the library address long value = addr - library.getStartAddress(); NativeStackCallInfo info = process.getAddress(value); if (info != null) { return info; } } } return new NativeStackCallInfo(library != null ? library.getLibraryName() : null, Long.toHexString(addr), ""); } private NativeLibraryMapInfo getLibraryFor(long addr) { Iterator<NativeLibraryMapInfo> it = mClientData.getNativeLibraryMapInfo(); while (it.hasNext()) { NativeLibraryMapInfo info = it.next(); if (info.isWithinLibrary(addr)) { return info; } } Log.d("ddm-nativeheap", "Failed finding Library for " + Long.toHexString(addr)); return null; } /** * update the Native Heap panel with the amount of allocation for which the * stack call has been computed. This is called from a non UI thread, but * will be executed in the UI thread. * * @param count the amount of allocation * @return false if the display was disposed and the update couldn't happen */ private boolean updateNHAllocationStackCalls(final ClientData clientData, final int count) { if (mDisplay.isDisposed() == false) { mDisplay.asyncExec(new Runnable() { public void run() { updateAllocationStackCalls(clientData, count); } }); return true; } return false; } } private class FillTableThread extends BackgroundThread { private LibraryAllocations mLibAlloc; private int mMax; public FillTableThread(LibraryAllocations liballoc, int m) { mLibAlloc = liballoc; mMax = m; } @Override public void run() { for (int i = mMax; i > 0 && isQuitting() == false; i -= 10) { updateNHLibraryAllocationTable(mLibAlloc, mMax - i, mMax - i + 10); } } /** * updates the library allocation table in the Native Heap panel. This is * called from a non UI thread, but will be executed in the UI thread. * * @param liballoc the current library allocation object being displayed * @param start start index of items that need to be displayed * @param end end index of the items that need to be displayed */ private void updateNHLibraryAllocationTable(final LibraryAllocations libAlloc, final int start, final int end) { if (mDisplay.isDisposed() == false) { mDisplay.asyncExec(new Runnable() { public void run() { updateLibraryAllocationTable(libAlloc, start, end); } }); } } } /** class to aggregate allocations per library */ public static class LibraryAllocations { private String mLibrary; private final ArrayList<NativeAllocationInfo> mLibAllocations = new ArrayList<NativeAllocationInfo>(); private int mSize; private int mCount; /** construct the aggregate object for a library */ public LibraryAllocations(final String lib) { mLibrary = lib; } /** get the library name */ public String getLibrary() { return mLibrary; } /** add a NativeAllocationInfo object to this aggregate object */ public void addAllocation(NativeAllocationInfo info) { mLibAllocations.add(info); } /** get an iterator on the NativeAllocationInfo objects */ public Iterator<NativeAllocationInfo> getAllocations() { return mLibAllocations.iterator(); } /** get a NativeAllocationInfo object by index */ public NativeAllocationInfo getAllocation(int index) { return mLibAllocations.get(index); } /** returns the NativeAllocationInfo object count */ public int getAllocationSize() { return mLibAllocations.size(); } /** returns the total allocation size */ public int getSize() { return mSize; } /** returns the number of allocations */ public int getCount() { return mCount; } /** * compute the allocation count and size for allocation objects added * through <code>addAllocation()</code>, and sort the objects by * total allocation size. */ public void computeAllocationSizeAndCount() { mSize = 0; mCount = 0; for (NativeAllocationInfo info : mLibAllocations) { mCount += info.getAllocationCount(); mSize += info.getAllocationCount() * info.getSize(); } Collections.sort(mLibAllocations, new Comparator<NativeAllocationInfo>() { public int compare(NativeAllocationInfo o1, NativeAllocationInfo o2) { return o2.getAllocationCount() * o2.getSize() - o1.getAllocationCount() * o1.getSize(); } }); } } /** * Create our control(s). */ @Override protected Control createControl(Composite parent) { mDisplay = parent.getDisplay(); mBase = new Composite(parent, SWT.NONE); GridLayout gl = new GridLayout(1, false); gl.horizontalSpacing = 0; gl.verticalSpacing = 0; mBase.setLayout(gl); mBase.setLayoutData(new GridData(GridData.FILL_BOTH)); // composite for <update btn> <status> Composite tmp = new Composite(mBase, SWT.NONE); tmp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); tmp.setLayout(gl = new GridLayout(2, false)); gl.marginWidth = gl.marginHeight = 0; mFullUpdateButton = new Button(tmp, SWT.NONE); mFullUpdateButton.setText("Full Update"); mFullUpdateButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { mBackUpClientData = null; mDisplayModeCombo.setEnabled(false); mSaveButton.setEnabled(false); emptyTables(); // if we already have a stack call computation for this // client // we stop it if (mStackCallThread != null && mStackCallThread.getClientData() == mClientData) { mStackCallThread.quit(); mStackCallThread = null; } mLibraryAllocations.clear(); Client client = getCurrentClient(); if (client != null) { client.requestNativeHeapInformation(); } } }); mUpdateStatus = new Label(tmp, SWT.NONE); mUpdateStatus.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); // top layout for the combos and oter controls on the right. Composite top_layout = new Composite(mBase, SWT.NONE); top_layout.setLayout(gl = new GridLayout(4, false)); gl.marginWidth = gl.marginHeight = 0; new Label(top_layout, SWT.NONE).setText("Show:"); mAllocDisplayCombo = new Combo(top_layout, SWT.DROP_DOWN | SWT.READ_ONLY); mAllocDisplayCombo.setLayoutData(new GridData( GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL)); mAllocDisplayCombo.add("All Allocations"); mAllocDisplayCombo.add("Pre-Zygote Allocations"); mAllocDisplayCombo.add("Zygote Child Allocations (Z)"); mAllocDisplayCombo.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { onAllocDisplayChange(); } }); mAllocDisplayCombo.select(0); // separator Label separator = new Label(top_layout, SWT.SEPARATOR | SWT.VERTICAL); GridData gd; separator.setLayoutData(gd = new GridData( GridData.VERTICAL_ALIGN_FILL | GridData.GRAB_VERTICAL)); gd.heightHint = 0; gd.verticalSpan = 2; mSaveButton = new Button(top_layout, SWT.PUSH); mSaveButton.setText("Save..."); mSaveButton.setEnabled(false); mSaveButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { FileDialog fileDialog = new FileDialog(mBase.getShell(), SWT.SAVE); fileDialog.setText("Save Allocations"); fileDialog.setFileName("allocations.txt"); String fileName = fileDialog.open(); if (fileName != null) { saveAllocations(fileName); } } }); /* * TODO: either fix the diff mechanism or remove it altogether. mDiffUpdateButton = new Button(top_layout, SWT.NONE); mDiffUpdateButton.setText("Update && Diff"); mDiffUpdateButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { // since this is an update and diff, we need to store the // current list // of mallocs mBackUpAllocations.clear(); mBackUpAllocations.addAll(mAllocations); mBackUpClientData = mClientData; mBackUpTotalMemory = mClientData.getTotalNativeMemory(); mDisplayModeCombo.setEnabled(false); emptyTables(); // if we already have a stack call computation for this // client // we stop it if (mStackCallThread != null && mStackCallThread.getClientData() == mClientData) { mStackCallThread.quit(); mStackCallThread = null; } mLibraryAllocations.clear(); Client client = getCurrentClient(); if (client != null) { client.requestNativeHeapInformation(); } } }); */ Label l = new Label(top_layout, SWT.NONE); l.setText("Display:"); mDisplayModeCombo = new Combo(top_layout, SWT.DROP_DOWN | SWT.READ_ONLY); mDisplayModeCombo.setLayoutData(new GridData( GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL)); mDisplayModeCombo.setItems(new String[] { "Allocation List", "By Libraries" }); mDisplayModeCombo.select(0); mDisplayModeCombo.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { switchDisplayMode(); } }); mDisplayModeCombo.setEnabled(false); mSymbolsButton = new Button(top_layout, SWT.PUSH); mSymbolsButton.setText("Load Symbols"); mSymbolsButton.setEnabled(false); // create a composite that will contains the actual content composites, // in stack mode layout. // This top level composite contains 2 other composites. // * one for both Allocations and Libraries mode // * one for flat mode (which is gone for now) mTopStackComposite = new Composite(mBase, SWT.NONE); mTopStackComposite.setLayout(mTopStackLayout = new StackLayout()); mTopStackComposite.setLayoutData(new GridData(GridData.FILL_BOTH)); // create 1st and 2nd modes createTableDisplay(mTopStackComposite); mTopStackLayout.topControl = mTableModeControl; mTopStackComposite.layout(); setUpdateStatus(NOT_SELECTED); // Work in progress // TODO add image display of native heap. //mImage = new Label(mBase, SWT.NONE); mBase.pack(); return mBase; } /** * Sets the focus to the proper control inside the panel. */ @Override public void setFocus() { // TODO } /** * Sent when an existing client information changed. * <p/> * This is sent from a non UI thread. * @param client the updated client. * @param changeMask the bit mask describing the changed properties. It can contain * any of the following values: {@link Client#CHANGE_INFO}, {@link Client#CHANGE_NAME} * {@link Client#CHANGE_DEBUGGER_INTEREST}, {@link Client#CHANGE_THREAD_MODE}, * {@link Client#CHANGE_THREAD_DATA}, {@link Client#CHANGE_HEAP_MODE}, * {@link Client#CHANGE_HEAP_DATA}, {@link Client#CHANGE_NATIVE_HEAP_DATA} * * @see IClientChangeListener#clientChanged(Client, int) */ public void clientChanged(final Client client, int changeMask) { if (client == getCurrentClient()) { if ((changeMask & Client.CHANGE_NATIVE_HEAP_DATA) == Client.CHANGE_NATIVE_HEAP_DATA) { if (mBase.isDisposed()) return; mBase.getDisplay().asyncExec(new Runnable() { public void run() { clientSelected(); } }); } } } /** * Sent when a new device is selected. The new device can be accessed * with {@link #getCurrentDevice()}. */ @Override public void deviceSelected() { // pass } /** * Sent when a new client is selected. The new client can be accessed * with {@link #getCurrentClient()}. */ @Override public void clientSelected() { if (mBase.isDisposed()) return; Client client = getCurrentClient(); mDisplayModeCombo.setEnabled(false); emptyTables(); Log.d("ddms", "NativeHeapPanel: changed " + client); if (client != null) { ClientData cd = client.getClientData(); mClientData = cd; // if (cd.getShowHeapUpdates()) setUpdateStatus(ENABLED); // else // setUpdateStatus(NOT_ENABLED); initAllocationDisplay(); //renderBitmap(cd); } else { mClientData = null; setUpdateStatus(NOT_SELECTED); } mBase.pack(); } /** * Update the UI with the newly compute stack calls, unless the UI switched * to a different client. * * @param cd the ClientData for which the stack call are being computed. * @param count the current count of allocations for which the stack calls * have been computed. */ @WorkerThread public void updateAllocationStackCalls(ClientData cd, int count) { // we have to check that the panel still shows the same clientdata than // the thread is computing for. if (cd == mClientData) { int total = mAllocations.size(); if (count == total) { // we're done: do something mDisplayModeCombo.setEnabled(true); mSaveButton.setEnabled(true); mStackCallThread = null; } else { // work in progress, update the progress bar. // mUiThread.setStatusLine("Computing stack call: " + count // + "/" + total); } // FIXME: attempt to only update when needed. // Because the number of pages is not related to mAllocations.size() anymore // due to pre-zygote/post-zygote display, update all the time. // At some point we should remove the pages anyway, since it's getting computed // really fast now. // if ((mCurrentPage + 1) * DISPLAY_PER_PAGE == count // || (count == total && mCurrentPage == mPageCount - 1)) { try { // get the current selection of the allocation int index = mAllocationTable.getSelectionIndex(); NativeAllocationInfo info = null; if (index != -1) { info = (NativeAllocationInfo)mAllocationTable.getItem(index).getData(); } // empty the table emptyTables(); // fill it again fillAllocationTable(); // reselect mAllocationTable.setSelection(index); // display detail table if needed if (info != null) { fillDetailTable(info); } } catch (SWTException e) { if (mAllocationTable.isDisposed()) { // looks like the table is disposed. Let's ignore it. } else { throw e; } } } else { // old client still running. doesn't really matter. } } @Override protected void setTableFocusListener() { addTableToFocusListener(mAllocationTable); addTableToFocusListener(mLibraryTable); addTableToFocusListener(mLibraryAllocationTable); addTableToFocusListener(mDetailTable); } protected void onAllocDisplayChange() { mAllocDisplayMode = mAllocDisplayCombo.getSelectionIndex(); // create the new list updateAllocDisplayList(); updateTotalMemoryDisplay(); // reset the ui. mCurrentPage = 0; updatePageUI(); switchDisplayMode(); } private void updateAllocDisplayList() { mTotalSize = 0; mDisplayedAllocations.clear(); for (NativeAllocationInfo info : mAllocations) { if (mAllocDisplayMode == ALLOC_DISPLAY_ALL || (mAllocDisplayMode == ALLOC_DISPLAY_PRE_ZYGOTE ^ info.isZygoteChild())) { mDisplayedAllocations.add(info); mTotalSize += info.getSize() * info.getAllocationCount(); } else { // skip this item continue; } } int count = mDisplayedAllocations.size(); mPageCount = count / DISPLAY_PER_PAGE; // need to add a page for the rest of the div if ((count % DISPLAY_PER_PAGE) > 0) { mPageCount++; } } private void updateTotalMemoryDisplay() { switch (mAllocDisplayMode) { case ALLOC_DISPLAY_ALL: mTotalMemoryLabel.setText(String.format("Total Memory: %1$s Bytes", sFormatter.format(mTotalSize))); break; case ALLOC_DISPLAY_PRE_ZYGOTE: mTotalMemoryLabel.setText(String.format("Zygote Memory: %1$s Bytes", sFormatter.format(mTotalSize))); break; case ALLOC_DISPLAY_POST_ZYGOTE: mTotalMemoryLabel.setText(String.format("Post-zygote Memory: %1$s Bytes", sFormatter.format(mTotalSize))); break; } } private void switchDisplayMode() { switch (mDisplayModeCombo.getSelectionIndex()) { case 0: {// allocations mTopStackLayout.topControl = mTableModeControl; mAllocationStackLayout.topControl = mAllocationModeTop; mAllocationStackComposite.layout(); mTopStackComposite.layout(); emptyTables(); fillAllocationTable(); } break; case 1: {// libraries mTopStackLayout.topControl = mTableModeControl; mAllocationStackLayout.topControl = mLibraryModeTopControl; mAllocationStackComposite.layout(); mTopStackComposite.layout(); emptyTables(); fillLibraryTable(); } break; } } private void initAllocationDisplay() { mAllocations.clear(); mAllocations.addAll(mClientData.getNativeAllocationList()); updateAllocDisplayList(); // if we have a previous clientdata and it matches the current one. we // do a diff between the new list and the old one. if (mBackUpClientData != null && mBackUpClientData == mClientData) { ArrayList<NativeAllocationInfo> add = new ArrayList<NativeAllocationInfo>(); // we go through the list of NativeAllocationInfo in the new list and check if // there's one with the same exact data (size, allocation, count and // stackcall addresses) in the old list. // if we don't find any, we add it to the "add" list for (NativeAllocationInfo mi : mAllocations) { boolean found = false; for (NativeAllocationInfo old_mi : mBackUpAllocations) { if (mi.equals(old_mi)) { found = true; break; } } if (found == false) { add.add(mi); } } // put the result in mAllocations mAllocations.clear(); mAllocations.addAll(add); // display the difference in memory usage. This is computed // calculating the memory usage of the objects in mAllocations. int count = 0; for (NativeAllocationInfo allocInfo : mAllocations) { count += allocInfo.getSize() * allocInfo.getAllocationCount(); } mTotalMemoryLabel.setText(String.format("Memory Difference: %1$s Bytes", sFormatter.format(count))); } else { // display the full memory usage updateTotalMemoryDisplay(); //mDiffUpdateButton.setEnabled(mClientData.getTotalNativeMemory() > 0); } mTotalMemoryLabel.pack(); // update the page ui mDisplayModeCombo.select(0); mLibraryAllocations.clear(); // reset to first page mCurrentPage = 0; // update the label updatePageUI(); // now fill the allocation Table with the current page switchDisplayMode(); // start the thread to compute the stack calls if (mAllocations.size() > 0) { mStackCallThread = new StackCallThread(mClientData); mStackCallThread.start(); } } private void updatePageUI() { // set the label and pack to update the layout, otherwise // the label will be cut off if the new size is bigger if (mPageCount == 0) { mPageLabel.setText("0 of 0 allocations."); } else { StringBuffer buffer = new StringBuffer(); // get our starting index int start = (mCurrentPage * DISPLAY_PER_PAGE) + 1; // end index, taking into account the last page can be half full int count = mDisplayedAllocations.size(); int end = Math.min(start + DISPLAY_PER_PAGE - 1, count); buffer.append(sFormatter.format(start)); buffer.append(" - "); buffer.append(sFormatter.format(end)); buffer.append(" of "); buffer.append(sFormatter.format(count)); buffer.append(" allocations."); mPageLabel.setText(buffer.toString()); } // handle the button enabled state. mPagePreviousButton.setEnabled(mCurrentPage > 0); // reminder: mCurrentPage starts at 0. mPageNextButton.setEnabled(mCurrentPage < mPageCount - 1); mPageLabel.pack(); mPageUIComposite.pack(); } private void fillAllocationTable() { // get the count int count = mDisplayedAllocations.size(); // get our starting index int start = mCurrentPage * DISPLAY_PER_PAGE; // loop for DISPLAY_PER_PAGE or till we reach count int end = start + DISPLAY_PER_PAGE; for (int i = start; i < end && i < count; i++) { NativeAllocationInfo info = mDisplayedAllocations.get(i); TableItem item = null; if (mAllocDisplayMode == ALLOC_DISPLAY_ALL) { item = new TableItem(mAllocationTable, SWT.NONE); item.setText(0, (info.isZygoteChild() ? "Z " : "") + sFormatter.format(info.getSize() * info.getAllocationCount())); item.setText(1, sFormatter.format(info.getAllocationCount())); item.setText(2, sFormatter.format(info.getSize())); } else if (mAllocDisplayMode == ALLOC_DISPLAY_PRE_ZYGOTE ^ info.isZygoteChild()) { item = new TableItem(mAllocationTable, SWT.NONE); item.setText(0, sFormatter.format(info.getSize() * info.getAllocationCount())); item.setText(1, sFormatter.format(info.getAllocationCount())); item.setText(2, sFormatter.format(info.getSize())); } else { // skip this item continue; } item.setData(info); NativeStackCallInfo bti = info.getRelevantStackCallInfo(); if (bti != null) { String lib = bti.getLibraryName(); String method = bti.getMethodName(); String source = bti.getSourceFile(); if (lib != null) item.setText(3, lib); if (method != null) item.setText(4, method); if (source != null) item.setText(5, source); } } } private void fillLibraryTable() { // fill the library table sortAllocationsPerLibrary(); for (LibraryAllocations liballoc : mLibraryAllocations) { if (liballoc != null) { TableItem item = new TableItem(mLibraryTable, SWT.NONE); String lib = liballoc.getLibrary(); item.setText(0, lib != null ? lib : ""); item.setText(1, sFormatter.format(liballoc.getSize())); item.setText(2, sFormatter.format(liballoc.getCount())); } } } private void fillLibraryAllocationTable() { mLibraryAllocationTable.removeAll(); mDetailTable.removeAll(); int index = mLibraryTable.getSelectionIndex(); if (index != -1) { LibraryAllocations liballoc = mLibraryAllocations.get(index); // start a thread that will fill table 10 at a time to keep the ui // responsive, but first we kill the previous one if there was one if (mFillTableThread != null) { mFillTableThread.quit(); } mFillTableThread = new FillTableThread(liballoc, liballoc.getAllocationSize()); mFillTableThread.start(); } } public void updateLibraryAllocationTable(LibraryAllocations liballoc, int start, int end) { try { if (mLibraryTable.isDisposed() == false) { int index = mLibraryTable.getSelectionIndex(); if (index != -1) { LibraryAllocations newliballoc = mLibraryAllocations.get( index); if (newliballoc == liballoc) { int count = liballoc.getAllocationSize(); for (int i = start; i < end && i < count; i++) { NativeAllocationInfo info = liballoc.getAllocation(i); TableItem item = new TableItem( mLibraryAllocationTable, SWT.NONE); item.setText(0, sFormatter.format( info.getSize() * info.getAllocationCount())); item.setText(1, sFormatter.format(info.getAllocationCount())); item.setText(2, sFormatter.format(info.getSize())); NativeStackCallInfo stackCallInfo = info.getRelevantStackCallInfo(); if (stackCallInfo != null) { item.setText(3, stackCallInfo.getMethodName()); } } } else { // we should quit the thread if (mFillTableThread != null) { mFillTableThread.quit(); mFillTableThread = null; } } } } } catch (SWTException e) { Log.e("ddms", "error when updating the library allocation table"); } } private void fillDetailTable(final NativeAllocationInfo mi) { mDetailTable.removeAll(); mDetailTable.setRedraw(false); try { // populate the detail Table with the back trace Long[] addresses = mi.getStackCallAddresses(); NativeStackCallInfo[] resolvedStackCall = mi.getResolvedStackCall(); if (resolvedStackCall == null) { return; } for (int i = 0 ; i < resolvedStackCall.length ; i++) { if (addresses[i] == null || addresses[i].longValue() == 0) { continue; } long addr = addresses[i].longValue(); NativeStackCallInfo source = resolvedStackCall[i]; TableItem item = new TableItem(mDetailTable, SWT.NONE); item.setText(0, String.format("%08x", addr)); //$NON-NLS-1$ String libraryName = source.getLibraryName(); String methodName = source.getMethodName(); String sourceFile = source.getSourceFile(); int lineNumber = source.getLineNumber(); if (libraryName != null) item.setText(1, libraryName); if (methodName != null) item.setText(2, methodName); if (sourceFile != null) item.setText(3, sourceFile); if (lineNumber != -1) item.setText(4, Integer.toString(lineNumber)); } } finally { mDetailTable.setRedraw(true); } } /* * Are updates enabled? */ private void setUpdateStatus(int status) { switch (status) { case NOT_SELECTED: mUpdateStatus.setText("Select a client to see heap info"); mAllocDisplayCombo.setEnabled(false); mFullUpdateButton.setEnabled(false); //mDiffUpdateButton.setEnabled(false); break; case NOT_ENABLED: mUpdateStatus.setText("Heap updates are " + "NOT ENABLED for this client"); mAllocDisplayCombo.setEnabled(false); mFullUpdateButton.setEnabled(false); //mDiffUpdateButton.setEnabled(false); break; case ENABLED: mUpdateStatus.setText("Press 'Full Update' to retrieve " + "latest data"); mAllocDisplayCombo.setEnabled(true); mFullUpdateButton.setEnabled(true); //mDiffUpdateButton.setEnabled(true); break; default: throw new RuntimeException(); } mUpdateStatus.pack(); } /** * Create the Table display. This includes a "detail" Table in the bottom * half and 2 modes in the top half: allocation Table and * library+allocations Tables. * * @param base the top parent to create the display into */ private void createTableDisplay(Composite base) { final int minPanelWidth = 60; final IPreferenceStore prefs = DdmUiPreferences.getStore(); // top level composite for mode 1 & 2 mTableModeControl = new Composite(base, SWT.NONE); GridLayout gl = new GridLayout(1, false); gl.marginLeft = gl.marginRight = gl.marginTop = gl.marginBottom = 0; mTableModeControl.setLayout(gl); mTableModeControl.setLayoutData(new GridData(GridData.FILL_BOTH)); mTotalMemoryLabel = new Label(mTableModeControl, SWT.NONE); mTotalMemoryLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); mTotalMemoryLabel.setText("Total Memory: 0 Bytes"); // the top half of these modes is dynamic final Composite sash_composite = new Composite(mTableModeControl, SWT.NONE); sash_composite.setLayout(new FormLayout()); sash_composite.setLayoutData(new GridData(GridData.FILL_BOTH)); // create the stacked composite mAllocationStackComposite = new Composite(sash_composite, SWT.NONE); mAllocationStackLayout = new StackLayout(); mAllocationStackComposite.setLayout(mAllocationStackLayout); mAllocationStackComposite.setLayoutData(new GridData( GridData.FILL_BOTH)); // create the top half for mode 1 createAllocationTopHalf(mAllocationStackComposite); // create the top half for mode 2 createLibraryTopHalf(mAllocationStackComposite); final Sash sash = new Sash(sash_composite, SWT.HORIZONTAL); // bottom half of these modes is the same: detail table createDetailTable(sash_composite); // init value for stack mAllocationStackLayout.topControl = mAllocationModeTop; // form layout data FormData data = new FormData(); data.top = new FormAttachment(mTotalMemoryLabel, 0); data.bottom = new FormAttachment(sash, 0); data.left = new FormAttachment(0, 0); data.right = new FormAttachment(100, 0); mAllocationStackComposite.setLayoutData(data); final FormData sashData = new FormData(); if (prefs != null && prefs.contains(PREFS_ALLOCATION_SASH)) { sashData.top = new FormAttachment(0, prefs.getInt(PREFS_ALLOCATION_SASH)); } else { sashData.top = new FormAttachment(50, 0); // 50% across } sashData.left = new FormAttachment(0, 0); sashData.right = new FormAttachment(100, 0); sash.setLayoutData(sashData); data = new FormData(); data.top = new FormAttachment(sash, 0); data.bottom = new FormAttachment(100, 0); data.left = new FormAttachment(0, 0); data.right = new FormAttachment(100, 0); mDetailTable.setLayoutData(data); // allow resizes, but cap at minPanelWidth sash.addListener(SWT.Selection, new Listener() { public void handleEvent(Event e) { Rectangle sashRect = sash.getBounds(); Rectangle panelRect = sash_composite.getClientArea(); int bottom = panelRect.height - sashRect.height - minPanelWidth; e.y = Math.max(Math.min(e.y, bottom), minPanelWidth); if (e.y != sashRect.y) { sashData.top = new FormAttachment(0, e.y); prefs.setValue(PREFS_ALLOCATION_SASH, e.y); sash_composite.layout(); } } }); } private void createDetailTable(Composite base) { final IPreferenceStore prefs = DdmUiPreferences.getStore(); mDetailTable = new Table(base, SWT.MULTI | SWT.FULL_SELECTION); mDetailTable.setLayoutData(new GridData(GridData.FILL_BOTH)); mDetailTable.setHeaderVisible(true); mDetailTable.setLinesVisible(true); TableHelper.createTableColumn(mDetailTable, "Address", SWT.RIGHT, "00000000", PREFS_DETAIL_ADDRESS, prefs); //$NON-NLS-1$ TableHelper.createTableColumn(mDetailTable, "Library", SWT.LEFT, "abcdefghijklmnopqrst", PREFS_DETAIL_LIBRARY, prefs); //$NON-NLS-1$ TableHelper.createTableColumn(mDetailTable, "Method", SWT.LEFT, "abcdefghijklmnopqrst", PREFS_DETAIL_METHOD, prefs); //$NON-NLS-1$ TableHelper.createTableColumn(mDetailTable, "File", SWT.LEFT, "abcdefghijklmnopqrstuvwxyz", PREFS_DETAIL_FILE, prefs); //$NON-NLS-1$ TableHelper.createTableColumn(mDetailTable, "Line", SWT.RIGHT, "9,999", PREFS_DETAIL_LINE, prefs); //$NON-NLS-1$ } private void createAllocationTopHalf(Composite b) { final IPreferenceStore prefs = DdmUiPreferences.getStore(); Composite base = new Composite(b, SWT.NONE); mAllocationModeTop = base; GridLayout gl = new GridLayout(1, false); gl.marginLeft = gl.marginRight = gl.marginTop = gl.marginBottom = 0; gl.verticalSpacing = 0; base.setLayout(gl); base.setLayoutData(new GridData(GridData.FILL_BOTH)); // horizontal layout for memory total and pages UI mPageUIComposite = new Composite(base, SWT.NONE); mPageUIComposite.setLayoutData(new GridData( GridData.HORIZONTAL_ALIGN_BEGINNING)); gl = new GridLayout(3, false); gl.marginLeft = gl.marginRight = gl.marginTop = gl.marginBottom = 0; gl.horizontalSpacing = 0; mPageUIComposite.setLayout(gl); // Page UI mPagePreviousButton = new Button(mPageUIComposite, SWT.NONE); mPagePreviousButton.setText("<"); mPagePreviousButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { mCurrentPage--; updatePageUI(); emptyTables(); fillAllocationTable(); } }); mPageNextButton = new Button(mPageUIComposite, SWT.NONE); mPageNextButton.setText(">"); mPageNextButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { mCurrentPage++; updatePageUI(); emptyTables(); fillAllocationTable(); } }); mPageLabel = new Label(mPageUIComposite, SWT.NONE); mPageLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); updatePageUI(); mAllocationTable = new Table(base, SWT.MULTI | SWT.FULL_SELECTION); mAllocationTable.setLayoutData(new GridData(GridData.FILL_BOTH)); mAllocationTable.setHeaderVisible(true); mAllocationTable.setLinesVisible(true); TableHelper.createTableColumn(mAllocationTable, "Total", SWT.RIGHT, "9,999,999", PREFS_ALLOC_TOTAL, prefs); //$NON-NLS-1$ TableHelper.createTableColumn(mAllocationTable, "Count", SWT.RIGHT, "9,999", PREFS_ALLOC_COUNT, prefs); //$NON-NLS-1$ TableHelper.createTableColumn(mAllocationTable, "Size", SWT.RIGHT, "999,999", PREFS_ALLOC_SIZE, prefs); //$NON-NLS-1$ TableHelper.createTableColumn(mAllocationTable, "Library", SWT.LEFT, "abcdefghijklmnopqrst", PREFS_ALLOC_LIBRARY, prefs); //$NON-NLS-1$ TableHelper.createTableColumn(mAllocationTable, "Method", SWT.LEFT, "abcdefghijklmnopqrst", PREFS_ALLOC_METHOD, prefs); //$NON-NLS-1$ TableHelper.createTableColumn(mAllocationTable, "File", SWT.LEFT, "abcdefghijklmnopqrstuvwxyz", PREFS_ALLOC_FILE, prefs); //$NON-NLS-1$ mAllocationTable.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { // get the selection index int index = mAllocationTable.getSelectionIndex(); TableItem item = mAllocationTable.getItem(index); if (item != null && item.getData() instanceof NativeAllocationInfo) { fillDetailTable((NativeAllocationInfo)item.getData()); } } }); } private void createLibraryTopHalf(Composite base) { final int minPanelWidth = 60; final IPreferenceStore prefs = DdmUiPreferences.getStore(); // create a composite that'll contain 2 tables horizontally final Composite top = new Composite(base, SWT.NONE); mLibraryModeTopControl = top; top.setLayout(new FormLayout()); top.setLayoutData(new GridData(GridData.FILL_BOTH)); // first table: library mLibraryTable = new Table(top, SWT.MULTI | SWT.FULL_SELECTION); mLibraryTable.setLayoutData(new GridData(GridData.FILL_BOTH)); mLibraryTable.setHeaderVisible(true); mLibraryTable.setLinesVisible(true); TableHelper.createTableColumn(mLibraryTable, "Library", SWT.LEFT, "abcdefghijklmnopqrstuvwxyz", PREFS_LIB_LIBRARY, prefs); //$NON-NLS-1$ TableHelper.createTableColumn(mLibraryTable, "Size", SWT.RIGHT, "9,999,999", PREFS_LIB_SIZE, prefs); //$NON-NLS-1$ TableHelper.createTableColumn(mLibraryTable, "Count", SWT.RIGHT, "9,999", PREFS_LIB_COUNT, prefs); //$NON-NLS-1$ mLibraryTable.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { fillLibraryAllocationTable(); } }); final Sash sash = new Sash(top, SWT.VERTICAL); // 2nd table: allocation per library mLibraryAllocationTable = new Table(top, SWT.MULTI | SWT.FULL_SELECTION); mLibraryAllocationTable.setLayoutData(new GridData(GridData.FILL_BOTH)); mLibraryAllocationTable.setHeaderVisible(true); mLibraryAllocationTable.setLinesVisible(true); TableHelper.createTableColumn(mLibraryAllocationTable, "Total", SWT.RIGHT, "9,999,999", PREFS_LIBALLOC_TOTAL, prefs); //$NON-NLS-1$ TableHelper.createTableColumn(mLibraryAllocationTable, "Count", SWT.RIGHT, "9,999", PREFS_LIBALLOC_COUNT, prefs); //$NON-NLS-1$ TableHelper.createTableColumn(mLibraryAllocationTable, "Size", SWT.RIGHT, "999,999", PREFS_LIBALLOC_SIZE, prefs); //$NON-NLS-1$ TableHelper.createTableColumn(mLibraryAllocationTable, "Method", SWT.LEFT, "abcdefghijklmnopqrst", PREFS_LIBALLOC_METHOD, prefs); //$NON-NLS-1$ mLibraryAllocationTable.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { // get the index of the selection in the library table int index1 = mLibraryTable.getSelectionIndex(); // get the index in the library allocation table int index2 = mLibraryAllocationTable.getSelectionIndex(); // get the MallocInfo object LibraryAllocations liballoc = mLibraryAllocations.get(index1); NativeAllocationInfo info = liballoc.getAllocation(index2); fillDetailTable(info); } }); // form layout data FormData data = new FormData(); data.top = new FormAttachment(0, 0); data.bottom = new FormAttachment(100, 0); data.left = new FormAttachment(0, 0); data.right = new FormAttachment(sash, 0); mLibraryTable.setLayoutData(data); final FormData sashData = new FormData(); if (prefs != null && prefs.contains(PREFS_LIBRARY_SASH)) { sashData.left = new FormAttachment(0, prefs.getInt(PREFS_LIBRARY_SASH)); } else { sashData.left = new FormAttachment(50, 0); } sashData.bottom = new FormAttachment(100, 0); sashData.top = new FormAttachment(0, 0); // 50% across sash.setLayoutData(sashData); data = new FormData(); data.top = new FormAttachment(0, 0); data.bottom = new FormAttachment(100, 0); data.left = new FormAttachment(sash, 0); data.right = new FormAttachment(100, 0); mLibraryAllocationTable.setLayoutData(data); // allow resizes, but cap at minPanelWidth sash.addListener(SWT.Selection, new Listener() { public void handleEvent(Event e) { Rectangle sashRect = sash.getBounds(); Rectangle panelRect = top.getClientArea(); int right = panelRect.width - sashRect.width - minPanelWidth; e.x = Math.max(Math.min(e.x, right), minPanelWidth); if (e.x != sashRect.x) { sashData.left = new FormAttachment(0, e.x); prefs.setValue(PREFS_LIBRARY_SASH, e.y); top.layout(); } } }); } private void emptyTables() { mAllocationTable.removeAll(); mLibraryTable.removeAll(); mLibraryAllocationTable.removeAll(); mDetailTable.removeAll(); } private void sortAllocationsPerLibrary() { if (mClientData != null) { mLibraryAllocations.clear(); // create a hash map of LibraryAllocations to access aggregate // objects already created HashMap<String, LibraryAllocations> libcache = new HashMap<String, LibraryAllocations>(); // get the allocation count int count = mDisplayedAllocations.size(); for (int i = 0; i < count; i++) { NativeAllocationInfo allocInfo = mDisplayedAllocations.get(i); NativeStackCallInfo stackCallInfo = allocInfo.getRelevantStackCallInfo(); if (stackCallInfo != null) { String libraryName = stackCallInfo.getLibraryName(); LibraryAllocations liballoc = libcache.get(libraryName); if (liballoc == null) { // didn't find a library allocation object already // created so we create one liballoc = new LibraryAllocations(libraryName); // add it to the cache libcache.put(libraryName, liballoc); // add it to the list mLibraryAllocations.add(liballoc); } // add the MallocInfo object to it. liballoc.addAllocation(allocInfo); } } // now that the list is created, we need to compute the size and // sort it by size. This will also sort the MallocInfo objects // inside each LibraryAllocation objects. for (LibraryAllocations liballoc : mLibraryAllocations) { liballoc.computeAllocationSizeAndCount(); } // now we sort it Collections.sort(mLibraryAllocations, new Comparator<LibraryAllocations>() { public int compare(LibraryAllocations o1, LibraryAllocations o2) { return o2.getSize() - o1.getSize(); } }); } } private void renderBitmap(ClientData cd) { byte[] pixData; // Atomically get and clear the heap data. synchronized (cd) { if (serializeHeapData(cd.getVmHeapData()) == false) { // no change, we return. return; } pixData = getSerializedData(); ImageData id = createLinearHeapImage(pixData, 200, mMapPalette); Image image = new Image(mBase.getDisplay(), id); mImage.setImage(image); mImage.pack(true); } } /* * Create color palette for map. Set up titles for legend. */ private static PaletteData createPalette() { RGB colors[] = new RGB[NUM_PALETTE_ENTRIES]; colors[0] = new RGB(192, 192, 192); // non-heap pixels are gray mMapLegend[0] = "(heap expansion area)"; colors[1] = new RGB(0, 0, 0); // free chunks are black mMapLegend[1] = "free"; colors[HeapSegmentElement.KIND_OBJECT + 2] = new RGB(0, 0, 255); // objects are blue mMapLegend[HeapSegmentElement.KIND_OBJECT + 2] = "data object"; colors[HeapSegmentElement.KIND_CLASS_OBJECT + 2] = new RGB(0, 255, 0); // class objects are green mMapLegend[HeapSegmentElement.KIND_CLASS_OBJECT + 2] = "class object"; colors[HeapSegmentElement.KIND_ARRAY_1 + 2] = new RGB(255, 0, 0); // byte/bool arrays are red mMapLegend[HeapSegmentElement.KIND_ARRAY_1 + 2] = "1-byte array (byte[], boolean[])"; colors[HeapSegmentElement.KIND_ARRAY_2 + 2] = new RGB(255, 128, 0); // short/char arrays are orange mMapLegend[HeapSegmentElement.KIND_ARRAY_2 + 2] = "2-byte array (short[], char[])"; colors[HeapSegmentElement.KIND_ARRAY_4 + 2] = new RGB(255, 255, 0); // obj/int/float arrays are yellow mMapLegend[HeapSegmentElement.KIND_ARRAY_4 + 2] = "4-byte array (object[], int[], float[])"; colors[HeapSegmentElement.KIND_ARRAY_8 + 2] = new RGB(255, 128, 128); // long/double arrays are pink mMapLegend[HeapSegmentElement.KIND_ARRAY_8 + 2] = "8-byte array (long[], double[])"; colors[HeapSegmentElement.KIND_UNKNOWN + 2] = new RGB(255, 0, 255); // unknown objects are cyan mMapLegend[HeapSegmentElement.KIND_UNKNOWN + 2] = "unknown object"; colors[HeapSegmentElement.KIND_NATIVE + 2] = new RGB(64, 64, 64); // native objects are dark gray mMapLegend[HeapSegmentElement.KIND_NATIVE + 2] = "non-Java object"; return new PaletteData(colors); } private void saveAllocations(String fileName) { try { PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(fileName))); for (NativeAllocationInfo alloc : mAllocations) { out.println(alloc.toString()); } out.close(); } catch (IOException e) { Log.e("Native", e); } } }