1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ide.eclipse.gltrace; 18 19 import com.android.ddmlib.AdbCommandRejectedException; 20 import com.android.ddmlib.AndroidDebugBridge; 21 import com.android.ddmlib.IDevice; 22 import com.android.ddmlib.IDevice.DeviceUnixSocketNamespace; 23 import com.android.ddmlib.IShellOutputReceiver; 24 import com.android.ddmlib.ShellCommandUnresponsiveException; 25 import com.android.ddmlib.TimeoutException; 26 27 import org.eclipse.jface.action.IAction; 28 import org.eclipse.jface.dialogs.MessageDialog; 29 import org.eclipse.jface.viewers.ISelection; 30 import org.eclipse.jface.window.Window; 31 import org.eclipse.swt.widgets.Display; 32 import org.eclipse.swt.widgets.Shell; 33 import org.eclipse.ui.IWorkbenchWindow; 34 import org.eclipse.ui.IWorkbenchWindowActionDelegate; 35 36 import java.io.DataInputStream; 37 import java.io.DataOutputStream; 38 import java.io.FileNotFoundException; 39 import java.io.FileOutputStream; 40 import java.io.IOException; 41 import java.net.Socket; 42 import java.util.concurrent.Semaphore; 43 44 public class CollectTraceAction implements IWorkbenchWindowActionDelegate { 45 /** Abstract Unix Domain Socket Name used by the gltrace device code. */ 46 private static final String GLTRACE_UDS = "gltrace"; 47 48 /** Local port that is forwarded to the device's {@link #GLTRACE_UDS} socket. */ 49 private static final int LOCAL_FORWARDED_PORT = 6039; 50 51 @Override 52 public void run(IAction action) { 53 connectToDevice(); 54 } 55 56 @Override 57 public void selectionChanged(IAction action, ISelection selection) { 58 } 59 60 @Override 61 public void dispose() { 62 } 63 64 @Override 65 public void init(IWorkbenchWindow window) { 66 } 67 68 private void connectToDevice() { 69 Shell shell = Display.getDefault().getActiveShell(); 70 GLTraceOptionsDialog dlg = new GLTraceOptionsDialog(shell); 71 if (dlg.open() != Window.OK) { 72 return; 73 } 74 75 TraceOptions traceOptions = dlg.getTraceOptions(); 76 77 IDevice device = getDevice(traceOptions.device); 78 try { 79 setupForwarding(device, LOCAL_FORWARDED_PORT); 80 } catch (Exception e) { 81 MessageDialog.openError(shell, "Setup GL Trace", 82 "Error while setting up port forwarding: " + e.getMessage()); 83 } 84 85 try { 86 startActivity(device, traceOptions.activityToTrace); 87 } catch (Exception e) { 88 MessageDialog.openError(shell, "Setup GL Trace", 89 "Error while launching application: " + e.getMessage()); 90 return; 91 } 92 93 try { 94 // wait a couple of seconds for the application to launch on the device 95 Thread.sleep(2000); 96 } catch (InterruptedException e) { 97 // can't be interrupted 98 } 99 100 // if everything went well, the app should now be waiting for the gl debugger 101 // to connect 102 startTracing(shell, traceOptions, LOCAL_FORWARDED_PORT); 103 104 // once tracing is complete, remove port forwarding 105 disablePortForwarding(device, LOCAL_FORWARDED_PORT); 106 } 107 108 private void startTracing(Shell shell, TraceOptions traceOptions, int port) { 109 FileOutputStream fos = null; 110 try { 111 fos = new FileOutputStream(traceOptions.traceDestination, false); 112 } catch (FileNotFoundException e) { 113 // input path is valid, so this cannot occur 114 } 115 116 Socket socket = new Socket(); 117 DataInputStream traceDataStream = null; 118 DataOutputStream traceCommandsStream = null; 119 try { 120 socket.connect(new java.net.InetSocketAddress("127.0.0.1", port)); //$NON-NLS-1$ 121 socket.setTcpNoDelay(true); 122 traceDataStream = new DataInputStream(socket.getInputStream()); 123 traceCommandsStream = new DataOutputStream(socket.getOutputStream()); 124 } catch (IOException e) { 125 MessageDialog.openError(shell, 126 "OpenGL Trace", 127 "Unable to connect to remote GL Trace Server: " + e.getMessage()); 128 return; 129 } 130 131 // create channel to send trace commands to device 132 TraceCommandWriter traceCommandWriter = new TraceCommandWriter(traceCommandsStream); 133 try { 134 traceCommandWriter.setTraceOptions(traceOptions.collectFbOnEglSwap, 135 traceOptions.collectFbOnGlDraw, 136 traceOptions.collectTextureData); 137 } catch (IOException e) { 138 MessageDialog.openError(shell, 139 "OpenGL Trace", 140 "Unexpected error while setting trace options: " + e.getMessage()); 141 closeSocket(socket); 142 return; 143 } 144 145 // create trace writer that writes to a trace file 146 TraceFileWriter traceFileWriter = new TraceFileWriter(fos, traceDataStream); 147 traceFileWriter.start(); 148 149 GLTraceCollectorDialog dlg = new GLTraceCollectorDialog(shell, 150 traceFileWriter, 151 traceCommandWriter, 152 traceOptions); 153 dlg.open(); 154 155 traceFileWriter.stopTracing(); 156 traceCommandWriter.close(); 157 closeSocket(socket); 158 } 159 160 private void closeSocket(Socket socket) { 161 try { 162 socket.close(); 163 } catch (IOException e) { 164 // ignore error while closing socket 165 } 166 } 167 168 private void startActivity(IDevice device, String appName) 169 throws TimeoutException, AdbCommandRejectedException, 170 ShellCommandUnresponsiveException, IOException, InterruptedException { 171 String startAppCmd = String.format( 172 "am start -S --opengl-trace %s -a android.intent.action.MAIN -c android.intent.category.LAUNCHER", //$NON-NLS-1$ 173 appName); 174 175 Semaphore launchCompletionSempahore = new Semaphore(0); 176 StartActivityOutputReceiver receiver = new StartActivityOutputReceiver( 177 launchCompletionSempahore); 178 device.executeShellCommand(startAppCmd, receiver); 179 180 // wait until shell finishes launch 181 launchCompletionSempahore.acquire(); 182 183 // throw exception if there was an error during launch 184 String output = receiver.getOutput(); 185 if (output.contains("Error")) { //$NON-NLS-1$ 186 throw new RuntimeException(output); 187 } 188 } 189 190 private void setupForwarding(IDevice device, int i) 191 throws TimeoutException, AdbCommandRejectedException, IOException { 192 device.createForward(i, GLTRACE_UDS, DeviceUnixSocketNamespace.ABSTRACT); 193 } 194 195 private void disablePortForwarding(IDevice device, int port) { 196 try { 197 device.removeForward(port, GLTRACE_UDS, DeviceUnixSocketNamespace.ABSTRACT); 198 } catch (Exception e) { 199 // ignore exceptions; 200 } 201 } 202 203 private IDevice getDevice(String deviceName) { 204 IDevice[] devices = AndroidDebugBridge.getBridge().getDevices(); 205 206 for (IDevice device : devices) { 207 String name = device.getAvdName(); 208 if (name == null) { 209 name = device.getSerialNumber(); 210 } 211 212 if (name.equals(deviceName)) { 213 return device; 214 } 215 } 216 217 return null; 218 } 219 220 private static class StartActivityOutputReceiver implements IShellOutputReceiver { 221 private Semaphore mSemaphore; 222 private StringBuffer sb = new StringBuffer(300); 223 224 public StartActivityOutputReceiver(Semaphore s) { 225 mSemaphore = s; 226 } 227 228 @Override 229 public void addOutput(byte[] data, int offset, int length) { 230 String d = new String(data, offset, length); 231 sb.append(d); 232 } 233 234 @Override 235 public void flush() { 236 mSemaphore.release(); 237 } 238 239 @Override 240 public boolean isCancelled() { 241 return false; 242 } 243 244 public String getOutput() { 245 return sb.toString(); 246 } 247 } 248 } 249