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
diff --git a/Android.mk b/Android.mk
index 1df2af3..8a7d72ae 100644
--- a/Android.mk
+++ b/Android.mk
@@ -244,6 +244,7 @@
 	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.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 @@
         mRootNode.setClipToBounds(false);
         mNativeProxy = nCreateProxy(translucent, rootNodePtr);
 
-        AtlasInitializer.sInstance.init(context, mNativeProxy);
+        ProcessInitializer.sInstance.init(context, mNativeProxy);
 
         loadSystemProperties();
     }
@@ -410,15 +412,44 @@
         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 @@
                             // 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 @@
     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 @@
 
     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 @@
     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 @@
     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 @@
 
 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 @@
     { "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 7df61f27..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 @@
         = 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::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 @@
         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;
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 @@
     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;
+    static uint32_t findPercentile(const ProfileData* data, int p);
+    static void dumpData(const ProfileData* data, int fd);
+
     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 */
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 @@
         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 @@
 
     // 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 @@
     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 @@
     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 @@
     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 @@
                 }
             }
 
+            if (!disableNonCoreServices) {
+                ServiceManager.addService(GraphicsStatsService.GRAPHICS_STATS_SERVICE,
+                        new GraphicsStatsService(context));
+            }
+
             if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_PRINTING)) {
                 mSystemServiceManager.startService(PRINT_MANAGER_SERVICE_CLASS);
             }