Commit edc524c9 authored by John Reck's avatar John Reck
Browse files

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
parent 1cef4196
......@@ -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 \
......
/**
* 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));
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;
......
......@@ -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 */
......
......@@ -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;
};
......
......@@ -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);
}
......
......@@ -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;
......
/*
* 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) {}
}
}
}
......@@ -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);
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment