OSDN Git Service

DO NOT MERGE Backport of limited jank-tracking metrics
authorJohn Reck <jreck@google.com>
Thu, 19 Feb 2015 22:36:50 +0000 (14:36 -0800)
committerThe Android Automerger <android-build@google.com>
Thu, 14 May 2015 00:29:19 +0000 (17:29 -0700)
Bug: 19821830

Cherry-pick of ba6adf66d3c44c0aa2fd8a224862ff1901d64300
Cherry-pick of e70c5754d01f2ab0ff47ea3eabaa88aca5ed2a36

Change-Id: Id342fa0ab345f204bec58acf45ce72f6de950cfb

27 files changed:
core/java/android/app/ActivityThread.java
core/java/android/view/Choreographer.java
core/java/android/view/FrameInfo.java [new file with mode: 0644]
core/java/android/view/HardwareRenderer.java
core/java/android/view/ThreadedRenderer.java
core/java/android/view/ViewRootImpl.java
core/java/android/view/WindowManagerGlobal.java
core/jni/android_view_Surface.cpp
core/jni/android_view_ThreadedRenderer.cpp
libs/hwui/Android.mk
libs/hwui/FrameInfo.cpp [new file with mode: 0644]
libs/hwui/FrameInfo.h [new file with mode: 0644]
libs/hwui/JankTracker.cpp [new file with mode: 0644]
libs/hwui/JankTracker.h [new file with mode: 0644]
libs/hwui/renderthread/CanvasContext.cpp
libs/hwui/renderthread/CanvasContext.h
libs/hwui/renderthread/DrawFrameTask.cpp
libs/hwui/renderthread/DrawFrameTask.h
libs/hwui/renderthread/RenderProxy.cpp
libs/hwui/renderthread/RenderProxy.h
libs/hwui/renderthread/RenderThread.cpp
libs/hwui/renderthread/RenderThread.h
libs/hwui/renderthread/TimeLord.cpp
libs/hwui/renderthread/TimeLord.h
libs/hwui/tests/main.cpp
libs/hwui/utils/Macros.h
libs/hwui/utils/RingBuffer.h [new file with mode: 0644]

index 8353d54..4a8714c 100644 (file)
@@ -1083,7 +1083,7 @@ public final class ActivityThread {
         @Override
         public void dumpGfxInfo(FileDescriptor fd, String[] args) {
             dumpGraphicsInfo(fd);
-            WindowManagerGlobal.getInstance().dumpGfxInfo(fd);
+            WindowManagerGlobal.getInstance().dumpGfxInfo(fd, args);
         }
 
         @Override
index f41afcf..c8149d9 100644 (file)
@@ -141,6 +141,19 @@ public final class Choreographer {
     private long mFrameIntervalNanos;
 
     /**
+     * Contains information about the current frame for jank-tracking,
+     * mainly timings of key events along with a bit of metadata about
+     * view tree state
+     *
+     * TODO: Is there a better home for this? Currently Choreographer
+     * is the only one with CALLBACK_ANIMATION start time, hence why this
+     * resides here.
+     *
+     * @hide
+     */
+    FrameInfo mFrameInfo = new FrameInfo();
+
+    /**
      * Callback type: Input callback.  Runs first.
      * @hide
      */
@@ -513,6 +526,7 @@ public final class Choreographer {
                 return; // no work to do
             }
 
+            long intendedFrameTimeNanos = frameTimeNanos;
             startNanos = System.nanoTime();
             final long jitterNanos = startNanos - frameTimeNanos;
             if (jitterNanos >= mFrameIntervalNanos) {
@@ -541,12 +555,18 @@ public final class Choreographer {
                 return;
             }
 
+            mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
             mFrameScheduled = false;
             mLastFrameTimeNanos = frameTimeNanos;
         }
 
+        mFrameInfo.markInputHandlingStart();
         doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
+
+        mFrameInfo.markAnimationsStart();
         doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
+
+        mFrameInfo.markPerformTraversalsStart();
         doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
 
         if (DEBUG) {
diff --git a/core/java/android/view/FrameInfo.java b/core/java/android/view/FrameInfo.java
new file mode 100644 (file)
index 0000000..c79547c
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2015 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 android.view;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Class that contains all the timing information for the current frame. This
+ * is used in conjunction with the hardware renderer to provide
+ * continous-monitoring jank events
+ *
+ * All times in nanoseconds from CLOCK_MONOTONIC/System.nanoTime()
+ *
+ * To minimize overhead from System.nanoTime() calls we infer durations of
+ * things by knowing the ordering of the events. For example, to know how
+ * long layout & measure took it's displayListRecordStart - performTraversalsStart.
+ *
+ * These constants must be kept in sync with FrameInfo.h in libhwui and are
+ * used for indexing into AttachInfo's mFrameInfo long[], which is intended
+ * to be quick to pass down to native via JNI, hence a pre-packed format
+ *
+ * @hide
+ */
+final class FrameInfo {
+
+    long[] mFrameInfo = new long[9];
+
+    // Various flags set to provide extra metadata about the current frame
+    private static final int FLAGS = 0;
+
+    // Is this the first-draw following a window layout?
+    public static final long FLAG_WINDOW_LAYOUT_CHANGED = 1;
+
+    @IntDef(flag = true, value = {
+            FLAG_WINDOW_LAYOUT_CHANGED })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface FrameInfoFlags {}
+
+    // The intended vsync time, unadjusted by jitter
+    private static final int INTENDED_VSYNC = 1;
+
+    // Jitter-adjusted vsync time, this is what was used as input into the
+    // animation & drawing system
+    private static final int VSYNC = 2;
+
+    // The time of the oldest input event
+    private static final int OLDEST_INPUT_EVENT = 3;
+
+    // The time of the newest input event
+    private static final int NEWEST_INPUT_EVENT = 4;
+
+    // When input event handling started
+    private static final int HANDLE_INPUT_START = 5;
+
+    // When animation evaluations started
+    private static final int ANIMATION_START = 6;
+
+    // When ViewRootImpl#performTraversals() started
+    private static final int PERFORM_TRAVERSALS_START = 7;
+
+    // When View:draw() started
+    private static final int DRAW_START = 8;
+
+    public void setVsync(long intendedVsync, long usedVsync) {
+        mFrameInfo[INTENDED_VSYNC] = intendedVsync;
+        mFrameInfo[VSYNC] = usedVsync;
+        mFrameInfo[OLDEST_INPUT_EVENT] = Long.MAX_VALUE;
+        mFrameInfo[NEWEST_INPUT_EVENT] = 0;
+        mFrameInfo[FLAGS] = 0;
+    }
+
+    public void updateInputEventTime(long inputEventTime, long inputEventOldestTime) {
+        if (inputEventOldestTime < mFrameInfo[OLDEST_INPUT_EVENT]) {
+            mFrameInfo[OLDEST_INPUT_EVENT] = inputEventOldestTime;
+        }
+        if (inputEventTime > mFrameInfo[NEWEST_INPUT_EVENT]) {
+            mFrameInfo[NEWEST_INPUT_EVENT] = inputEventTime;
+        }
+    }
+
+    public void markInputHandlingStart() {
+        mFrameInfo[HANDLE_INPUT_START] = System.nanoTime();
+    }
+
+    public void markAnimationsStart() {
+        mFrameInfo[ANIMATION_START] = System.nanoTime();
+    }
+
+    public void markPerformTraversalsStart() {
+        mFrameInfo[PERFORM_TRAVERSALS_START] = System.nanoTime();
+    }
+
+    public void markDrawStart() {
+        mFrameInfo[DRAW_START] = System.nanoTime();
+    }
+
+    public void addFlags(@FrameInfoFlags long flags) {
+        mFrameInfo[FLAGS] |= flags;
+    }
+
+}
index c5c3f83..aa61885 100644 (file)
@@ -278,7 +278,7 @@ public abstract class HardwareRenderer {
     /**
      * Outputs extra debugging information in the specified file descriptor.
      */
-    abstract void dumpGfxInfo(PrintWriter pw, FileDescriptor fd);
+    abstract void dumpGfxInfo(PrintWriter pw, FileDescriptor fd, String[] args);
 
     /**
      * Loads system properties used by the renderer. This method is invoked
index ad4a048..df0838f 100644 (file)
@@ -16,8 +16,7 @@
 
 package android.view;
 
-import com.android.internal.R;
-
+import android.annotation.IntDef;
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
@@ -27,16 +26,18 @@ import android.graphics.drawable.Drawable;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.SystemProperties;
 import android.os.Trace;
 import android.util.Log;
 import android.util.LongSparseArray;
-import android.util.TimeUtils;
 import android.view.Surface.OutOfResourcesException;
 import android.view.View.AttachInfo;
 
+import com.android.internal.R;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.HashSet;
 
@@ -74,6 +75,14 @@ public class ThreadedRenderer extends HardwareRenderer {
         PROFILE_PROPERTY_VISUALIZE_BARS,
     };
 
+    private static final int FLAG_DUMP_FRAMESTATS   = 1 << 0;
+    private static final int FLAG_DUMP_RESET        = 1 << 1;
+
+    @IntDef(flag = true, value = {
+            FLAG_DUMP_FRAMESTATS, FLAG_DUMP_RESET })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DumpFlags {}
+
     // Size of the rendered content.
     private int mWidth, mHeight;
 
@@ -93,12 +102,12 @@ public class ThreadedRenderer extends HardwareRenderer {
     private final float mLightRadius;
     private final int mAmbientShadowAlpha;
     private final int mSpotShadowAlpha;
+    private final float mDensity;
 
     private long mNativeProxy;
     private boolean mInitialized = false;
     private RenderNode mRootNode;
     private Choreographer mChoreographer;
-    private boolean mProfilingEnabled;
     private boolean mRootNodeNeedsUpdate;
 
     ThreadedRenderer(Context context, boolean translucent) {
@@ -110,6 +119,7 @@ public class ThreadedRenderer extends HardwareRenderer {
                 (int) (255 * a.getFloat(R.styleable.Lighting_ambientShadowAlpha, 0) + 0.5f);
         mSpotShadowAlpha = (int) (255 * a.getFloat(R.styleable.Lighting_spotShadowAlpha, 0) + 0.5f);
         a.recycle();
+        mDensity = context.getResources().getDisplayMetrics().density;
 
         long rootNodePtr = nCreateRootRenderNode();
         mRootNode = RenderNode.adopt(rootNodePtr);
@@ -214,7 +224,7 @@ public class ThreadedRenderer extends HardwareRenderer {
         mRootNode.setLeftTopRightBottom(-mInsetLeft, -mInsetTop, mSurfaceWidth, mSurfaceHeight);
         nSetup(mNativeProxy, mSurfaceWidth, mSurfaceHeight,
                 lightX, mLightY, mLightZ, mLightRadius,
-                mAmbientShadowAlpha, mSpotShadowAlpha);
+                mAmbientShadowAlpha, mSpotShadowAlpha, mDensity);
     }
 
     @Override
@@ -233,32 +243,25 @@ public class ThreadedRenderer extends HardwareRenderer {
     }
 
     @Override
-    void dumpGfxInfo(PrintWriter pw, FileDescriptor fd) {
+    void dumpGfxInfo(PrintWriter pw, FileDescriptor fd, String[] args) {
         pw.flush();
-        nDumpProfileInfo(mNativeProxy, fd);
-    }
-
-    private static int search(String[] values, String value) {
-        for (int i = 0; i < values.length; i++) {
-            if (values[i].equals(value)) return i;
+        int flags = 0;
+        for (int i = 0; i < args.length; i++) {
+            switch (args[i]) {
+                case "framestats":
+                    flags |= FLAG_DUMP_FRAMESTATS;
+                    break;
+                case "reset":
+                    flags |= FLAG_DUMP_RESET;
+                    break;
+            }
         }
-        return -1;
-    }
-
-    private static boolean checkIfProfilingRequested() {
-        String profiling = SystemProperties.get(HardwareRenderer.PROFILE_PROPERTY);
-        int graphType = search(VISUALIZERS, profiling);
-        return (graphType >= 0) || Boolean.parseBoolean(profiling);
+        nDumpProfileInfo(mNativeProxy, fd, flags);
     }
 
     @Override
     boolean loadSystemProperties() {
         boolean changed = nLoadSystemProperties(mNativeProxy);
-        boolean wantProfiling = checkIfProfilingRequested();
-        if (wantProfiling != mProfilingEnabled) {
-            mProfilingEnabled = wantProfiling;
-            changed = true;
-        }
         if (changed) {
             invalidateRoot();
         }
@@ -307,20 +310,12 @@ public class ThreadedRenderer extends HardwareRenderer {
     @Override
     void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks) {
         attachInfo.mIgnoreDirtyState = true;
-        long frameTimeNanos = mChoreographer.getFrameTimeNanos();
-        attachInfo.mDrawingTime = frameTimeNanos / TimeUtils.NANOS_PER_MS;
 
-        long recordDuration = 0;
-        if (mProfilingEnabled) {
-            recordDuration = System.nanoTime();
-        }
+        final Choreographer choreographer = attachInfo.mViewRootImpl.mChoreographer;
+        choreographer.mFrameInfo.markDrawStart();
 
         updateRootDisplayList(view, callbacks);
 
-        if (mProfilingEnabled) {
-            recordDuration = System.nanoTime() - recordDuration;
-        }
-
         attachInfo.mIgnoreDirtyState = false;
 
         // register animating rendernodes which started animating prior to renderer
@@ -337,8 +332,8 @@ public class ThreadedRenderer extends HardwareRenderer {
             attachInfo.mPendingAnimatingRenderNodes = null;
         }
 
-        int syncResult = nSyncAndDrawFrame(mNativeProxy, frameTimeNanos,
-                recordDuration, view.getResources().getDisplayMetrics().density);
+        final long[] frameInfo = choreographer.mFrameInfo.mFrameInfo;
+        int syncResult = nSyncAndDrawFrame(mNativeProxy, frameInfo, frameInfo.length);
         if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) {
             setEnabled(false);
             attachInfo.mViewRootImpl.mSurface.release();
@@ -500,10 +495,9 @@ public class ThreadedRenderer extends HardwareRenderer {
     private static native boolean nPauseSurface(long nativeProxy, Surface window);
     private static native void nSetup(long nativeProxy, int width, int height,
             float lightX, float lightY, float lightZ, float lightRadius,
-            int ambientShadowAlpha, int spotShadowAlpha);
+            int ambientShadowAlpha, int spotShadowAlpha, float density);
     private static native void nSetOpaque(long nativeProxy, boolean opaque);
-    private static native int nSyncAndDrawFrame(long nativeProxy,
-            long frameTimeNanos, long recordDuration, float density);
+    private static native int nSyncAndDrawFrame(long nativeProxy, long[] frameInfo, int size);
     private static native void nDestroy(long nativeProxy);
     private static native void nRegisterAnimatingRenderNode(long rootRenderNode, long animatingNode);
 
@@ -523,5 +517,6 @@ public class ThreadedRenderer extends HardwareRenderer {
     private static native void nStopDrawing(long nativeProxy);
     private static native void nNotifyFramePending(long nativeProxy);
 
-    private static native void nDumpProfileInfo(long nativeProxy, FileDescriptor fd);
+    private static native void nDumpProfileInfo(long nativeProxy, FileDescriptor fd,
+            @DumpFlags int dumpFlags);
 }
index e4d82b1..3566140 100644 (file)
@@ -56,6 +56,7 @@ import android.util.AndroidRuntimeException;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.Slog;
+import android.util.TimeUtils;
 import android.util.TypedValue;
 import android.view.Surface.OutOfResourcesException;
 import android.view.View.AttachInfo;
@@ -1502,6 +1503,7 @@ public final class ViewRootImpl implements ViewParent,
                         // to resume them
                         mDirty.set(0, 0, mWidth, mHeight);
                     }
+                    mChoreographer.mFrameInfo.addFlags(FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED);
                 }
                 final int surfaceGenerationId = mSurface.getGenerationId();
                 relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
@@ -2505,6 +2507,9 @@ public final class ViewRootImpl implements ViewParent,
             }
         }
 
+        mAttachInfo.mDrawingTime =
+                mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;
+
         if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
             if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
                 // If accessibility focus moved, always invalidate the root.
@@ -2624,7 +2629,6 @@ public final class ViewRootImpl implements ViewParent,
 
             dirty.setEmpty();
             mIsAnimating = false;
-            attachInfo.mDrawingTime = SystemClock.uptimeMillis();
             mView.mPrivateFlags |= View.PFLAG_DRAWN;
 
             if (DEBUG_DRAW) {
@@ -5778,6 +5782,16 @@ public final class ViewRootImpl implements ViewParent,
             Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
                     mPendingInputEventCount);
 
+            long eventTime = q.mEvent.getEventTimeNano();
+            long oldestEventTime = eventTime;
+            if (q.mEvent instanceof MotionEvent) {
+                MotionEvent me = (MotionEvent)q.mEvent;
+                if (me.getHistorySize() > 0) {
+                    oldestEventTime = me.getHistoricalEventTimeNano(0);
+                }
+            }
+            mChoreographer.mFrameInfo.updateInputEventTime(eventTime, oldestEventTime);
+
             deliverInputEvent(q);
         }
 
index a14c766..643340a 100644 (file)
@@ -459,7 +459,7 @@ public final class WindowManagerGlobal {
         }
     }
 
-    public void dumpGfxInfo(FileDescriptor fd) {
+    public void dumpGfxInfo(FileDescriptor fd, String[] args) {
         FileOutputStream fout = new FileOutputStream(fd);
         PrintWriter pw = new FastPrintWriter(fout);
         try {
@@ -476,7 +476,7 @@ public final class WindowManagerGlobal {
                     HardwareRenderer renderer =
                             root.getView().mAttachInfo.mHardwareRenderer;
                     if (renderer != null) {
-                        renderer.dumpGfxInfo(pw, fd);
+                        renderer.dumpGfxInfo(pw, fd, args);
                     }
                 }
 
index a39ff8e..8590aca 100644 (file)
@@ -49,6 +49,7 @@
 
 #include <AnimationContext.h>
 #include <DisplayListRenderer.h>
+#include <FrameInfo.h>
 #include <RenderNode.h>
 #include <renderthread/RenderProxy.h>
 
@@ -394,7 +395,7 @@ static jlong create(JNIEnv* env, jclass clazz, jlong rootNodePtr, jlong surfaceP
     proxy->initialize(surface);
     // Shadows can't be used via this interface, so just set the light source
     // to all 0s. (and width & height are unused, TODO remove them)
-    proxy->setup(0, 0, (Vector3){0, 0, 0}, 0, 0, 0);
+    proxy->setup(0, 0, (Vector3){0, 0, 0}, 0, 0, 0, 1.0f);
     return (jlong) proxy;
 }
 
@@ -406,8 +407,11 @@ static void setSurface(JNIEnv* env, jclass clazz, jlong rendererPtr, jlong surfa
 
 static void draw(JNIEnv* env, jclass clazz, jlong rendererPtr) {
     RenderProxy* proxy = reinterpret_cast<RenderProxy*>(rendererPtr);
-    nsecs_t frameTimeNs = systemTime(CLOCK_MONOTONIC);
-    proxy->syncAndDrawFrame(frameTimeNs, 0, 1.0f);
+    nsecs_t vsync = systemTime(CLOCK_MONOTONIC);
+    UiFrameInfoBuilder(proxy->frameInfo())
+            .setVsync(vsync, vsync)
+            .addFlag(FrameInfoFlags::kSurfaceCanvas);
+    proxy->syncAndDrawFrame();
 }
 
 static void destroy(JNIEnv* env, jclass clazz, jlong rendererPtr) {
index b9d849c..00d7fc8 100644 (file)
@@ -283,10 +283,10 @@ static jboolean android_view_ThreadedRenderer_pauseSurface(JNIEnv* env, jobject
 static void android_view_ThreadedRenderer_setup(JNIEnv* env, jobject clazz, jlong proxyPtr,
         jint width, jint height,
         jfloat lightX, jfloat lightY, jfloat lightZ, jfloat lightRadius,
-        jint ambientShadowAlpha, jint spotShadowAlpha) {
+        jint ambientShadowAlpha, jint spotShadowAlpha, jfloat density) {
     RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
     proxy->setup(width, height, (Vector3){lightX, lightY, lightZ}, lightRadius,
-            ambientShadowAlpha, spotShadowAlpha);
+            ambientShadowAlpha, spotShadowAlpha, density);
 }
 
 static void android_view_ThreadedRenderer_setOpaque(JNIEnv* env, jobject clazz,
@@ -296,9 +296,13 @@ static void android_view_ThreadedRenderer_setOpaque(JNIEnv* env, jobject clazz,
 }
 
 static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz,
-        jlong proxyPtr, jlong frameTimeNanos, jlong recordDuration, jfloat density) {
+        jlong proxyPtr, jlongArray frameInfo, jint frameInfoSize) {
+    LOG_ALWAYS_FATAL_IF(frameInfoSize != UI_THREAD_FRAME_INFO_SIZE,
+            "Mismatched size expectations, given %d expected %d",
+            frameInfoSize, UI_THREAD_FRAME_INFO_SIZE);
     RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
-    return proxy->syncAndDrawFrame(frameTimeNanos, recordDuration, density);
+    env->GetLongArrayRegion(frameInfo, 0, frameInfoSize, proxy->frameInfo());
+    return proxy->syncAndDrawFrame();
 }
 
 static void android_view_ThreadedRenderer_destroy(JNIEnv* env, jobject clazz,
@@ -393,10 +397,10 @@ static void android_view_ThreadedRenderer_notifyFramePending(JNIEnv* env, jobjec
 }
 
 static void android_view_ThreadedRenderer_dumpProfileInfo(JNIEnv* env, jobject clazz,
-        jlong proxyPtr, jobject javaFileDescriptor) {
+        jlong proxyPtr, jobject javaFileDescriptor, jint dumpFlags) {
     RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
     int fd = jniGetFDFromFileDescriptor(env, javaFileDescriptor);
-    proxy->dumpProfileInfo(fd);
+    proxy->dumpProfileInfo(fd, dumpFlags);
 }
 
 #endif
@@ -430,9 +434,9 @@ static JNINativeMethod gMethods[] = {
     { "nInitialize", "(JLandroid/view/Surface;)Z", (void*) android_view_ThreadedRenderer_initialize },
     { "nUpdateSurface", "(JLandroid/view/Surface;)V", (void*) android_view_ThreadedRenderer_updateSurface },
     { "nPauseSurface", "(JLandroid/view/Surface;)Z", (void*) android_view_ThreadedRenderer_pauseSurface },
-    { "nSetup", "(JIIFFFFII)V", (void*) android_view_ThreadedRenderer_setup },
+    { "nSetup", "(JIIFFFFIIF)V", (void*) android_view_ThreadedRenderer_setup },
     { "nSetOpaque", "(JZ)V", (void*) android_view_ThreadedRenderer_setOpaque },
-    { "nSyncAndDrawFrame", "(JJJF)I", (void*) android_view_ThreadedRenderer_syncAndDrawFrame },
+    { "nSyncAndDrawFrame", "(J[JI)I", (void*) android_view_ThreadedRenderer_syncAndDrawFrame },
     { "nDestroy", "(J)V", (void*) android_view_ThreadedRenderer_destroy },
     { "nRegisterAnimatingRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_registerAnimatingRenderNode },
     { "nInvokeFunctor", "(JZ)V", (void*) android_view_ThreadedRenderer_invokeFunctor },
@@ -447,7 +451,7 @@ static JNINativeMethod gMethods[] = {
     { "nFence", "(J)V", (void*) android_view_ThreadedRenderer_fence },
     { "nStopDrawing", "(J)V", (void*) android_view_ThreadedRenderer_stopDrawing },
     { "nNotifyFramePending", "(J)V", (void*) android_view_ThreadedRenderer_notifyFramePending },
-    { "nDumpProfileInfo", "(JLjava/io/FileDescriptor;)V", (void*) android_view_ThreadedRenderer_dumpProfileInfo },
+    { "nDumpProfileInfo", "(JLjava/io/FileDescriptor;I)V", (void*) android_view_ThreadedRenderer_dumpProfileInfo },
 #endif
     { "setupShadersDiskCache", "(Ljava/lang/String;)V",
                 (void*) android_view_ThreadedRenderer_setupShadersDiskCache },
index 49560ff..507472a 100644 (file)
@@ -18,6 +18,7 @@ ifeq ($(USE_OPENGL_RENDERER),true)
                AssetAtlas.cpp \
                DamageAccumulator.cpp \
                FontRenderer.cpp \
+               FrameInfo.cpp \
                GammaFontRenderer.cpp \
                Caches.cpp \
                DisplayList.cpp \
@@ -32,6 +33,7 @@ ifeq ($(USE_OPENGL_RENDERER),true)
                GradientCache.cpp \
                Image.cpp \
                Interpolator.cpp \
+               JankTracker.cpp \
                Layer.cpp \
                LayerCache.cpp \
                LayerRenderer.cpp \
diff --git a/libs/hwui/FrameInfo.cpp b/libs/hwui/FrameInfo.cpp
new file mode 100644 (file)
index 0000000..6da1fa8
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+#include "FrameInfo.h"
+
+#include <cstring>
+
+namespace android {
+namespace uirenderer {
+
+void FrameInfo::importUiThreadInfo(int64_t* info) {
+    memcpy(mFrameInfo, info, UI_THREAD_FRAME_INFO_SIZE * sizeof(int64_t));
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h
new file mode 100644 (file)
index 0000000..3c31677
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+#ifndef FRAMEINFO_H_
+#define FRAMEINFO_H_
+
+#include "utils/Macros.h"
+
+#include <cutils/compiler.h>
+#include <utils/Timers.h>
+
+#include <memory.h>
+
+namespace android {
+namespace uirenderer {
+
+#define UI_THREAD_FRAME_INFO_SIZE 9
+
+HWUI_ENUM(FrameInfoIndex,
+    kFlags = 0,
+    kIntendedVsync,
+    kVsync,
+    kOldestInputEvent,
+    kNewestInputEvent,
+    kHandleInputStart,
+    kAnimationStart,
+    kPerformTraversalsStart,
+    kDrawStart,
+    // End of UI frame info
+
+    kSyncStart,
+    kIssueDrawCommandsStart,
+    kSwapBuffers,
+    kFrameCompleted,
+
+    // Must be the last value!
+    kNumIndexes
+);
+
+HWUI_ENUM(FrameInfoFlags,
+    kWindowLayoutChanged = 1 << 0,
+    kRTAnimation = 1 << 1,
+    kSurfaceCanvas = 1 << 2,
+);
+
+class ANDROID_API UiFrameInfoBuilder {
+public:
+    UiFrameInfoBuilder(int64_t* buffer) : mBuffer(buffer) {
+        memset(mBuffer, 0, UI_THREAD_FRAME_INFO_SIZE * sizeof(int64_t));
+    }
+
+    UiFrameInfoBuilder& setVsync(nsecs_t vsyncTime, nsecs_t intendedVsync) {
+        mBuffer[FrameInfoIndex::kVsync] = vsyncTime;
+        mBuffer[FrameInfoIndex::kIntendedVsync] = intendedVsync;
+        return *this;
+    }
+
+    UiFrameInfoBuilder& addFlag(FrameInfoFlagsEnum flag) {
+        mBuffer[FrameInfoIndex::kFlags] |= static_cast<uint64_t>(flag);
+        return *this;
+    }
+
+private:
+    int64_t* mBuffer;
+};
+
+class FrameInfo {
+public:
+    void importUiThreadInfo(int64_t* info);
+
+    void markSyncStart() {
+        mFrameInfo[FrameInfoIndex::kSyncStart] = systemTime(CLOCK_MONOTONIC);
+    }
+
+    void markIssueDrawCommandsStart() {
+        mFrameInfo[FrameInfoIndex::kIssueDrawCommandsStart] = systemTime(CLOCK_MONOTONIC);
+    }
+
+    void markSwapBuffers() {
+        mFrameInfo[FrameInfoIndex::kSwapBuffers] = systemTime(CLOCK_MONOTONIC);
+    }
+
+    void markFrameCompleted() {
+        mFrameInfo[FrameInfoIndex::kFrameCompleted] = systemTime(CLOCK_MONOTONIC);
+    }
+
+    int64_t operator[](FrameInfoIndexEnum index) const {
+        if (index == FrameInfoIndex::kNumIndexes) return 0;
+        return mFrameInfo[static_cast<int>(index)];
+    }
+
+    int64_t operator[](int index) const {
+        if (index < 0 || index >= FrameInfoIndex::kNumIndexes) return 0;
+        return mFrameInfo[static_cast<int>(index)];
+    }
+
+private:
+    int64_t mFrameInfo[FrameInfoIndex::kNumIndexes];
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* FRAMEINFO_H_ */
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
new file mode 100644 (file)
index 0000000..f7c8195
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+#include "JankTracker.h"
+
+#include <algorithm>
+#include <cstdio>
+#include <inttypes.h>
+
+namespace android {
+namespace uirenderer {
+
+static const char* JANK_TYPE_NAMES[] = {
+        "Missed Vsync",
+        "High input latency",
+        "Slow UI thread",
+        "Slow bitmap uploads",
+        "Slow draw",
+};
+
+struct Comparison {
+    FrameInfoIndexEnum start;
+    FrameInfoIndexEnum end;
+};
+
+static const Comparison COMPARISONS[] = {
+        {FrameInfoIndex::kIntendedVsync, FrameInfoIndex::kVsync},
+        {FrameInfoIndex::kOldestInputEvent, FrameInfoIndex::kVsync},
+        {FrameInfoIndex::kVsync, FrameInfoIndex::kSyncStart},
+        {FrameInfoIndex::kSyncStart, FrameInfoIndex::kIssueDrawCommandsStart},
+        {FrameInfoIndex::kIssueDrawCommandsStart, FrameInfoIndex::kFrameCompleted},
+};
+
+// If the event exceeds 10 seconds throw it away, this isn't a jank event
+// it's an ANR and will be handled as such
+static const int64_t IGNORE_EXCEEDING = seconds_to_nanoseconds(10);
+
+/*
+ * Frames that are exempt from jank metrics.
+ * First-draw frames, for example, are expected to
+ * be slow, this is hidden from the user with window animations and
+ * other tricks
+ *
+ * Similarly, we don't track direct-drawing via Surface:lockHardwareCanvas()
+ * for now
+ *
+ * TODO: kSurfaceCanvas can negatively impact other drawing by using up
+ * time on the RenderThread, figure out how to attribute that as a jank-causer
+ */
+static const int64_t EXEMPT_FRAMES_FLAGS
+        = FrameInfoFlags::kWindowLayoutChanged
+        | FrameInfoFlags::kSurfaceCanvas;
+
+JankTracker::JankTracker(nsecs_t frameIntervalNanos) {
+    reset();
+    setFrameInterval(frameIntervalNanos);
+}
+
+void JankTracker::setFrameInterval(nsecs_t frameInterval) {
+    mFrameInterval = frameInterval;
+    mThresholds[kMissedVsync] = 1;
+    /*
+     * Due to interpolation and sample rate differences between the touch
+     * panel and the display (example, 85hz touch panel driving a 60hz display)
+     * we call high latency 1.5 * frameinterval
+     *
+     * NOTE: Be careful when tuning this! A theoretical 1,000hz touch panel
+     * on a 60hz display will show kOldestInputEvent - kIntendedVsync of being 15ms
+     * Thus this must always be larger than frameInterval, or it will fail
+     */
+    mThresholds[kHighInputLatency] = static_cast<int64_t>(1.5 * frameInterval);
+
+    // Note that these do not add up to 1. This is intentional. It's to deal
+    // with variance in values, and should be sort of an upper-bound on what
+    // is reasonable to expect.
+    mThresholds[kSlowUI] = static_cast<int64_t>(.5 * frameInterval);
+    mThresholds[kSlowSync] = static_cast<int64_t>(.2 * frameInterval);
+    mThresholds[kSlowRT] = static_cast<int64_t>(.75 * frameInterval);
+
+}
+
+void JankTracker::addFrame(const FrameInfo& frame) {
+    using namespace FrameInfoIndex;
+    mTotalFrameCount++;
+    // Fast-path for jank-free frames
+    int64_t totalDuration = frame[kFrameCompleted] - frame[kIntendedVsync];
+    uint32_t framebucket = std::min(
+            static_cast<typeof sizeof(mFrameCounts)>(ns2ms(totalDuration)),
+            sizeof(mFrameCounts) / sizeof(mFrameCounts[0]));
+    // Keep the fast path as fast as possible.
+    if (CC_LIKELY(totalDuration < mFrameInterval)) {
+        mFrameCounts[framebucket]++;
+        return;
+    }
+
+    if (frame[kFlags] & EXEMPT_FRAMES_FLAGS) {
+        return;
+    }
+
+    mFrameCounts[framebucket]++;
+    mJankFrameCount++;
+
+    for (int i = 0; i < NUM_BUCKETS; i++) {
+        int64_t delta = frame[COMPARISONS[i].end] - frame[COMPARISONS[i].start];
+        if (delta >= mThresholds[i] && delta < IGNORE_EXCEEDING) {
+            mBuckets[i].count++;
+        }
+    }
+}
+
+void JankTracker::dump(int fd) {
+    FILE* file = fdopen(fd, "a");
+    fprintf(file, "\nFrame stats:");
+    fprintf(file, "\n  Total frames rendered: %u", mTotalFrameCount);
+    fprintf(file, "\n  Janky frames: %u (%.2f%%)", mJankFrameCount,
+            (float) mJankFrameCount / (float) mTotalFrameCount * 100.0f);
+    fprintf(file, "\n  90th percentile: %ums", findPercentile(90));
+    fprintf(file, "\n  95th percentile: %ums", findPercentile(95));
+    fprintf(file, "\n  99th percentile: %ums", findPercentile(99));
+    for (int i = 0; i < NUM_BUCKETS; i++) {
+        fprintf(file, "\n   Number %s: %u", JANK_TYPE_NAMES[i], mBuckets[i].count);
+    }
+    fprintf(file, "\n");
+    fflush(file);
+}
+
+void JankTracker::reset() {
+    memset(mBuckets, 0, sizeof(mBuckets));
+    memset(mFrameCounts, 0, sizeof(mFrameCounts));
+    mTotalFrameCount = 0;
+    mJankFrameCount = 0;
+}
+
+uint32_t JankTracker::findPercentile(int percentile) {
+    int pos = percentile * mTotalFrameCount / 100;
+    int remaining = mTotalFrameCount - pos;
+    for (int i = sizeof(mFrameCounts) / sizeof(mFrameCounts[0]) - 1; i >= 0; i--) {
+        remaining -= mFrameCounts[i];
+        if (remaining <= 0) {
+            return i;
+        }
+    }
+    return 0;
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/JankTracker.h b/libs/hwui/JankTracker.h
new file mode 100644 (file)
index 0000000..3d4929b
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+#ifndef JANKTRACKER_H_
+#define JANKTRACKER_H_
+
+#include "FrameInfo.h"
+#include "renderthread/TimeLord.h"
+#include "utils/RingBuffer.h"
+
+#include <memory>
+
+namespace android {
+namespace uirenderer {
+
+enum JankType {
+    kMissedVsync = 0,
+    kHighInputLatency,
+    kSlowUI,
+    kSlowSync,
+    kSlowRT,
+
+    // must be last
+    NUM_BUCKETS,
+};
+
+struct JankBucket {
+    // Number of frames that hit this bucket
+    uint32_t count;
+};
+
+// TODO: Replace DrawProfiler with this
+class JankTracker {
+public:
+    JankTracker(nsecs_t frameIntervalNanos);
+
+    void setFrameInterval(nsecs_t frameIntervalNanos);
+
+    void addFrame(const FrameInfo& frame);
+
+    void dump(int fd);
+    void reset();
+
+private:
+    uint32_t findPercentile(int p);
+
+    JankBucket mBuckets[NUM_BUCKETS];
+    int64_t mThresholds[NUM_BUCKETS];
+    uint32_t mFrameCounts[128];
+
+    int64_t mFrameInterval;
+    uint32_t mTotalFrameCount;
+    uint32_t mJankFrameCount;
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* JANKTRACKER_H_ */
index 75bd067..2eaa771 100644 (file)
@@ -47,7 +47,8 @@ CanvasContext::CanvasContext(RenderThread& thread, bool translucent,
         , mOpaque(!translucent)
         , mCanvas(NULL)
         , mHaveNewSurface(false)
-        , mRootRenderNode(rootRenderNode) {
+        , mRootRenderNode(rootRenderNode)
+        , mCurrentFrameInfo(NULL) {
     mAnimationContext = contextFactory->createAnimationContext(mRenderThread.timeLord());
     mRenderThread.renderState().registerCanvasContext(this);
 }
@@ -152,9 +153,13 @@ void CanvasContext::processLayerUpdate(DeferredLayerUpdater* layerUpdater) {
     }
 }
 
-void CanvasContext::prepareTree(TreeInfo& info) {
+void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo) {
     mRenderThread.removeFrameCallback(this);
 
+    mCurrentFrameInfo = &mFrames.next();
+    mCurrentFrameInfo->importUiThreadInfo(uiFrameInfo);
+    mCurrentFrameInfo->markSyncStart();
+
     info.damageAccumulator = &mDamageAccumulator;
     info.renderer = mCanvas;
     if (mPrefetechedLayers.size() && info.mode == TreeInfo::MODE_FULL) {
@@ -204,6 +209,7 @@ void CanvasContext::draw() {
             "drawRenderNode called on a context with no canvas or surface!");
 
     profiler().markPlaybackStart();
+    mCurrentFrameInfo->markIssueDrawCommandsStart();
 
     SkRect dirty;
     mDamageAccumulator.finish(&dirty);
@@ -241,12 +247,19 @@ void CanvasContext::draw() {
 
     profiler().markPlaybackEnd();
 
+    // Even if we decided to cancel the frame, from the perspective of jank
+    // metrics the frame was swapped at this point
+    mCurrentFrameInfo->markSwapBuffers();
+
     if (status & DrawGlInfo::kStatusDrew) {
         swapBuffers();
     } else {
         mEglManager.cancelFrame();
     }
 
+    // TODO: Use a fence for real completion?
+    mCurrentFrameInfo->markFrameCompleted();
+    mRenderThread.jankTracker().addFrame(*mCurrentFrameInfo);
     profiler().finishFrame();
 }
 
@@ -259,9 +272,14 @@ void CanvasContext::doFrame() {
     ATRACE_CALL();
 
     profiler().startFrame();
+    int64_t frameInfo[UI_THREAD_FRAME_INFO_SIZE];
+    UiFrameInfoBuilder(frameInfo)
+        .addFlag(FrameInfoFlags::kRTAnimation)
+        .setVsync(mRenderThread.timeLord().computeFrameTimeNanos(),
+                mRenderThread.timeLord().latestVsync());
 
     TreeInfo info(TreeInfo::MODE_RT_ONLY, mRenderThread.renderState());
-    prepareTree(info);
+    prepareTree(info, frameInfo);
     if (info.out.canDrawThisFrame) {
         draw();
     }
@@ -374,6 +392,28 @@ void CanvasContext::setTextureAtlas(RenderThread& thread,
     thread.eglManager().setTextureAtlas(buffer, map, mapSize);
 }
 
+void CanvasContext::dumpFrames(int fd) {
+    FILE* file = fdopen(fd, "a");
+    fprintf(file, "\n\n---PROFILEDATA---");
+    for (size_t i = 0; i < mFrames.size(); i++) {
+        FrameInfo& frame = mFrames[i];
+        if (frame[FrameInfoIndex::kSyncStart] == 0) {
+            continue;
+        }
+        fprintf(file, "\n");
+        for (int i = 0; i < FrameInfoIndex::kNumIndexes; i++) {
+            fprintf(file, "%" PRId64 ",", frame[i]);
+        }
+    }
+    fprintf(file, "\n---PROFILEDATA---\n\n");
+    fflush(file);
+}
+
+void CanvasContext::resetFrameStats() {
+    mFrames.clear();
+    mRenderThread.jankTracker().reset();
+}
+
 } /* namespace renderthread */
 } /* namespace uirenderer */
 } /* namespace android */
index 0cc2c7c..0e8be9d 100644 (file)
 #ifndef CANVASCONTEXT_H_
 #define CANVASCONTEXT_H_
 
-#include <set>
+#include "DamageAccumulator.h"
+#include "DrawProfiler.h"
+#include "IContextFactory.h"
+#include "FrameInfo.h"
+#include "RenderNode.h"
+#include "utils/RingBuffer.h"
+#include "renderthread/RenderTask.h"
+#include "renderthread/RenderThread.h"
 
 #include <cutils/compiler.h>
 #include <EGL/egl.h>
 #include <utils/Functor.h>
 #include <utils/Vector.h>
 
-#include "../DamageAccumulator.h"
-#include "../DrawProfiler.h"
-#include "../IContextFactory.h"
-#include "../RenderNode.h"
-#include "RenderTask.h"
-#include "RenderThread.h"
-
-#define FUNCTOR_PROCESS_DELAY 4
+#include <set>
 
 namespace android {
 namespace uirenderer {
@@ -75,7 +75,7 @@ public:
     void setOpaque(bool opaque);
     void makeCurrent();
     void processLayerUpdate(DeferredLayerUpdater* layerUpdater);
-    void prepareTree(TreeInfo& info);
+    void prepareTree(TreeInfo& info, int64_t* uiFrameInfo);
     void draw();
     void destroy();
 
@@ -103,6 +103,9 @@ public:
 
     DrawProfiler& profiler() { return mProfiler; }
 
+    void dumpFrames(int fd);
+    void resetFrameStats();
+
 private:
     friend class RegisterFrameCallbackTask;
     // TODO: Replace with something better for layer & other GL object
@@ -133,6 +136,9 @@ private:
     const sp<RenderNode> mRootRenderNode;
 
     DrawProfiler mProfiler;
+    FrameInfo* mCurrentFrameInfo;
+    // Ring buffer large enough for 1 second worth of frames
+    RingBuffer<FrameInfo, 60> mFrames;
 
     std::set<RenderNode*> mPrefetechedLayers;
 };
index 97b31a9..23a0202 100644 (file)
@@ -34,8 +34,6 @@ namespace renderthread {
 DrawFrameTask::DrawFrameTask()
         : mRenderThread(NULL)
         , mContext(NULL)
-        , mFrameTimeNanos(0)
-        , mRecordDurationNanos(0)
         , mDensity(1.0f) // safe enough default
         , mSyncResult(kSync_OK) {
 }
@@ -68,18 +66,12 @@ void DrawFrameTask::removeLayerUpdate(DeferredLayerUpdater* layer) {
     }
 }
 
-int DrawFrameTask::drawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos) {
+int DrawFrameTask::drawFrame() {
     LOG_ALWAYS_FATAL_IF(!mContext, "Cannot drawFrame with no CanvasContext!");
 
     mSyncResult = kSync_OK;
-    mFrameTimeNanos = frameTimeNanos;
-    mRecordDurationNanos = recordDurationNanos;
     postAndWait();
 
-    // Reset the single-frame data
-    mFrameTimeNanos = 0;
-    mRecordDurationNanos = 0;
-
     return mSyncResult;
 }
 
@@ -93,7 +85,7 @@ void DrawFrameTask::run() {
     ATRACE_NAME("DrawFrame");
 
     mContext->profiler().setDensity(mDensity);
-    mContext->profiler().startFrame(mRecordDurationNanos);
+    mContext->profiler().startFrame();
 
     bool canUnblockUiThread;
     bool canDrawThisFrame;
@@ -122,7 +114,7 @@ void DrawFrameTask::run() {
 
 bool DrawFrameTask::syncFrameState(TreeInfo& info) {
     ATRACE_CALL();
-    mRenderThread->timeLord().vsyncReceived(mFrameTimeNanos);
+    mRenderThread->timeLord().vsyncReceived(mFrameInfo[FrameInfoIndex::kVsync]);
     mContext->makeCurrent();
     Caches::getInstance().textureCache.resetMarkInUse();
 
@@ -130,7 +122,7 @@ bool DrawFrameTask::syncFrameState(TreeInfo& info) {
         mContext->processLayerUpdate(mLayers[i].get());
     }
     mLayers.clear();
-    mContext->prepareTree(info);
+    mContext->prepareTree(info, mFrameInfo);
 
     // This is after the prepareTree so that any pending operations
     // (RenderNode tree state, prefetched layers, etc...) will be flushed.
index 28f6cb2..eccb87f 100644 (file)
@@ -25,6 +25,7 @@
 #include "RenderTask.h"
 
 #include "../Rect.h"
+#include "../FrameInfo.h"
 #include "../TreeInfo.h"
 
 namespace android {
@@ -62,7 +63,9 @@ public:
     void removeLayerUpdate(DeferredLayerUpdater* layer);
 
     void setDensity(float density) { mDensity = density; }
-    int drawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos);
+    int drawFrame();
+
+    int64_t* frameInfo() { return mFrameInfo; }
 
     virtual void run();
 
@@ -80,12 +83,12 @@ private:
     /*********************************************
      *  Single frame data
      *********************************************/
-    nsecs_t mFrameTimeNanos;
-    nsecs_t mRecordDurationNanos;
     float mDensity;
     std::vector< sp<DeferredLayerUpdater> > mLayers;
 
     int mSyncResult;
+
+    int64_t mFrameInfo[UI_THREAD_FRAME_INFO_SIZE];
 };
 
 } /* namespace renderthread */
index 6d063a4..088c65b 100644 (file)
 
 #include "RenderProxy.h"
 
-#include "CanvasContext.h"
-#include "RenderTask.h"
-#include "RenderThread.h"
-
-#include "../DeferredLayerUpdater.h"
-#include "../DisplayList.h"
-#include "../LayerRenderer.h"
-#include "../Rect.h"
+#include "DeferredLayerUpdater.h"
+#include "DisplayList.h"
+#include "LayerRenderer.h"
+#include "Rect.h"
+#include "renderthread/CanvasContext.h"
+#include "renderthread/RenderTask.h"
+#include "renderthread/RenderThread.h"
+#include "utils/Macros.h"
 
 namespace android {
 namespace uirenderer {
@@ -52,6 +52,11 @@ namespace renderthread {
     MethodInvokeRenderTask* task = new MethodInvokeRenderTask((RunnableMethod) Bridge_ ## method); \
     ARGS(method) *args = (ARGS(method) *) task->payload()
 
+HWUI_ENUM(DumpFlags,
+        kFrameStats = 1 << 0,
+        kReset      = 1 << 1,
+);
+
 CREATE_BRIDGE4(createContext, RenderThread* thread, bool translucent,
         RenderNode* rootRenderNode, IContextFactory* contextFactory) {
     return new CanvasContext(*args->thread, args->translucent,
@@ -92,7 +97,7 @@ void RenderProxy::destroyContext() {
 }
 
 CREATE_BRIDGE2(setFrameInterval, RenderThread* thread, nsecs_t frameIntervalNanos) {
-    args->thread->timeLord().setFrameInterval(args->frameIntervalNanos);
+    args->thread->setFrameInterval(args->frameIntervalNanos);
     return NULL;
 }
 
@@ -175,7 +180,8 @@ CREATE_BRIDGE7(setup, CanvasContext* context, int width, int height,
 }
 
 void RenderProxy::setup(int width, int height, const Vector3& lightCenter, float lightRadius,
-        uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
+        uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha, float density) {
+    mDrawFrameTask.setDensity(density);
     SETUP_TASK(setup);
     args->context = mContext;
     args->width = width;
@@ -199,10 +205,12 @@ void RenderProxy::setOpaque(bool opaque) {
     post(task);
 }
 
-int RenderProxy::syncAndDrawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos,
-        float density) {
-    mDrawFrameTask.setDensity(density);
-    return mDrawFrameTask.drawFrame(frameTimeNanos, recordDurationNanos);
+int64_t* RenderProxy::frameInfo() {
+    return mDrawFrameTask.frameInfo();
+}
+
+int RenderProxy::syncAndDrawFrame() {
+    return mDrawFrameTask.drawFrame();
 }
 
 CREATE_BRIDGE1(destroy, CanvasContext* context) {
@@ -371,19 +379,28 @@ void RenderProxy::notifyFramePending() {
     mRenderThread.queueAtFront(task);
 }
 
-CREATE_BRIDGE2(dumpProfileInfo, CanvasContext* context, int fd) {
+CREATE_BRIDGE3(dumpProfileInfo, CanvasContext* context, int fd, int dumpFlags) {
     args->context->profiler().dumpData(args->fd);
+
+    if (args->dumpFlags & DumpFlags::kFrameStats) {
+        args->context->dumpFrames(args->fd);
+    }
+    if (args->dumpFlags & DumpFlags::kReset) {
+        args->context->resetFrameStats();
+    }
     return NULL;
 }
 
-void RenderProxy::dumpProfileInfo(int fd) {
+void RenderProxy::dumpProfileInfo(int fd, int dumpFlags) {
     SETUP_TASK(dumpProfileInfo);
     args->context = mContext;
     args->fd = fd;
+    args->dumpFlags = dumpFlags;
     postAndWait(task);
 }
 
-CREATE_BRIDGE1(outputLogBuffer, int fd) {
+CREATE_BRIDGE2(outputLogBuffer, int fd, RenderThread* thread) {
+    args->thread->jankTracker().dump(args->fd);
     RenderNode::outputLogBuffer(args->fd);
     return NULL;
 }
@@ -391,6 +408,7 @@ CREATE_BRIDGE1(outputLogBuffer, int fd) {
 void RenderProxy::outputLogBuffer(int fd) {
     SETUP_TASK(outputLogBuffer);
     args->fd = fd;
+    args->thread = &RenderThread::getInstance();
     staticPostAndWait(task);
 }
 
index fd1fe05..d86f1fc 100644 (file)
@@ -71,10 +71,10 @@ public:
     ANDROID_API void updateSurface(const sp<ANativeWindow>& window);
     ANDROID_API bool pauseSurface(const sp<ANativeWindow>& window);
     ANDROID_API void setup(int width, int height, const Vector3& lightCenter, float lightRadius,
-            uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
+            uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha, float density);
     ANDROID_API void setOpaque(bool opaque);
-    ANDROID_API int syncAndDrawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos,
-            float density);
+    ANDROID_API int64_t* frameInfo();
+    ANDROID_API int syncAndDrawFrame();
     ANDROID_API void destroy();
 
     ANDROID_API static void invokeFunctor(Functor* functor, bool waitForCompletion);
@@ -95,7 +95,7 @@ public:
     ANDROID_API void stopDrawing();
     ANDROID_API void notifyFramePending();
 
-    ANDROID_API void dumpProfileInfo(int fd);
+    ANDROID_API void dumpProfileInfo(int fd, int dumpFlags);
     ANDROID_API static void outputLogBuffer(int fd);
 
     ANDROID_API void setTextureAtlas(const sp<GraphicBuffer>& buffer, int64_t* map, size_t size);
index 84826b7..7a8c3ce 100644 (file)
@@ -143,7 +143,8 @@ RenderThread::RenderThread() : Thread(true), Singleton<RenderThread>()
         , mFrameCallbackTaskPending(false)
         , mFrameCallbackTask(0)
         , mRenderState(NULL)
-        , mEglManager(NULL) {
+        , mEglManager(NULL)
+        , mJankTracker(NULL) {
     mFrameCallbackTask = new DispatchFrameCallbacks(this);
     mLooper = new Looper(false);
     run("RenderThread");
@@ -153,6 +154,11 @@ RenderThread::~RenderThread() {
     LOG_ALWAYS_FATAL("Can't destroy the render thread");
 }
 
+void RenderThread::setFrameInterval(nsecs_t frameInterval) {
+    mTimeLord.setFrameInterval(frameInterval);
+    mJankTracker->setFrameInterval(frameInterval);
+}
+
 void RenderThread::initializeDisplayEventReceiver() {
     LOG_ALWAYS_FATAL_IF(mDisplayEventReceiver, "Initializing a second DisplayEventReceiver?");
     mDisplayEventReceiver = new DisplayEventReceiver();
@@ -169,6 +175,7 @@ void RenderThread::initThreadLocals() {
     initializeDisplayEventReceiver();
     mEglManager = new EglManager(*this);
     mRenderState = new RenderState(*this);
+    mJankTracker = new JankTracker(mTimeLord.frameIntervalNanos());
 }
 
 int RenderThread::displayEventReceiverCallback(int fd, int events, void* data) {
index 99c2e15..8a28a35 100644 (file)
@@ -19,8 +19,8 @@
 
 #include "RenderTask.h"
 
-#include <memory>
-#include <set>
+#include "../JankTracker.h"
+#include "TimeLord.h"
 
 #include <cutils/compiler.h>
 #include <utils/Looper.h>
@@ -28,7 +28,8 @@
 #include <utils/Singleton.h>
 #include <utils/Thread.h>
 
-#include "TimeLord.h"
+#include <memory>
+#include <set>
 
 namespace android {
 
@@ -85,9 +86,12 @@ public:
     // the next vsync. If it is not currently registered this does nothing.
     void pushBackFrameCallback(IFrameCallback* callback);
 
+    void setFrameInterval(nsecs_t frameInterval);
+
     TimeLord& timeLord() { return mTimeLord; }
     RenderState& renderState() { return *mRenderState; }
     EglManager& eglManager() { return *mEglManager; }
+    JankTracker& jankTracker() { return *mJankTracker; }
 
 protected:
     virtual bool threadLoop();
@@ -132,6 +136,8 @@ private:
     TimeLord mTimeLord;
     RenderState* mRenderState;
     EglManager* mEglManager;
+
+    JankTracker* mJankTracker;
 };
 
 } /* namespace renderthread */
index f187493..f846d6a 100644 (file)
@@ -32,7 +32,7 @@ bool TimeLord::vsyncReceived(nsecs_t vsync) {
     return false;
 }
 
-nsecs_t TimeLord::computeFrameTimeMs() {
+nsecs_t TimeLord::computeFrameTimeNanos() {
     // Logic copied from Choreographer.java
     nsecs_t now = systemTime(CLOCK_MONOTONIC);
     nsecs_t jitterNanos = now - mFrameTimeNanos;
@@ -40,7 +40,11 @@ nsecs_t TimeLord::computeFrameTimeMs() {
         nsecs_t lastFrameOffset = jitterNanos % mFrameIntervalNanos;
         mFrameTimeNanos = now - lastFrameOffset;
     }
-    return nanoseconds_to_milliseconds(mFrameTimeNanos);
+    return mFrameTimeNanos;
+}
+
+nsecs_t TimeLord::computeFrameTimeMs() {
+    return nanoseconds_to_milliseconds(computeFrameTimeNanos());
 }
 
 } /* namespace renderthread */
index 7c155d2..5464399 100644 (file)
@@ -29,9 +29,13 @@ class RenderThread;
 class TimeLord {
 public:
     void setFrameInterval(nsecs_t intervalNanos) { mFrameIntervalNanos = intervalNanos; }
+    nsecs_t frameIntervalNanos() const { return mFrameIntervalNanos; }
+
     // returns true if the vsync is newer, false if it was rejected for staleness
     bool vsyncReceived(nsecs_t vsync);
+    nsecs_t latestVsync() { return mFrameTimeNanos; }
     nsecs_t computeFrameTimeMs();
+    nsecs_t computeFrameTimeNanos();
 
 private:
     friend class RenderThread;
index 2d99e9f..33b8103 100644 (file)
@@ -88,7 +88,7 @@ int main(int argc, char* argv[]) {
     proxy->initialize(surface);
     float lightX = width / 2.0;
     proxy->setup(width, height, (Vector3){lightX, dp(-200.0f), dp(800.0f)},
-            dp(800.0f), 255 * 0.075, 255 * 0.15);
+            dp(800.0f), 255 * 0.075, 255 * 0.15, gDisplay.density);
 
     android::uirenderer::Rect DUMMY;
 
@@ -116,8 +116,7 @@ int main(int argc, char* argv[]) {
             cards[ci]->mutateStagingProperties().setTranslationY(i);
             cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
         }
-        nsecs_t frameTimeNs = systemTime(CLOCK_MONOTONIC);
-        proxy->syncAndDrawFrame(frameTimeNs, 0, gDisplay.density);
+        proxy->syncAndDrawFrame();
         usleep(12000);
     }
 
index 5b7c87c..068b32b 100644 (file)
         friend inline int compare_type(const Type& lhs, const Type& rhs) { return lhs.compare(rhs); } \
         friend inline hash_t hash_type(const Type& entry) { return entry.hash(); }
 
+#define HWUI_ENUM(name, ...) \
+    namespace name { \
+        enum _##name { \
+            __VA_ARGS__ \
+        }; \
+    } \
+    typedef enum name::_##name name##Enum
+
 #endif /* MACROS_H */
diff --git a/libs/hwui/utils/RingBuffer.h b/libs/hwui/utils/RingBuffer.h
new file mode 100644 (file)
index 0000000..62b22fd
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+#ifndef RINGBUFFER_H_
+#define RINGBUFFER_H_
+
+#include "utils/Macros.h"
+
+#include <stddef.h>
+
+namespace android {
+namespace uirenderer {
+
+template<class T, size_t SIZE>
+class RingBuffer {
+    PREVENT_COPY_AND_ASSIGN(RingBuffer);
+
+public:
+    RingBuffer() : mHead(-1), mCount(0) {}
+    ~RingBuffer() {}
+
+    size_t capacity() { return SIZE; }
+    size_t size() { return mCount; }
+
+    T& next() {
+        mHead = (mHead + 1) % SIZE;
+        if (mCount < SIZE) {
+            mCount++;
+        }
+        return mBuffer[mHead];
+    }
+
+    T& front() {
+        return this[0];
+    }
+
+    T& back() {
+        return this[size() - 1];
+    }
+
+    T& operator[](size_t index) {
+        return mBuffer[(mHead + index + 1) % mCount];
+    }
+
+    void clear() {
+        mCount = 0;
+        mHead = -1;
+    }
+
+private:
+    T mBuffer[SIZE];
+    int mHead;
+    size_t mCount;
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif /* RINGBUFFER_H_ */