[go: nahoru, domu]

ddms: Add support for sampling profiler

This CL adds supports for sampling based profiling.

When method profiling is initiated, we detect whether sampling
profiling is supported and if so, we show a dialog that allows
users to select the type of profiling to be performed.

The methods to obtain and display trace data is unchanged.

Change-Id: I8ec1a2c2311d6ffa7f73b5690d71e9d2253d09f2
diff --git a/ddms/app/src/main/java/com/android/ddms/UIThread.java b/ddms/app/src/main/java/com/android/ddms/UIThread.java
index 1310429..7680f08 100644
--- a/ddms/app/src/main/java/com/android/ddms/UIThread.java
+++ b/ddms/app/src/main/java/com/android/ddms/UIThread.java
@@ -1719,7 +1719,8 @@
 
             if (data.hasFeature(ClientData.FEATURE_PROFILING)) {
                 mTBProfiling.setEnabled(true);
-                if (data.getMethodProfilingStatus() == MethodProfilingStatus.ON) {
+                if (data.getMethodProfilingStatus() == MethodProfilingStatus.TRACER_ON
+                        || data.getMethodProfilingStatus() == MethodProfilingStatus.SAMPLER_ON) {
                     mTBProfiling.setToolTipText("Stop Method Profiling");
                     mTBProfiling.setImage(mTracingStopImage);
                 } else {
@@ -1729,7 +1730,7 @@
             } else {
                 mTBProfiling.setEnabled(false);
                 mTBProfiling.setImage(mTracingStartImage);
-                mTBProfiling.setToolTipText("Start Method Profiling (not supported by this VM)");
+                mTBProfiling.setToolTipText("Method Profiling (not supported by this VM)");
             }
         } else {
             // list is empty, disable these
diff --git a/ddms/ddmuilib/src/main/java/com/android/ddmuilib/DevicePanel.java b/ddms/ddmuilib/src/main/java/com/android/ddmuilib/DevicePanel.java
index a24b8a0..fb97062 100644
--- a/ddms/ddmuilib/src/main/java/com/android/ddmuilib/DevicePanel.java
+++ b/ddms/ddmuilib/src/main/java/com/android/ddmuilib/DevicePanel.java
@@ -16,6 +16,7 @@
 
 package com.android.ddmuilib;
 
+import com.android.annotations.NonNull;
 import com.android.ddmlib.AndroidDebugBridge;
 import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener;
 import com.android.ddmlib.AndroidDebugBridge.IDebugBridgeChangeListener;
@@ -26,7 +27,10 @@
 import com.android.ddmlib.DdmPreferences;
 import com.android.ddmlib.IDevice;
 import com.android.ddmlib.IDevice.DeviceState;
+import com.android.ddmuilib.vmtrace.VmTraceOptionsDialog;
+import com.google.common.base.Throwables;
 
+import org.eclipse.jface.dialogs.MessageDialog;
 import org.eclipse.jface.preference.IPreferenceStore;
 import org.eclipse.jface.viewers.ILabelProviderListener;
 import org.eclipse.jface.viewers.ITableLabelProvider;
@@ -35,6 +39,7 @@
 import org.eclipse.jface.viewers.TreeSelection;
 import org.eclipse.jface.viewers.TreeViewer;
 import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.window.Window;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.SWTException;
 import org.eclipse.swt.events.SelectionAdapter;
@@ -48,8 +53,10 @@
 import org.eclipse.swt.widgets.TreeColumn;
 import org.eclipse.swt.widgets.TreeItem;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Locale;
+import java.util.concurrent.TimeUnit;
 
 /**
  * A display of both the devices and their clients.
@@ -310,7 +317,6 @@
 
     /**
      * Creates the {@link DevicePanel} object.
-     * @param loader
      * @param advancedPortSupport if true the device panel will add support for selected client port
      * and display the ports in the ui.
      */
@@ -447,8 +453,49 @@
     }
 
     public void toggleMethodProfiling() {
-        if (mCurrentClient != null) {
-            mCurrentClient.toggleMethodProfiling();
+        if (mCurrentClient == null) {
+            return;
+        }
+
+        try {
+            toggleMethodProfiling(mCurrentClient);
+        } catch (IOException e) {
+            MessageDialog.openError(mTree.getShell(), "Method Profiling",
+                    "Unexpected I/O error while starting/stopping profiling: "
+                            + Throwables.getRootCause(e).getMessage());
+        }
+    }
+
+    private void toggleMethodProfiling(@NonNull Client client) throws IOException {
+        ClientData cd = mCurrentClient.getClientData();
+        if (cd.getMethodProfilingStatus() == ClientData.MethodProfilingStatus.TRACER_ON) {
+            mCurrentClient.stopMethodTracer();
+        } else if (cd.getMethodProfilingStatus() == ClientData.MethodProfilingStatus.SAMPLER_ON) {
+            mCurrentClient.stopSamplingProfiler();
+        } else {
+            boolean supportsSampling = cd.hasFeature(ClientData.FEATURE_SAMPLING_PROFILER);
+
+            // default to tracing
+            boolean shouldUseTracing = true;
+            int samplingIntervalMicros = 1;
+
+            // if client supports sampling, then ask the user to choose the method
+            if (supportsSampling) {
+                VmTraceOptionsDialog dialog = new VmTraceOptionsDialog(mTree.getShell());
+                if (dialog.open() == Window.CANCEL) {
+                    return;
+                }
+                shouldUseTracing = dialog.shouldUseTracing();
+                if (!shouldUseTracing) {
+                    samplingIntervalMicros = dialog.getSamplingIntervalMicros();
+                }
+            }
+
+            if (shouldUseTracing) {
+                mCurrentClient.startMethodTracer();
+            } else {
+                mCurrentClient.startSamplingProfiler(samplingIntervalMicros, TimeUnit.MICROSECONDS);
+            }
         }
     }
 
@@ -469,8 +516,6 @@
      * <p/>
      * This is sent from a non UI thread.
      * @param bridge the new {@link AndroidDebugBridge} object.
-     *
-     * @see IDebugBridgeChangeListener#serverChanged(AndroidDebugBridge)
      */
     @Override
     public void bridgeChanged(final AndroidDebugBridge bridge) {
@@ -563,7 +608,7 @@
      * @param device the device that was updated.
      * @param changeMask the mask indicating what changed.
      *
-     * @see IDeviceChangeListener#deviceChanged(IDevice)
+     * @see IDeviceChangeListener#deviceChanged(IDevice,int)
      */
     @Override
     public void deviceChanged(final IDevice device, int changeMask) {
diff --git a/ddms/ddmuilib/src/main/java/com/android/ddmuilib/vmtrace/VmTraceOptionsDialog.java b/ddms/ddmuilib/src/main/java/com/android/ddmuilib/vmtrace/VmTraceOptionsDialog.java
new file mode 100644
index 0000000..b3cfb4e
--- /dev/null
+++ b/ddms/ddmuilib/src/main/java/com/android/ddmuilib/vmtrace/VmTraceOptionsDialog.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2013 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.vmtrace;
+
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+/** Dialog that allows users to select between method tracing or sampler based profiling. */
+public class VmTraceOptionsDialog extends Dialog {
+    private static final int DEFAULT_SAMPLING_INTERVAL_US = 1000;
+
+    // Static variables that maintain state across invocations of the dialog
+    private static boolean sTracingEnabled = true;
+    private static int sSamplingIntervalUs = DEFAULT_SAMPLING_INTERVAL_US;
+
+    public VmTraceOptionsDialog(Shell parentShell) {
+        super(parentShell);
+    }
+
+    @Override
+    protected void configureShell(Shell newShell) {
+        super.configureShell(newShell);
+        newShell.setText("Profiling Options");
+    }
+
+    @Override
+    protected Control createDialogArea(Composite shell) {
+        int horizontalIndent = 30;
+
+        Composite parent = (Composite) super.createDialogArea(shell);
+        Composite c = new Composite(parent, SWT.NONE);
+        c.setLayout(new GridLayout(2, false));
+        c.setLayoutData(new GridData(GridData.FILL_BOTH));
+
+        final Button useTracingButton = new Button(c, SWT.RADIO);
+        useTracingButton.setText("Trace based profiling");
+        useTracingButton.setSelection(sTracingEnabled);
+        GridData gd = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING,
+                GridData.VERTICAL_ALIGN_CENTER, true, true, 2, 1);
+        useTracingButton.setLayoutData(gd);
+
+        Label l = new Label(c, SWT.NONE);
+        l.setText("Trace based profiling works by tracing the entry and exit of every method.\n"
+                + "This gives an accurate view of the execution, but has a high overhead.");
+        gd = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING, GridData.VERTICAL_ALIGN_CENTER, true,
+                true, 2, 1);
+        gd.horizontalIndent = horizontalIndent;
+        l.setLayoutData(gd);
+
+        final Button useSamplingButton = new Button(c, SWT.RADIO);
+        useSamplingButton.setText("Sample based profiling");
+        useSamplingButton.setSelection(!sTracingEnabled);
+        gd = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING, GridData.VERTICAL_ALIGN_CENTER, true,
+                true, 2, 1);
+        useSamplingButton.setLayoutData(gd);
+
+        l = new Label(c, SWT.NONE);
+        l.setText("Sample based profiling works by interrupting the VM at a given frequency and "
+                + "collecting the call stacks at that time.\n"
+                + "This has a much lower overhead, but statistical sampling requires longer runs "
+                + "to obtain a representative sample.");
+        gd = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING, GridData.VERTICAL_ALIGN_CENTER, true,
+                true, 2, 1);
+        gd.horizontalIndent = horizontalIndent;
+        l.setLayoutData(gd);
+
+        l = new Label(c, SWT.NONE);
+        l.setText("Sampling frequency (microseconds): ");
+        gd = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING, GridData.VERTICAL_ALIGN_END,
+                false, true);
+        gd.horizontalIndent = horizontalIndent;
+        l.setLayoutData(gd);
+
+        final Text samplingIntervalTextField = new Text(c, SWT.BORDER);
+        gd = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING, GridData.VERTICAL_ALIGN_CENTER, true,
+                true);
+        gd.widthHint = 100;
+        samplingIntervalTextField.setLayoutData(gd);
+        samplingIntervalTextField.setEnabled(!sTracingEnabled);
+        samplingIntervalTextField.setText(Integer.toString(sSamplingIntervalUs));
+        samplingIntervalTextField.addModifyListener(new ModifyListener() {
+            @Override
+            public void modifyText(ModifyEvent modifyEvent) {
+                int v = getIntegerValue(samplingIntervalTextField.getText());
+                getButton(IDialogConstants.OK_ID).setEnabled(v > 0);
+                sSamplingIntervalUs = v > 0 ? v : DEFAULT_SAMPLING_INTERVAL_US;
+            }
+
+            private int getIntegerValue(String text) {
+                try {
+                    return Integer.parseInt(text);
+                } catch (NumberFormatException e) {
+                    return -1;
+                }
+            }
+        });
+
+        SelectionAdapter selectionAdapter = new SelectionAdapter() {
+            @Override
+            public void widgetSelected(SelectionEvent event) {
+                sTracingEnabled = useTracingButton.getSelection();
+                samplingIntervalTextField.setEnabled(!sTracingEnabled);
+            }
+        };
+        useTracingButton.addSelectionListener(selectionAdapter);
+        useSamplingButton.addSelectionListener(selectionAdapter);
+
+        return c;
+    }
+
+    public boolean shouldUseTracing() {
+        return sTracingEnabled;
+    }
+
+    public int getSamplingIntervalMicros() {
+        return sSamplingIntervalUs;
+    }
+}