summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohn Reck <jreck@google.com>2015-03-18 15:24:33 -0700
committerJohn Reck <jreck@google.com>2015-03-27 11:50:56 -0700
commitedc524c90506d80e0fc5fb67e8de7b8f3ef53439 (patch)
tree098c18daa80655fe0fa3faab7c39332685c163ef
parent1cef4196886b0cc1238111d396c1e3665a5fd2ae (diff)
downloadframeworks_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.mk1
-rw-r--r--core/java/android/view/IGraphicsStats.aidl26
-rw-r--r--core/java/android/view/ThreadedRenderer.java42
-rw-r--r--core/jni/android_view_ThreadedRenderer.cpp20
-rw-r--r--libs/hwui/JankTracker.cpp171
-rw-r--r--libs/hwui/JankTracker.h38
-rw-r--r--libs/hwui/renderthread/CanvasContext.cpp8
-rw-r--r--libs/hwui/renderthread/CanvasContext.h13
-rw-r--r--libs/hwui/renderthread/RenderProxy.cpp13
-rw-r--r--libs/hwui/renderthread/RenderProxy.h1
-rw-r--r--services/core/java/com/android/server/GraphicsStatsService.java256
-rw-r--r--services/java/com/android/server/SystemServer.java5
12 files changed, 535 insertions, 59 deletions
diff --git a/Android.mk b/Android.mk
index 1df2af3..8a7d72a 100644
--- a/Android.mk
+++ b/Android.mk
@@ -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);
}