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 \