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

Commit 06f5bc70 authored by Andres Morales's avatar Andres Morales
Browse files

expose hwui frame stats through FrameStatsObserver

Change-Id: I88884bafc8e2f6d7f67a36d3609490e83cf8afd5
parent 0ed21de7
Loading
Loading
Loading
Loading
+122 −0
Original line number Diff line number Diff line
/*
 * 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);
    }
}
+35 −0
Original line number Diff line number Diff line
@@ -24,7 +24,9 @@ import android.graphics.Bitmap;
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.Surface.OutOfResourcesException;
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 @@ public final class ThreadedRenderer {
    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 @@ public final class ThreadedRenderer {
        }
    }

    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 @@ public final class ThreadedRenderer {
    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);
}
+31 −0
Original line number Diff line number Diff line
@@ -109,6 +109,7 @@ import com.android.internal.view.menu.MenuBuilder;
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;
@@ -5379,6 +5380,36 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        getListenerInfo().mOnCreateContextMenuListener = l;
    }
    /**
     * 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
+35 −0
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import android.graphics.drawable.Drawable;
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 @@ public abstract class Window {
        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;
+156 −0
Original line number Diff line number Diff line
@@ -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 @@ namespace android {
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 @@ private:
    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);
@@ -467,6 +570,42 @@ static void android_view_ThreadedRenderer_setContentDrawBounds(JNIEnv* env,
    proxy->setContentDrawBounds(left, top, right, bottom);
}

// ----------------------------------------------------------------------------
// 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 @@ static const JNINativeMethod gMethods[] = {
    { "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));
}

Loading