aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/CollectTraceAction.java
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/CollectTraceAction.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/CollectTraceAction.java428
1 files changed, 428 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/CollectTraceAction.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/CollectTraceAction.java
new file mode 100644
index 000000000..c13ab461b
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/CollectTraceAction.java
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) 2011 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.ide.eclipse.gltrace;
+
+import com.android.ddmlib.AdbCommandRejectedException;
+import com.android.ddmlib.AndroidDebugBridge;
+import com.android.ddmlib.Client;
+import com.android.ddmlib.IDevice;
+import com.android.ddmlib.IDevice.DeviceUnixSocketNamespace;
+import com.android.ddmlib.IShellOutputReceiver;
+import com.android.ddmlib.ShellCommandUnresponsiveException;
+import com.android.ddmlib.TimeoutException;
+import com.android.ide.eclipse.gltrace.editors.GLFunctionTraceViewer;
+import com.google.common.io.Closeables;
+import com.google.common.util.concurrent.SimpleTimeLimiter;
+
+import org.eclipse.core.filesystem.EFS;
+import org.eclipse.core.filesystem.IFileStore;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorReference;
+import org.eclipse.ui.IURIEditorInput;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.IWorkbenchWindowActionDelegate;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.WorkbenchException;
+import org.eclipse.ui.ide.IDE;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.Socket;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+public class CollectTraceAction implements IWorkbenchWindowActionDelegate {
+ /** Abstract Unix Domain Socket Name used by the gltrace device code. */
+ private static final String GLTRACE_UDS = "gltrace"; //$NON-NLS-1$
+
+ /** Local port that is forwarded to the device's {@link #GLTRACE_UDS} socket. */
+ private static final int LOCAL_FORWARDED_PORT = 6039;
+
+ /** Activity name to use for a system activity that has already been launched. */
+ private static final String SYSTEM_APP = "system"; //$NON-NLS-1$
+
+ /** Time to wait for the application to launch (seconds) */
+ private static final int LAUNCH_TIMEOUT = 15;
+
+ /** Time to wait for the application to die (seconds) */
+ private static final int KILL_TIMEOUT = 5;
+
+ private static final int MIN_API_LEVEL = 16;
+
+ @Override
+ public void run(IAction action) {
+ connectToDevice();
+ }
+
+ @Override
+ public void selectionChanged(IAction action, ISelection selection) {
+ }
+
+ @Override
+ public void dispose() {
+ }
+
+ @Override
+ public void init(IWorkbenchWindow window) {
+ }
+
+ private void connectToDevice() {
+ Shell shell = Display.getDefault().getActiveShell();
+ GLTraceOptionsDialog dlg = new GLTraceOptionsDialog(shell);
+ if (dlg.open() != Window.OK) {
+ return;
+ }
+
+ TraceOptions traceOptions = dlg.getTraceOptions();
+
+ IDevice device = getDevice(traceOptions.device);
+ String apiLevelString = device.getProperty(IDevice.PROP_BUILD_API_LEVEL);
+ int apiLevel;
+ try {
+ apiLevel = Integer.parseInt(apiLevelString);
+ } catch (NumberFormatException e) {
+ apiLevel = MIN_API_LEVEL;
+ }
+ if (apiLevel < MIN_API_LEVEL) {
+ MessageDialog.openError(shell, "GL Trace",
+ String.format("OpenGL Tracing is only supported on devices at API Level %1$d."
+ + "The selected device '%2$s' provides API level %3$s.",
+ MIN_API_LEVEL, traceOptions.device, apiLevelString));
+ return;
+ }
+
+ try {
+ setupForwarding(device, LOCAL_FORWARDED_PORT);
+ } catch (Exception e) {
+ MessageDialog.openError(shell, "Setup GL Trace",
+ "Error while setting up port forwarding: " + e.getMessage());
+ return;
+ }
+
+ try {
+ if (!SYSTEM_APP.equals(traceOptions.appToTrace)) {
+ startActivity(device, traceOptions.appToTrace, traceOptions.activityToTrace,
+ traceOptions.isActivityNameFullyQualified);
+ }
+ } catch (Exception e) {
+ MessageDialog.openError(shell, "Setup GL Trace",
+ "Error while launching application: " + e.getMessage());
+ return;
+ }
+
+ // if everything went well, the app should now be waiting for the gl debugger
+ // to connect
+ startTracing(shell, traceOptions, LOCAL_FORWARDED_PORT);
+
+ // once tracing is complete, remove port forwarding
+ disablePortForwarding(device, LOCAL_FORWARDED_PORT);
+
+ // and finally open the editor to view the file
+ openInEditor(shell, traceOptions.traceDestination);
+ }
+
+ public static void openInEditor(Shell shell, String traceFilePath) {
+ final IFileStore fileStore = EFS.getLocalFileSystem().getStore(new Path(traceFilePath));
+ if (!fileStore.fetchInfo().exists()) {
+ return;
+ }
+
+ final IWorkbench workbench = PlatformUI.getWorkbench();
+ IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
+ if (window == null) {
+ return;
+ }
+
+ IWorkbenchPage page = window.getActivePage();
+ if (page == null) {
+ return;
+ }
+
+ try {
+ workbench.showPerspective("com.android.ide.eclipse.gltrace.perspective", window);
+ } catch (WorkbenchException e) {
+ }
+
+ // if there is a editor already open, then refresh its model
+ GLFunctionTraceViewer viewer = getOpenTraceViewer(page, traceFilePath);
+ if (viewer != null) {
+ viewer.setInput(shell, traceFilePath);
+ }
+
+ // open the editor (if not open), or bring it to foreground if it is already open
+ try {
+ IDE.openEditorOnFileStore(page, fileStore);
+ } catch (PartInitException e) {
+ GlTracePlugin.getDefault().logMessage(
+ "Unexpected error while opening gltrace file in editor: " + e);
+ return;
+ }
+ }
+
+ /**
+ * Returns the editor part that has the provided file path open.
+ * @param page page containing editors
+ * @param traceFilePath file that should be open in an editor
+ * @return if given trace file is already open, then a reference to that editor part,
+ * null otherwise
+ */
+ private static GLFunctionTraceViewer getOpenTraceViewer(IWorkbenchPage page,
+ String traceFilePath) {
+ IEditorReference[] editorRefs = page.getEditorReferences();
+ for (IEditorReference ref : editorRefs) {
+ String id = ref.getId();
+ if (!GLFunctionTraceViewer.ID.equals(id)) {
+ continue;
+ }
+
+ IEditorInput input = null;
+ try {
+ input = ref.getEditorInput();
+ } catch (PartInitException e) {
+ continue;
+ }
+
+ if (!(input instanceof IURIEditorInput)) {
+ continue;
+ }
+
+ if (traceFilePath.equals(((IURIEditorInput) input).getURI().getPath())) {
+ return (GLFunctionTraceViewer) ref.getEditor(true);
+ }
+ }
+
+ return null;
+ }
+
+ @SuppressWarnings("resource") // Closeables.closeQuietly
+ public static void startTracing(Shell shell, TraceOptions traceOptions, int port) {
+ Socket socket = new Socket();
+ DataInputStream traceDataStream = null;
+ DataOutputStream traceCommandsStream = null;
+ try {
+ socket.connect(new java.net.InetSocketAddress("127.0.0.1", port)); //$NON-NLS-1$
+ socket.setTcpNoDelay(true);
+ traceDataStream = new DataInputStream(socket.getInputStream());
+ traceCommandsStream = new DataOutputStream(socket.getOutputStream());
+ } catch (IOException e) {
+ MessageDialog.openError(shell,
+ "OpenGL Trace",
+ "Unable to connect to remote GL Trace Server: " + e.getMessage());
+ return;
+ }
+
+ // create channel to send trace commands to device
+ TraceCommandWriter traceCommandWriter = new TraceCommandWriter(traceCommandsStream);
+ try {
+ traceCommandWriter.setTraceOptions(traceOptions.collectFbOnEglSwap,
+ traceOptions.collectFbOnGlDraw,
+ traceOptions.collectTextureData);
+ } catch (IOException e) {
+ MessageDialog.openError(shell,
+ "OpenGL Trace",
+ "Unexpected error while setting trace options: " + e.getMessage());
+ closeSocket(socket);
+ return;
+ }
+
+ FileOutputStream fos = null;
+ try {
+ fos = new FileOutputStream(traceOptions.traceDestination, false);
+ } catch (FileNotFoundException e) {
+ // input path is valid, so this cannot occur
+ }
+
+ // create trace writer that writes to a trace file
+ TraceFileWriter traceFileWriter = new TraceFileWriter(fos, traceDataStream);
+ traceFileWriter.start();
+
+ GLTraceCollectorDialog dlg = new GLTraceCollectorDialog(shell,
+ traceFileWriter,
+ traceCommandWriter,
+ traceOptions);
+ dlg.open();
+
+ traceFileWriter.stopTracing();
+ traceCommandWriter.close();
+ closeSocket(socket);
+ }
+
+ private static void closeSocket(Socket socket) {
+ try {
+ socket.close();
+ } catch (IOException e) {
+ // ignore error while closing socket
+ }
+ }
+
+ private void startActivity(IDevice device, String appPackage, String activity,
+ boolean isActivityNameFullyQualified)
+ throws TimeoutException, AdbCommandRejectedException,
+ ShellCommandUnresponsiveException, IOException, InterruptedException {
+ killApp(device, appPackage); // kill app if it is already running
+ waitUntilAppKilled(device, appPackage, KILL_TIMEOUT);
+
+ StringBuilder activityPath = new StringBuilder(appPackage);
+ if (!activity.isEmpty()) {
+ activityPath.append('/');
+ if (!isActivityNameFullyQualified) {
+ activityPath.append('.');
+ }
+ activityPath.append(activity);
+ }
+ String startAppCmd = String.format(
+ "am start --opengl-trace %s -a android.intent.action.MAIN -c android.intent.category.LAUNCHER", //$NON-NLS-1$
+ activityPath.toString());
+
+ Semaphore launchCompletionSempahore = new Semaphore(0);
+ StartActivityOutputReceiver receiver = new StartActivityOutputReceiver(
+ launchCompletionSempahore);
+ device.executeShellCommand(startAppCmd, receiver);
+
+ // wait until shell finishes launch command
+ launchCompletionSempahore.acquire();
+
+ // throw exception if there was an error during launch
+ String output = receiver.getOutput();
+ if (output.contains("Error")) { //$NON-NLS-1$
+ throw new RuntimeException(output);
+ }
+
+ // wait until the app itself has been launched
+ waitUntilAppLaunched(device, appPackage, LAUNCH_TIMEOUT);
+ }
+
+ private void killApp(IDevice device, String appName) {
+ Client client = device.getClient(appName);
+ if (client != null) {
+ client.kill();
+ }
+ }
+
+ private void waitUntilAppLaunched(final IDevice device, final String appName, int timeout) {
+ Callable<Boolean> c = new Callable<Boolean>() {
+ @Override
+ public Boolean call() throws Exception {
+ Client client;
+ do {
+ client = device.getClient(appName);
+ } while (client == null);
+
+ return Boolean.TRUE;
+ }
+ };
+ try {
+ new SimpleTimeLimiter().callWithTimeout(c, timeout, TimeUnit.SECONDS, true);
+ } catch (Exception e) {
+ throw new RuntimeException("Timed out waiting for application to launch.");
+ }
+
+ // once the app has launched, wait an additional couple of seconds
+ // for it to start up
+ try {
+ Thread.sleep(2000);
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ }
+
+ private void waitUntilAppKilled(final IDevice device, final String appName, int timeout) {
+ Callable<Boolean> c = new Callable<Boolean>() {
+ @Override
+ public Boolean call() throws Exception {
+ Client client;
+ while ((client = device.getClient(appName)) != null) {
+ client.kill();
+ }
+ return Boolean.TRUE;
+ }
+ };
+ try {
+ new SimpleTimeLimiter().callWithTimeout(c, timeout, TimeUnit.SECONDS, true);
+ } catch (Exception e) {
+ throw new RuntimeException("Timed out waiting for running application to die.");
+ }
+ }
+
+ public static void setupForwarding(IDevice device, int i)
+ throws TimeoutException, AdbCommandRejectedException, IOException {
+ device.createForward(i, GLTRACE_UDS, DeviceUnixSocketNamespace.ABSTRACT);
+ }
+
+ public static void disablePortForwarding(IDevice device, int port) {
+ try {
+ device.removeForward(port, GLTRACE_UDS, DeviceUnixSocketNamespace.ABSTRACT);
+ } catch (Exception e) {
+ // ignore exceptions;
+ }
+ }
+
+ private IDevice getDevice(String deviceName) {
+ IDevice[] devices = AndroidDebugBridge.getBridge().getDevices();
+
+ for (IDevice device : devices) {
+ if (device.getName().equals(deviceName)) {
+ return device;
+ }
+ }
+
+ return null;
+ }
+
+ private static class StartActivityOutputReceiver implements IShellOutputReceiver {
+ private Semaphore mSemaphore;
+ private StringBuffer sb = new StringBuffer(300);
+
+ public StartActivityOutputReceiver(Semaphore s) {
+ mSemaphore = s;
+ }
+
+ @Override
+ public void addOutput(byte[] data, int offset, int length) {
+ String d = new String(data, offset, length);
+ sb.append(d);
+ }
+
+ @Override
+ public void flush() {
+ mSemaphore.release();
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+
+ public String getOutput() {
+ return sb.toString();
+ }
+ }
+}