OSDN Git Service

Add support for VMs that can stream hprof data through JDWP. Do not merge
authorXavier Ducrohet <xav@android.com>
Sat, 30 Jan 2010 02:11:14 +0000 (18:11 -0800)
committerXavier Ducrohet <xav@android.com>
Tue, 2 Feb 2010 19:33:56 +0000 (11:33 -0800)
Integrated from master for inclusion in the SDK Tools r5

Change-Id: Ifad984f5b0ae187428cb7d871b039dddf0db4ba7

ddms/app/src/com/android/ddms/UIThread.java
ddms/libs/ddmlib/src/com/android/ddmlib/Client.java
ddms/libs/ddmlib/src/com/android/ddmlib/ClientData.java
ddms/libs/ddmlib/src/com/android/ddmlib/HandleHeap.java
ddms/libs/ddmlib/src/com/android/ddmlib/HandleProfiling.java
ddms/libs/ddmuilib/src/com/android/ddmuilib/InfoPanel.java
ddms/libs/ddmuilib/src/com/android/ddmuilib/handler/BaseFileHandler.java
ddms/libs/ddmuilib/src/com/android/ddmuilib/handler/MethodProfilingHandler.java
eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/views/DeviceView.java

index c98b3f1..7940c74 100644 (file)
@@ -292,7 +292,6 @@ public class UIThread implements IUiSelectionListener, IClientChangeListener {
                 mCurrentActivator.selectAll();
             }
         }
-
     }
 
     /**
@@ -305,13 +304,15 @@ public class UIThread implements IUiSelectionListener, IClientChangeListener {
             super(parentShell);
         }
 
-        public void onFailure(final Client client) {
+        public void onEndFailure(final Client client, final String message) {
             mDisplay.asyncExec(new Runnable() {
                 public void run() {
                     try {
-                        displayError("Unable to create HPROF file for application '%1$s'.\n" +
+                        displayErrorFromUiThread(
+                                "Unable to create HPROF file for application '%1$s'\n\n%2$s" +
                                 "Check logcat for more information.",
-                                client.getClientData().getClientDescription());
+                                client.getClientData().getClientDescription(),
+                                message != null ? message + "\n\n" : "");
                     } 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
@@ -333,16 +334,16 @@ public class UIThread implements IUiSelectionListener, IClientChangeListener {
                                     client.getClientData().getClientDescription() + ".hprof",
                                     remoteFilePath, "Save HPROF file");
                             if (result != null && result.getCode() != SyncService.RESULT_OK) {
-                                displayError(
+                                displayErrorFromUiThread(
                                         "Unable to download HPROF file from device '%1$s'.\n\n%2$s",
                                         device.getSerialNumber(), result.getMessage());
                             }
                         } else {
-                            displayError("Unable to download HPROF file from device '%1$s'.",
+                            displayErrorFromUiThread("Unable to download HPROF file from device '%1$s'.",
                                     device.getSerialNumber());
                         }
                     } catch (Exception e) {
-                        displayError("Unable to download HPROF file from device '%1$s'.",
+                        displayErrorFromUiThread("Unable to download HPROF file from device '%1$s'.",
                                 device.getSerialNumber());
 
                     } finally {
@@ -354,9 +355,18 @@ public class UIThread implements IUiSelectionListener, IClientChangeListener {
             });
         }
 
-        private void displayError(String format, Object... args) {
-            MessageDialog.openError(mParentShell, "HPROF Error",
-                    String.format(format, args));
+        public void onSuccess(final byte[] data, final Client client) {
+            mDisplay.asyncExec(new Runnable() {
+                public void run() {
+                    promptAndSave(client.getClientData().getClientDescription() + ".hprof", data,
+                            "Save HPROF file");
+                }
+            });
+        }
+
+        @Override
+        protected String getDialogTitle() {
+            return "HPROF Error";
         }
     }
 
index fa53bef..5991026 100644 (file)
@@ -230,13 +230,13 @@ public class Client {
      * Makes the VM dump an HPROF file
      */
     public void dumpHprof() {
-        boolean canStream = false; //mClientData.hasFeature(ClientData.FEATURE_HPROF_STREAMING);
+        boolean canStream = mClientData.hasFeature(ClientData.FEATURE_HPROF_STREAMING);
         try {
             if (canStream) {
                 HandleHeap.sendHPDS(this);
             } else {
-                String file = "/sdcard/" + mClientData.getClientDescription().replaceAll("\\:.*", "") +
-                    ".hprof";
+                String file = "/sdcard/" + mClientData.getClientDescription().replaceAll(
+                        "\\:.*", "") + ".hprof";
                 HandleHeap.sendHPDU(this, file);
             }
         } catch (IOException e) {
index d5fe2d5..7f4b5dd 100644 (file)
@@ -18,7 +18,6 @@ package com.android.ddmlib;
 
 import com.android.ddmlib.HeapSegment.HeapSegmentElement;
 
-import java.io.File;
 import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
@@ -304,10 +303,18 @@ public class ClientData {
         void onSuccess(String remoteFilePath, Client client);
 
         /**
-         * Called when the HPROF dump failed.
-         * @param client the client for which the HPROF file was.
+         * Called when a HPROF dump was successful.
+         * @param data the data containing the HPROF file, streamed from the VM
+         * @param client the client that was profiled.
+         */
+        void onSuccess(byte[] data, Client client);
+
+        /**
+         * Called when a hprof dump failed to end on the VM side
+         * @param client the client that was profiled.
+         * @param message an optional (<code>null<code> ok) error message to be displayed.
          */
-        void onFailure(Client client);
+        void onEndFailure(Client client, String message);
     }
 
     /**
@@ -323,10 +330,10 @@ public class ClientData {
 
         /**
          * Called when a method tracing was successful.
-         * @param remoteFilePath the device-side path of the trace file.
+         * @param data the data containing the trace file, streamed from the VM
          * @param client the client that was profiled.
          */
-        void onSuccess(File localFile, Client client);
+        void onSuccess(byte[] data, Client client);
 
         /**
          * Called when method tracing failed to start
@@ -341,13 +348,6 @@ public class ClientData {
          * @param message an optional (<code>null<code> ok) error message to be displayed.
          */
         void onEndFailure(Client client, String message);
-
-        /**
-         * Called when method tracing failed to end locally.
-         * @param client the client that was profiled.
-         * @param message the mandatory message to display.
-         */
-        void onEndLocalFailure(Client client, String message);
     }
 
     /**
index f3f5497..e5b403b 100644 (file)
@@ -272,7 +272,6 @@ final class HandleHeap extends ChunkHandler {
         finishChunkPacket(packet, CHUNK_HPDS, buf.position());
         Log.d("ddm-heap", "Sending " + name(CHUNK_HPDS));
         client.sendAndConsume(packet, mInst);
-        client.getClientData().setPendingHprofDump("[streaming]");
     }
 
     /*
@@ -296,7 +295,7 @@ final class HandleHeap extends ChunkHandler {
 
                 Log.d("ddm-heap", "Heap dump request has finished");
             } else {
-                handler.onFailure(client);
+                handler.onEndFailure(client, null);
                 Log.w("ddm-heap", "Heap dump request failed (check device log)");
             }
         }
@@ -307,7 +306,15 @@ final class HandleHeap extends ChunkHandler {
      * hprof dump.
      */
     private void handleHPDS(Client client, ByteBuffer data) {
-        Log.w("ddm-prof", "got hprof file, size: " + data.capacity() + " bytes");
+        IHprofDumpHandler handler = ClientData.getHprofDumpHandler();
+        if (handler != null) {
+            byte[] stuff = new byte[data.capacity()];
+            data.get(stuff, 0, stuff.length);
+
+            Log.d("ddm-hprof", "got hprof file, size: " + data.capacity() + " bytes");
+
+            handler.onSuccess(stuff, client);
+        }
     }
 
     /**
index 8526975..0595267 100644 (file)
@@ -19,8 +19,6 @@ package com.android.ddmlib;
 import com.android.ddmlib.ClientData.IMethodProfilingHandler;
 import com.android.ddmlib.ClientData.MethodProfilingStatus;
 
-import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.nio.ByteBuffer;
 
@@ -217,36 +215,12 @@ final class HandleProfiling extends ChunkHandler {
     private void handleMPSE(Client client, ByteBuffer data) {
         IMethodProfilingHandler handler = ClientData.getMethodProfilingHandler();
         if (handler != null) {
-            FileOutputStream fos = null;
-            try {
-                File f = File.createTempFile(client.getClientData().getClientDescription(),
-                        ".trace");
-                fos = new FileOutputStream(f);
-
-                byte[] stuff = new byte[data.capacity()];
-                data.get(stuff, 0, stuff.length);
-
-                fos.write(stuff);
-                fos.close();
-                fos = null;
-
-                Log.d("ddm-prof", "got trace file, size: " + data.capacity() + " bytes");
-
-                handler.onSuccess(f, client);
-            } catch (IOException e) {
-                handler.onEndLocalFailure(client, e.getMessage());
-
-                Log.e("ddm-prof", "fail to write trace file: " + e.getMessage());
-                Log.e("ddm-prof", e);
-            } finally {
-                if (fos != null) {
-                    try {
-                        fos.close();
-                    } catch (IOException e) {
-                         //ignore
-                    }
-                }
-            }
+            byte[] stuff = new byte[data.capacity()];
+            data.get(stuff, 0, stuff.length);
+
+            Log.d("ddm-prof", "got trace file, size: " + stuff.length + " bytes");
+
+            handler.onSuccess(stuff, client);
         }
 
         client.getClientData().setMethodProfilingStatus(MethodProfilingStatus.OFF);
index d6979cb..ed402c0 100644 (file)
@@ -175,7 +175,9 @@ public class InfoPanel extends TablePanel {
             }
 
             item = mTable.getItem(ENT_SUPPORTS_HPROF);
-            if (cd.hasFeature(ClientData.FEATURE_HPROF)) {
+            if (cd.hasFeature(ClientData.FEATURE_HPROF_STREAMING)) {
+                item.setText(1, "Yes");
+            } else if (cd.hasFeature(ClientData.FEATURE_HPROF)) {
                 item.setText(1, "Yes (Application must be able to write on the SD Card)");
             } else {
                 item.setText(1, "No");
index 3a2a2ef..6c086db 100644 (file)
@@ -23,12 +23,17 @@ import com.android.ddmlib.SyncService.SyncResult;
 import com.android.ddmuilib.SyncProgressMonitor;
 
 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.swt.SWT;
+import org.eclipse.swt.widgets.Display;
 import org.eclipse.swt.widgets.FileDialog;
 import org.eclipse.swt.widgets.Shell;
 
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
 
 /**
@@ -37,7 +42,7 @@ import java.lang.reflect.InvocationTargetException;
  * @see IHprofDumpHandler
  * @see IMethodProfilingHandler
  */
-public class BaseFileHandler {
+public abstract class BaseFileHandler {
 
     protected final Shell mParentShell;
 
@@ -45,6 +50,8 @@ public class BaseFileHandler {
         mParentShell = parentShell;
     }
 
+    protected abstract String getDialogTitle();
+
     /**
      * Prompts the user for a save location and pulls the remote files into this location.
      * <p/>This <strong>must</strong> be called from the UI Thread.
@@ -74,7 +81,39 @@ public class BaseFileHandler {
     }
 
     /**
-     * Pulls a file off of a device
+     * Prompts the user for a save location and copies a temp file into it.
+     * <p/>This <strong>must</strong> be called from the UI Thread.
+     * @param localFileName The default local name
+     * @param tempFilePath The name of the temp file to copy.
+     * @param title The title of the File Save dialog.
+     * @return true if success, false on error or cancel.
+     */
+    protected boolean promptAndSave(String localFileName, byte[] data, String title) {
+        FileDialog fileDialog = new FileDialog(mParentShell, SWT.SAVE);
+
+        fileDialog.setText(title);
+        fileDialog.setFileName(localFileName);
+
+        String localFilePath = fileDialog.open();
+        if (localFilePath != null) {
+            try {
+                saveFile(data, new File(localFilePath));
+                return true;
+            } catch (IOException e) {
+                String errorMsg = e.getMessage();
+                displayErrorInUiThread(
+                        "Failed to save file '%1$s'%2$s",
+                        localFilePath,
+                        errorMsg != null ? ":\n" + errorMsg : ".");
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Pulls a file off of a device. This displays a {@link ProgressMonitorDialog} and therefore
+     * must be run from the UI Thread.
      * @param sync the {@link SyncService} to use to pull the file.
      * @param localFilePath the path of the local file to create
      * @param remoteFilePath the path of the remote file to pull
@@ -100,4 +139,61 @@ public class BaseFileHandler {
 
         return res[0];
     }
+
+    /**
+     * Display an error message.
+     * <p/>This will call about to {@link Display} to run this in an async {@link Runnable} in the
+     * UI Thread. This is safe to be called from a non-UI Thread.
+     * @param format the string to display
+     * @param args the string arguments
+     */
+    protected void displayErrorInUiThread(final String format, final Object... args) {
+        mParentShell.getDisplay().asyncExec(new Runnable() {
+            public void run() {
+                MessageDialog.openError(mParentShell, getDialogTitle(),
+                        String.format(format, args));
+            }
+        });
+    }
+
+    /**
+     * Display an error message.
+     * This must be called from the UI Thread.
+     * @param format the string to display
+     * @param args the string arguments
+     */
+    protected void displayErrorFromUiThread(final String format, final Object... args) {
+        MessageDialog.openError(mParentShell, getDialogTitle(),
+                String.format(format, args));
+    }
+
+    /**
+     * Saves a given data into a temp file and returns its corresponding {@link File} object.
+     * @param data the data to save
+     * @return the File into which the data was written or null if it failed.
+     * @throws IOException
+     */
+    protected File saveTempFile(byte[] data) throws IOException {
+        File f = File.createTempFile("ddms", null);
+        saveFile(data, f);
+        return f;
+    }
+
+    /**
+     * Saves some data into a given File.
+     * @param data the data to save
+     * @param output the file into the data is saved.
+     * @throws IOException
+     */
+    protected void saveFile(byte[] data, File output) throws IOException {
+        FileOutputStream fos = null;
+        try {
+            fos = new FileOutputStream(output);
+            fos.write(data);
+        } finally {
+            if (fos != null) {
+                fos.close();
+            }
+        }
+    }
 }
index 15cb907..b1d7f2a 100644 (file)
@@ -25,7 +25,6 @@ import com.android.ddmlib.SyncService.SyncResult;
 import com.android.ddmuilib.DdmUiPreferences;
 import com.android.ddmuilib.console.DdmConsole;
 
-import org.eclipse.jface.dialogs.MessageDialog;
 import org.eclipse.swt.widgets.Shell;
 
 import java.io.BufferedReader;
@@ -45,46 +44,32 @@ public class MethodProfilingHandler extends BaseFileHandler
         super(parentShell);
     }
 
-    public void onStartFailure(final Client client, final String message) {
-        mParentShell.getDisplay().asyncExec(new Runnable() {
-            public void run() {
-                displayError(
-                        "Unable to create Method Profiling file for application '%1$s'\n\n%2$s" +
-                        "Check logcat for more information.",
-                        client.getClientData().getClientDescription(),
-                        message != null ? message + "\n\n" : "");
-            }
-        });
+    @Override
+    protected String getDialogTitle() {
+        return "Method Profiling Error";
     }
 
-    public void onEndFailure(final Client client, final String message) {
-        mParentShell.getDisplay().asyncExec(new Runnable() {
-            public void run() {
-                displayError(
-                        "Unable to finish Method Profiling for application '%1$s'\n\n%2$s" +
-                        "Check logcat for more information.",
-                        client.getClientData().getClientDescription(),
-                        message != null ? message + "\n\n" : "");
-            }
-        });
+    public void onStartFailure(final Client client, final String message) {
+        displayErrorInUiThread(
+                "Unable to create Method Profiling file for application '%1$s'\n\n%2$s" +
+                "Check logcat for more information.",
+                client.getClientData().getClientDescription(),
+                message != null ? message + "\n\n" : "");
     }
 
-    public void onEndLocalFailure(final Client client, final String message) {
-        mParentShell.getDisplay().asyncExec(new Runnable() {
-            public void run() {
-                displayError(
-                        "Unable to write trace file locally for application '%1$s'\n\n%2$s",
-                        client.getClientData().getClientDescription(),
-                        message);
-            }
-        });
+    public void onEndFailure(final Client client, final String message) {
+        displayErrorInUiThread(
+                "Unable to finish Method Profiling for application '%1$s'\n\n%2$s" +
+                "Check logcat for more information.",
+                client.getClientData().getClientDescription(),
+                message != null ? message + "\n\n" : "");
     }
 
     public void onSuccess(final String remoteFilePath, final Client client) {
         mParentShell.getDisplay().asyncExec(new Runnable() {
             public void run() {
                 if (remoteFilePath == null) {
-                    displayError(
+                    displayErrorFromUiThread(
                             "Unable to download trace file: unknown file name.\n" +
                             "This can happen if you disconnected the device while recording the trace.");
                     return;
@@ -97,11 +82,11 @@ public class MethodProfilingHandler extends BaseFileHandler
                     if (sync != null) {
                         pullAndOpen(sync, remoteFilePath);
                     } else {
-                        displayError("Unable to download trace file from device '%1$s'.",
+                        displayErrorFromUiThread("Unable to download trace file from device '%1$s'.",
                                 device.getSerialNumber());
                     }
                 } catch (Exception e) {
-                    displayError("Unable to download trace file from device '%1$s'.",
+                    displayErrorFromUiThread("Unable to download trace file from device '%1$s'.",
                             device.getSerialNumber());
                 }
             }
@@ -109,10 +94,21 @@ public class MethodProfilingHandler extends BaseFileHandler
         });
     }
 
-    public void onSuccess(File localFile, final Client client) {
-        openInTraceview(localFile.getAbsolutePath());
+    public void onSuccess(byte[] data, final Client client) {
+        try {
+            File tempFile = saveTempFile(data);
+            openInTraceview(tempFile.getAbsolutePath());
+        } catch (IOException e) {
+            String errorMsg = e.getMessage();
+            displayErrorInUiThread(
+                    "Failed to save trace data into temp file%1$s",
+                    errorMsg != null ? ":\n" + errorMsg : ".");
+        }
     }
 
+    /**
+     * pulls and open a file. This is run from the UI thread.
+     */
     private void pullAndOpen(SyncService sync, String remoteFilePath)
             throws InvocationTargetException, InterruptedException, IOException {
         // get a temp file
@@ -126,12 +122,12 @@ public class MethodProfilingHandler extends BaseFileHandler
                 // open the temp file in traceview
                 openInTraceview(tempPath);
             } else {
-                displayError("Unable to download trace file:\n\n%1$s",
+                displayErrorFromUiThread("Unable to download trace file:\n\n%1$s",
                         result.getMessage());
             }
         } else {
             // this really shouldn't happen.
-            displayError("Unable to download trace file.");
+            displayErrorFromUiThread("Unable to download trace file.");
         }
     }
 
@@ -174,9 +170,4 @@ public class MethodProfilingHandler extends BaseFileHandler
             Log.e("traceview", e);
         }
     }
-
-    private void displayError(String format, Object... args) {
-        MessageDialog.openError(mParentShell, "Method Profiling Error",
-                String.format(format, args));
-    }
 }
index 71efb50..a13f3f2 100644 (file)
@@ -95,13 +95,21 @@ public class DeviceView extends ViewPart implements IUiSelectionListener, IClien
             super(parentShell);
         }
 
-        public void onFailure(final Client client) {
+        @Override
+        protected String getDialogTitle() {
+            return "HPROF Error";
+        }
+
+
+        public void onEndFailure(final Client client, final String message) {
             mParentShell.getDisplay().asyncExec(new Runnable() {
                 public void run() {
                     try {
-                        displayError("Unable to create HPROF file for application '%1$s'.\n" +
+                        displayErrorFromUiThread(
+                                "Unable to create HPROF file for application '%1$s'.\n\n%2$s" +
                                 "Check logcat for more information.",
-                                client.getClientData().getClientDescription());
+                                client.getClientData().getClientDescription(),
+                                message != null ? message + "\n\n" : "");
                     } 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
@@ -140,16 +148,16 @@ public class DeviceView extends ViewPart implements IUiSelectionListener, IClien
                             }
 
                             if (result != null && result.getCode() != SyncService.RESULT_OK) {
-                                displayError(
+                                displayErrorFromUiThread(
                                         "Unable to download HPROF file from device '%1$s'.\n\n%2$s",
                                         device.getSerialNumber(), result.getMessage());
                             }
                         } else {
-                            displayError("Unable to download HPROF file from device '%1$s'.",
+                            displayErrorFromUiThread("Unable to download HPROF file from device '%1$s'.",
                                     device.getSerialNumber());
                         }
                     } catch (Exception e) {
-                        displayError("Unable to download HPROF file from device '%1$s'.",
+                        displayErrorFromUiThread("Unable to download HPROF file from device '%1$s'.",
                                 device.getSerialNumber());
 
                     } finally {
@@ -161,6 +169,32 @@ public class DeviceView extends ViewPart implements IUiSelectionListener, IClien
             });
         }
 
+        public void onSuccess(final byte[] data, final Client client) {
+            mParentShell.getDisplay().asyncExec(new Runnable() {
+                public void run() {
+                    // get from the preference what action to take
+                    IPreferenceStore store = DdmsPlugin.getDefault().getPreferenceStore();
+                    String value = store.getString(PreferenceInitializer.ATTR_HPROF_ACTION);
+
+                    if (ACTION_OPEN.equals(value)) {
+                        try {
+                            File tempFile = saveTempFile(data);
+                            open(tempFile.getAbsolutePath());
+                        } catch (Exception e) {
+                            String errorMsg = e.getMessage();
+                            displayErrorFromUiThread(
+                                    "Failed to save hprof data into temp file%1$s",
+                                    errorMsg != null ? ":\n" + errorMsg : ".");
+                        }
+                    } else {
+                        // default action is ACTION_SAVE
+                        promptAndSave(client.getClientData().getClientDescription() + DOT_HPROF,
+                                data, "Save HPROF file");
+                    }
+                }
+            });
+        }
+
         private void open(String path) throws IOException, InterruptedException, PartInitException {
             // make a temp file to convert the hprof into something
             // readable by normal tools
@@ -182,11 +216,6 @@ public class DeviceView extends ViewPart implements IUiSelectionListener, IClien
                         fileStore);
             }
         }
-
-        private void displayError(String format, Object... args) {
-            MessageDialog.openError(mParentShell, "HPROF Error",
-                    String.format(format, args));
-        }
     }