expose hwui frame stats through FrameStatsObserver
Change-Id: I88884bafc8e2f6d7f67a36d3609490e83cf8afd5
diff --git a/core/java/android/view/FrameStatsObserver.java b/core/java/android/view/FrameStatsObserver.java
new file mode 100644
index 0000000..0add607
--- /dev/null
+++ b/core/java/android/view/FrameStatsObserver.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2016 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.NonNull;
+import android.util.Log;
+import android.os.Looper;
+import android.os.MessageQueue;
+
+import com.android.internal.util.VirtualRefBasePtr;
+
+import java.lang.NullPointerException;
+import java.lang.ref.WeakReference;
+import java.lang.SuppressWarnings;
+
+/**
+ * Provides streaming access to frame stats information from the rendering
+ * subsystem to apps.
+ *
+ * @hide
+ */
+public abstract class FrameStatsObserver {
+ private static final String TAG = "FrameStatsObserver";
+
+ private MessageQueue mMessageQueue;
+ private long[] mBuffer;
+
+ private FrameStats mFrameStats;
+
+ /* package */ ThreadedRenderer mRenderer;
+ /* package */ VirtualRefBasePtr mNative;
+
+ /**
+ * Containing class for frame statistics reported
+ * by the rendering subsystem.
+ */
+ public static class FrameStats {
+ /**
+ * Precise timing data for various milestones in a frame
+ * lifecycle.
+ *
+ * This data is exactly the same as what is returned by
+ * `adb shell dumpsys gfxinfo <PACKAGE_NAME> framestats`
+ *
+ * The fields reported may change from release to release.
+ *
+ * @see {@link http://developer.android.com/training/testing/performance.html}
+ * for a description of the fields present.
+ */
+ public long[] mTimingData;
+ }
+
+ /**
+ * Creates a FrameStatsObserver
+ *
+ * @param looper the looper to use when invoking callbacks
+ */
+ public FrameStatsObserver(@NonNull Looper looper) {
+ if (looper == null) {
+ throw new NullPointerException("looper cannot be null");
+ }
+
+ mMessageQueue = looper.getQueue();
+ if (mMessageQueue == null) {
+ throw new IllegalStateException("invalid looper, null message queue\n");
+ }
+
+ mFrameStats = new FrameStats();
+ }
+
+ /**
+ * Called on provided looper when frame stats data is available
+ * for the previous frame.
+ *
+ * Clients of this class must do as little work as possible within
+ * this callback, as the buffer is shared between the producer and consumer.
+ *
+ * If the consumer is still executing within this method when there is new
+ * data available that data will be dropped. The producer cannot
+ * wait on the consumer.
+ *
+ * @param data the newly available data
+ */
+ public abstract void onDataAvailable(FrameStats data);
+
+ /**
+ * Returns the number of reports dropped as a result of a slow
+ * consumer.
+ */
+ public long getDroppedReportCount() {
+ if (mRenderer == null) {
+ return 0;
+ }
+
+ return mRenderer.getDroppedFrameReportCount();
+ }
+
+ public boolean isRegistered() {
+ return mRenderer != null && mNative != null;
+ }
+
+ // === called by native === //
+ @SuppressWarnings("unused")
+ private void notifyDataAvailable() {
+ mFrameStats.mTimingData = mBuffer;
+ onDataAvailable(mFrameStats);
+ }
+}
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 0e4bc84..78a63a6 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -24,7 +24,9 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Binder;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -34,12 +36,14 @@
import android.view.View.AttachInfo;
import com.android.internal.R;
+import com.android.internal.util.VirtualRefBasePtr;
import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.HashSet;
/**
* Hardware renderer that proxies the rendering to a render thread. Most calls
@@ -339,6 +343,8 @@
private boolean mEnabled;
private boolean mRequested = true;
+ private HashSet<FrameStatsObserver> mFrameStatsObservers;
+
ThreadedRenderer(Context context, boolean translucent) {
final TypedArray a = context.obtainStyledAttributes(null, R.styleable.Lighting, 0, 0);
mLightY = a.getDimension(R.styleable.Lighting_lightY, 0);
@@ -947,6 +953,31 @@
}
}
+ void addFrameStatsObserver(FrameStatsObserver fso) {
+ if (mFrameStatsObservers == null) {
+ mFrameStatsObservers = new HashSet<>();
+ }
+
+ long nativeFso = nAddFrameStatsObserver(mNativeProxy, fso);
+ fso.mRenderer = this;
+ fso.mNative = new VirtualRefBasePtr(nativeFso);
+ mFrameStatsObservers.add(fso);
+ }
+
+ void removeFrameStatsObserver(FrameStatsObserver fso) {
+ if (!mFrameStatsObservers.remove(fso)) {
+ throw new IllegalArgumentException("attempt to remove FrameStatsObserver that was never added");
+ }
+
+ nRemoveFrameStatsObserver(mNativeProxy, fso.mNative.get());
+ fso.mRenderer = null;
+ fso.mNative = null;
+ }
+
+ long getDroppedFrameReportCount() {
+ return nGetDroppedFrameReportCount(mNativeProxy);
+ }
+
static native void setupShadersDiskCache(String cacheFile);
private static native void nSetAtlas(long nativeProxy, GraphicBuffer buffer, long[] map);
@@ -1000,4 +1031,8 @@
private static native void nDrawRenderNode(long nativeProxy, long rootRenderNode);
private static native void nSetContentDrawBounds(long nativeProxy, int left,
int top, int right, int bottom);
+
+ private static native long nAddFrameStatsObserver(long nativeProxy, FrameStatsObserver fso);
+ private static native void nRemoveFrameStatsObserver(long nativeProxy, long nativeFso);
+ private static native long nGetDroppedFrameReportCount(long nativeProxy);
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index b5b0baa..1b8549b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -109,6 +109,7 @@
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
+import java.lang.NullPointerException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
@@ -5380,6 +5381,36 @@
}
/**
+ * Set an observer to collect stats for each frame rendered for this view.
+ *
+ * @hide
+ */
+ public void addFrameStatsObserver(FrameStatsObserver fso) {
+ if (mAttachInfo != null) {
+ if (mAttachInfo.mHardwareRenderer != null) {
+ mAttachInfo.mHardwareRenderer.addFrameStatsObserver(fso);
+ } else {
+ throw new IllegalStateException("View must be hardware-accelerated");
+ }
+ } else {
+ // TODO: store as pending registration and merge when we are attached to a surface
+ throw new IllegalStateException("View not yet attached");
+ }
+ }
+
+ /**
+ * Remove observer configured to collect frame stats for this view.
+ *
+ * @hide
+ */
+ public void removeFrameStatsObserver(FrameStatsObserver fso) {
+ ThreadedRenderer renderer = getHardwareRenderer();
+ if (renderer != null) {
+ renderer.removeFrameStatsObserver(fso);
+ }
+ }
+
+ /**
* Call this view's OnClickListener, if it is defined. Performs all normal
* actions associated with clicking: reporting accessibility event, playing
* a sound, etc.
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index dfe0cc7..ee70891 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -34,6 +34,7 @@
import android.media.session.MediaController;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemProperties;
@@ -794,6 +795,40 @@
return mCallback;
}
+ /**
+ * Set an observer to collect frame stats for each frame rendererd in this window.
+ *
+ * Must be in hardware rendering mode.
+ * @hide
+ */
+ public final void addFrameStatsObserver(@NonNull FrameStatsObserver fso) {
+ final View decorView = getDecorView();
+ if (decorView == null) {
+ throw new IllegalStateException("can't observe a Window without an attached view");
+ }
+
+ if (fso == null) {
+ throw new NullPointerException("FrameStatsObserver cannot be null");
+ }
+
+ if (fso.isRegistered()) {
+ throw new IllegalStateException("FrameStatsObserver already registered on a Window.");
+ }
+
+ decorView.addFrameStatsObserver(fso);
+ }
+
+ /**
+ * Remove observer and stop listening to frame stats for this window.
+ * @hide
+ */
+ public final void removeFrameStatsObserver(FrameStatsObserver fso) {
+ final View decorView = getDecorView();
+ if (decorView != null) {
+ getDecorView().removeFrameStatsObserver(fso);
+ }
+ }
+
/** @hide */
public final void setOnWindowDismissedCallback(OnWindowDismissedCallback dcb) {
mOnWindowDismissedCallback = dcb;
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 5aa6a73..edced56 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -28,14 +28,18 @@
#include <EGL/eglext.h>
#include <EGL/egl_cache.h>
+#include <utils/Looper.h>
+#include <utils/RefBase.h>
#include <utils/StrongPointer.h>
#include <android_runtime/android_view_Surface.h>
#include <system/window.h>
#include "android_view_GraphicBuffer.h"
+#include "android_os_MessageQueue.h"
#include <Animator.h>
#include <AnimationContext.h>
+#include <FrameInfo.h>
#include <IContextFactory.h>
#include <JankTracker.h>
#include <RenderNode.h>
@@ -50,6 +54,12 @@
using namespace android::uirenderer;
using namespace android::uirenderer::renderthread;
+struct {
+ jfieldID buffer;
+ jfieldID messageQueue;
+ jmethodID notifyData;
+} gFrameStatsObserverClassInfo;
+
static JNIEnv* getenv(JavaVM* vm) {
JNIEnv* env;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
@@ -207,6 +217,99 @@
RootRenderNode* mRootNode;
};
+class ObserverProxy;
+
+class NotifyHandler : public MessageHandler {
+public:
+ NotifyHandler(JavaVM* vm) : mVm(vm) {}
+
+ void setObserver(ObserverProxy* observer) {
+ mObserver = observer;
+ }
+
+ void setBuffer(BufferPool::Buffer* buffer) {
+ mBuffer = buffer;
+ }
+
+ virtual void handleMessage(const Message& message);
+
+private:
+ JavaVM* mVm;
+
+ sp<ObserverProxy> mObserver;
+ BufferPool::Buffer* mBuffer;
+};
+
+class ObserverProxy : public FrameStatsObserver {
+public:
+ ObserverProxy(JavaVM *vm, jobject fso) : mVm(vm) {
+ JNIEnv* env = getenv(mVm);
+
+ jlongArray longArrayLocal = env->NewLongArray(kBufferSize);
+ LOG_ALWAYS_FATAL_IF(longArrayLocal == nullptr,
+ "OOM: can't allocate frame stats buffer");
+ env->SetObjectField(fso, gFrameStatsObserverClassInfo.buffer, longArrayLocal);
+
+ mFsoWeak = env->NewWeakGlobalRef(fso);
+ LOG_ALWAYS_FATAL_IF(mFsoWeak == nullptr,
+ "unable to create frame stats observer reference");
+
+ jobject messageQueueLocal =
+ env->GetObjectField(fso, gFrameStatsObserverClassInfo.messageQueue);
+ mMessageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueLocal);
+ LOG_ALWAYS_FATAL_IF(mMessageQueue == nullptr, "message queue not available");
+
+ mMessageHandler = new NotifyHandler(mVm);
+ LOG_ALWAYS_FATAL_IF(mMessageHandler == nullptr,
+ "OOM: unable to allocate NotifyHandler");
+ }
+
+ ~ObserverProxy() {
+ JNIEnv* env = getenv(mVm);
+ env->DeleteWeakGlobalRef(mFsoWeak);
+ }
+
+ jweak getJavaObjectRef() {
+ return mFsoWeak;
+ }
+
+ virtual void notify(BufferPool::Buffer* buffer) {
+ buffer->incRef();
+ mMessageHandler->setBuffer(buffer);
+ mMessageHandler->setObserver(this);
+ mMessageQueue->getLooper()->sendMessage(mMessageHandler, mMessage);
+ }
+
+private:
+ static const int kBufferSize = static_cast<int>(FrameInfoIndex::NumIndexes);
+
+ JavaVM* mVm;
+ jweak mFsoWeak;
+
+ sp<MessageQueue> mMessageQueue;
+ sp<NotifyHandler> mMessageHandler;
+ Message mMessage;
+};
+
+void NotifyHandler::handleMessage(const Message& message) {
+ JNIEnv* env = getenv(mVm);
+
+ jobject target = env->NewLocalRef(mObserver->getJavaObjectRef());
+
+ if (target != nullptr) {
+ jobject javaBuffer = env->GetObjectField(target, gFrameStatsObserverClassInfo.buffer);
+ if (javaBuffer != nullptr) {
+ env->SetLongArrayRegion(reinterpret_cast<jlongArray>(javaBuffer),
+ 0, mBuffer->getSize(), mBuffer->getBuffer());
+ env->CallVoidMethod(target, gFrameStatsObserverClassInfo.notifyData);
+ env->DeleteLocalRef(target);
+ }
+ }
+
+ mBuffer->release();
+ mObserver.clear();
+}
+
static void android_view_ThreadedRenderer_setAtlas(JNIEnv* env, jobject clazz,
jlong proxyPtr, jobject graphicBuffer, jlongArray atlasMapArray) {
sp<GraphicBuffer> buffer = graphicBufferForJavaObject(env, graphicBuffer);
@@ -468,6 +571,42 @@
}
// ----------------------------------------------------------------------------
+// FrameStatsObserver
+// ----------------------------------------------------------------------------
+
+static jlong android_view_ThreadedRenderer_addFrameStatsObserver(JNIEnv* env,
+ jclass clazz, jlong proxyPtr, jobject fso) {
+ JavaVM* vm = nullptr;
+ if (env->GetJavaVM(&vm) != JNI_OK) {
+ LOG_ALWAYS_FATAL("Unable to get Java VM");
+ return 0;
+ }
+
+ renderthread::RenderProxy* renderProxy =
+ reinterpret_cast<renderthread::RenderProxy*>(proxyPtr);
+
+ FrameStatsObserver* observer = new ObserverProxy(vm, fso);
+ renderProxy->addFrameStatsObserver(observer);
+ return reinterpret_cast<jlong>(observer);
+}
+
+static void android_view_ThreadedRenderer_removeFrameStatsObserver(JNIEnv* env, jclass clazz,
+ jlong proxyPtr, jlong observerPtr) {
+ FrameStatsObserver* observer = reinterpret_cast<FrameStatsObserver*>(observerPtr);
+ renderthread::RenderProxy* renderProxy =
+ reinterpret_cast<renderthread::RenderProxy*>(proxyPtr);
+
+ renderProxy->removeFrameStatsObserver(observer);
+}
+
+static jint android_view_ThreadedRenderer_getDroppedFrameReportCount(JNIEnv* env, jclass clazz,
+ jlong proxyPtr) {
+ renderthread::RenderProxy* renderProxy =
+ reinterpret_cast<renderthread::RenderProxy*>(proxyPtr);
+ return renderProxy->getDroppedFrameReportCount();
+}
+
+// ----------------------------------------------------------------------------
// Shaders
// ----------------------------------------------------------------------------
@@ -523,9 +662,26 @@
{ "nRemoveRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_removeRenderNode},
{ "nDrawRenderNode", "(JJ)V", (void*) android_view_ThreadedRendererd_drawRenderNode},
{ "nSetContentDrawBounds", "(JIIII)V", (void*)android_view_ThreadedRenderer_setContentDrawBounds},
+ { "nAddFrameStatsObserver",
+ "(JLandroid/view/FrameStatsObserver;)J",
+ (void*)android_view_ThreadedRenderer_addFrameStatsObserver },
+ { "nRemoveFrameStatsObserver",
+ "(JJ)V",
+ (void*)android_view_ThreadedRenderer_removeFrameStatsObserver },
+ { "nGetDroppedFrameReportCount",
+ "(J)J",
+ (void*)android_view_ThreadedRenderer_getDroppedFrameReportCount },
};
int register_android_view_ThreadedRenderer(JNIEnv* env) {
+ jclass clazz = FindClassOrDie(env, "android/view/FrameStatsObserver");
+ gFrameStatsObserverClassInfo.messageQueue =
+ GetFieldIDOrDie(env, clazz, "mMessageQueue", "Landroid/os/MessageQueue;");
+ gFrameStatsObserverClassInfo.buffer =
+ GetFieldIDOrDie(env, clazz, "mBuffer", "[J");
+ gFrameStatsObserverClassInfo.notifyData =
+ GetMethodIDOrDie(env, clazz, "notifyDataAvailable", "()V");
+
return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
}