Home | History | Annotate | Download | only in gltrace
      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