Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f9284695 authored by Romain Guy's avatar Romain Guy
Browse files

Add new ViewDebug APIs to profile the event queue.

Change-Id: I225bf288780b0244f459316e2765cfa29cd22c89
parent fed878d9
Loading
Loading
Loading
Loading
+9 −6
Original line number Diff line number Diff line
@@ -14035,6 +14035,7 @@ package android.os {
    method public void dispatchMessage(android.os.Message);
    method public final void dump(android.util.Printer, java.lang.String);
    method public final android.os.Looper getLooper();
    method public java.lang.String getMessageName(android.os.Message);
    method public void handleMessage(android.os.Message);
    method public final boolean hasMessages(int);
    method public final boolean hasMessages(int, java.lang.Object);
@@ -14104,13 +14105,13 @@ package android.os {
  public class Looper {
    method public void dump(android.util.Printer, java.lang.String);
    method public static final synchronized android.os.Looper getMainLooper();
    method public static synchronized android.os.Looper getMainLooper();
    method public java.lang.Thread getThread();
    method public static final void loop();
    method public static final android.os.Looper myLooper();
    method public static final android.os.MessageQueue myQueue();
    method public static final void prepare();
    method public static final void prepareMainLooper();
    method public static void loop();
    method public static android.os.Looper myLooper();
    method public static android.os.MessageQueue myQueue();
    method public static void prepare();
    method public static void prepareMainLooper();
    method public void quit();
    method public void setMessageLogging(android.util.Printer);
  }
@@ -22642,8 +22643,10 @@ package android.view {
    ctor public ViewDebug();
    method public static void dumpCapturedView(java.lang.String, java.lang.Object);
    method public static void startHierarchyTracing(java.lang.String, android.view.View);
    method public static void startLooperProfiling(java.io.File);
    method public static void startRecyclerTracing(java.lang.String, android.view.View);
    method public static void stopHierarchyTracing();
    method public static void stopLooperProfiling();
    method public static void stopRecyclerTracing();
    method public static void trace(android.view.View, android.view.ViewDebug.RecyclerTraceType, int...);
    method public static void trace(android.view.View, android.view.ViewDebug.HierarchyTraceType);
+15 −0
Original line number Diff line number Diff line
@@ -168,6 +168,21 @@ public class Handler {
        mCallback = callback;
    }

    /**
     * Returns a string representing the name of the specified message.
     * The default implementation will either return the class name of the
     * message callback if any, or the hexadecimal representation of the
     * message "what" field.
     *  
     * @param message The message whose name is being queried 
     */
    public String getMessageName(Message message) {
        if (message.callback != null) {
            return message.callback.getClass().getName();
        }
        return "0x" + Integer.toHexString(message.what);
    }

    /**
     * Returns a new {@link android.os.Message Message} from the global message pool. More efficient than
     * creating and allocating new instances. The retrieved message has its handler set to this instance (Message.target == this).
+37 −32
Original line number Diff line number Diff line
@@ -52,7 +52,6 @@ import android.util.PrefixPrinter;
  */
public class Looper {
    private static final String TAG = "Looper";
    private static final boolean LOG_V = Log.isLoggable(TAG, Log.VERBOSE);

    // sThreadLocal.get() will return null unless you've called prepare().
    private static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
@@ -70,7 +69,7 @@ public class Looper {
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    public static final void prepare() {
    public static void prepare() {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
@@ -83,7 +82,7 @@ public class Looper {
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     */
    public static final void prepareMainLooper() {
    public static void prepareMainLooper() {
        prepare();
        setMainLooper(myLooper());
        myLooper().mQueue.mQuitAllowed = false;
@@ -95,7 +94,7 @@ public class Looper {

    /** Returns the application's main looper, which lives in the main thread of the application.
     */
    public synchronized static final Looper getMainLooper() {
    public synchronized static Looper getMainLooper() {
        return mMainLooper;
    }

@@ -103,7 +102,7 @@ public class Looper {
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static final void loop() {
    public static void loop() {
        Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
@@ -122,20 +121,36 @@ public class Looper {
                    // No target is a magic identifier for the quit message.
                    return;
                }
                if (me.mLogging != null) me.mLogging.println(
                        ">>>>> Dispatching to " + msg.target + " "
                        + msg.callback + ": " + msg.what
                        );

                long wallStart = 0;
                long threadStart = 0;

                // This must be in a local variable, in case a UI event sets the logger
                Printer logging = me.mLogging;
                if (logging != null) {
                    logging.println(">>>>> Dispatching to " + msg.target + " " +
                            msg.callback + ": " + msg.what);
                    wallStart = System.currentTimeMillis();
                    threadStart = SystemClock.currentThreadTimeMillis();
                }

                msg.target.dispatchMessage(msg);
                if (me.mLogging != null) me.mLogging.println(
                        "<<<<< Finished to    " + msg.target + " "
                        + msg.callback);

                if (logging != null) {
                    long wallTime = System.currentTimeMillis() - wallStart;
                    long threadTime = SystemClock.currentThreadTimeMillis() - threadStart;

                    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                    if (logging instanceof Profiler) {
                        ((Profiler) logging).profile(msg, wallStart, wallTime, threadTime);
                    }
                }

                // Make sure that during the course of dispatching the
                // identity of the thread wasn't corrupted.
                final long newIdent = Binder.clearCallingIdentity();
                if (ident != newIdent) {
                    Log.wtf("Looper", "Thread identity changed from 0x"
                    Log.wtf(TAG, "Thread identity changed from 0x"
                            + Long.toHexString(ident) + " to 0x"
                            + Long.toHexString(newIdent) + " while dispatching to "
                            + msg.target.getClass().getName() + " "
@@ -151,7 +166,7 @@ public class Looper {
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static final Looper myLooper() {
    public static Looper myLooper() {
        return sThreadLocal.get();
    }

@@ -173,7 +188,7 @@ public class Looper {
     * thread.  This must be called from a thread running a Looper, or a
     * NullPointerException will be thrown.
     */
    public static final MessageQueue myQueue() {
    public static MessageQueue myQueue() {
        return myLooper().mQueue;
    }

@@ -225,23 +240,13 @@ public class Looper {
    }

    public String toString() {
        return "Looper{"
            + Integer.toHexString(System.identityHashCode(this))
            + "}";
        return "Looper{" + Integer.toHexString(System.identityHashCode(this)) + "}";
    }

    static class HandlerException extends Exception {

        HandlerException(Message message, Throwable cause) {
            super(createMessage(cause), cause);
        }

        static String createMessage(Throwable cause) {
            String causeMsg = cause.getMessage();
            if (causeMsg == null) {
                causeMsg = cause.toString();
            }
            return causeMsg;
        }
    /**
     * @hide
     */
    public static interface Profiler {
        void profile(Message message, long wallStart, long wallTime, long threadTime);
    }
}
+56 −0
Original line number Diff line number Diff line
@@ -2208,6 +2208,62 @@ public final class ViewAncestor extends Handler implements ViewParent,
    public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID = 1022;
    public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_TEXT = 1023;

    @Override
    public String getMessageName(Message message) {
        switch (message.what) {
            case DO_TRAVERSAL:
                return "DO_TRAVERSAL";
            case DIE:
                return "DIE";
            case RESIZED:
                return "RESIZED";
            case RESIZED_REPORT:
                return "RESIZED_REPORT";
            case WINDOW_FOCUS_CHANGED:
                return "WINDOW_FOCUS_CHANGED";
            case DISPATCH_KEY:
                return "DISPATCH_KEY";
            case DISPATCH_POINTER:
                return "DISPATCH_POINTER";
            case DISPATCH_TRACKBALL:
                return "DISPATCH_TRACKBALL";
            case DISPATCH_APP_VISIBILITY:
                return "DISPATCH_APP_VISIBILITY";
            case DISPATCH_GET_NEW_SURFACE:
                return "DISPATCH_GET_NEW_SURFACE";
            case FINISHED_EVENT:
                return "FINISHED_EVENT";
            case DISPATCH_KEY_FROM_IME:
                return "DISPATCH_KEY_FROM_IME";
            case FINISH_INPUT_CONNECTION:
                return "FINISH_INPUT_CONNECTION";
            case CHECK_FOCUS:
                return "CHECK_FOCUS";
            case CLOSE_SYSTEM_DIALOGS:
                return "CLOSE_SYSTEM_DIALOGS";
            case DISPATCH_DRAG_EVENT:
                return "DISPATCH_DRAG_EVENT";
            case DISPATCH_DRAG_LOCATION_EVENT:
                return "DISPATCH_DRAG_LOCATION_EVENT";
            case DISPATCH_SYSTEM_UI_VISIBILITY:
                return "DISPATCH_SYSTEM_UI_VISIBILITY";
            case DISPATCH_GENERIC_MOTION:
                return "DISPATCH_GENERIC_MOTION";
            case UPDATE_CONFIGURATION:
                return "UPDATE_CONFIGURATION";
            case DO_PERFORM_ACCESSIBILITY_ACTION:
                return "DO_PERFORM_ACCESSIBILITY_ACTION";
            case DO_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID:
                return "DO_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID";
            case DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID:
                return "DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID";
            case DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_TEXT:
                return "DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_TEXT";
                                                                                                                                                                                                                                    
        }
        return super.getMessageName(message);
    }

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
+143 −25
Original line number Diff line number Diff line
@@ -16,41 +16,45 @@

package android.view;

import android.util.Log;
import android.util.DisplayMetrics;
import android.content.res.Resources;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Environment;
import android.os.Debug;
import android.os.Environment;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Printer;

import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.FileOutputStream;
import java.io.DataOutputStream;
import java.io.OutputStreamWriter;
import java.io.BufferedOutputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.lang.annotation.Target;
import java.io.OutputStreamWriter;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * Various debugging/tracing tools related to {@link View} and the view hierarchy.
@@ -105,13 +109,6 @@ public class ViewDebug {
     */
    public static final boolean DEBUG_PROFILE_LAYOUT = false;

    /**
     * Profiles real fps (times between draws) and displays the result.
     *
     * @hide
     */
    public static final boolean DEBUG_SHOW_FPS = false;

    /**
     * Enables detailed logging of drag/drop operations.
     * @hide
@@ -396,6 +393,9 @@ public class ViewDebug {
    private static List<RecyclerTrace> sRecyclerTraces;
    private static String sRecyclerTracePrefix;

    private static final ThreadLocal<LooperProfiler> sLooperProfilerStorage =
            new ThreadLocal<LooperProfiler>();

    /**
     * Returns the number of instanciated Views.
     *
@@ -418,6 +418,124 @@ public class ViewDebug {
        return Debug.countInstancesOfClass(ViewAncestor.class);
    }

    /**
     * Starts profiling the looper associated with the current thread.
     * You must call {@link #stopLooperProfiling} to end profiling
     * and obtain the traces. Both methods must be invoked on the
     * same thread.
     * 
     * @param traceFile The path where to write the looper traces
     * 
     * @see #stopLooperProfiling() 
     */
    public static void startLooperProfiling(File traceFile) {
        if (sLooperProfilerStorage.get() == null) {
            LooperProfiler profiler = new LooperProfiler(traceFile);
            sLooperProfilerStorage.set(profiler);
            Looper.myLooper().setMessageLogging(profiler);
        }
    }

    /**
     * Stops profiling the looper associated with the current thread.
     * 
     * @see #startLooperProfiling(java.io.File) 
     */
    public static void stopLooperProfiling() {
        LooperProfiler profiler = sLooperProfilerStorage.get();
        if (profiler != null) {
            sLooperProfilerStorage.remove();
            Looper.myLooper().setMessageLogging(null);
            profiler.save();
        }
    }

    private static class LooperProfiler implements Looper.Profiler, Printer {
        private static final int LOOPER_PROFILER_VERSION = 1;

        private static final String LOG_TAG = "LooperProfiler";

        private final ArrayList<Entry> mTraces = new ArrayList<Entry>(512);
        private final File mTraceFile;

        public LooperProfiler(File traceFile) {
            mTraceFile = traceFile;
        }

        @Override
        public void println(String x) {
            // Ignore messages
        }

        @Override
        public void profile(Message message, long wallStart, long wallTime, long threadTime) {
            Entry entry = new Entry();
            entry.messageId = message.what;
            entry.name = message.getTarget().getMessageName(message);
            entry.wallStart = wallStart;
            entry.wallTime = wallTime;
            entry.threadTime = threadTime;

            mTraces.add(entry);
        }

        void save() {
            // Don't block the UI thread
            new Thread(new Runnable() {
                @Override
                public void run() {
                    saveTraces();
                }
            }, "LooperProfiler[" + mTraceFile + "]").start();
        }

        private void saveTraces() {
            FileOutputStream fos;
            try {
                fos = new FileOutputStream(mTraceFile);
            } catch (FileNotFoundException e) {
                Log.e(LOG_TAG, "Could not open trace file: " + mTraceFile);
                return;
            }

            DataOutputStream out = new DataOutputStream(new BufferedOutputStream(fos));

            try {
                out.writeInt(LOOPER_PROFILER_VERSION);
                out.writeInt(mTraces.size());
                for (Entry entry : mTraces) {
                    saveTrace(entry, out);
                }

                Log.d(LOG_TAG, "Looper traces ready: " + mTraceFile);
            } catch (IOException e) {
                Log.e(LOG_TAG, "Could not write trace file: ", e);
            } finally {
                try {
                    out.close();
                } catch (IOException e) {
                    // Ignore
                }
            }
        }

        private void saveTrace(Entry entry, DataOutputStream out) throws IOException {
            out.writeInt(entry.messageId);
            out.writeUTF(entry.name);
            out.writeLong(entry.wallStart);
            out.writeLong(entry.wallTime);
            out.writeLong(entry.threadTime);
        }

        static class Entry {
            int messageId;
            String name;
            long wallStart;
            long wallTime;
            long threadTime;
        }
    }

    /**
     * Outputs a trace to the currently opened recycler traces. The trace records the type of
     * recycler action performed on the supplied view as well as a number of parameters.
Loading