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