Add "Dump HPROF file" to DDMS/DDMS plugin
This uses the (new in cupcake) VM command through JDWP.
Older VMs are detected through the (also new) 'FEAT' command that notifies
which features the VM supports.
The hprof file is right now saved in /sdcard. Due to donut+ apps not having
the SD Card permission by default, we may need to change this in the (near)
future.
Upon completion of the dump by the VM, DDMS will give the user a file selector
to choose a place to save the file on the host machine.
Future improvements: run (our own) hat, or hprof-conv and a standard hprof
tool (Eclipse MAT integration for instance). This should be configurable
by the user.
Change-Id: I33696b0263e3d0788ad5d90cedf3cd17393d2f9b
diff --git a/ddms/app/src/com/android/ddms/UIThread.java b/ddms/app/src/com/android/ddms/UIThread.java
index 86cab20..0e091e9 100644
--- a/ddms/app/src/com/android/ddms/UIThread.java
+++ b/ddms/app/src/com/android/ddms/UIThread.java
@@ -18,13 +18,16 @@
import com.android.ddmlib.AndroidDebugBridge;
import com.android.ddmlib.Client;
+import com.android.ddmlib.ClientData;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.Log;
+import com.android.ddmlib.SyncService;
+import com.android.ddmlib.ClientData.IHprofDumpHandler;
import com.android.ddmlib.Log.ILogOutput;
import com.android.ddmlib.Log.LogLevel;
+import com.android.ddmlib.SyncService.SyncResult;
import com.android.ddmuilib.AllocationPanel;
import com.android.ddmuilib.DevicePanel;
-import com.android.ddmuilib.DevicePanel.IUiSelectionListener;
import com.android.ddmuilib.EmulatorControlPanel;
import com.android.ddmuilib.HeapPanel;
import com.android.ddmuilib.ITableFocusListener;
@@ -33,9 +36,11 @@
import com.android.ddmuilib.InfoPanel;
import com.android.ddmuilib.NativeHeapPanel;
import com.android.ddmuilib.ScreenShotDialog;
+import com.android.ddmuilib.SyncProgressMonitor;
import com.android.ddmuilib.SysinfoPanel;
import com.android.ddmuilib.TablePanel;
import com.android.ddmuilib.ThreadPanel;
+import com.android.ddmuilib.DevicePanel.IUiSelectionListener;
import com.android.ddmuilib.actions.ToolItemAction;
import com.android.ddmuilib.explorer.DeviceExplorer;
import com.android.ddmuilib.log.event.EventLogPanel;
@@ -44,7 +49,10 @@
import com.android.ddmuilib.logcat.LogPanel;
import com.android.ddmuilib.logcat.LogPanel.ILogFilterStorageManager;
+import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.dialogs.ProgressMonitorDialog;
+import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceStore;
import org.eclipse.swt.SWT;
@@ -62,6 +70,7 @@
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
+import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.FormAttachment;
@@ -72,6 +81,7 @@
import org.eclipse.swt.widgets.Composite;
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.Menu;
@@ -164,6 +174,7 @@
private ToolItem mTBShowHeapUpdates;
private ToolItem mTBHalt;
private ToolItem mTBCauseGc;
+ private ToolItem mTBDumpHprof;
private ImageLoader mDdmsImageLoader;
private ImageLoader mDdmuiLibImageLoader;
@@ -241,6 +252,7 @@
private EventLogPanel mEventLogPanel;
+
private class TableFocusListener implements ITableFocusListener {
private IFocusedTableActivator mCurrentActivator;
@@ -280,6 +292,99 @@
}
+ private class HProfHandler implements IHprofDumpHandler {
+
+ private final Shell mParentShell;
+
+ public HProfHandler(Shell parentShell) {
+ mParentShell = parentShell;
+ }
+
+ public void onFailure(final Client client) {
+ mDisplay.asyncExec(new Runnable() {
+ public void run() {
+ try {
+ MessageDialog.openError(mParentShell, "HPROF Error",
+ String.format(
+ "Unable to create HPROF file for application '%1$s'.\n" +
+ "Check logcat for more information.",
+ client.getClientData().getClientDescription()));
+ } finally {
+ // this will make sure the dump hprof button is re-enabled for the
+ // current selection. as the client is finished dumping an hprof file
+ enableButtons();
+ }
+ }
+ });
+ }
+
+ public void onSuccess(final String file, final Client client) {
+ mDisplay.asyncExec(new Runnable() {
+ public void run() {
+ final IDevice device = client.getDevice();
+ try {
+ // get the sync service to pull the HPROF file
+ final SyncService sync = client.getDevice().getSyncService();
+ if (sync != null) {
+ promptAndPull(device, client, sync, file);
+ } else {
+ MessageDialog.openError(mParentShell, "HPROF Error",
+ String.format(
+ "Unable to download HPROF file from device '%1$s'.",
+ device.getSerialNumber()));
+ }
+ } catch (Exception e) {
+ MessageDialog.openError(mParentShell, "HPROF Error",
+ String.format("Unable to download HPROF file from device '%1$s'.",
+ device.getSerialNumber()));
+
+ } finally {
+ // this will make sure the dump hprof button is re-enabled for the
+ // current selection. as the client is finished dumping an hprof file
+ enableButtons();
+ }
+ }
+ });
+ }
+
+ private void promptAndPull(final IDevice device, final Client client,
+ final SyncService sync, final String remoteFile) {
+ try {
+ FileDialog fileDialog = new FileDialog(mParentShell, SWT.SAVE);
+
+ fileDialog.setText("Save HPROF file");
+ fileDialog.setFileName(
+ client.getClientData().getClientDescription() + ".hprof");
+
+ final String localFileName = fileDialog.open();
+ if (localFileName != null) {
+ final File localFile = new File(localFileName);
+
+ new ProgressMonitorDialog(mParentShell).run(true, true,
+ new IRunnableWithProgress() {
+ public void run(IProgressMonitor monitor) {
+ SyncResult result = sync.pullFile(remoteFile, localFileName,
+ new SyncProgressMonitor(monitor, String.format(
+ "Pulling %1$s from the device",
+ localFile.getName())));
+
+ if (result.getCode() != SyncService.RESULT_OK) {
+ MessageDialog.openError(mParentShell, "HPROF Error",
+ String.format("Failed to pull %1$s: %2$s", remoteFile,
+ result.getMessage()));
+ }
+
+ sync.close();
+ }
+ });
+ }
+ } catch (Exception e) {
+ MessageDialog.openError(mParentShell, "HPROF Error",
+ String.format("Unable to download HPROF file from device '%1$s'.",
+ device.getSerialNumber()));
+ }
+ }
+ }
/**
* Generic constructor.
@@ -328,7 +433,7 @@
public void runUI() {
Display.setAppName("ddms");
mDisplay = new Display();
- Shell shell = new Shell(mDisplay);
+ final Shell shell = new Shell(mDisplay);
// create the image loaders for DDMS and DDMUILIB
mDdmsImageLoader = new ImageLoader(this.getClass());
@@ -360,6 +465,9 @@
}
});
+ // set the handler for hprof dump
+ ClientData.setHprofDumpHandler(new HProfHandler(shell));
+
// [try to] ensure ADB is running
String adbLocation = System.getProperty("com.android.ddms.bindir"); //$NON-NLS-1$
if (adbLocation != null && adbLocation.length() != 0) {
@@ -965,6 +1073,23 @@
}
});
+ // add "cause GC" button
+ mTBDumpHprof = new ToolItem(toolBar, SWT.PUSH);
+ mTBDumpHprof.setToolTipText("Dump HPROF file");
+ mTBDumpHprof.setEnabled(false);
+ mTBDumpHprof.setImage(ImageHelper.loadImage(mDdmuiLibImageLoader, display,
+ DevicePanel.ICON_HPROF, DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null));
+ mTBDumpHprof.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ mDevicePanel.dumpHprof();
+
+ // this will make sure the dump hprof button is disabled for the current selection
+ // as the client is already dumping an hprof file
+ enableButtons();
+ }
+ });
+
toolBar.pack();
}
@@ -1305,15 +1430,33 @@
ToolItemAction pullAction = new ToolItemAction(toolBar, SWT.PUSH);
pullAction.item.setToolTipText("Pull File from Device");
- pullAction.item.setImage(mDdmuiLibImageLoader.loadImage("pull.png", mDisplay)); //$NON-NLS-1$
+ Image image = mDdmuiLibImageLoader.loadImage("pull.png", mDisplay); //$NON-NLS-1$
+ if (image != null) {
+ pullAction.item.setImage(image);
+ } else {
+ // this is for debugging purpose when the icon is missing
+ pullAction.item.setText("Pull"); //$NON-NLS-1$
+ }
ToolItemAction pushAction = new ToolItemAction(toolBar, SWT.PUSH);
pushAction.item.setToolTipText("Push file onto Device");
- pushAction.item.setImage(mDdmuiLibImageLoader.loadImage("push.png", mDisplay)); //$NON-NLS-1$
+ image = mDdmuiLibImageLoader.loadImage("push.png", mDisplay); //$NON-NLS-1$
+ if (image != null) {
+ pushAction.item.setImage(image);
+ } else {
+ // this is for debugging purpose when the icon is missing
+ pushAction.item.setText("Push"); //$NON-NLS-1$
+ }
ToolItemAction deleteAction = new ToolItemAction(toolBar, SWT.PUSH);
deleteAction.item.setToolTipText("Delete");
- deleteAction.item.setImage(mDdmuiLibImageLoader.loadImage("delete.png", mDisplay)); //$NON-NLS-1$
+ image = mDdmuiLibImageLoader.loadImage("delete.png", mDisplay); //$NON-NLS-1$
+ if (image != null) {
+ deleteAction.item.setImage(image);
+ } else {
+ // this is for debugging purpose when the icon is missing
+ deleteAction.item.setText("Delete"); //$NON-NLS-1$
+ }
// device explorer
mExplorer = new DeviceExplorer();
@@ -1438,6 +1581,9 @@
mTBShowHeapUpdates.setEnabled(true);
mTBHalt.setEnabled(true);
mTBCauseGc.setEnabled(true);
+ mTBDumpHprof.setEnabled(
+ mCurrentClient.getClientData().hasFeature(ClientData.FEATURE_HPROF) &&
+ mCurrentClient.getClientData().hasPendingHprofDump() == false);
} else {
// list is empty, disable these
mTBShowThreadUpdates.setSelection(false);
@@ -1446,6 +1592,7 @@
mTBShowHeapUpdates.setEnabled(false);
mTBHalt.setEnabled(false);
mTBCauseGc.setEnabled(false);
+ mTBDumpHprof.setEnabled(false);
}
}
diff --git a/ddms/libs/ddmlib/src/com/android/ddmlib/Client.java b/ddms/libs/ddmlib/src/com/android/ddmlib/Client.java
index c8e5498..1c47b85 100644
--- a/ddms/libs/ddmlib/src/com/android/ddmlib/Client.java
+++ b/ddms/libs/ddmlib/src/com/android/ddmlib/Client.java
@@ -224,6 +224,20 @@
}
/**
+ * Makes the VM dump an HPROF file
+ */
+ public void dumpHprof() {
+ try {
+ String file = "/sdcard/" + mClientData.getClientDescription().replaceAll("\\:", ".") +
+ ".hprof";
+ HandleHeap.sendHPDU(this, file);
+ } catch (IOException e) {
+ Log.w("ddms", "Send of HPDU message failed");
+ // ignore
+ }
+ }
+
+ /**
* Enables or disables the thread update.
* <p/>If <code>true</code> the VM will be able to send thread information. Thread information
* must be requested with {@link #requestThreadUpdate()}.
diff --git a/ddms/libs/ddmlib/src/com/android/ddmlib/ClientData.java b/ddms/libs/ddmlib/src/com/android/ddmlib/ClientData.java
index d396d15..eea609c 100644
--- a/ddms/libs/ddmlib/src/com/android/ddmlib/ClientData.java
+++ b/ddms/libs/ddmlib/src/com/android/ddmlib/ClientData.java
@@ -112,6 +112,8 @@
*/
public final static String FEATURE_HPROF = "hprof-heap-dump"; // $NON-NLS-1$
+ private static IHprofDumpHandler sHprofDumpHandler;
+
// is this a DDM-aware client?
private boolean mIsDdmAware;
@@ -127,7 +129,7 @@
// how interested are we in a debugger?
private int mDebuggerInterest;
- // List of supported feature by the client.
+ // List of supported features by the client.
private final HashSet<String> mFeatures = new HashSet<String>();
// Thread tracking (THCR, THDE).
@@ -156,6 +158,8 @@
private AllocationInfo[] mAllocations;
private int mAllocationStatus = ALLOCATION_TRACKING_UNKNOWN;
+ private String mPendingHprofDump;
+
/**
* Heap Information.
* <p/>The heap is composed of several {@link HeapSegment} objects.
@@ -252,10 +256,36 @@
public Map<Integer, ArrayList<HeapSegmentElement>> getProcessedHeapMap() {
return mProcessedHeapMap;
}
-
-
}
+ /**
+ * Handlers able to act on HPROF dumps.
+ */
+ public interface IHprofDumpHandler {
+ /**
+ * Called when a HPROF dump succeeded.
+ * @param remoteFile the device-side filename of the HPROF file.
+ * @param client the client for which the HPROF file was.
+ */
+ void onSuccess(String remoteFile, Client client);
+
+ /**
+ * Called when the HPROF dump failed.
+ * @param client the client for which the HPROF file was.
+ */
+ void onFailure(Client client);
+ }
+
+ /**
+ * Sets the handler to receive notifications when an HPROF dump succeeded or failed.
+ */
+ public static void setHprofDumpHandler(IHprofDumpHandler handler) {
+ sHprofDumpHandler = handler;
+ }
+
+ static IHprofDumpHandler getHprofDumpHandler() {
+ return sHprofDumpHandler;
+ }
/**
* Generic constructor.
@@ -530,5 +560,17 @@
public boolean hasFeature(String feature) {
return mFeatures.contains(feature);
}
+
+ void setPendingHprofDump(String pendingHprofDump) {
+ mPendingHprofDump = pendingHprofDump;
+ }
+
+ String getPendingHprofDump() {
+ return mPendingHprofDump;
+ }
+
+ public boolean hasPendingHprofDump() {
+ return mPendingHprofDump != null;
+ }
}
diff --git a/ddms/libs/ddmlib/src/com/android/ddmlib/HandleHeap.java b/ddms/libs/ddmlib/src/com/android/ddmlib/HandleHeap.java
index 5111638..f186899 100644
--- a/ddms/libs/ddmlib/src/com/android/ddmlib/HandleHeap.java
+++ b/ddms/libs/ddmlib/src/com/android/ddmlib/HandleHeap.java
@@ -16,6 +16,8 @@
package com.android.ddmlib;
+import com.android.ddmlib.ClientData.IHprofDumpHandler;
+
import java.io.IOException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
@@ -243,6 +245,7 @@
finishChunkPacket(packet, CHUNK_HPDU, buf.position());
Log.d("ddm-heap", "Sending " + name(CHUNK_HPDU) + " '" + fileName +"'");
client.sendAndConsume(packet, mInst);
+ client.getClientData().setPendingHprofDump(fileName);
}
/*
@@ -251,13 +254,24 @@
private void handleHPDU(Client client, ByteBuffer data) {
byte result;
+ // get the filename and make the client not have pending HPROF dump anymore.
+ String filename = client.getClientData().getPendingHprofDump();
+ client.getClientData().setPendingHprofDump(null);
+
+ // get the dump result
result = data.get();
- if (result == 0) {
- Log.i("ddm-heap", "Heap dump request has finished");
- // TODO: stuff
- } else {
- Log.w("ddm-heap", "Heap dump request failed (check device log)");
+ // get the app-level handler for HPROF dump
+ IHprofDumpHandler handler = ClientData.getHprofDumpHandler();
+ if (handler != null) {
+ if (result == 0) {
+ handler.onSuccess(filename, client);
+
+ Log.i("ddm-heap", "Heap dump request has finished");
+ } else {
+ handler.onFailure(client);
+ Log.w("ddm-heap", "Heap dump request failed (check device log)");
+ }
}
}
@@ -317,7 +331,7 @@
enabled = (data.get() != 0);
Log.d("ddm-heap", "REAQ says: enabled=" + enabled);
-
+
client.getClientData().setAllocationStatus(enabled);
}
@@ -359,7 +373,7 @@
str = "double";
}
}
-
+
// now add the array part
for (int a = 0 ; a < array; a++) {
str = str + "[]";
@@ -410,7 +424,7 @@
* (xb) class name strings
* (xb) method name strings
* (xb) source file strings
- *
+ *
* As with other DDM traffic, strings are sent as a 4-byte length
* followed by UTF-16 data.
*/
@@ -498,10 +512,10 @@
list.add(new AllocationInfo(classNames[classNameIndex],
totalSize, (short) threadId, steArray));
}
-
+
// sort biggest allocations first.
Collections.sort(list);
-
+
client.getClientData().setAllocations(list.toArray(new AllocationInfo[numEntries]));
}
@@ -521,11 +535,11 @@
for (StackTraceElement ste: rec.getStackTrace()) {
if (ste.isNativeMethod()) {
- System.out.println(" " + ste.getClassName()
+ System.out.println(" " + ste.getClassName()
+ "." + ste.getMethodName()
+ " (Native method)");
} else {
- System.out.println(" " + ste.getClassName()
+ System.out.println(" " + ste.getClassName()
+ "." + ste.getMethodName()
+ " (" + ste.getFileName()
+ ":" + ste.getLineNumber() + ")");
diff --git a/ddms/libs/ddmlib/src/com/android/ddmlib/SyncService.java b/ddms/libs/ddmlib/src/com/android/ddmlib/SyncService.java
index c1d1d3b..7abe557 100644
--- a/ddms/libs/ddmlib/src/com/android/ddmlib/SyncService.java
+++ b/ddms/libs/ddmlib/src/com/android/ddmlib/SyncService.java
@@ -374,6 +374,29 @@
}
/**
+ * Pulls a single file.
+ * <p/>Because this method just deals with a String for the remote file instead of a
+ * {@link FileEntry}, the size of the file being pulled is unknown and the
+ * {@link ISyncProgressMonitor} will not properly show the progress
+ * @param remoteFilepath the full path to the remote file
+ * @param localFilename The local destination.
+ * @param monitor The progress monitor. Cannot be null.
+ * @return a {@link SyncResult} object with a code and an optional message.
+ *
+ * @see #getNullProgressMonitor()
+ */
+ public SyncResult pullFile(String remoteFilepath, String localFilename,
+ ISyncProgressMonitor monitor) {
+ monitor.start(0);
+ //TODO: use the {@link FileListingService} to get the file size.
+
+ SyncResult result = doPullFile(remoteFilepath, localFilename, monitor);
+
+ monitor.stop();
+ return result;
+ }
+
+ /**
* Push several files.
* @param local An array of loca files to push
* @param remote the remote {@link FileEntry} representing a directory.
@@ -798,7 +821,6 @@
return new SyncResult(RESULT_OK);
}
-
/**
* Returns the mode of the remote file.
* @param path the remote file
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/DevicePanel.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/DevicePanel.java
index d9d6fa1..7532151 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/DevicePanel.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/DevicePanel.java
@@ -75,6 +75,7 @@
public final static String ICON_HEAP = "heap.png"; //$NON-NLS-1$
public final static String ICON_HALT = "halt.png"; //$NON-NLS-1$
public final static String ICON_GC = "gc.png"; //$NON-NLS-1$
+ public final static String ICON_HPROF = "hprof.png"; //$NON-NLS-1$
private IDevice mCurrentDevice;
private Client mCurrentClient;
@@ -423,6 +424,13 @@
}
}
+ public void dumpHprof() {
+ if (mCurrentClient != null) {
+ mCurrentClient.dumpHprof();
+ }
+ }
+
+
public void setEnabledHeapOnSelectedClient(boolean enable) {
if (mCurrentClient != null) {
mCurrentClient.setHeapUpdateEnabled(enable);
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/SyncProgressMonitor.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/SyncProgressMonitor.java
new file mode 100644
index 0000000..5925984
--- /dev/null
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/SyncProgressMonitor.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2009 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.SyncService.ISyncProgressMonitor;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+
+/**
+ * Implementation of the {@link ISyncProgressMonitor} wrapping an Eclipse {@link IProgressMonitor}.
+ */
+public class SyncProgressMonitor implements ISyncProgressMonitor {
+
+ private IProgressMonitor mMonitor;
+ private String mName;
+
+ public SyncProgressMonitor(IProgressMonitor monitor, String name) {
+ mMonitor = monitor;
+ mName = name;
+ }
+
+ public void start(int totalWork) {
+ mMonitor.beginTask(mName, totalWork);
+ }
+
+ public void stop() {
+ mMonitor.done();
+ }
+
+ public void advance(int work) {
+ mMonitor.worked(work);
+ }
+
+ public boolean isCanceled() {
+ return mMonitor.isCanceled();
+ }
+
+ public void startSubTask(String name) {
+ mMonitor.subTask(name);
+ }
+}
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/explorer/DeviceExplorer.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/explorer/DeviceExplorer.java
index 4652b31..66843a4 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/explorer/DeviceExplorer.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/explorer/DeviceExplorer.java
@@ -25,6 +25,7 @@
import com.android.ddmlib.SyncService.SyncResult;
import com.android.ddmuilib.DdmUiPreferences;
import com.android.ddmuilib.Panel;
+import com.android.ddmuilib.SyncProgressMonitor;
import com.android.ddmuilib.TableHelper;
import com.android.ddmuilib.actions.ICommonAction;
import com.android.ddmuilib.console.DdmConsole;
@@ -103,41 +104,6 @@
private String mDefaultSave;
- /**
- * Implementation of the SyncService.ISyncProgressMonitor. It wraps a jFace IProgressMonitor
- * and just forward the calls to the jFace object.
- */
- private static class SyncProgressMonitor implements ISyncProgressMonitor {
-
- private IProgressMonitor mMonitor;
- private String mName;
-
- SyncProgressMonitor(IProgressMonitor monitor, String name) {
- mMonitor = monitor;
- mName = name;
- }
-
- public void start(int totalWork) {
- mMonitor.beginTask(mName, totalWork);
- }
-
- public void stop() {
- mMonitor.done();
- }
-
- public void advance(int work) {
- mMonitor.worked(work);
- }
-
- public boolean isCanceled() {
- return mMonitor.isCanceled();
- }
-
- public void startSubTask(String name) {
- mMonitor.subTask(name);
- }
- }
-
public DeviceExplorer() {
}
diff --git a/ddms/libs/ddmuilib/src/resources/images/hprof.png b/ddms/libs/ddmuilib/src/resources/images/hprof.png
new file mode 100644
index 0000000..123d062
--- /dev/null
+++ b/ddms/libs/ddmuilib/src/resources/images/hprof.png
Binary files differ
diff --git a/eclipse/plugins/.gitignore b/eclipse/plugins/.gitignore
index 63ac0cf..cc105d2 100644
--- a/eclipse/plugins/.gitignore
+++ b/eclipse/plugins/.gitignore
@@ -30,6 +30,7 @@
com.android.ide.eclipse.ddms/icons/gc.png
com.android.ide.eclipse.ddms/icons/halt.png
com.android.ide.eclipse.ddms/icons/heap.png
+com.android.ide.eclipse.ddms/icons/hprof.png
com.android.ide.eclipse.ddms/icons/i.png
com.android.ide.eclipse.ddms/icons/importBug.png
com.android.ide.eclipse.ddms/icons/load.png
diff --git a/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/views/DeviceView.java b/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/views/DeviceView.java
index 30172f5..7f9c3c8 100644
--- a/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/views/DeviceView.java
+++ b/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/views/DeviceView.java
@@ -17,30 +17,41 @@
package com.android.ide.eclipse.ddms.views;
+import com.android.ddmlib.AndroidDebugBridge;
import com.android.ddmlib.Client;
import com.android.ddmlib.ClientData;
-import com.android.ddmlib.AndroidDebugBridge;
import com.android.ddmlib.IDevice;
+import com.android.ddmlib.SyncService;
+import com.android.ddmlib.ClientData.IHprofDumpHandler;
+import com.android.ddmlib.SyncService.SyncResult;
import com.android.ddmuilib.DevicePanel;
import com.android.ddmuilib.ScreenShotDialog;
+import com.android.ddmuilib.SyncProgressMonitor;
import com.android.ddmuilib.DevicePanel.IUiSelectionListener;
import com.android.ide.eclipse.ddms.DdmsPlugin;
import com.android.ide.eclipse.ddms.DdmsPlugin.IDebugLauncher;
+import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.dialogs.ProgressMonitorDialog;
+import org.eclipse.jface.operation.IRunnableWithProgress;
+import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.ViewPart;
+import java.io.File;
+
public class DeviceView extends ViewPart implements IUiSelectionListener {
private final static boolean USE_SELECTED_DEBUG_PORT = true;
@@ -48,6 +59,8 @@
public static final String ID =
"com.android.ide.eclipse.ddms.views.DeviceView"; //$NON-NLS-1$
+ private static DeviceView sThis;
+
private DevicePanel mDeviceList;
private Action mResetAdbAction;
private Action mCaptureAction;
@@ -56,9 +69,103 @@
private Action mGcAction;
private Action mKillAppAction;
private Action mDebugAction;
+ private Action mHprofAction;
private IDebugLauncher mDebugLauncher;
- private static DeviceView sThis;
+ private class HProfHandler implements IHprofDumpHandler {
+
+ private final Shell mParentShell;
+
+ public HProfHandler(Shell parentShell) {
+ mParentShell = parentShell;
+ }
+
+ public void onFailure(final Client client) {
+ mParentShell.getDisplay().asyncExec(new Runnable() {
+ public void run() {
+ try {
+ MessageDialog.openError(mParentShell, "HPROF Error",
+ String.format(
+ "Unable to create HPROF file for application '%1$s'.\n" +
+ "Check logcat for more information.",
+ client.getClientData().getClientDescription()));
+ } finally {
+ // this will make sure the dump hprof button is re-enabled for the
+ // current selection. as the client is finished dumping an hprof file
+ doSelectionChanged(mDeviceList.getSelectedClient());
+ }
+ }
+ });
+ }
+
+ public void onSuccess(final String file, final Client client) {
+ mParentShell.getDisplay().asyncExec(new Runnable() {
+ public void run() {
+ final IDevice device = client.getDevice();
+ try {
+ // get the sync service to pull the HPROF file
+ final SyncService sync = client.getDevice().getSyncService();
+ if (sync != null) {
+ promptAndPull(device, client, sync, file);
+ } else {
+ MessageDialog.openError(mParentShell, "HPROF Error",
+ String.format(
+ "Unable to download HPROF file from device '%1$s'.",
+ device.getSerialNumber()));
+ }
+ } catch (Exception e) {
+ MessageDialog.openError(mParentShell, "HPROF Error",
+ String.format("Unable to download HPROF file from device '%1$s'.",
+ device.getSerialNumber()));
+
+ } finally {
+ // this will make sure the dump hprof button is re-enabled for the
+ // current selection. as the client is finished dumping an hprof file
+ doSelectionChanged(mDeviceList.getSelectedClient());
+ }
+ }
+ });
+ }
+
+ private void promptAndPull(final IDevice device, final Client client,
+ final SyncService sync, final String remoteFile) {
+ try {
+ FileDialog fileDialog = new FileDialog(mParentShell, SWT.SAVE);
+
+ fileDialog.setText("Save HPROF file");
+ fileDialog.setFileName(
+ client.getClientData().getClientDescription() + ".hprof");
+
+ final String localFileName = fileDialog.open();
+ if (localFileName != null) {
+ final File localFile = new File(localFileName);
+
+ new ProgressMonitorDialog(mParentShell).run(true, true,
+ new IRunnableWithProgress() {
+ public void run(IProgressMonitor monitor) {
+ SyncResult result = sync.pullFile(remoteFile, localFileName,
+ new SyncProgressMonitor(monitor, String.format(
+ "Pulling %1$s from the device",
+ localFile.getName())));
+
+ if (result.getCode() != SyncService.RESULT_OK) {
+ MessageDialog.openError(mParentShell, "HPROF Error",
+ String.format("Failed to pull %1$s: %2$s", remoteFile,
+ result.getMessage()));
+ }
+
+ sync.close();
+ }
+ });
+ }
+ } catch (Exception e) {
+ MessageDialog.openError(mParentShell, "HPROF Error",
+ String.format("Unable to download HPROF file from device '%1$s'.",
+ device.getSerialNumber()));
+ }
+ }
+ }
+
public DeviceView() {
// the view is declared with allowMultiple="false" so we
@@ -86,6 +193,8 @@
@Override
public void createPartControl(Composite parent) {
+ ClientData.setHprofDumpHandler(new HProfHandler(parent.getShell()));
+
mDeviceList = new DevicePanel(DdmsPlugin.getImageLoader(), USE_SELECTED_DEBUG_PORT);
mDeviceList.createPanel(parent);
mDeviceList.addSelectionListener(this);
@@ -156,6 +265,18 @@
mGcAction.setImageDescriptor(DdmsPlugin.getImageLoader()
.loadDescriptor(DevicePanel.ICON_GC));
+ mHprofAction = new Action() {
+ @Override
+ public void run() {
+ mDeviceList.dumpHprof();
+ doSelectionChanged(mDeviceList.getSelectedClient());
+ }
+ };
+ mHprofAction.setText("Dump HPROF file");
+ mHprofAction.setToolTipText("Dump HPROF file");
+ mHprofAction.setImageDescriptor(DdmsPlugin.getImageLoader()
+ .loadDescriptor(DevicePanel.ICON_HPROF));
+
mUpdateHeapAction = new Action("Update Heap", IAction.AS_CHECK_BOX) {
@Override
public void run() {
@@ -270,6 +391,9 @@
mUpdateThreadAction.setEnabled(true);
mUpdateThreadAction.setChecked(selectedClient.isThreadUpdateEnabled());
+ mHprofAction.setEnabled(
+ selectedClient.getClientData().hasFeature(ClientData.FEATURE_HPROF) &&
+ selectedClient.getClientData().hasPendingHprofDump() == false);
} else {
if (USE_SELECTED_DEBUG_PORT) {
// set the client as the debug client
@@ -286,6 +410,7 @@
mUpdateHeapAction.setEnabled(false);
mUpdateThreadAction.setEnabled(false);
mUpdateThreadAction.setChecked(false);
+ mHprofAction.setEnabled(false);
}
}
@@ -307,6 +432,7 @@
menuManager.add(mUpdateHeapAction);
menuManager.add(new Separator());
menuManager.add(mGcAction);
+ menuManager.add(mHprofAction);
menuManager.add(new Separator());
menuManager.add(mKillAppAction);
menuManager.add(new Separator());
@@ -321,6 +447,9 @@
toolBarManager.add(mUpdateThreadAction);
toolBarManager.add(mUpdateHeapAction);
toolBarManager.add(new Separator());
+ toolBarManager.add(mGcAction);
+ toolBarManager.add(mHprofAction);
+ toolBarManager.add(new Separator());
toolBarManager.add(mKillAppAction);
toolBarManager.add(new Separator());
toolBarManager.add(mCaptureAction);
diff --git a/eclipse/scripts/create_ddms_symlinks.sh b/eclipse/scripts/create_ddms_symlinks.sh
index 276cf9b..a5b20a8 100755
--- a/eclipse/scripts/create_ddms_symlinks.sh
+++ b/eclipse/scripts/create_ddms_symlinks.sh
@@ -65,7 +65,7 @@
e.png edit.png empty.png emulator.png \
forward.png \
gc.png \
- heap.png halt.png \
+ heap.png halt.png hprof.png \
i.png importBug.png \
load.png \
pause.png play.png pull.png push.png \