diff options
24 files changed, 1171 insertions, 167 deletions
diff --git a/Android.mk b/Android.mk index b26543dee0c2..e645ac11a2d6 100644 --- a/Android.mk +++ b/Android.mk @@ -322,6 +322,7 @@ LOCAL_SRC_FILES += \ core/java/android/view/IAppTransitionAnimationSpecsFuture.aidl \ core/java/android/view/IDockedStackListener.aidl \ core/java/android/view/IGraphicsStats.aidl \ + core/java/android/view/IGraphicsStatsCallback.aidl \ core/java/android/view/IInputFilter.aidl \ core/java/android/view/IInputFilterHost.aidl \ core/java/android/view/IOnKeyguardExitResult.aidl \ @@ -1421,6 +1422,24 @@ endif include $(BUILD_JAVA_LIBRARY) +# ==== c++ proto device library ============================== +include $(CLEAR_VARS) +LOCAL_MODULE := libplatformprotos +# b/34740546, work around clang-tidy segmentation fault. +LOCAL_TIDY_CHECKS := -modernize* +LOCAL_PROTOC_OPTIMIZE_TYPE := lite +LOCAL_PROTOC_FLAGS := \ + --include_source_info \ + -Iexternal/protobuf/src +LOCAL_SRC_FILES := \ + $(call all-proto-files-under, core/proto) \ + $(call all-proto-files-under, libs/incident/proto) +LOCAL_C_INCLUDES := \ + $(call generated-sources-dir-for,STATIC_LIBRARIES,libplatformprotos,)/proto +LOCAL_EXPORT_C_INCLUDES := \ + $(call generated-sources-dir-for,STATIC_LIBRARIES,libplatformprotos,)/proto +include $(BUILD_STATIC_LIBRARY) + # ==== c++ proto host library ============================== include $(CLEAR_VARS) LOCAL_MODULE := libplatformprotos diff --git a/core/java/android/view/IGraphicsStats.aidl b/core/java/android/view/IGraphicsStats.aidl index c235eb261ee6..e6b750b73406 100644 --- a/core/java/android/view/IGraphicsStats.aidl +++ b/core/java/android/view/IGraphicsStats.aidl @@ -17,10 +17,11 @@ package android.view; import android.os.ParcelFileDescriptor; +import android.view.IGraphicsStatsCallback; /** * @hide */ interface IGraphicsStats { - ParcelFileDescriptor requestBufferForProcess(String packageName, IBinder token); + ParcelFileDescriptor requestBufferForProcess(String packageName, IGraphicsStatsCallback callback); } diff --git a/core/java/android/view/IGraphicsStatsCallback.aidl b/core/java/android/view/IGraphicsStatsCallback.aidl new file mode 100644 index 000000000000..f70e1419c317 --- /dev/null +++ b/core/java/android/view/IGraphicsStatsCallback.aidl @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2017, 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; + +/** + * @hide + */ +oneway interface IGraphicsStatsCallback { + void onRotateGraphicsStatsBuffer(); +} diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index 4ceb23628fb4..4bb7968e11b5 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -25,9 +25,9 @@ import android.graphics.Bitmap; import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.AnimatedVectorDrawable; -import android.os.Binder; import android.os.IBinder; import android.os.ParcelFileDescriptor; +import android.os.RemoteException; import android.os.ServiceManager; import android.os.Trace; import android.util.Log; @@ -248,10 +248,10 @@ public final class ThreadedRenderer { * * @return A threaded renderer backed by OpenGL. */ - public static ThreadedRenderer create(Context context, boolean translucent) { + public static ThreadedRenderer create(Context context, boolean translucent, String name) { ThreadedRenderer renderer = null; if (isAvailable()) { - renderer = new ThreadedRenderer(context, translucent); + renderer = new ThreadedRenderer(context, translucent, name); } return renderer; } @@ -275,10 +275,6 @@ public final class ThreadedRenderer { nOverrideProperty(name, value); } - public static void dumpProfileData(byte[] data, FileDescriptor fd) { - nDumpProfileData(data, fd); - } - // Keep in sync with DrawFrameTask.h SYNC_* flags // Nothing interesting to report private static final int SYNC_OK = 0; @@ -334,7 +330,7 @@ public final class ThreadedRenderer { private boolean mEnabled; private boolean mRequested = true; - ThreadedRenderer(Context context, boolean translucent) { + ThreadedRenderer(Context context, boolean translucent, String name) { final TypedArray a = context.obtainStyledAttributes(null, R.styleable.Lighting, 0, 0); mLightY = a.getDimension(R.styleable.Lighting_lightY, 0); mLightZ = a.getDimension(R.styleable.Lighting_lightZ, 0); @@ -348,6 +344,7 @@ public final class ThreadedRenderer { mRootNode = RenderNode.adopt(rootNodePtr); mRootNode.setClipToBounds(false); mNativeProxy = nCreateProxy(translucent, rootNodePtr); + nSetName(mNativeProxy, name); ProcessInitializer.sInstance.init(context, mNativeProxy); @@ -815,15 +812,6 @@ public final class ThreadedRenderer { } /** - * Optional, sets the name of the renderer. Useful for debugging purposes. - * - * @param name The name of this renderer, can be null - */ - void setName(String name) { - nSetName(mNativeProxy, name); - } - - /** * Blocks until all previously queued work has completed. */ void fence() { @@ -884,20 +872,29 @@ public final class ThreadedRenderer { private static class ProcessInitializer { static ProcessInitializer sInstance = new ProcessInitializer(); - private static IBinder sProcToken; private boolean mInitialized = false; + private Context mAppContext; + private IGraphicsStats mGraphicsStatsService; + private IGraphicsStatsCallback mGraphicsStatsCallback = new IGraphicsStatsCallback.Stub() { + @Override + public void onRotateGraphicsStatsBuffer() throws RemoteException { + rotateBuffer(); + } + }; + private ProcessInitializer() {} synchronized void init(Context context, long renderProxy) { if (mInitialized) return; mInitialized = true; + mAppContext = context.getApplicationContext(); initSched(context, renderProxy); - initGraphicsStats(context, renderProxy); + initGraphicsStats(); } - private static void initSched(Context context, long renderProxy) { + private void initSched(Context context, long renderProxy) { try { int tid = nGetRenderThreadTid(renderProxy); ActivityManager.getService().setRenderThread(tid); @@ -906,17 +903,28 @@ public final class ThreadedRenderer { } } - private static void initGraphicsStats(Context context, long renderProxy) { + private void initGraphicsStats() { try { IBinder binder = ServiceManager.getService("graphicsstats"); if (binder == null) return; - IGraphicsStats graphicsStatsService = IGraphicsStats.Stub - .asInterface(binder); - sProcToken = new Binder(); - final String pkg = context.getApplicationInfo().packageName; - ParcelFileDescriptor pfd = graphicsStatsService. - requestBufferForProcess(pkg, sProcToken); - nSetProcessStatsBuffer(renderProxy, pfd.getFd()); + mGraphicsStatsService = IGraphicsStats.Stub.asInterface(binder); + requestBuffer(); + } catch (Throwable t) { + Log.w(LOG_TAG, "Could not acquire gfx stats buffer", t); + } + } + + private void rotateBuffer() { + nRotateProcessStatsBuffer(); + requestBuffer(); + } + + private void requestBuffer() { + try { + final String pkg = mAppContext.getApplicationInfo().packageName; + ParcelFileDescriptor pfd = mGraphicsStatsService + .requestBufferForProcess(pkg, mGraphicsStatsCallback); + nSetProcessStatsBuffer(pfd.getFd()); pfd.close(); } catch (Throwable t) { Log.w(LOG_TAG, "Could not acquire gfx stats buffer", t); @@ -936,7 +944,8 @@ public final class ThreadedRenderer { static native void setupShadersDiskCache(String cacheFile); - private static native void nSetProcessStatsBuffer(long nativeProxy, int fd); + private static native void nRotateProcessStatsBuffer(); + private static native void nSetProcessStatsBuffer(int fd); private static native int nGetRenderThreadTid(long nativeProxy); private static native long nCreateRootRenderNode(); @@ -981,7 +990,6 @@ public final class ThreadedRenderer { private static native void nDumpProfileInfo(long nativeProxy, FileDescriptor fd, @DumpFlags int dumpFlags); - private static native void nDumpProfileData(byte[] data, FileDescriptor fd); private static native void nAddRenderNode(long nativeProxy, long rootRenderNode, boolean placeFront); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 6cdd483370b6..c81e9385897f 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -887,9 +887,9 @@ public final class ViewRootImpl implements ViewParent, final boolean hasSurfaceInsets = insets.left != 0 || insets.right != 0 || insets.top != 0 || insets.bottom != 0; final boolean translucent = attrs.format != PixelFormat.OPAQUE || hasSurfaceInsets; - mAttachInfo.mThreadedRenderer = ThreadedRenderer.create(mContext, translucent); + mAttachInfo.mThreadedRenderer = ThreadedRenderer.create(mContext, translucent, + attrs.getTitle().toString()); if (mAttachInfo.mThreadedRenderer != null) { - mAttachInfo.mThreadedRenderer.setName(attrs.getTitle().toString()); mAttachInfo.mHardwareAccelerated = mAttachInfo.mHardwareAccelerationRequested = true; } diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp index d37f96aeca30..37eae48a7a11 100644 --- a/core/jni/android_view_ThreadedRenderer.cpp +++ b/core/jni/android_view_ThreadedRenderer.cpp @@ -43,7 +43,6 @@ #include <FrameInfo.h> #include <FrameMetricsObserver.h> #include <IContextFactory.h> -#include <JankTracker.h> #include <PropertyValuesAnimatorSet.h> #include <RenderNode.h> #include <renderthread/CanvasContext.h> @@ -587,10 +586,13 @@ static jboolean android_view_ThreadedRenderer_supportsOpenGL(JNIEnv* env, jobjec return atoi(prop) > 0 ? JNI_TRUE : JNI_FALSE; } +static void android_view_ThreadedRenderer_rotateProcessStatsBuffer(JNIEnv* env, jobject clazz) { + RenderProxy::rotateProcessStatsBuffer(); +} + static void android_view_ThreadedRenderer_setProcessStatsBuffer(JNIEnv* env, jobject clazz, - jlong proxyPtr, jint fd) { - RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); - proxy->setProcessStatsBuffer(fd); + jint fd) { + RenderProxy::setProcessStatsBuffer(fd); } static jint android_view_ThreadedRenderer_getRenderThreadTid(JNIEnv* env, jobject clazz, @@ -817,15 +819,6 @@ static void android_view_ThreadedRenderer_dumpProfileInfo(JNIEnv* env, jobject c proxy->dumpProfileInfo(fd, dumpFlags); } -static void android_view_ThreadedRenderer_dumpProfileData(JNIEnv* env, jobject clazz, - jbyteArray jdata, jobject javaFileDescriptor) { - int fd = jniGetFDFromFileDescriptor(env, javaFileDescriptor); - ScopedByteArrayRO buffer(env, jdata); - if (buffer.get()) { - JankTracker::dumpBuffer(buffer.get(), buffer.size(), fd); - } -} - static void android_view_ThreadedRenderer_addRenderNode(JNIEnv* env, jobject clazz, jlong proxyPtr, jlong renderNodePtr, jboolean placeFront) { RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); @@ -910,7 +903,8 @@ const char* const kClassPathName = "android/view/ThreadedRenderer"; static const JNINativeMethod gMethods[] = { { "nSupportsOpenGL", "()Z", (void*) android_view_ThreadedRenderer_supportsOpenGL }, - { "nSetProcessStatsBuffer", "(JI)V", (void*) android_view_ThreadedRenderer_setProcessStatsBuffer }, + { "nRotateProcessStatsBuffer", "()V", (void*) android_view_ThreadedRenderer_rotateProcessStatsBuffer }, + { "nSetProcessStatsBuffer", "(I)V", (void*) android_view_ThreadedRenderer_setProcessStatsBuffer }, { "nGetRenderThreadTid", "(J)I", (void*) android_view_ThreadedRenderer_getRenderThreadTid }, { "nCreateRootRenderNode", "()J", (void*) android_view_ThreadedRenderer_createRootRenderNode }, { "nCreateProxy", "(ZJ)J", (void*) android_view_ThreadedRenderer_createProxy }, @@ -943,7 +937,6 @@ static const JNINativeMethod gMethods[] = { { "nNotifyFramePending", "(J)V", (void*) android_view_ThreadedRenderer_notifyFramePending }, { "nSerializeDisplayListTree", "(J)V", (void*) android_view_ThreadedRenderer_serializeDisplayListTree }, { "nDumpProfileInfo", "(JLjava/io/FileDescriptor;I)V", (void*) android_view_ThreadedRenderer_dumpProfileInfo }, - { "nDumpProfileData", "([BLjava/io/FileDescriptor;)V", (void*) android_view_ThreadedRenderer_dumpProfileData }, { "setupShadersDiskCache", "(Ljava/lang/String;)V", (void*) android_view_ThreadedRenderer_setupShadersDiskCache }, { "nAddRenderNode", "(JJZ)V", (void*) android_view_ThreadedRenderer_addRenderNode}, diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto index ba1d6646adae..a2f07d989ab3 100644 --- a/core/proto/android/os/incident.proto +++ b/core/proto/android/os/incident.proto @@ -21,6 +21,7 @@ option java_outer_classname = "IncidentProtoMetadata"; import "frameworks/base/libs/incident/proto/android/privacy.proto"; import "frameworks/base/core/proto/android/service/appwidget.proto"; +import "frameworks/base/core/proto/android/service/graphicsstats.proto"; import "frameworks/base/core/proto/android/service/fingerprint.proto"; import "frameworks/base/core/proto/android/service/netstats.proto"; import "frameworks/base/core/proto/android/service/notification.proto"; @@ -57,4 +58,5 @@ message IncidentProto { android.providers.settings.SettingsServiceDumpProto settings = 3002; android.service.appwidget.AppWidgetServiceDumpProto appwidget = 3003; android.service.notification.NotificationServiceDumpProto notification = 3004; + android.service.GraphicsStatsServiceDumpProto graphicsstats = 3005; } diff --git a/core/proto/android/service/graphicsstats.proto b/core/proto/android/service/graphicsstats.proto new file mode 100644 index 000000000000..6dbfe4850892 --- /dev/null +++ b/core/proto/android/service/graphicsstats.proto @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2017 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. + */ + +syntax = "proto3"; + +package android.service; + +option java_multiple_files = true; +option java_outer_classname = "GraphicsStatsServiceProto"; + +message GraphicsStatsServiceDumpProto { + repeated GraphicsStatsProto stats = 1; +} + +message GraphicsStatsProto { + + // The package name of the app + string package_name = 1; + + // The version code of the app + int32 version_code = 2; + + // The start & end timestamps in UTC as + // milliseconds since January 1, 1970 + // Compatible with java.util.Date#setTime() + int64 stats_start = 3; + int64 stats_end = 4; + + // The aggregated statistics for the package + GraphicsStatsJankSummaryProto summary = 5; + + // The frame time histogram for the package + repeated GraphicsStatsHistogramBucketProto histogram = 6; +} + +message GraphicsStatsJankSummaryProto { + // Distinct frame count. + int32 total_frames = 1; + + // Number of frames with slow render time. Frames are considered janky if + // they took more than a vsync interval (typically 16.667ms) to be rendered. + int32 janky_frames = 2; + + // Number of "missed vsync" events. + int32 missed_vsync_count = 3; + + // Number of "high input latency" events. + int32 high_input_latency_count = 4; + + // Number of "slow UI thread" events. + int32 slow_ui_thread_count = 5; + + // Number of "slow bitmap upload" events. + int32 slow_bitmap_upload_count = 6; + + // Number of "slow draw" events. + int32 slow_draw_count = 7; +} + +message GraphicsStatsHistogramBucketProto { + // Lower bound of render time in milliseconds. + int32 render_millis = 1; + // Number of frames in the bucket. + int32 frame_count = 2; +} diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index ff40c8a9fb92..9515b8291bb6 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -47,6 +47,7 @@ hwui_src_files := \ renderthread/RenderThread.cpp \ renderthread/TimeLord.cpp \ renderthread/Frame.cpp \ + service/GraphicsStatsService.cpp \ thread/TaskManager.cpp \ utils/Blur.cpp \ utils/GLUtils.cpp \ @@ -293,6 +294,7 @@ LOCAL_SRC_FILES += \ tests/unit/GlopBuilderTests.cpp \ tests/unit/GpuMemoryTrackerTests.cpp \ tests/unit/GradientCacheTests.cpp \ + tests/unit/GraphicsStatsServiceTests.cpp \ tests/unit/LayerUpdateQueueTests.cpp \ tests/unit/LeakCheckTests.cpp \ tests/unit/LinearAllocatorTests.cpp \ diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp index 2132c2b171dc..7be71eec4f76 100644 --- a/libs/hwui/JankTracker.cpp +++ b/libs/hwui/JankTracker.cpp @@ -110,7 +110,7 @@ static uint32_t frameCountIndexForFrameTime(nsecs_t frameTime) { } // Only called when dumping stats, less performance sensitive -static uint32_t frameTimeForFrameCountIndex(uint32_t index) { +int32_t JankTracker::frameTimeForFrameCountIndex(uint32_t index) { index = index + kBucketMinThreshold; if (index > kBucket2msIntervals) { index += (index - kBucket2msIntervals); @@ -123,6 +123,10 @@ static uint32_t frameTimeForFrameCountIndex(uint32_t index) { return index; } +int32_t JankTracker::frameTimeForSlowFrameCountIndex(uint32_t index) { + return (index * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs; +} + JankTracker::JankTracker(const DisplayInfo& displayInfo) { // By default this will use malloc memory. It may be moved later to ashmem // if there is shared space for it and a request comes in to do that. @@ -161,8 +165,25 @@ void JankTracker::freeData() { mData = nullptr; } +void JankTracker::rotateStorage() { + // If we are mapped we want to stop using the ashmem backend and switch to malloc + // We are expecting a switchStorageToAshmem call to follow this, but it's not guaranteed + // If we aren't sitting on top of ashmem then just do a reset() as it's functionally + // equivalent do a free, malloc, reset. + if (mIsMapped) { + freeData(); + mData = new ProfileData; + } + reset(); +} + void JankTracker::switchStorageToAshmem(int ashmemfd) { int regionSize = ashmem_get_size_region(ashmemfd); + if (regionSize < 0) { + int err = errno; + ALOGW("Failed to get ashmem region size from fd %d, err %d %s", ashmemfd, err, strerror(err)); + return; + } if (regionSize < static_cast<int>(sizeof(ProfileData))) { ALOGW("Ashmem region is too small! Received %d, required %u", regionSize, static_cast<unsigned int>(sizeof(ProfileData))); @@ -279,15 +300,19 @@ void JankTracker::addFrame(const FrameInfo& frame) { } } -void JankTracker::dumpBuffer(const void* buffer, size_t bufsize, int fd) { - if (bufsize < sizeof(ProfileData)) { - return; +void JankTracker::dumpData(int fd, const ProfileDataDescription* description, const ProfileData* data) { + if (description) { + switch (description->type) { + case JankTrackerType::Generic: + break; + case JankTrackerType::Package: + dprintf(fd, "\nPackage: %s", description->name.c_str()); + break; + case JankTrackerType::Window: + dprintf(fd, "\nWindow: %s", description->name.c_str()); + break; + } } - const ProfileData* data = reinterpret_cast<const ProfileData*>(buffer); - dumpData(data, fd); -} - -void JankTracker::dumpData(const ProfileData* data, int fd) { if (sFrameStart != FrameInfoIndex::IntendedVsync) { dprintf(fd, "\nNote: Data has been filtered!"); } @@ -308,7 +333,7 @@ void JankTracker::dumpData(const ProfileData* data, int fd) { data->frameCounts[i]); } for (size_t i = 0; i < data->slowFrameCounts.size(); i++) { - dprintf(fd, " %zums=%u", (i * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs, + dprintf(fd, " %ums=%u", frameTimeForSlowFrameCountIndex(i), data->slowFrameCounts[i]); } dprintf(fd, "\n"); diff --git a/libs/hwui/JankTracker.h b/libs/hwui/JankTracker.h index 8b482d5a804d..6ff5d890eaf7 100644 --- a/libs/hwui/JankTracker.h +++ b/libs/hwui/JankTracker.h @@ -54,29 +54,50 @@ struct ProfileData { nsecs_t statStartTime; }; +enum class JankTrackerType { + // The default, means there's no description set + Generic, + // The profile data represents a package + Package, + // The profile data is for a specific window + Window, +}; + +// Metadata about the ProfileData being collected +struct ProfileDataDescription { + JankTrackerType type; + std::string name; +}; + // TODO: Replace DrawProfiler with this class JankTracker { public: explicit JankTracker(const DisplayInfo& displayInfo); ~JankTracker(); + void setDescription(JankTrackerType type, const std::string&& name) { + mDescription.type = type; + mDescription.name = name; + } + void addFrame(const FrameInfo& frame); - void dump(int fd) { dumpData(mData, fd); } + void dump(int fd) { dumpData(fd, &mDescription, mData); } void reset(); + void rotateStorage(); void switchStorageToAshmem(int ashmemfd); uint32_t findPercentile(int p) { return findPercentile(mData, p); } - - ANDROID_API static void dumpBuffer(const void* buffer, size_t bufsize, int fd); + static int32_t frameTimeForFrameCountIndex(uint32_t index); + static int32_t frameTimeForSlowFrameCountIndex(uint32_t index); private: void freeData(); void setFrameInterval(nsecs_t frameIntervalNanos); static uint32_t findPercentile(const ProfileData* data, int p); - static void dumpData(const ProfileData* data, int fd); + static void dumpData(int fd, const ProfileDataDescription* description, const ProfileData* data); std::array<int64_t, NUM_BUCKETS> mThresholds; int64_t mFrameInterval; @@ -90,6 +111,7 @@ private: nsecs_t mDequeueTimeForgiveness = 0; ProfileData* mData; bool mIsMapped = false; + ProfileDataDescription mDescription; }; } /* namespace uirenderer */ diff --git a/libs/hwui/hwui_static_deps.mk b/libs/hwui/hwui_static_deps.mk index a75fd6a8d587..8826cfcc3100 100644 --- a/libs/hwui/hwui_static_deps.mk +++ b/libs/hwui/hwui_static_deps.mk @@ -22,11 +22,12 @@ LOCAL_SHARED_LIBRARIES += \ libskia \ libui \ libgui \ - libprotobuf-cpp-lite \ + libprotobuf-cpp-full \ libharfbuzz_ng \ libft2 \ libminikin \ - libandroidfw + libandroidfw \ + libRScpp -# enable RENDERSCRIPT -LOCAL_SHARED_LIBRARIES += libRScpp +LOCAL_STATIC_LIBRARIES += \ + libplatformprotos diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index a53e5e0d919a..02a9ffa3039c 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -590,6 +590,7 @@ DeferredLayerUpdater* CanvasContext::createTextureLayer() { } void CanvasContext::dumpFrames(int fd) { + mJankTracker.dump(fd); FILE* file = fdopen(fd, "a"); fprintf(file, "\n\n---PROFILEDATA---\n"); for (size_t i = 0; i < static_cast<size_t>(FrameInfoIndex::NumIndexes); i++) { @@ -615,6 +616,10 @@ void CanvasContext::resetFrameStats() { mRenderThread.jankTracker().reset(); } +void CanvasContext::setName(const std::string&& name) { + mJankTracker.setDescription(JankTrackerType::Window, std::move(name)); +} + void CanvasContext::serializeDisplayListTree() { #if ENABLE_RENDERNODE_SERIALIZATION using namespace google::protobuf::io; diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index aa01caa8fc25..738c09141a7a 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -155,8 +155,7 @@ public: void dumpFrames(int fd); void resetFrameStats(); - void setName(const std::string&& name) { mName = name; } - const std::string& name() { return mName; } + void setName(const std::string&& name); void serializeDisplayListTree(); diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index 11614fa144e3..f4a4773b2937 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -487,9 +487,22 @@ CREATE_BRIDGE2(setProcessStatsBuffer, RenderThread* thread, int fd) { void RenderProxy::setProcessStatsBuffer(int fd) { SETUP_TASK(setProcessStatsBuffer); - args->thread = &mRenderThread; + auto& rt = RenderThread::getInstance(); + args->thread = &rt; args->fd = dup(fd); - post(task); + rt.queue(task); +} + +CREATE_BRIDGE1(rotateProcessStatsBuffer, RenderThread* thread) { + args->thread->jankTracker().rotateStorage(); + return nullptr; +} + +void RenderProxy::rotateProcessStatsBuffer() { + SETUP_TASK(rotateProcessStatsBuffer); + auto& rt = RenderThread::getInstance(); + args->thread = &rt; + rt.queue(task); } int RenderProxy::getRenderThreadTid() { diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index 1629090d84a4..a60ed55c70d2 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -113,7 +113,8 @@ public: uint32_t frameTimePercentile(int p); ANDROID_API static void dumpGraphicsMemory(int fd); - ANDROID_API void setProcessStatsBuffer(int fd); + ANDROID_API static void rotateProcessStatsBuffer(); + ANDROID_API static void setProcessStatsBuffer(int fd); ANDROID_API int getRenderThreadTid(); ANDROID_API void serializeDisplayListTree(); diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h index d121bcf5b084..9bc5985e5b16 100644 --- a/libs/hwui/renderthread/RenderThread.h +++ b/libs/hwui/renderthread/RenderThread.h @@ -74,6 +74,7 @@ protected: }; class ANDROID_API RenderThread : public Thread { + PREVENT_COPY_AND_ASSIGN(RenderThread); public: // RenderThread takes complete ownership of tasks that are queued // and will delete them after they are run diff --git a/libs/hwui/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp new file mode 100644 index 000000000000..ab6420e990f9 --- /dev/null +++ b/libs/hwui/service/GraphicsStatsService.cpp @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2017 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 "GraphicsStatsService.h" + +#include "JankTracker.h" + +#include <frameworks/base/core/proto/android/service/graphicsstats.pb.h> +#include <google/protobuf/io/zero_copy_stream_impl.h> +#include <log/log.h> + +#include <inttypes.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +namespace android { +namespace uirenderer { + +using namespace google::protobuf; + +constexpr int32_t sCurrentFileVersion = 1; +constexpr int32_t sHeaderSize = 4; +static_assert(sizeof(sCurrentFileVersion) == sHeaderSize, "Header size is wrong"); + +constexpr int sHistogramSize = + std::tuple_size<decltype(ProfileData::frameCounts)>::value + + std::tuple_size<decltype(ProfileData::slowFrameCounts)>::value; + +static void mergeProfileDataIntoProto(service::GraphicsStatsProto* proto, + const std::string& package, int versionCode, int64_t startTime, int64_t endTime, + const ProfileData* data); +static void dumpAsTextToFd(service::GraphicsStatsProto* proto, int outFd); + +bool GraphicsStatsService::parseFromFile(const std::string& path, service::GraphicsStatsProto* output) { + + int fd = open(path.c_str(), O_RDONLY); + if (fd == -1) { + int err = errno; + // The file not existing is normal for addToDump(), so only log if + // we get an unexpected error + if (err != ENOENT) { + ALOGW("Failed to open '%s', errno=%d (%s)", path.c_str(), err, strerror(err)); + } + return false; + } + uint32_t file_version; + ssize_t bytesRead = read(fd, &file_version, sHeaderSize); + if (bytesRead != sHeaderSize || file_version != sCurrentFileVersion) { + ALOGW("Failed to read '%s', bytesRead=%zd file_version=%d", path.c_str(), bytesRead, + file_version); + close(fd); + return false; + } + + io::FileInputStream input(fd); + bool success = output->ParseFromZeroCopyStream(&input); + if (input.GetErrno() != 0) { + ALOGW("Error reading from fd=%d, path='%s' err=%d (%s)", + fd, path.c_str(), input.GetErrno(), strerror(input.GetErrno())); + success = false; + } else if (!success) { + ALOGW("Parse failed on '%s' error='%s'", + path.c_str(), output->InitializationErrorString().c_str()); + } + close(fd); + return success; +} + +void mergeProfileDataIntoProto(service::GraphicsStatsProto* proto, const std::string& package, + int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data) { + if (proto->stats_start() == 0 || proto->stats_start() > startTime) { + proto->set_stats_start(startTime); + } + if (proto->stats_end() == 0 || proto->stats_end() < endTime) { + proto->set_stats_end(endTime); + } + proto->set_package_name(package); + proto->set_version_code(versionCode); + auto summary = proto->mutable_summary(); + summary->set_total_frames(summary->total_frames() + data->totalFrameCount); + summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount); + summary->set_missed_vsync_count( + summary->missed_vsync_count() + data->jankTypeCounts[kMissedVsync]); + summary->set_high_input_latency_count( + summary->high_input_latency_count() + data->jankTypeCounts[kHighInputLatency]); + summary->set_slow_ui_thread_count( + summary->slow_ui_thread_count() + data->jankTypeCounts[kSlowUI]); + summary->set_slow_bitmap_upload_count( + summary->slow_bitmap_upload_count() + data->jankTypeCounts[kSlowSync]); + summary->set_slow_draw_count( + summary->slow_draw_count() + data->jankTypeCounts[kSlowRT]); + + bool creatingHistogram = false; + if (proto->histogram_size() == 0) { + proto->mutable_histogram()->Reserve(sHistogramSize); + creatingHistogram = true; + } else if (proto->histogram_size() != sHistogramSize) { + LOG_ALWAYS_FATAL("Histogram size mismatch, proto is %d expected %d", + proto->histogram_size(), sHistogramSize); + } + for (size_t i = 0; i < data->frameCounts.size(); i++) { + service::GraphicsStatsHistogramBucketProto* bucket; + int32_t renderTime = JankTracker::frameTimeForFrameCountIndex(i); + if (creatingHistogram) { + bucket = proto->add_histogram(); + bucket->set_render_millis(renderTime); + } else { + bucket = proto->mutable_histogram(i); + LOG_ALWAYS_FATAL_IF(bucket->render_millis() != renderTime, + "Frame time mistmatch %d vs. %d", bucket->render_millis(), renderTime); + } + bucket->set_frame_count(bucket->frame_count() + data->frameCounts[i]); + } + for (size_t i = 0; i < data->slowFrameCounts.size(); i++) { + service::GraphicsStatsHistogramBucketProto* bucket; + int32_t renderTime = JankTracker::frameTimeForSlowFrameCountIndex(i); + if (creatingHistogram) { + bucket = proto->add_histogram(); + bucket->set_render_millis(renderTime); + } else { + constexpr int offset = std::tuple_size<decltype(ProfileData::frameCounts)>::value; + bucket = proto->mutable_histogram(offset + i); + LOG_ALWAYS_FATAL_IF(bucket->render_millis() != renderTime, + "Frame time mistmatch %d vs. %d", bucket->render_millis(), renderTime); + } + bucket->set_frame_count(bucket->frame_count() + data->slowFrameCounts[i]); + } +} + +static int32_t findPercentile(service::GraphicsStatsProto* proto, int percentile) { + int32_t pos = percentile * proto->summary().total_frames() / 100; + int32_t remaining = proto->summary().total_frames() - pos; + for (auto it = proto->histogram().rbegin(); it != proto->histogram().rend(); ++it) { + remaining -= it->frame_count(); + if (remaining <= 0) { + return it->render_millis(); + } + } + return 0; +} + +void dumpAsTextToFd(service::GraphicsStatsProto* proto, int fd) { + // This isn't a full validation, just enough that we can deref at will + LOG_ALWAYS_FATAL_IF(proto->package_name().empty() + || !proto->has_summary(), "package_name() '%s' summary %d", + proto->package_name().c_str(), proto->has_summary()); + dprintf(fd, "\nPackage: %s", proto->package_name().c_str()); + dprintf(fd, "\nVersion: %d", proto->version_code()); + dprintf(fd, "\nStats since: %lldns", proto->stats_start()); + dprintf(fd, "\nStats end: %lldns", proto->stats_end()); + auto summary = proto->summary(); + dprintf(fd, "\nTotal frames rendered: %d", summary.total_frames()); + dprintf(fd, "\nJanky frames: %d (%.2f%%)", summary.janky_frames(), + (float) summary.janky_frames() / (float) summary.total_frames() * 100.0f); + dprintf(fd, "\n50th percentile: %dms", findPercentile(proto, 50)); + dprintf(fd, "\n90th percentile: %dms", findPercentile(proto, 90)); + dprintf(fd, "\n95th percentile: %dms", findPercentile(proto, 95)); + dprintf(fd, "\n99th percentile: %dms", findPercentile(proto, 99)); + dprintf(fd, "\nNumber Missed Vsync: %d", summary.missed_vsync_count()); + dprintf(fd, "\nNumber High input latency: %d", summary.high_input_latency_count()); + dprintf(fd, "\nNumber Slow UI thread: %d", summary.slow_ui_thread_count()); + dprintf(fd, "\nNumber Slow bitmap uploads: %d", summary.slow_bitmap_upload_count()); + dprintf(fd, "\nNumber Slow issue draw commands: %d", summary.slow_draw_count()); + dprintf(fd, "\nHISTOGRAM:"); + for (const auto& it : proto->histogram()) { + dprintf(fd, " %dms=%d", it.render_millis(), it.frame_count()); + } + dprintf(fd, "\n"); +} + +void GraphicsStatsService::saveBuffer(const std::string& path, const std::string& package, + int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data) { + service::GraphicsStatsProto statsProto; + if (!parseFromFile(path, &statsProto)) { + statsProto.Clear(); + } + mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data); + // Although we might not have read any data from the file, merging the existing data + // should always fully-initialize the proto + LOG_ALWAYS_FATAL_IF(!statsProto.IsInitialized(), "%s", + statsProto.InitializationErrorString().c_str()); + LOG_ALWAYS_FATAL_IF(statsProto.package_name().empty() + || !statsProto.has_summary(), "package_name() '%s' summary %d", + statsProto.package_name().c_str(), statsProto.has_summary()); + int outFd = open(path.c_str(), O_CREAT | O_RDWR | O_TRUNC, 0660); + if (outFd <= 0) { + int err = errno; + ALOGW("Failed to open '%s', error=%d (%s)", path.c_str(), err, strerror(err)); + return; + } + int wrote = write(outFd, &sCurrentFileVersion, sHeaderSize); + if (wrote != sHeaderSize) { + int err = errno; + ALOGW("Failed to write header to '%s', returned=%d errno=%d (%s)", + path.c_str(), wrote, err, strerror(err)); + close(outFd); + return; + } + { + io::FileOutputStream output(outFd); + bool success = statsProto.SerializeToZeroCopyStream(&output) && output.Flush(); + if (output.GetErrno() != 0) { + ALOGW("Error writing to fd=%d, path='%s' err=%d (%s)", + outFd, path.c_str(), output.GetErrno(), strerror(output.GetErrno())); + success = false; + } else if (!success) { + ALOGW("Serialize failed on '%s' unknown error", path.c_str()); + } + } + close(outFd); +} + +class GraphicsStatsService::Dump { +public: + Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {} + int fd() { return mFd; } + DumpType type() { return mType; } + service::GraphicsStatsServiceDumpProto& proto() { return mProto; } +private: + int mFd; + DumpType mType; + service::GraphicsStatsServiceDumpProto mProto; +}; + +GraphicsStatsService::Dump* GraphicsStatsService::createDump(int outFd, DumpType type) { + return new Dump(outFd, type); +} + +void GraphicsStatsService::addToDump(Dump* dump, const std::string& path, const std::string& package, + int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data) { + service::GraphicsStatsProto statsProto; + if (!path.empty() && !parseFromFile(path, &statsProto)) { + statsProto.Clear(); + } + if (data) { + mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data); + } + if (!statsProto.IsInitialized()) { + ALOGW("Failed to load profile data from path '%s' and data %p", + path.empty() ? "<empty>" : path.c_str(), data); + return; + } + + if (dump->type() == DumpType::Protobuf) { + dump->proto().add_stats()->CopyFrom(statsProto); + } else { + dumpAsTextToFd(&statsProto, dump->fd()); + } +} + +void GraphicsStatsService::addToDump(Dump* dump, const std::string& path) { + service::GraphicsStatsProto statsProto; + if (!parseFromFile(path, &statsProto)) { + return; + } + if (dump->type() == DumpType::Protobuf) { + dump->proto().add_stats()->CopyFrom(statsProto); + } else { + dumpAsTextToFd(&statsProto, dump->fd()); + } +} + +void GraphicsStatsService::finishDump(Dump* dump) { + if (dump->type() == DumpType::Protobuf) { + io::FileOutputStream stream(dump->fd()); + dump->proto().SerializeToZeroCopyStream(&stream); + } + delete dump; +} + +} /* namespace uirenderer */ +} /* namespace android */
\ No newline at end of file diff --git a/libs/hwui/service/GraphicsStatsService.h b/libs/hwui/service/GraphicsStatsService.h new file mode 100644 index 000000000000..d0fd60ee1aba --- /dev/null +++ b/libs/hwui/service/GraphicsStatsService.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2017 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. + */ + +#pragma once + +#include <string> + +#include "JankTracker.h" +#include "utils/Macros.h" + +namespace android { +namespace service { +class GraphicsStatsProto; +} + +namespace uirenderer { + +/* + * The exported entry points used by GraphicsStatsService.java in f/b/services/core + * + * NOTE: Avoid exporting a requirement on the protobuf itself. Keep the usage + * of the generated protobuf classes internal to libhwui.so to minimize library + * bloat. + */ +class GraphicsStatsService { +public: + class Dump; + enum class DumpType { + Text, + Protobuf, + }; + + ANDROID_API static void saveBuffer(const std::string& path, const std::string& package, + int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data); + + ANDROID_API static Dump* createDump(int outFd, DumpType type); + ANDROID_API static void addToDump(Dump* dump, const std::string& path, const std::string& package, + int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data); + ANDROID_API static void addToDump(Dump* dump, const std::string& path); + ANDROID_API static void finishDump(Dump* dump); + + // Visible for testing + static bool parseFromFile(const std::string& path, service::GraphicsStatsProto* output); +}; + +} /* namespace uirenderer */ +} /* namespace android */
\ No newline at end of file diff --git a/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp b/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp new file mode 100644 index 000000000000..cfe11347ff35 --- /dev/null +++ b/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp @@ -0,0 +1,159 @@ +/* + * 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. + */ + +#include <gtest/gtest.h> + +#include "service/GraphicsStatsService.h" + +#include <frameworks/base/core/proto/android/service/graphicsstats.pb.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +using namespace android; +using namespace android::uirenderer; + +std::string findRootPath() { + char path[1024]; + ssize_t r = readlink("/proc/self/exe", path, 1024); + // < 1023 because we need room for the null terminator + if (r <= 0 || r > 1023) { + int err = errno; + fprintf(stderr, "Failed to read from /proc/self/exe; r=%zd, err=%d (%s)\n", + r, err, strerror(err)); + exit(EXIT_FAILURE); + } + while (--r > 0) { + if (path[r] == '/') { + path[r] = '\0'; + return std::string(path); + } + } + return std::string(); +} + +// No code left untested +TEST(GraphicsStats, findRootPath) { + std::string expected = "/data/nativetest/hwui_unit_tests"; + EXPECT_EQ(expected, findRootPath()); +} + +TEST(GraphicsStats, saveLoad) { + std::string path = findRootPath() + "/test_saveLoad"; + std::string packageName = "com.test.saveLoad"; + ProfileData mockData; + mockData.jankFrameCount = 20; + mockData.totalFrameCount = 100; + mockData.statStartTime = 10000; + // Fill with patterned data we can recognize but which won't map to a + // memset or basic for iteration count + for (size_t i = 0; i < mockData.frameCounts.size(); i++) { + mockData.frameCounts[i] = ((i % 10) + 1) * 2; + } + for (size_t i = 0; i < mockData.slowFrameCounts.size(); i++) { + mockData.slowFrameCounts[i] = (i % 5) + 1; + } + GraphicsStatsService::saveBuffer(path, packageName, 5, 3000, 7000, &mockData); + service::GraphicsStatsProto loadedProto; + EXPECT_TRUE(GraphicsStatsService::parseFromFile(path, &loadedProto)); + // Clean up the file + unlink(path.c_str()); + + EXPECT_EQ(packageName, loadedProto.package_name()); + EXPECT_EQ(5, loadedProto.version_code()); + EXPECT_EQ(3000, loadedProto.stats_start()); + EXPECT_EQ(7000, loadedProto.stats_end()); + // ASSERT here so we don't continue with a nullptr deref crash if this is false + ASSERT_TRUE(loadedProto.has_summary()); + EXPECT_EQ(20, loadedProto.summary().janky_frames()); + EXPECT_EQ(100, loadedProto.summary().total_frames()); + EXPECT_EQ(mockData.frameCounts.size() + mockData.slowFrameCounts.size(), + (size_t) loadedProto.histogram_size()); + for (size_t i = 0; i < (size_t) loadedProto.histogram_size(); i++) { + int expectedCount, expectedBucket; + if (i < mockData.frameCounts.size()) { + expectedCount = ((i % 10) + 1) * 2; + expectedBucket = JankTracker::frameTimeForFrameCountIndex(i); + } else { + int temp = i - mockData.frameCounts.size(); + expectedCount = (temp % 5) + 1; + expectedBucket = JankTracker::frameTimeForSlowFrameCountIndex(temp); + } + EXPECT_EQ(expectedCount, loadedProto.histogram().Get(i).frame_count()); + EXPECT_EQ(expectedBucket, loadedProto.histogram().Get(i).render_millis()); + } +} + +TEST(GraphicsStats, merge) { + std::string path = findRootPath() + "/test_merge"; + std::string packageName = "com.test.merge"; + ProfileData mockData; + mockData.jankFrameCount = 20; + mockData.totalFrameCount = 100; + mockData.statStartTime = 10000; + // Fill with patterned data we can recognize but which won't map to a + // memset or basic for iteration count + for (size_t i = 0; i < mockData.frameCounts.size(); i++) { + mockData.frameCounts[i] = ((i % 10) + 1) * 2; + } + for (size_t i = 0; i < mockData.slowFrameCounts.size(); i++) { + mockData.slowFrameCounts[i] = (i % 5) + 1; + } + GraphicsStatsService::saveBuffer(path, packageName, 5, 3000, 7000, &mockData); + mockData.jankFrameCount = 50; + mockData.totalFrameCount = 500; + for (size_t i = 0; i < mockData.frameCounts.size(); i++) { + mockData.frameCounts[i] = (i % 5) + 1; + } + for (size_t i = 0; i < mockData.slowFrameCounts.size(); i++) { + mockData.slowFrameCounts[i] = ((i % 10) + 1) * 2; + } + GraphicsStatsService::saveBuffer(path, packageName, 5, 7050, 10000, &mockData); + + service::GraphicsStatsProto loadedProto; + EXPECT_TRUE(GraphicsStatsService::parseFromFile(path, &loadedProto)); + // Clean up the file + unlink(path.c_str()); + + EXPECT_EQ(packageName, loadedProto.package_name()); + EXPECT_EQ(5, loadedProto.version_code()); + EXPECT_EQ(3000, loadedProto.stats_start()); + EXPECT_EQ(10000, loadedProto.stats_end()); + // ASSERT here so we don't continue with a nullptr deref crash if this is false + ASSERT_TRUE(loadedProto.has_summary()); + EXPECT_EQ(20 + 50, loadedProto.summary().janky_frames()); + EXPECT_EQ(100 + 500, loadedProto.summary().total_frames()); + EXPECT_EQ(mockData.frameCounts.size() + mockData.slowFrameCounts.size(), + (size_t) loadedProto.histogram_size()); + for (size_t i = 0; i < (size_t) loadedProto.histogram_size(); i++) { + int expectedCount, expectedBucket; + if (i < mockData.frameCounts.size()) { + expectedCount = ((i % 10) + 1) * 2; + expectedCount += (i % 5) + 1; + expectedBucket = JankTracker::frameTimeForFrameCountIndex(i); + } else { + int temp = i - mockData.frameCounts.size(); + expectedCount = (temp % 5) + 1; + expectedCount += ((temp % 10) + 1) * 2; + expectedBucket = JankTracker::frameTimeForSlowFrameCountIndex(temp); + } + EXPECT_EQ(expectedCount, loadedProto.histogram().Get(i).frame_count()); + EXPECT_EQ(expectedBucket, loadedProto.histogram().Get(i).render_millis()); + } +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/GraphicsStatsService.java b/services/core/java/com/android/server/GraphicsStatsService.java index ecbe1ca98911..bdd80e382867 100644 --- a/services/core/java/com/android/server/GraphicsStatsService.java +++ b/services/core/java/com/android/server/GraphicsStatsService.java @@ -16,67 +16,145 @@ package com.android.server; +import android.app.AlarmManager; import android.app.AppOpsManager; import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; import android.os.Binder; +import android.os.Environment; +import android.os.Handler; +import android.os.HandlerThread; import android.os.IBinder; import android.os.MemoryFile; +import android.os.Message; import android.os.ParcelFileDescriptor; +import android.os.Process; import android.os.RemoteException; +import android.os.Trace; import android.util.Log; import android.view.IGraphicsStats; -import android.view.ThreadedRenderer; +import android.view.IGraphicsStatsCallback; +import java.io.File; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.HashSet; +import java.util.TimeZone; /** * This service's job is to collect aggregate rendering profile data. It * does this by allowing rendering processes to request an ashmem buffer - * to place their stats into. This buffer will be pre-initialized with historical - * data for that process if it exists (if the userId & packageName match a buffer - * in the historical log) + * to place their stats into. * - * This service does not itself attempt to understand the data in the buffer, - * its primary job is merely to manage distributing these buffers. However, - * it is assumed that this buffer is for ThreadedRenderer and delegates - * directly to ThreadedRenderer for dumping buffers. + * Buffers are rotated on a daily (in UTC) basis and only the 3 most-recent days + * are kept. * - * MEMORY USAGE: + * The primary consumer of this is incident reports and automated metric checking. It is not + * intended for end-developer consumption, for that we have gfxinfo. * - * This class consumes UP TO: - * 1) [active rendering processes] * (ASHMEM_SIZE * 2) - * 2) ASHMEM_SIZE (for scratch space used during dumping) - * 3) ASHMEM_SIZE * HISTORY_SIZE - * - * This is currently under 20KiB total memory in the worst case of - * 20 processes in history + 10 unique active processes. + * Buffer rotation process: + * 1) Alarm fires + * 2) onRotateGraphicsStatsBuffer() is sent to all active processes + * 3) Upon receiving the callback, the process will stop using the previous ashmem buffer and + * request a new one. + * 4) When that request is received we now know that the ashmem region is no longer in use so + * it gets queued up for saving to disk and a new ashmem region is created and returned + * for the process to use. * * @hide */ public class GraphicsStatsService extends IGraphicsStats.Stub { public static final String GRAPHICS_STATS_SERVICE = "graphicsstats"; private static final String TAG = "GraphicsStatsService"; - private static final int ASHMEM_SIZE = 464; - private static final int HISTORY_SIZE = 20; + + private static final int SAVE_BUFFER = 1; + private static final int DELETE_OLD = 2; + + // This isn't static because we need this to happen after registerNativeMethods, however + // the class is loaded (and thus static ctor happens) before that occurs. + private final int ASHMEM_SIZE = nGetAshmemSize(); + private final byte[] ZERO_DATA = new byte[ASHMEM_SIZE]; private final Context mContext; private final AppOpsManager mAppOps; + private final AlarmManager mAlarmManager; private final Object mLock = new Object(); private ArrayList<ActiveBuffer> mActive = new ArrayList<>(); - private HistoricalData[] mHistoricalLog = new HistoricalData[HISTORY_SIZE]; - private int mNextHistoricalSlot = 0; - private byte[] mTempBuffer = new byte[ASHMEM_SIZE]; + private File mGraphicsStatsDir; + private final Object mFileAccessLock = new Object(); + private Handler mWriteOutHandler; + private boolean mRotateIsScheduled = false; public GraphicsStatsService(Context context) { mContext = context; mAppOps = context.getSystemService(AppOpsManager.class); + mAlarmManager = context.getSystemService(AlarmManager.class); + File systemDataDir = new File(Environment.getDataDirectory(), "system"); + mGraphicsStatsDir = new File(systemDataDir, "graphicsstats"); + mGraphicsStatsDir.mkdirs(); + if (!mGraphicsStatsDir.exists()) { + throw new IllegalStateException("Graphics stats directory does not exist: " + + mGraphicsStatsDir.getAbsolutePath()); + } + HandlerThread bgthread = new HandlerThread("GraphicsStats-disk", Process.THREAD_PRIORITY_BACKGROUND); + bgthread.start(); + + mWriteOutHandler = new Handler(bgthread.getLooper(), new Handler.Callback() { + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case SAVE_BUFFER: + saveBuffer((HistoricalBuffer) msg.obj); + break; + case DELETE_OLD: + deleteOldBuffers(); + break; + } + return true; + } + }); + } + + /** + * Current rotation policy is to rotate at midnight UTC. We don't specify RTC_WAKEUP because + * rotation can be delayed if there's otherwise no activity. However exact is used because + * we don't want the system to delay it by TOO much. + */ + private void scheduleRotateLocked() { + if (mRotateIsScheduled) { + return; + } + mRotateIsScheduled = true; + Calendar calendar = normalizeDate(System.currentTimeMillis()); + calendar.add(Calendar.DATE, 1); + mAlarmManager.setExact(AlarmManager.RTC, calendar.getTimeInMillis(), TAG, this::onAlarm, + mWriteOutHandler); + } + + private void onAlarm() { + synchronized (mLock) { + mRotateIsScheduled = false; + scheduleRotateLocked(); + for (ActiveBuffer active : mActive) { + try { + active.mCallback.onRotateGraphicsStatsBuffer(); + } catch (RemoteException e) { + Log.w(TAG, String.format("Failed to notify '%s' (pid=%d) to rotate buffers", + active.mInfo.packageName, active.mPid), e); + } + } + } + // Give a few seconds for everyone to rotate before doing the cleanup + mWriteOutHandler.sendEmptyMessageDelayed(DELETE_OLD, 10000); } @Override - public ParcelFileDescriptor requestBufferForProcess(String packageName, IBinder token) + public ParcelFileDescriptor requestBufferForProcess(String packageName, IGraphicsStatsCallback token) throws RemoteException { int uid = Binder.getCallingUid(); int pid = Binder.getCallingPid(); @@ -84,9 +162,12 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { long callingIdentity = Binder.clearCallingIdentity(); try { mAppOps.checkPackage(uid, packageName); + PackageInfo info = mContext.getPackageManager().getPackageInfo(packageName, 0); synchronized (mLock) { - pfd = requestBufferForProcessLocked(token, uid, pid, packageName); + pfd = requestBufferForProcessLocked(token, uid, pid, packageName, info.versionCode); } + } catch (PackageManager.NameNotFoundException ex) { + throw new RemoteException("Unable to find package: '" + packageName + "'"); } finally { Binder.restoreCallingIdentity(callingIdentity); } @@ -95,51 +176,130 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { private ParcelFileDescriptor getPfd(MemoryFile file) { try { + if (!file.getFileDescriptor().valid()) { + throw new IllegalStateException("Invalid file descriptor"); + } return new ParcelFileDescriptor(file.getFileDescriptor()); } catch (IOException ex) { throw new IllegalStateException("Failed to get PFD from memory file", ex); } } - private ParcelFileDescriptor requestBufferForProcessLocked(IBinder token, - int uid, int pid, String packageName) throws RemoteException { - ActiveBuffer buffer = fetchActiveBuffersLocked(token, uid, pid, packageName); + private ParcelFileDescriptor requestBufferForProcessLocked(IGraphicsStatsCallback token, + int uid, int pid, String packageName, int versionCode) throws RemoteException { + ActiveBuffer buffer = fetchActiveBuffersLocked(token, uid, pid, packageName, versionCode); + scheduleRotateLocked(); return getPfd(buffer.mProcessBuffer); } - private void processDied(ActiveBuffer buffer) { - synchronized (mLock) { - mActive.remove(buffer); - Log.d("GraphicsStats", "Buffer count: " + mActive.size()); + private Calendar normalizeDate(long timestamp) { + Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + calendar.setTimeInMillis(timestamp); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + return calendar; + } + + private File pathForApp(BufferInfo info) { + String subPath = String.format("%d/%s/%d/total", + normalizeDate(info.startTime).getTimeInMillis(), info.packageName, info.versionCode); + return new File(mGraphicsStatsDir, subPath); + } + + private void saveBuffer(HistoricalBuffer buffer) { + if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { + Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "saving graphicsstats for " + buffer.mInfo.packageName); } - HistoricalData data = buffer.mPreviousData; - buffer.mPreviousData = null; - if (data == null) { - data = mHistoricalLog[mNextHistoricalSlot]; - if (data == null) { - data = new HistoricalData(); + synchronized (mFileAccessLock) { + File path = pathForApp(buffer.mInfo); + File parent = path.getParentFile(); + parent.mkdirs(); + if (!parent.exists()) { + Log.w(TAG, "Unable to create path: '" + parent.getAbsolutePath() + "'"); + return; } + nSaveBuffer(path.getAbsolutePath(), buffer.mInfo.packageName, buffer.mInfo.versionCode, + buffer.mInfo.startTime, buffer.mInfo.endTime, buffer.mData); + } + Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); + } + + private void deleteRecursiveLocked(File file) { + if (file.isDirectory()) { + for (File child : file.listFiles()) { + deleteRecursiveLocked(child); + } + } + if (!file.delete()) { + Log.w(TAG, "Failed to delete '" + file.getAbsolutePath() + "'!"); + } + } + + private void deleteOldBuffers() { + Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "deleting old graphicsstats buffers"); + synchronized (mFileAccessLock) { + File[] files = mGraphicsStatsDir.listFiles(); + if (files == null || files.length <= 3) { + return; + } + long[] sortedDates = new long[files.length]; + for (int i = 0; i < files.length; i++) { + try { + sortedDates[i] = Long.parseLong(files[i].getName()); + } catch (NumberFormatException ex) { + // Skip unrecognized folders + } + } + if (sortedDates.length <= 3) { + return; + } + Arrays.sort(sortedDates); + for (int i = 0; i < sortedDates.length - 3; i++) { + deleteRecursiveLocked(new File(mGraphicsStatsDir, Long.toString(sortedDates[i]))); + } + } + Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); + } + + private void addToSaveQueue(ActiveBuffer buffer) { + try { + HistoricalBuffer data = new HistoricalBuffer(buffer); + Message.obtain(mWriteOutHandler, SAVE_BUFFER, data).sendToTarget(); + } catch (IOException e) { + Log.w(TAG, "Failed to copy graphicsstats from " + buffer.mInfo.packageName, e); } - data.update(buffer.mPackageName, buffer.mUid, buffer.mProcessBuffer); buffer.closeAllBuffers(); + } - mHistoricalLog[mNextHistoricalSlot] = data; - mNextHistoricalSlot = (mNextHistoricalSlot + 1) % mHistoricalLog.length; + private void processDied(ActiveBuffer buffer) { + synchronized (mLock) { + mActive.remove(buffer); + } + addToSaveQueue(buffer); } - private ActiveBuffer fetchActiveBuffersLocked(IBinder token, int uid, int pid, - String packageName) throws RemoteException { + private ActiveBuffer fetchActiveBuffersLocked(IGraphicsStatsCallback token, int uid, int pid, + String packageName, int versionCode) throws RemoteException { int size = mActive.size(); + long today = normalizeDate(System.currentTimeMillis()).getTimeInMillis(); for (int i = 0; i < size; i++) { - ActiveBuffer buffers = mActive.get(i); - if (buffers.mPid == pid - && buffers.mUid == uid) { - return buffers; + ActiveBuffer buffer = mActive.get(i); + if (buffer.mPid == pid + && buffer.mUid == uid) { + // If the buffer is too old we remove it and return a new one + if (buffer.mInfo.startTime < today) { + buffer.binderDied(); + break; + } else { + return buffer; + } } } // Didn't find one, need to create it try { - ActiveBuffer buffers = new ActiveBuffer(token, uid, pid, packageName); + ActiveBuffer buffers = new ActiveBuffer(token, uid, pid, packageName, versionCode); mActive.add(buffers); return buffers; } catch (IOException ex) { @@ -147,71 +307,106 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { } } - private HistoricalData removeHistoricalDataLocked(int uid, String packageName) { - for (int i = 0; i < mHistoricalLog.length; i++) { - final HistoricalData data = mHistoricalLog[i]; - if (data != null && data.mUid == uid - && data.mPackageName.equals(packageName)) { - if (i == mNextHistoricalSlot) { - mHistoricalLog[i] = null; - } else { - mHistoricalLog[i] = mHistoricalLog[mNextHistoricalSlot]; - mHistoricalLog[mNextHistoricalSlot] = null; + private HashSet<File> dumpActiveLocked(long dump, ArrayList<HistoricalBuffer> buffers) { + HashSet<File> skipFiles = new HashSet<>(buffers.size()); + for (int i = 0; i < buffers.size(); i++) { + HistoricalBuffer buffer = buffers.get(i); + File path = pathForApp(buffer.mInfo); + skipFiles.add(path); + nAddToDump(dump, path.getAbsolutePath(), buffer.mInfo.packageName, + buffer.mInfo.versionCode, buffer.mInfo.startTime, buffer.mInfo.endTime, + buffer.mData); + } + return skipFiles; + } + + private void dumpHistoricalLocked(long dump, HashSet<File> skipFiles) { + for (File date : mGraphicsStatsDir.listFiles()) { + for (File pkg : date.listFiles()) { + for (File version : pkg.listFiles()) { + File data = new File(version, "total"); + if (skipFiles.contains(data)) { + continue; + } + nAddToDump(dump, data.getAbsolutePath()); } - return data; } } - return null; } @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); + boolean dumpProto = false; + for (String str : args) { + if ("--proto".equals(str)) { + dumpProto = true; + break; + } + } + ArrayList<HistoricalBuffer> buffers; synchronized (mLock) { + buffers = new ArrayList<>(mActive.size()); for (int i = 0; i < mActive.size(); i++) { - final ActiveBuffer buffer = mActive.get(i); - fout.print("Package: "); - fout.print(buffer.mPackageName); - fout.flush(); try { - buffer.mProcessBuffer.readBytes(mTempBuffer, 0, 0, ASHMEM_SIZE); - ThreadedRenderer.dumpProfileData(mTempBuffer, fd); - } catch (IOException e) { - fout.println("Failed to dump"); + buffers.add(new HistoricalBuffer(mActive.get(i))); + } catch (IOException ex) { + // Ignore } - fout.println(); } - for (HistoricalData buffer : mHistoricalLog) { - if (buffer == null) continue; - fout.print("Package: "); - fout.print(buffer.mPackageName); - fout.flush(); - ThreadedRenderer.dumpProfileData(buffer.mBuffer, fd); - fout.println(); + } + long dump = nCreateDump(fd.getInt$(), dumpProto); + try { + synchronized (mFileAccessLock) { + HashSet<File> skipList = dumpActiveLocked(dump, buffers); + buffers.clear(); + dumpHistoricalLocked(dump, skipList); } + } finally { + nFinishDump(dump); + } + } + + private static native int nGetAshmemSize(); + private static native long nCreateDump(int outFd, boolean isProto); + private static native void nAddToDump(long dump, String path, String packageName, + int versionCode, long startTime, long endTime, byte[] data); + private static native void nAddToDump(long dump, String path); + private static native void nFinishDump(long dump); + private static native void nSaveBuffer(String path, String packageName, int versionCode, + long startTime, long endTime, byte[] data); + + private final class BufferInfo { + final String packageName; + final int versionCode; + long startTime; + long endTime; + + BufferInfo(String packageName, int versionCode, long startTime) { + this.packageName = packageName; + this.versionCode = versionCode; + this.startTime = startTime; } } private final class ActiveBuffer implements DeathRecipient { + final BufferInfo mInfo; final int mUid; final int mPid; - final String mPackageName; + final IGraphicsStatsCallback mCallback; final IBinder mToken; MemoryFile mProcessBuffer; - HistoricalData mPreviousData; - ActiveBuffer(IBinder token, int uid, int pid, String packageName) + ActiveBuffer(IGraphicsStatsCallback token, int uid, int pid, String packageName, int versionCode) throws RemoteException, IOException { + mInfo = new BufferInfo(packageName, versionCode, System.currentTimeMillis()); mUid = uid; mPid = pid; - mPackageName = packageName; - mToken = token; + mCallback = token; + mToken = mCallback.asBinder(); mToken.linkToDeath(this, 0); - mProcessBuffer = new MemoryFile("GFXStats-" + uid, ASHMEM_SIZE); - mPreviousData = removeHistoricalDataLocked(mUid, mPackageName); - if (mPreviousData != null) { - mProcessBuffer.writeBytes(mPreviousData.mBuffer, 0, 0, ASHMEM_SIZE); - } + mProcessBuffer = new MemoryFile("GFXStats-" + pid, ASHMEM_SIZE); + mProcessBuffer.writeBytes(ZERO_DATA, 0, 0, ASHMEM_SIZE); } @Override @@ -228,17 +423,13 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { } } - private final static class HistoricalData { - final byte[] mBuffer = new byte[ASHMEM_SIZE]; - int mUid; - String mPackageName; - - void update(String packageName, int uid, MemoryFile file) { - mUid = uid; - mPackageName = packageName; - try { - file.readBytes(mBuffer, 0, 0, ASHMEM_SIZE); - } catch (IOException e) {} + private final class HistoricalBuffer { + final BufferInfo mInfo; + final byte[] mData = new byte[ASHMEM_SIZE]; + HistoricalBuffer(ActiveBuffer active) throws IOException { + mInfo = active.mInfo; + mInfo.endTime = System.currentTimeMillis(); + active.mProcessBuffer.readBytes(mData, 0, 0, ASHMEM_SIZE); } } } diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk index 2c3cda55c9fe..ac95db5c34b4 100644 --- a/services/core/jni/Android.mk +++ b/services/core/jni/Android.mk @@ -30,6 +30,7 @@ LOCAL_SRC_FILES += \ $(LOCAL_REL_DIR)/com_android_server_UsbHostManager.cpp \ $(LOCAL_REL_DIR)/com_android_server_VibratorService.cpp \ $(LOCAL_REL_DIR)/com_android_server_PersistentDataBlockService.cpp \ + $(LOCAL_REL_DIR)/com_android_server_GraphicsStatsService.cpp \ $(LOCAL_REL_DIR)/onload.cpp LOCAL_C_INCLUDES += \ @@ -37,7 +38,6 @@ LOCAL_C_INCLUDES += \ external/scrypt/lib/crypto \ frameworks/base/services \ frameworks/base/libs \ - frameworks/base/libs/hwui \ frameworks/base/core/jni \ frameworks/native/services \ system/core/libappfuse/include \ @@ -76,6 +76,7 @@ LOCAL_SHARED_LIBRARIES += \ libhidltransport \ libhwbinder \ libutils \ + libhwui \ android.hardware.audio.common@2.0 \ android.hardware.contexthub@1.0 \ android.hardware.gnss@1.0 \ diff --git a/services/core/jni/com_android_server_GraphicsStatsService.cpp b/services/core/jni/com_android_server_GraphicsStatsService.cpp new file mode 100644 index 000000000000..5d5728da61af --- /dev/null +++ b/services/core/jni/com_android_server_GraphicsStatsService.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2017 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. + */ + +#define LOG_TAG "GraphicsStatsService" + +#include <jni.h> +#include <log/log.h> +#include <nativehelper/JNIHelp.h> +#include <ScopedPrimitiveArray.h> +#include <ScopedUtfChars.h> +#include <JankTracker.h> +#include <service/GraphicsStatsService.h> + +namespace android { + +using namespace android::uirenderer; + +static jint getAshmemSize(JNIEnv*, jobject) { + return sizeof(ProfileData); +} + +static jlong createDump(JNIEnv*, jobject, jint fd, jboolean isProto) { + GraphicsStatsService::Dump* dump = GraphicsStatsService::createDump(fd, isProto + ? GraphicsStatsService::DumpType::Protobuf : GraphicsStatsService::DumpType::Text); + return reinterpret_cast<jlong>(dump); +} + +static void addToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath, jstring jpackage, + jint versionCode, jlong startTime, jlong endTime, jbyteArray jdata) { + std::string path; + const ProfileData* data = nullptr; + LOG_ALWAYS_FATAL_IF(jdata == nullptr && jpath == nullptr, "Path and data can't both be null"); + if (jdata != nullptr) { + ScopedByteArrayRO buffer(env, jdata); + LOG_ALWAYS_FATAL_IF(buffer.size() != sizeof(ProfileData), + "Buffer size %zu doesn't match expected %zu!", buffer.size(), sizeof(ProfileData)); + data = reinterpret_cast<const ProfileData*>(buffer.get()); + } + if (jpath != nullptr) { + ScopedUtfChars pathChars(env, jpath); + LOG_ALWAYS_FATAL_IF(pathChars.size() <= 0 || !pathChars.c_str(), "Failed to get path chars"); + path.assign(pathChars.c_str(), pathChars.size()); + } + ScopedUtfChars packageChars(env, jpackage); + LOG_ALWAYS_FATAL_IF(packageChars.size() <= 0 || !packageChars.c_str(), "Failed to get path chars"); + GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr); + LOG_ALWAYS_FATAL_IF(!dump, "null passed for dump pointer"); + + const std::string package(packageChars.c_str(), packageChars.size()); + GraphicsStatsService::addToDump(dump, path, package, versionCode, startTime, endTime, data); +} + +static void addFileToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath) { + ScopedUtfChars pathChars(env, jpath); + LOG_ALWAYS_FATAL_IF(pathChars.size() <= 0 || !pathChars.c_str(), "Failed to get path chars"); + const std::string path(pathChars.c_str(), pathChars.size()); + GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr); + GraphicsStatsService::addToDump(dump, path); +} + +static void finishDump(JNIEnv*, jobject, jlong dumpPtr) { + GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr); + GraphicsStatsService::finishDump(dump); +} + +static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jstring jpackage, + jint versionCode, jlong startTime, jlong endTime, jbyteArray jdata) { + ScopedByteArrayRO buffer(env, jdata); + LOG_ALWAYS_FATAL_IF(buffer.size() != sizeof(ProfileData), + "Buffer size %zu doesn't match expected %zu!", buffer.size(), sizeof(ProfileData)); + ScopedUtfChars pathChars(env, jpath); + LOG_ALWAYS_FATAL_IF(pathChars.size() <= 0 || !pathChars.c_str(), "Failed to get path chars"); + ScopedUtfChars packageChars(env, jpackage); + LOG_ALWAYS_FATAL_IF(packageChars.size() <= 0 || !packageChars.c_str(), "Failed to get path chars"); + + const std::string path(pathChars.c_str(), pathChars.size()); + const std::string package(packageChars.c_str(), packageChars.size()); + const ProfileData* data = reinterpret_cast<const ProfileData*>(buffer.get()); + GraphicsStatsService::saveBuffer(path, package, versionCode, startTime, endTime, data); +} + +static const JNINativeMethod sMethods[] = { + { "nGetAshmemSize", "()I", (void*) getAshmemSize }, + { "nCreateDump", "(IZ)J", (void*) createDump }, + { "nAddToDump", "(JLjava/lang/String;Ljava/lang/String;IJJ[B)V", (void*) addToDump }, + { "nAddToDump", "(JLjava/lang/String;)V", (void*) addFileToDump }, + { "nFinishDump", "(J)V", (void*) finishDump }, + { "nSaveBuffer", "(Ljava/lang/String;Ljava/lang/String;IJJ[B)V", (void*) saveBuffer }, +}; + +int register_android_server_GraphicsStatsService(JNIEnv* env) +{ + return jniRegisterNativeMethods(env, "com/android/server/GraphicsStatsService", + sMethods, NELEM(sMethods)); +} + +} // namespace android
\ No newline at end of file diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index 899640e138e3..f22b330b5b9d 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -46,6 +46,7 @@ int register_android_server_PersistentDataBlockService(JNIEnv* env); int register_android_server_Watchdog(JNIEnv* env); int register_android_server_HardwarePropertiesManagerService(JNIEnv* env); int register_android_server_SyntheticPasswordManager(JNIEnv* env); +int register_android_server_GraphicsStatsService(JNIEnv* env); }; using namespace android; @@ -87,6 +88,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_HardwarePropertiesManagerService(env); register_android_server_storage_AppFuse(env); register_android_server_SyntheticPasswordManager(env); + register_android_server_GraphicsStatsService(env); return JNI_VERSION_1_4; } |