Merge "Add GraphicsStatsService"
This commit is contained in:
@@ -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 \
|
||||
|
||||
26
core/java/android/view/IGraphicsStats.aidl
Normal file
26
core/java/android/view/IGraphicsStats.aidl
Normal file
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 },
|
||||
};
|
||||
|
||||
@@ -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));
|
||||
for (int i = 0; i < NUM_BUCKETS; i++) {
|
||||
fprintf(file, "\n Number %s: %u", JANK_TYPE_NAMES[i], mBuckets[i].count);
|
||||
void JankTracker::dumpBuffer(const void* buffer, size_t bufsize, int fd) {
|
||||
if (bufsize < sizeof(ProfileData)) {
|
||||
return;
|
||||
}
|
||||
fprintf(file, "\n");
|
||||
fflush(file);
|
||||
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++) {
|
||||
dprintf(fd, "\nNumber %s: %u", JANK_TYPE_NAMES[i], data->jankTypeCounts[i]);
|
||||
}
|
||||
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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
static uint32_t findPercentile(const ProfileData* data, int p);
|
||||
static void dumpData(const ProfileData* data, int fd);
|
||||
|
||||
std::array<JankBucket, NUM_BUCKETS> mBuckets;
|
||||
std::array<int64_t, NUM_BUCKETS> mThresholds;
|
||||
std::array<uint32_t, 128> mFrameCounts;
|
||||
|
||||
int64_t mFrameInterval;
|
||||
uint32_t mTotalFrameCount;
|
||||
uint32_t mJankFrameCount;
|
||||
ProfileData* mData;
|
||||
bool mIsMapped = false;
|
||||
};
|
||||
|
||||
} /* namespace uirenderer */
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -441,6 +441,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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
256
services/core/java/com/android/server/GraphicsStatsService.java
Normal file
256
services/core/java/com/android/server/GraphicsStatsService.java
Normal file
@@ -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) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -912,6 +912,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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user