Loading Android.mk +1 −0 Original line number Diff line number Diff line Loading @@ -245,6 +245,7 @@ LOCAL_SRC_FILES += \ core/java/android/view/accessibility/IAccessibilityManagerClient.aidl \ core/java/android/view/IApplicationToken.aidl \ core/java/android/view/IAssetAtlas.aidl \ core/java/android/view/IGraphicsStats.aidl \ core/java/android/view/IInputFilter.aidl \ core/java/android/view/IInputFilterHost.aidl \ core/java/android/view/IOnKeyguardExitResult.aidl \ Loading core/java/android/view/IGraphicsStats.aidl 0 → 100644 +26 −0 Original line number Diff line number Diff line /** * Copyright (c) 2015, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.view; import android.os.ParcelFileDescriptor; /** * @hide */ interface IGraphicsStats { ParcelFileDescriptor requestBufferForProcess(String packageName, IBinder token); } core/java/android/view/ThreadedRenderer.java +37 −5 Original line number Diff line number Diff line Loading @@ -23,7 +23,9 @@ import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Binder; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceManager; import android.os.Trace; Loading Loading @@ -124,7 +126,7 @@ public class ThreadedRenderer extends HardwareRenderer { mRootNode.setClipToBounds(false); mNativeProxy = nCreateProxy(translucent, rootNodePtr); AtlasInitializer.sInstance.init(context, mNativeProxy); ProcessInitializer.sInstance.init(context, mNativeProxy); loadSystemProperties(); } Loading Loading @@ -410,15 +412,44 @@ public class ThreadedRenderer extends HardwareRenderer { nTrimMemory(level); } private static class AtlasInitializer { static AtlasInitializer sInstance = new AtlasInitializer(); public static void dumpProfileData(byte[] data, FileDescriptor fd) { nDumpProfileData(data, fd); } private static class ProcessInitializer { static ProcessInitializer sInstance = new ProcessInitializer(); static IGraphicsStats sGraphicsStatsService; private static IBinder sProcToken; private boolean mInitialized = false; private AtlasInitializer() {} private ProcessInitializer() {} synchronized void init(Context context, long renderProxy) { if (mInitialized) return; mInitialized = true; initGraphicsStats(context, renderProxy); initAssetAtlas(context, renderProxy); } private static void initGraphicsStats(Context context, long renderProxy) { IBinder binder = ServiceManager.getService("graphicsstats"); if (binder == null) return; sGraphicsStatsService = IGraphicsStats.Stub.asInterface(binder); sProcToken = new Binder(); try { final String pkg = context.getApplicationInfo().packageName; ParcelFileDescriptor pfd = sGraphicsStatsService. requestBufferForProcess(pkg, sProcToken); nSetProcessStatsBuffer(renderProxy, pfd.getFd()); pfd.close(); } catch (Exception e) { Log.w(LOG_TAG, "Could not acquire gfx stats buffer", e); } } private static void initAssetAtlas(Context context, long renderProxy) { IBinder binder = ServiceManager.getService("assetatlas"); if (binder == null) return; Loading @@ -432,7 +463,6 @@ public class ThreadedRenderer extends HardwareRenderer { // TODO Remove after fixing b/15425820 validateMap(context, map); nSetAtlas(renderProxy, buffer, map); mInitialized = true; } // If IAssetAtlas is not the same class as the IBinder // we are using a remote service and we can safely Loading Loading @@ -477,6 +507,7 @@ public class ThreadedRenderer extends HardwareRenderer { static native void setupShadersDiskCache(String cacheFile); private static native void nSetAtlas(long nativeProxy, GraphicBuffer buffer, long[] map); private static native void nSetProcessStatsBuffer(long nativeProxy, int fd); private static native long nCreateRootRenderNode(); private static native long nCreateProxy(boolean translucent, long rootRenderNode); Loading Loading @@ -514,4 +545,5 @@ public class ThreadedRenderer extends HardwareRenderer { private static native void nDumpProfileInfo(long nativeProxy, FileDescriptor fd, @DumpFlags int dumpFlags); private static native void nDumpProfileData(byte[] data, FileDescriptor fd); } core/jni/android_view_ThreadedRenderer.cpp +20 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ #include "jni.h" #include <nativehelper/JNIHelp.h> #include "core_jni_helpers.h" #include <ScopedPrimitiveArray.h> #include <EGL/egl.h> #include <EGL/eglext.h> Loading @@ -35,6 +36,7 @@ #include <Animator.h> #include <AnimationContext.h> #include <IContextFactory.h> #include <JankTracker.h> #include <RenderNode.h> #include <renderthread/CanvasContext.h> #include <renderthread/RenderProxy.h> Loading Loading @@ -219,6 +221,12 @@ static void android_view_ThreadedRenderer_setAtlas(JNIEnv* env, jobject clazz, proxy->setTextureAtlas(buffer, map, len); } static void android_view_ThreadedRenderer_setProcessStatsBuffer(JNIEnv* env, jobject clazz, jlong proxyPtr, jint fd) { RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); proxy->setProcessStatsBuffer(fd); } static jlong android_view_ThreadedRenderer_createRootRenderNode(JNIEnv* env, jobject clazz) { RootRenderNode* node = new RootRenderNode(env); node->incStrong(0); Loading Loading @@ -403,6 +411,16 @@ 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); } } // ---------------------------------------------------------------------------- // Shaders // ---------------------------------------------------------------------------- Loading @@ -423,6 +441,7 @@ const char* const kClassPathName = "android/view/ThreadedRenderer"; static JNINativeMethod gMethods[] = { { "nSetAtlas", "(JLandroid/view/GraphicBuffer;[J)V", (void*) android_view_ThreadedRenderer_setAtlas }, { "nSetProcessStatsBuffer", "(JI)V", (void*) android_view_ThreadedRenderer_setProcessStatsBuffer }, { "nCreateRootRenderNode", "()J", (void*) android_view_ThreadedRenderer_createRootRenderNode }, { "nCreateProxy", "(ZJ)J", (void*) android_view_ThreadedRenderer_createProxy }, { "nDeleteProxy", "(J)V", (void*) android_view_ThreadedRenderer_deleteProxy }, Loading @@ -449,6 +468,7 @@ static JNINativeMethod gMethods[] = { { "nStopDrawing", "(J)V", (void*) android_view_ThreadedRenderer_stopDrawing }, { "nNotifyFramePending", "(J)V", (void*) android_view_ThreadedRenderer_notifyFramePending }, { "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 }, }; Loading libs/hwui/JankTracker.cpp +141 −30 Original line number Diff line number Diff line Loading @@ -16,8 +16,12 @@ #include "JankTracker.h" #include <algorithm> #include <cutils/ashmem.h> #include <cutils/log.h> #include <cstdio> #include <errno.h> #include <inttypes.h> #include <sys/mman.h> namespace android { namespace uirenderer { Loading Loading @@ -63,11 +67,114 @@ static const int64_t EXEMPT_FRAMES_FLAGS = FrameInfoFlags::kWindowLayoutChanged | FrameInfoFlags::kSurfaceCanvas; // The bucketing algorithm controls so to speak // If a frame is <= to this it goes in bucket 0 static const uint32_t kBucketMinThreshold = 7; // If a frame is > this, start counting in increments of 2ms static const uint32_t kBucket2msIntervals = 32; // If a frame is > this, start counting in increments of 4ms static const uint32_t kBucket4msIntervals = 48; // This will be called every frame, performance sensitive // Uses bit twiddling to avoid branching while achieving the packing desired static uint32_t frameCountIndexForFrameTime(nsecs_t frameTime, uint32_t max) { uint32_t index = static_cast<uint32_t>(ns2ms(frameTime)); // If index > kBucketMinThreshold mask will be 0xFFFFFFFF as a result // of negating 1 (twos compliment, yaay) else mask will be 0 uint32_t mask = -(index > kBucketMinThreshold); // If index > threshold, this will essentially perform: // amountAboveThreshold = index - threshold; // index = threshold + (amountAboveThreshold / 2) // However if index is <= this will do nothing. It will underflow, do // a right shift by 0 (no-op), then overflow back to the original value index = ((index - kBucket4msIntervals) >> (index > kBucket4msIntervals)) + kBucket4msIntervals; index = ((index - kBucket2msIntervals) >> (index > kBucket2msIntervals)) + kBucket2msIntervals; // If index was < minThreshold at the start of all this it's going to // be a pretty garbage value right now. However, mask is 0 so we'll end // up with the desired result of 0. index = (index - kBucketMinThreshold) & mask; return index < max ? index : max; } // Only called when dumping stats, less performance sensitive static uint32_t frameTimeForFrameCountIndex(uint32_t index) { index = index + kBucketMinThreshold; if (index > kBucket2msIntervals) { index += (index - kBucket2msIntervals); } if (index > kBucket4msIntervals) { // This works because it was already doubled by the above if // 1 is added to shift slightly more towards the middle of the bucket index += (index - kBucket4msIntervals) + 1; } return index; } JankTracker::JankTracker(nsecs_t frameIntervalNanos) { // 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. mData = new ProfileData; reset(); setFrameInterval(frameIntervalNanos); } JankTracker::~JankTracker() { freeData(); } void JankTracker::freeData() { if (mIsMapped) { munmap(mData, sizeof(ProfileData)); } else { delete mData; } mIsMapped = false; mData = nullptr; } void JankTracker::switchStorageToAshmem(int ashmemfd) { int regionSize = ashmem_get_size_region(ashmemfd); if (regionSize < static_cast<int>(sizeof(ProfileData))) { ALOGW("Ashmem region is too small! Received %d, required %u", regionSize, sizeof(ProfileData)); return; } ProfileData* newData = reinterpret_cast<ProfileData*>( mmap(NULL, sizeof(ProfileData), PROT_READ | PROT_WRITE, MAP_SHARED, ashmemfd, 0)); if (newData == MAP_FAILED) { int err = errno; ALOGW("Failed to move profile data to ashmem fd %d, error = %d", ashmemfd, err); return; } // The new buffer may have historical data that we want to build on top of // But let's make sure we don't overflow Just In Case uint32_t divider = 0; if (newData->totalFrameCount > (1 << 24)) { divider = 4; } for (size_t i = 0; i < mData->jankTypeCounts.size(); i++) { newData->jankTypeCounts[i] >>= divider; newData->jankTypeCounts[i] += mData->jankTypeCounts[i]; } for (size_t i = 0; i < mData->frameCounts.size(); i++) { newData->frameCounts[i] >>= divider; newData->frameCounts[i] += mData->frameCounts[i]; } newData->jankFrameCount >>= divider; newData->jankFrameCount += mData->jankFrameCount; newData->totalFrameCount >>= divider; newData->totalFrameCount += mData->totalFrameCount; freeData(); mData = newData; mIsMapped = true; } void JankTracker::setFrameInterval(nsecs_t frameInterval) { mFrameInterval = frameInterval; mThresholds[kMissedVsync] = 1; Loading @@ -92,16 +199,15 @@ void JankTracker::setFrameInterval(nsecs_t frameInterval) { } void JankTracker::addFrame(const FrameInfo& frame) { mTotalFrameCount++; mData->totalFrameCount++; // Fast-path for jank-free frames int64_t totalDuration = frame[FrameInfoIndex::kFrameCompleted] - frame[FrameInfoIndex::kIntendedVsync]; uint32_t framebucket = std::min( static_cast<typeof mFrameCounts.size()>(ns2ms(totalDuration)), mFrameCounts.size()); uint32_t framebucket = frameCountIndexForFrameTime( totalDuration, mData->frameCounts.size()); // Keep the fast path as fast as possible. if (CC_LIKELY(totalDuration < mFrameInterval)) { mFrameCounts[framebucket]++; mData->frameCounts[framebucket]++; return; } Loading @@ -109,47 +215,52 @@ void JankTracker::addFrame(const FrameInfo& frame) { return; } mFrameCounts[framebucket]++; mJankFrameCount++; mData->frameCounts[framebucket]++; mData->jankFrameCount++; for (int i = 0; i < NUM_BUCKETS; i++) { int64_t delta = frame[COMPARISONS[i].end] - frame[COMPARISONS[i].start]; if (delta >= mThresholds[i] && delta < IGNORE_EXCEEDING) { mBuckets[i].count++; mData->jankTypeCounts[i]++; } } } void JankTracker::dumpBuffer(const void* buffer, size_t bufsize, int fd) { if (bufsize < sizeof(ProfileData)) { return; } const ProfileData* data = reinterpret_cast<const ProfileData*>(buffer); dumpData(data, fd); } void JankTracker::dump(int fd) { FILE* file = fdopen(fd, "a"); fprintf(file, "\nFrame stats:"); fprintf(file, "\n Total frames rendered: %u", mTotalFrameCount); fprintf(file, "\n Janky frames: %u (%.2f%%)", mJankFrameCount, (float) mJankFrameCount / (float) mTotalFrameCount * 100.0f); fprintf(file, "\n 90th percentile: %ums", findPercentile(90)); fprintf(file, "\n 95th percentile: %ums", findPercentile(95)); fprintf(file, "\n 99th percentile: %ums", findPercentile(99)); void JankTracker::dumpData(const ProfileData* data, int fd) { dprintf(fd, "\nTotal frames rendered: %u", data->totalFrameCount); dprintf(fd, "\nJanky frames: %u (%.2f%%)", data->jankFrameCount, (float) data->jankFrameCount / (float) data->totalFrameCount * 100.0f); dprintf(fd, "\n90th percentile: %ums", findPercentile(data, 90)); dprintf(fd, "\n95th percentile: %ums", findPercentile(data, 95)); dprintf(fd, "\n99th percentile: %ums", findPercentile(data, 99)); for (int i = 0; i < NUM_BUCKETS; i++) { fprintf(file, "\n Number %s: %u", JANK_TYPE_NAMES[i], mBuckets[i].count); dprintf(fd, "\nNumber %s: %u", JANK_TYPE_NAMES[i], data->jankTypeCounts[i]); } fprintf(file, "\n"); fflush(file); dprintf(fd, "\n"); } void JankTracker::reset() { mBuckets.fill({0}); mFrameCounts.fill(0); mTotalFrameCount = 0; mJankFrameCount = 0; mData->jankTypeCounts.fill(0); mData->frameCounts.fill(0); mData->totalFrameCount = 0; mData->jankFrameCount = 0; } uint32_t JankTracker::findPercentile(int percentile) { int pos = percentile * mTotalFrameCount / 100; int remaining = mTotalFrameCount - pos; for (int i = mFrameCounts.size() - 1; i >= 0; i--) { remaining -= mFrameCounts[i]; uint32_t JankTracker::findPercentile(const ProfileData* data, int percentile) { int pos = percentile * data->totalFrameCount / 100; int remaining = data->totalFrameCount - pos; for (int i = data->frameCounts.size() - 1; i >= 0; i--) { remaining -= data->frameCounts[i]; if (remaining <= 0) { return i; return frameTimeForFrameCountIndex(i); } } return 0; Loading Loading
Android.mk +1 −0 Original line number Diff line number Diff line Loading @@ -245,6 +245,7 @@ LOCAL_SRC_FILES += \ core/java/android/view/accessibility/IAccessibilityManagerClient.aidl \ core/java/android/view/IApplicationToken.aidl \ core/java/android/view/IAssetAtlas.aidl \ core/java/android/view/IGraphicsStats.aidl \ core/java/android/view/IInputFilter.aidl \ core/java/android/view/IInputFilterHost.aidl \ core/java/android/view/IOnKeyguardExitResult.aidl \ Loading
core/java/android/view/IGraphicsStats.aidl 0 → 100644 +26 −0 Original line number Diff line number Diff line /** * Copyright (c) 2015, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.view; import android.os.ParcelFileDescriptor; /** * @hide */ interface IGraphicsStats { ParcelFileDescriptor requestBufferForProcess(String packageName, IBinder token); }
core/java/android/view/ThreadedRenderer.java +37 −5 Original line number Diff line number Diff line Loading @@ -23,7 +23,9 @@ import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Binder; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceManager; import android.os.Trace; Loading Loading @@ -124,7 +126,7 @@ public class ThreadedRenderer extends HardwareRenderer { mRootNode.setClipToBounds(false); mNativeProxy = nCreateProxy(translucent, rootNodePtr); AtlasInitializer.sInstance.init(context, mNativeProxy); ProcessInitializer.sInstance.init(context, mNativeProxy); loadSystemProperties(); } Loading Loading @@ -410,15 +412,44 @@ public class ThreadedRenderer extends HardwareRenderer { nTrimMemory(level); } private static class AtlasInitializer { static AtlasInitializer sInstance = new AtlasInitializer(); public static void dumpProfileData(byte[] data, FileDescriptor fd) { nDumpProfileData(data, fd); } private static class ProcessInitializer { static ProcessInitializer sInstance = new ProcessInitializer(); static IGraphicsStats sGraphicsStatsService; private static IBinder sProcToken; private boolean mInitialized = false; private AtlasInitializer() {} private ProcessInitializer() {} synchronized void init(Context context, long renderProxy) { if (mInitialized) return; mInitialized = true; initGraphicsStats(context, renderProxy); initAssetAtlas(context, renderProxy); } private static void initGraphicsStats(Context context, long renderProxy) { IBinder binder = ServiceManager.getService("graphicsstats"); if (binder == null) return; sGraphicsStatsService = IGraphicsStats.Stub.asInterface(binder); sProcToken = new Binder(); try { final String pkg = context.getApplicationInfo().packageName; ParcelFileDescriptor pfd = sGraphicsStatsService. requestBufferForProcess(pkg, sProcToken); nSetProcessStatsBuffer(renderProxy, pfd.getFd()); pfd.close(); } catch (Exception e) { Log.w(LOG_TAG, "Could not acquire gfx stats buffer", e); } } private static void initAssetAtlas(Context context, long renderProxy) { IBinder binder = ServiceManager.getService("assetatlas"); if (binder == null) return; Loading @@ -432,7 +463,6 @@ public class ThreadedRenderer extends HardwareRenderer { // TODO Remove after fixing b/15425820 validateMap(context, map); nSetAtlas(renderProxy, buffer, map); mInitialized = true; } // If IAssetAtlas is not the same class as the IBinder // we are using a remote service and we can safely Loading Loading @@ -477,6 +507,7 @@ public class ThreadedRenderer extends HardwareRenderer { static native void setupShadersDiskCache(String cacheFile); private static native void nSetAtlas(long nativeProxy, GraphicBuffer buffer, long[] map); private static native void nSetProcessStatsBuffer(long nativeProxy, int fd); private static native long nCreateRootRenderNode(); private static native long nCreateProxy(boolean translucent, long rootRenderNode); Loading Loading @@ -514,4 +545,5 @@ public class ThreadedRenderer extends HardwareRenderer { private static native void nDumpProfileInfo(long nativeProxy, FileDescriptor fd, @DumpFlags int dumpFlags); private static native void nDumpProfileData(byte[] data, FileDescriptor fd); }
core/jni/android_view_ThreadedRenderer.cpp +20 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ #include "jni.h" #include <nativehelper/JNIHelp.h> #include "core_jni_helpers.h" #include <ScopedPrimitiveArray.h> #include <EGL/egl.h> #include <EGL/eglext.h> Loading @@ -35,6 +36,7 @@ #include <Animator.h> #include <AnimationContext.h> #include <IContextFactory.h> #include <JankTracker.h> #include <RenderNode.h> #include <renderthread/CanvasContext.h> #include <renderthread/RenderProxy.h> Loading Loading @@ -219,6 +221,12 @@ static void android_view_ThreadedRenderer_setAtlas(JNIEnv* env, jobject clazz, proxy->setTextureAtlas(buffer, map, len); } static void android_view_ThreadedRenderer_setProcessStatsBuffer(JNIEnv* env, jobject clazz, jlong proxyPtr, jint fd) { RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); proxy->setProcessStatsBuffer(fd); } static jlong android_view_ThreadedRenderer_createRootRenderNode(JNIEnv* env, jobject clazz) { RootRenderNode* node = new RootRenderNode(env); node->incStrong(0); Loading Loading @@ -403,6 +411,16 @@ 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); } } // ---------------------------------------------------------------------------- // Shaders // ---------------------------------------------------------------------------- Loading @@ -423,6 +441,7 @@ const char* const kClassPathName = "android/view/ThreadedRenderer"; static JNINativeMethod gMethods[] = { { "nSetAtlas", "(JLandroid/view/GraphicBuffer;[J)V", (void*) android_view_ThreadedRenderer_setAtlas }, { "nSetProcessStatsBuffer", "(JI)V", (void*) android_view_ThreadedRenderer_setProcessStatsBuffer }, { "nCreateRootRenderNode", "()J", (void*) android_view_ThreadedRenderer_createRootRenderNode }, { "nCreateProxy", "(ZJ)J", (void*) android_view_ThreadedRenderer_createProxy }, { "nDeleteProxy", "(J)V", (void*) android_view_ThreadedRenderer_deleteProxy }, Loading @@ -449,6 +468,7 @@ static JNINativeMethod gMethods[] = { { "nStopDrawing", "(J)V", (void*) android_view_ThreadedRenderer_stopDrawing }, { "nNotifyFramePending", "(J)V", (void*) android_view_ThreadedRenderer_notifyFramePending }, { "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 }, }; Loading
libs/hwui/JankTracker.cpp +141 −30 Original line number Diff line number Diff line Loading @@ -16,8 +16,12 @@ #include "JankTracker.h" #include <algorithm> #include <cutils/ashmem.h> #include <cutils/log.h> #include <cstdio> #include <errno.h> #include <inttypes.h> #include <sys/mman.h> namespace android { namespace uirenderer { Loading Loading @@ -63,11 +67,114 @@ static const int64_t EXEMPT_FRAMES_FLAGS = FrameInfoFlags::kWindowLayoutChanged | FrameInfoFlags::kSurfaceCanvas; // The bucketing algorithm controls so to speak // If a frame is <= to this it goes in bucket 0 static const uint32_t kBucketMinThreshold = 7; // If a frame is > this, start counting in increments of 2ms static const uint32_t kBucket2msIntervals = 32; // If a frame is > this, start counting in increments of 4ms static const uint32_t kBucket4msIntervals = 48; // This will be called every frame, performance sensitive // Uses bit twiddling to avoid branching while achieving the packing desired static uint32_t frameCountIndexForFrameTime(nsecs_t frameTime, uint32_t max) { uint32_t index = static_cast<uint32_t>(ns2ms(frameTime)); // If index > kBucketMinThreshold mask will be 0xFFFFFFFF as a result // of negating 1 (twos compliment, yaay) else mask will be 0 uint32_t mask = -(index > kBucketMinThreshold); // If index > threshold, this will essentially perform: // amountAboveThreshold = index - threshold; // index = threshold + (amountAboveThreshold / 2) // However if index is <= this will do nothing. It will underflow, do // a right shift by 0 (no-op), then overflow back to the original value index = ((index - kBucket4msIntervals) >> (index > kBucket4msIntervals)) + kBucket4msIntervals; index = ((index - kBucket2msIntervals) >> (index > kBucket2msIntervals)) + kBucket2msIntervals; // If index was < minThreshold at the start of all this it's going to // be a pretty garbage value right now. However, mask is 0 so we'll end // up with the desired result of 0. index = (index - kBucketMinThreshold) & mask; return index < max ? index : max; } // Only called when dumping stats, less performance sensitive static uint32_t frameTimeForFrameCountIndex(uint32_t index) { index = index + kBucketMinThreshold; if (index > kBucket2msIntervals) { index += (index - kBucket2msIntervals); } if (index > kBucket4msIntervals) { // This works because it was already doubled by the above if // 1 is added to shift slightly more towards the middle of the bucket index += (index - kBucket4msIntervals) + 1; } return index; } JankTracker::JankTracker(nsecs_t frameIntervalNanos) { // 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. mData = new ProfileData; reset(); setFrameInterval(frameIntervalNanos); } JankTracker::~JankTracker() { freeData(); } void JankTracker::freeData() { if (mIsMapped) { munmap(mData, sizeof(ProfileData)); } else { delete mData; } mIsMapped = false; mData = nullptr; } void JankTracker::switchStorageToAshmem(int ashmemfd) { int regionSize = ashmem_get_size_region(ashmemfd); if (regionSize < static_cast<int>(sizeof(ProfileData))) { ALOGW("Ashmem region is too small! Received %d, required %u", regionSize, sizeof(ProfileData)); return; } ProfileData* newData = reinterpret_cast<ProfileData*>( mmap(NULL, sizeof(ProfileData), PROT_READ | PROT_WRITE, MAP_SHARED, ashmemfd, 0)); if (newData == MAP_FAILED) { int err = errno; ALOGW("Failed to move profile data to ashmem fd %d, error = %d", ashmemfd, err); return; } // The new buffer may have historical data that we want to build on top of // But let's make sure we don't overflow Just In Case uint32_t divider = 0; if (newData->totalFrameCount > (1 << 24)) { divider = 4; } for (size_t i = 0; i < mData->jankTypeCounts.size(); i++) { newData->jankTypeCounts[i] >>= divider; newData->jankTypeCounts[i] += mData->jankTypeCounts[i]; } for (size_t i = 0; i < mData->frameCounts.size(); i++) { newData->frameCounts[i] >>= divider; newData->frameCounts[i] += mData->frameCounts[i]; } newData->jankFrameCount >>= divider; newData->jankFrameCount += mData->jankFrameCount; newData->totalFrameCount >>= divider; newData->totalFrameCount += mData->totalFrameCount; freeData(); mData = newData; mIsMapped = true; } void JankTracker::setFrameInterval(nsecs_t frameInterval) { mFrameInterval = frameInterval; mThresholds[kMissedVsync] = 1; Loading @@ -92,16 +199,15 @@ void JankTracker::setFrameInterval(nsecs_t frameInterval) { } void JankTracker::addFrame(const FrameInfo& frame) { mTotalFrameCount++; mData->totalFrameCount++; // Fast-path for jank-free frames int64_t totalDuration = frame[FrameInfoIndex::kFrameCompleted] - frame[FrameInfoIndex::kIntendedVsync]; uint32_t framebucket = std::min( static_cast<typeof mFrameCounts.size()>(ns2ms(totalDuration)), mFrameCounts.size()); uint32_t framebucket = frameCountIndexForFrameTime( totalDuration, mData->frameCounts.size()); // Keep the fast path as fast as possible. if (CC_LIKELY(totalDuration < mFrameInterval)) { mFrameCounts[framebucket]++; mData->frameCounts[framebucket]++; return; } Loading @@ -109,47 +215,52 @@ void JankTracker::addFrame(const FrameInfo& frame) { return; } mFrameCounts[framebucket]++; mJankFrameCount++; mData->frameCounts[framebucket]++; mData->jankFrameCount++; for (int i = 0; i < NUM_BUCKETS; i++) { int64_t delta = frame[COMPARISONS[i].end] - frame[COMPARISONS[i].start]; if (delta >= mThresholds[i] && delta < IGNORE_EXCEEDING) { mBuckets[i].count++; mData->jankTypeCounts[i]++; } } } void JankTracker::dumpBuffer(const void* buffer, size_t bufsize, int fd) { if (bufsize < sizeof(ProfileData)) { return; } const ProfileData* data = reinterpret_cast<const ProfileData*>(buffer); dumpData(data, fd); } void JankTracker::dump(int fd) { FILE* file = fdopen(fd, "a"); fprintf(file, "\nFrame stats:"); fprintf(file, "\n Total frames rendered: %u", mTotalFrameCount); fprintf(file, "\n Janky frames: %u (%.2f%%)", mJankFrameCount, (float) mJankFrameCount / (float) mTotalFrameCount * 100.0f); fprintf(file, "\n 90th percentile: %ums", findPercentile(90)); fprintf(file, "\n 95th percentile: %ums", findPercentile(95)); fprintf(file, "\n 99th percentile: %ums", findPercentile(99)); void JankTracker::dumpData(const ProfileData* data, int fd) { dprintf(fd, "\nTotal frames rendered: %u", data->totalFrameCount); dprintf(fd, "\nJanky frames: %u (%.2f%%)", data->jankFrameCount, (float) data->jankFrameCount / (float) data->totalFrameCount * 100.0f); dprintf(fd, "\n90th percentile: %ums", findPercentile(data, 90)); dprintf(fd, "\n95th percentile: %ums", findPercentile(data, 95)); dprintf(fd, "\n99th percentile: %ums", findPercentile(data, 99)); for (int i = 0; i < NUM_BUCKETS; i++) { fprintf(file, "\n Number %s: %u", JANK_TYPE_NAMES[i], mBuckets[i].count); dprintf(fd, "\nNumber %s: %u", JANK_TYPE_NAMES[i], data->jankTypeCounts[i]); } fprintf(file, "\n"); fflush(file); dprintf(fd, "\n"); } void JankTracker::reset() { mBuckets.fill({0}); mFrameCounts.fill(0); mTotalFrameCount = 0; mJankFrameCount = 0; mData->jankTypeCounts.fill(0); mData->frameCounts.fill(0); mData->totalFrameCount = 0; mData->jankFrameCount = 0; } uint32_t JankTracker::findPercentile(int percentile) { int pos = percentile * mTotalFrameCount / 100; int remaining = mTotalFrameCount - pos; for (int i = mFrameCounts.size() - 1; i >= 0; i--) { remaining -= mFrameCounts[i]; uint32_t JankTracker::findPercentile(const ProfileData* data, int percentile) { int pos = percentile * data->totalFrameCount / 100; int remaining = data->totalFrameCount - pos; for (int i = data->frameCounts.size() - 1; i >= 0; i--) { remaining -= data->frameCounts[i]; if (remaining <= 0) { return i; return frameTimeForFrameCountIndex(i); } } return 0; Loading