diff --git a/tools/ddms/app/src/com/android/ddms/UIThread.java b/tools/ddms/app/src/com/android/ddms/UIThread.java
index 86cab208f..0e091e9df 100644
--- a/tools/ddms/app/src/com/android/ddms/UIThread.java
+++ b/tools/ddms/app/src/com/android/ddms/UIThread.java
@@ -18,13 +18,16 @@ package com.android.ddms;
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.ImageLoader;
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.LogFilter;
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.events.ShellListener;
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.layout.GridLayout;
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 @@ public class UIThread implements IUiSelectionListener {
private ToolItem mTBShowHeapUpdates;
private ToolItem mTBHalt;
private ToolItem mTBCauseGc;
+ private ToolItem mTBDumpHprof;
private ImageLoader mDdmsImageLoader;
private ImageLoader mDdmuiLibImageLoader;
@@ -241,6 +252,7 @@ public class UIThread implements IUiSelectionListener {
private EventLogPanel mEventLogPanel;
+
private class TableFocusListener implements ITableFocusListener {
private IFocusedTableActivator mCurrentActivator;
@@ -280,6 +292,99 @@ public class UIThread implements IUiSelectionListener {
}
+ 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 class UIThread implements IUiSelectionListener {
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 @@ public class UIThread implements IUiSelectionListener {
}
});
+ // 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 @@ public class UIThread implements IUiSelectionListener {
}
});
+ // 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 @@ public class UIThread implements IUiSelectionListener {
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 @@ public class UIThread implements IUiSelectionListener {
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 @@ public class UIThread implements IUiSelectionListener {
mTBShowHeapUpdates.setEnabled(false);
mTBHalt.setEnabled(false);
mTBCauseGc.setEnabled(false);
+ mTBDumpHprof.setEnabled(false);
}
}
diff --git a/tools/ddms/libs/ddmlib/src/com/android/ddmlib/Client.java b/tools/ddms/libs/ddmlib/src/com/android/ddmlib/Client.java
index c8e549877..1c47b8526 100644
--- a/tools/ddms/libs/ddmlib/src/com/android/ddmlib/Client.java
+++ b/tools/ddms/libs/ddmlib/src/com/android/ddmlib/Client.java
@@ -223,6 +223,20 @@ public class Client {
}
}
+ /**
+ * 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.
*
If true the VM will be able to send thread information. Thread information
diff --git a/tools/ddms/libs/ddmlib/src/com/android/ddmlib/ClientData.java b/tools/ddms/libs/ddmlib/src/com/android/ddmlib/ClientData.java
index d396d1525..eea609ccc 100644
--- a/tools/ddms/libs/ddmlib/src/com/android/ddmlib/ClientData.java
+++ b/tools/ddms/libs/ddmlib/src/com/android/ddmlib/ClientData.java
@@ -112,6 +112,8 @@ public class ClientData {
*/
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 @@ public class ClientData {
// 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 mFeatures = new HashSet();
// Thread tracking (THCR, THDE).
@@ -156,6 +158,8 @@ public class ClientData {
private AllocationInfo[] mAllocations;
private int mAllocationStatus = ALLOCATION_TRACKING_UNKNOWN;
+ private String mPendingHprofDump;
+
/**
* Heap Information.
* The heap is composed of several {@link HeapSegment} objects.
@@ -252,10 +256,36 @@ public class ClientData {
public Map> 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 class ClientData {
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/tools/ddms/libs/ddmlib/src/com/android/ddmlib/HandleHeap.java b/tools/ddms/libs/ddmlib/src/com/android/ddmlib/HandleHeap.java
index 51116387e..f186899b8 100644
--- a/tools/ddms/libs/ddmlib/src/com/android/ddmlib/HandleHeap.java
+++ b/tools/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 @@ final class HandleHeap extends ChunkHandler {
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 @@ final class HandleHeap extends ChunkHandler {
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 @@ final class HandleHeap extends ChunkHandler {
enabled = (data.get() != 0);
Log.d("ddm-heap", "REAQ says: enabled=" + enabled);
-
+
client.getClientData().setAllocationStatus(enabled);
}
@@ -359,7 +373,7 @@ final class HandleHeap extends ChunkHandler {
str = "double";
}
}
-
+
// now add the array part
for (int a = 0 ; a < array; a++) {
str = str + "[]";
@@ -410,7 +424,7 @@ final class HandleHeap extends ChunkHandler {
* (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 @@ final class HandleHeap extends ChunkHandler {
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 @@ final class HandleHeap extends ChunkHandler {
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/tools/ddms/libs/ddmlib/src/com/android/ddmlib/SyncService.java b/tools/ddms/libs/ddmlib/src/com/android/ddmlib/SyncService.java
index c1d1d3b93..7abe557da 100644
--- a/tools/ddms/libs/ddmlib/src/com/android/ddmlib/SyncService.java
+++ b/tools/ddms/libs/ddmlib/src/com/android/ddmlib/SyncService.java
@@ -373,6 +373,29 @@ public final class SyncService {
return result;
}
+ /**
+ * Pulls a single file.
+ * 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
@@ -798,7 +821,6 @@ public final class SyncService {
return new SyncResult(RESULT_OK);
}
-
/**
* Returns the mode of the remote file.
* @param path the remote file
diff --git a/tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/DevicePanel.java b/tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/DevicePanel.java
index d9d6fa1dc..75321519d 100644
--- a/tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/DevicePanel.java
+++ b/tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/DevicePanel.java
@@ -75,6 +75,7 @@ public final class DevicePanel extends Panel implements IDebugBridgeChangeListen
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 final class DevicePanel extends Panel implements IDebugBridgeChangeListen
}
}
+ public void dumpHprof() {
+ if (mCurrentClient != null) {
+ mCurrentClient.dumpHprof();
+ }
+ }
+
+
public void setEnabledHeapOnSelectedClient(boolean enable) {
if (mCurrentClient != null) {
mCurrentClient.setHeapUpdateEnabled(enable);
diff --git a/tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/SyncProgressMonitor.java b/tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/SyncProgressMonitor.java
new file mode 100644
index 000000000..59259849c
--- /dev/null
+++ b/tools/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/tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/explorer/DeviceExplorer.java b/tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/explorer/DeviceExplorer.java
index 4652b3186..66843a473 100644
--- a/tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/explorer/DeviceExplorer.java
+++ b/tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/explorer/DeviceExplorer.java
@@ -25,6 +25,7 @@ import com.android.ddmlib.SyncService.ISyncProgressMonitor;
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 @@ public class DeviceExplorer extends Panel {
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/tools/ddms/libs/ddmuilib/src/resources/images/hprof.png b/tools/ddms/libs/ddmuilib/src/resources/images/hprof.png
new file mode 100644
index 000000000..123d06207
Binary files /dev/null and b/tools/ddms/libs/ddmuilib/src/resources/images/hprof.png differ
diff --git a/tools/eclipse/plugins/.gitignore b/tools/eclipse/plugins/.gitignore
index 63ac0cf1d..cc105d221 100644
--- a/tools/eclipse/plugins/.gitignore
+++ b/tools/eclipse/plugins/.gitignore
@@ -30,6 +30,7 @@ com.android.ide.eclipse.ddms/icons/forward.png
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/tools/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/views/DeviceView.java b/tools/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/views/DeviceView.java
index 30172f59d..7f9c3c895 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/views/DeviceView.java
+++ b/tools/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 class DeviceView extends ViewPart implements IUiSelectionListener {
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 @@ public class DeviceView extends ViewPart implements IUiSelectionListener {
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 @@ public class DeviceView extends ViewPart implements IUiSelectionListener {
@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 @@ public class DeviceView extends ViewPart implements IUiSelectionListener {
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 @@ public class DeviceView extends ViewPart implements IUiSelectionListener {
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 @@ public class DeviceView extends ViewPart implements IUiSelectionListener {
mUpdateHeapAction.setEnabled(false);
mUpdateThreadAction.setEnabled(false);
mUpdateThreadAction.setChecked(false);
+ mHprofAction.setEnabled(false);
}
}
@@ -307,6 +432,7 @@ public class DeviceView extends ViewPart implements IUiSelectionListener {
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 @@ public class DeviceView extends ViewPart implements IUiSelectionListener {
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/tools/eclipse/scripts/create_ddms_symlinks.sh b/tools/eclipse/scripts/create_ddms_symlinks.sh
index 276cf9b2b..a5b20a879 100755
--- a/tools/eclipse/scripts/create_ddms_symlinks.sh
+++ b/tools/eclipse/scripts/create_ddms_symlinks.sh
@@ -65,7 +65,7 @@ for i in \
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 \