blob: 46461bf321664c75ace83a09c4ee092dedbf5cce [file] [log] [blame]
/*
* 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);
}
}
}