diff options
author | John Reck <jreck@google.com> | 2015-03-18 15:24:33 -0700 |
---|---|---|
committer | John Reck <jreck@google.com> | 2015-03-27 11:50:56 -0700 |
commit | edc524c90506d80e0fc5fb67e8de7b8f3ef53439 (patch) | |
tree | 098c18daa80655fe0fa3faab7c39332685c163ef | |
parent | 1cef4196886b0cc1238111d396c1e3665a5fd2ae (diff) | |
download | frameworks_base-edc524c90506d80e0fc5fb67e8de7b8f3ef53439.zip frameworks_base-edc524c90506d80e0fc5fb67e8de7b8f3ef53439.tar.gz frameworks_base-edc524c90506d80e0fc5fb67e8de7b8f3ef53439.tar.bz2 |
Add GraphicsStatsService
More S's for More Speed
Split JankTracker's backing data from the
class to allow for data relocation to/from ashmem regions
Pack the jank tracking data to fit in 256 bytes
Change-Id: Ife86a64b71a328fbd0c8075fe6a0404e081f725b
-rw-r--r-- | Android.mk | 1 | ||||
-rw-r--r-- | core/java/android/view/IGraphicsStats.aidl | 26 | ||||
-rw-r--r-- | core/java/android/view/ThreadedRenderer.java | 42 | ||||
-rw-r--r-- | core/jni/android_view_ThreadedRenderer.cpp | 20 | ||||
-rw-r--r-- | libs/hwui/JankTracker.cpp | 171 | ||||
-rw-r--r-- | libs/hwui/JankTracker.h | 38 | ||||
-rw-r--r-- | libs/hwui/renderthread/CanvasContext.cpp | 8 | ||||
-rw-r--r-- | libs/hwui/renderthread/CanvasContext.h | 13 | ||||
-rw-r--r-- | libs/hwui/renderthread/RenderProxy.cpp | 13 | ||||
-rw-r--r-- | libs/hwui/renderthread/RenderProxy.h | 1 | ||||
-rw-r--r-- | services/core/java/com/android/server/GraphicsStatsService.java | 256 | ||||
-rw-r--r-- | services/java/com/android/server/SystemServer.java | 5 |
12 files changed, 535 insertions, 59 deletions
@@ -244,6 +244,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 \ diff --git a/core/java/android/view/IGraphicsStats.aidl b/core/java/android/view/IGraphicsStats.aidl new file mode 100644 index 0000000..c235eb2 --- /dev/null +++ b/core/java/android/view/IGraphicsStats.aidl @@ -0,0 +1,26 @@ +/** + * 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); +} diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index 031be07..87d5d9a 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -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; @@ -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(); } @@ -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; @@ -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 @@ -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); @@ -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); } diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp index 3d9a9ed..11b3805 100644 --- a/core/jni/android_view_ThreadedRenderer.cpp +++ b/core/jni/android_view_ThreadedRenderer.cpp @@ -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> @@ -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> @@ -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); @@ -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 // ---------------------------------------------------------------------------- @@ -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 }, @@ -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 }, }; diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp index 7df61f2..48f5dc1 100644 --- a/libs/hwui/JankTracker.cpp +++ b/libs/hwui/JankTracker.cpp @@ -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 { @@ -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; @@ -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; } @@ -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::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::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::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; diff --git a/libs/hwui/JankTracker.h b/libs/hwui/JankTracker.h index ae339ec..4783001 100644 --- a/libs/hwui/JankTracker.h +++ b/libs/hwui/JankTracker.h @@ -20,6 +20,8 @@ #include "renderthread/TimeLord.h" #include "utils/RingBuffer.h" +#include <cutils/compiler.h> + #include <array> #include <memory> @@ -37,33 +39,45 @@ enum JankType { NUM_BUCKETS, }; -struct JankBucket { - // Number of frames that hit this bucket - uint32_t count; +// Try to keep as small as possible, should match ASHMEM_SIZE in +// GraphicsStatsService.java +struct ProfileData { + std::array<uint32_t, NUM_BUCKETS> jankTypeCounts; + // See comments on kBucket* constants for what this holds + std::array<uint32_t, 57> frameCounts; + + uint32_t totalFrameCount; + uint32_t jankFrameCount; }; // TODO: Replace DrawProfiler with this class JankTracker { public: JankTracker(nsecs_t frameIntervalNanos); - - void setFrameInterval(nsecs_t frameIntervalNanos); + ~JankTracker(); void addFrame(const FrameInfo& frame); - void dump(int fd); + void dump(int fd) { dumpData(mData, fd); } void reset(); + 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); + private: - uint32_t findPercentile(int p); + void freeData(); + void setFrameInterval(nsecs_t frameIntervalNanos); - std::array<JankBucket, NUM_BUCKETS> mBuckets; - std::array<int64_t, NUM_BUCKETS> mThresholds; - std::array<uint32_t, 128> mFrameCounts; + static uint32_t findPercentile(const ProfileData* data, int p); + static void dumpData(const ProfileData* data, int fd); + std::array<int64_t, NUM_BUCKETS> mThresholds; int64_t mFrameInterval; - uint32_t mTotalFrameCount; - uint32_t mJankFrameCount; + ProfileData* mData; + bool mIsMapped = false; }; } /* namespace uirenderer */ diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 9456073..9237151 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -41,15 +41,10 @@ CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode, IContextFactory* contextFactory) : mRenderThread(thread) , mEglManager(thread.eglManager()) - , mEglSurface(EGL_NO_SURFACE) - , mBufferPreserved(false) - , mSwapBehavior(kSwap_default) , mOpaque(!translucent) - , mCanvas(nullptr) - , mHaveNewSurface(false) , mAnimationContext(contextFactory->createAnimationContext(mRenderThread.timeLord())) , mRootRenderNode(rootRenderNode) - , mCurrentFrameInfo(nullptr) { + , mJankTracker(thread.timeLord().frameIntervalNanos()) { mRenderThread.renderState().registerCanvasContext(this); mProfiler.setDensity(mRenderThread.mainDisplayInfo().density); } @@ -258,6 +253,7 @@ void CanvasContext::draw() { // TODO: Use a fence for real completion? mCurrentFrameInfo->markFrameCompleted(); + mJankTracker.addFrame(*mCurrentFrameInfo); mRenderThread.jankTracker().addFrame(*mCurrentFrameInfo); profiler().finishFrame(); } diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index c3904c2..f5f1f54 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -127,23 +127,24 @@ private: RenderThread& mRenderThread; EglManager& mEglManager; sp<ANativeWindow> mNativeWindow; - EGLSurface mEglSurface; - bool mBufferPreserved; - SwapBehavior mSwapBehavior; + EGLSurface mEglSurface = EGL_NO_SURFACE; + bool mBufferPreserved = false; + SwapBehavior mSwapBehavior = kSwap_default; bool mOpaque; - OpenGLRenderer* mCanvas; - bool mHaveNewSurface; + OpenGLRenderer* mCanvas = nullptr; + bool mHaveNewSurface = false; DamageAccumulator mDamageAccumulator; std::unique_ptr<AnimationContext> mAnimationContext; const sp<RenderNode> mRootRenderNode; DrawProfiler mProfiler; - FrameInfo* mCurrentFrameInfo; + FrameInfo* mCurrentFrameInfo = nullptr; // Ring buffer large enough for 1 second worth of frames RingBuffer<FrameInfo, 60> mFrames; std::string mName; + JankTracker mJankTracker; std::set<RenderNode*> mPrefetechedLayers; }; diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index ea4216c..bffbfcf 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -440,6 +440,19 @@ void RenderProxy::setTextureAtlas(const sp<GraphicBuffer>& buffer, int64_t* map, post(task); } +CREATE_BRIDGE2(setProcessStatsBuffer, RenderThread* thread, int fd) { + args->thread->jankTracker().switchStorageToAshmem(args->fd); + close(args->fd); + return nullptr; +} + +void RenderProxy::setProcessStatsBuffer(int fd) { + SETUP_TASK(setProcessStatsBuffer); + args->thread = &mRenderThread; + args->fd = dup(fd); + post(task); +} + void RenderProxy::post(RenderTask* task) { mRenderThread.queue(task); } diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index 43cbe07..29c6f08 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -99,6 +99,7 @@ public: ANDROID_API static void dumpGraphicsMemory(int fd); ANDROID_API void setTextureAtlas(const sp<GraphicBuffer>& buffer, int64_t* map, size_t size); + ANDROID_API void setProcessStatsBuffer(int fd); private: RenderThread& mRenderThread; diff --git a/services/core/java/com/android/server/GraphicsStatsService.java b/services/core/java/com/android/server/GraphicsStatsService.java new file mode 100644 index 0000000..c79fdfc --- /dev/null +++ b/services/core/java/com/android/server/GraphicsStatsService.java @@ -0,0 +1,256 @@ +/* + * 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 com.android.server; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Binder; +import android.os.IBinder; +import android.os.MemoryFile; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.util.Log; +import android.view.IGraphicsStats; +import android.view.ThreadedRenderer; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * 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) + * + * 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. + * + * MEMORY USAGE: + * + * 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 + * + * Currently ASHMEM_SIZE is 256 bytes and HISTORY_SIZE is 10. Assuming + * the system then also has 10 active rendering processes in the worst case + * this would end up using under 10KiB (8KiB for the buffers, plus some overhead + * for userId, pid, package name, and a couple other objects) + * + * @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 = 256; + private static final int HISTORY_SIZE = 10; + + private final Context mContext; + 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]; + + public GraphicsStatsService(Context context) { + mContext = context; + } + + private boolean isValid(int uid, String packageName) { + try { + PackageInfo info = mContext.getPackageManager().getPackageInfo(packageName, 0); + return info.applicationInfo.uid == uid; + } catch (NameNotFoundException e) { + } + return false; + } + + @Override + public ParcelFileDescriptor requestBufferForProcess(String packageName, IBinder token) + throws RemoteException { + int uid = Binder.getCallingUid(); + int pid = Binder.getCallingPid(); + ParcelFileDescriptor pfd = null; + long callingIdentity = Binder.clearCallingIdentity(); + try { + if (!isValid(uid, packageName)) { + throw new RemoteException("Invalid package name"); + } + synchronized (mLock) { + pfd = requestBufferForProcessLocked(token, uid, pid, packageName); + } + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + return pfd; + } + + private ParcelFileDescriptor getPfd(MemoryFile file) { + try { + 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); + return getPfd(buffer.mProcessBuffer); + } + + private void processDied(ActiveBuffer buffer) { + synchronized (mLock) { + mActive.remove(buffer); + Log.d("GraphicsStats", "Buffer count: " + mActive.size()); + } + HistoricalData data = buffer.mPreviousData; + buffer.mPreviousData = null; + if (data == null) { + data = mHistoricalLog[mNextHistoricalSlot]; + if (data == null) { + data = new HistoricalData(); + } + } + data.update(buffer.mPackageName, buffer.mUid, buffer.mProcessBuffer); + buffer.closeAllBuffers(); + + mHistoricalLog[mNextHistoricalSlot] = data; + mNextHistoricalSlot = (mNextHistoricalSlot + 1) % mHistoricalLog.length; + } + + private ActiveBuffer fetchActiveBuffersLocked(IBinder token, int uid, int pid, + String packageName) throws RemoteException { + int size = mActive.size(); + for (int i = 0; i < size; i++) { + ActiveBuffer buffers = mActive.get(i); + if (buffers.mPid == pid + && buffers.mUid == uid) { + return buffers; + } + } + // Didn't find one, need to create it + try { + ActiveBuffer buffers = new ActiveBuffer(token, uid, pid, packageName); + mActive.add(buffers); + return buffers; + } catch (IOException ex) { + throw new RemoteException("Failed to allocate space"); + } + } + + 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; + } + return data; + } + } + return null; + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); + synchronized (mLock) { + 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"); + } + 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(); + } + } + } + + private final class ActiveBuffer implements DeathRecipient { + final int mUid; + final int mPid; + final String mPackageName; + final IBinder mToken; + MemoryFile mProcessBuffer; + HistoricalData mPreviousData; + + ActiveBuffer(IBinder token, int uid, int pid, String packageName) + throws RemoteException, IOException { + mUid = uid; + mPid = pid; + mPackageName = packageName; + mToken = token; + 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); + } + } + + @Override + public void binderDied() { + mToken.unlinkToDeath(this, 0); + processDied(this); + } + + void closeAllBuffers() { + if (mProcessBuffer != null) { + mProcessBuffer.close(); + mProcessBuffer = null; + } + } + } + + 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) {} + } + } +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index ae2c54b..2effb44 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -925,6 +925,11 @@ public final class SystemServer { } } + if (!disableNonCoreServices) { + ServiceManager.addService(GraphicsStatsService.GRAPHICS_STATS_SERVICE, + new GraphicsStatsService(context)); + } + if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_PRINTING)) { mSystemServiceManager.startService(PRINT_MANAGER_SERVICE_CLASS); } |