Merge "Add GraphicsStatsService"

This commit is contained in:
John Reck
2015-03-30 19:39:25 +00:00
committed by Android (Google) Code Review
12 changed files with 536 additions and 60 deletions

View File

@@ -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 \

View 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);
}

View File

@@ -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);
}

View File

@@ -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 },
};

View File

@@ -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;

View File

@@ -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 */

View File

@@ -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();
}

View File

@@ -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;
};

View File

@@ -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);
}

View File

@@ -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;

View 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) {}
}
}
}

View File

@@ -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);
}