Expand JankTracker
Bug: 27922347
* Dump the full histogram
* Expand the histogram to have a slow-frame section with
large 50ms buckets to raise the cap to 5s to give more
insight into system-health
* Stop excluding first-frame metrics as we want to include
those in our global tracking. Automated tests already filter
these out by doing resets before running anyway.
Change-Id: Idaba8aad591f59d10a6477b11efc0767ff715083
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index 47d0108..d1ff670 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -56,24 +56,17 @@
static const int64_t IGNORE_EXCEEDING = seconds_to_nanoseconds(10);
/*
- * Frames that are exempt from jank metrics.
- * First-draw frames, for example, are expected to
- * be slow, this is hidden from the user with window animations and
- * other tricks
- *
- * Similarly, we don't track direct-drawing via Surface:lockHardwareCanvas()
+ * We don't track direct-drawing via Surface:lockHardwareCanvas()
* for now
*
* TODO: kSurfaceCanvas can negatively impact other drawing by using up
* time on the RenderThread, figure out how to attribute that as a jank-causer
*/
-static const int64_t EXEMPT_FRAMES_FLAGS
- = FrameInfoFlags::WindowLayoutChanged
- | FrameInfoFlags::SurfaceCanvas;
+static const int64_t EXEMPT_FRAMES_FLAGS = FrameInfoFlags::SurfaceCanvas;
// The bucketing algorithm controls so to speak
// If a frame is <= to this it goes in bucket 0
-static const uint32_t kBucketMinThreshold = 7;
+static const uint32_t kBucketMinThreshold = 5;
// 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
@@ -84,9 +77,14 @@
// and filter it out of the frame profile data
static FrameInfoIndex sFrameStart = FrameInfoIndex::IntendedVsync;
+// The interval of the slow frame histogram
+static const uint32_t kSlowFrameBucketIntervalMs = 50;
+// The start point of the slow frame bucket in ms
+static const uint32_t kSlowFrameBucketStartMs = 150;
+
// 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) {
+static uint32_t frameCountIndexForFrameTime(nsecs_t frameTime) {
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
@@ -104,7 +102,7 @@
// 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;
+ return index;
}
// Only called when dumping stats, less performance sensitive
@@ -211,63 +209,34 @@
}
-static bool shouldReplace(SlowFrame& existing, SlowFrame& candidate) {
- if (candidate.whenHours - existing.whenHours >= 24) {
- // If the old slowframe is over 24 hours older than the candidate,
- // replace it. It's too stale
- return true;
- }
- if (candidate.frametimeMs > existing.frametimeMs) {
- return true;
- }
- return false;
-}
-
-void JankTracker::updateSlowest(const FrameInfo& frame) {
- uint16_t durationMs = static_cast<uint16_t>(std::min(
- ns2ms(frame[FrameInfoIndex::FrameCompleted] - frame[FrameInfoIndex::IntendedVsync]),
- static_cast<nsecs_t>(std::numeric_limits<uint16_t>::max())));
- uint16_t startHours = static_cast<uint16_t>(std::lround(
- ns2s(frame[FrameInfoIndex::IntendedVsync]) / 3600.0f));
- SlowFrame* toReplace = nullptr;
- SlowFrame thisFrame{startHours, durationMs};
- // First find the best candidate for replacement
- for (SlowFrame& existing : mData->slowestFrames) {
- // If we should replace the current data with the replacement candidate,
- // it means the current data is worse than the replacement candidate
- if (!toReplace || shouldReplace(existing, *toReplace)) {
- toReplace = &existing;
- }
- }
- // Now see if we should replace it
- if (shouldReplace(*toReplace, thisFrame)) {
- *toReplace = thisFrame;
- }
-}
-
void JankTracker::addFrame(const FrameInfo& frame) {
mData->totalFrameCount++;
// Fast-path for jank-free frames
int64_t totalDuration =
frame[FrameInfoIndex::FrameCompleted] - frame[sFrameStart];
- uint32_t framebucket = frameCountIndexForFrameTime(
- totalDuration, mData->frameCounts.size() - 1);
+ uint32_t framebucket = frameCountIndexForFrameTime(totalDuration);
// Keep the fast path as fast as possible.
if (CC_LIKELY(totalDuration < mFrameInterval)) {
mData->frameCounts[framebucket]++;
return;
}
- // For slowest frames we are still interested in frames that are otherwise
- // exempt (such as first-draw). Although those frames don't directly impact
- // smoothness, they do impact responsiveness.
- updateSlowest(frame);
-
+ // Only things like Surface.lockHardwareCanvas() are exempt from tracking
if (frame[FrameInfoIndex::Flags] & EXEMPT_FRAMES_FLAGS) {
return;
}
- mData->frameCounts[framebucket]++;
+ if (framebucket <= mData->frameCounts.size()) {
+ mData->frameCounts[framebucket]++;
+ } else {
+ framebucket = (ns2ms(totalDuration) - kSlowFrameBucketStartMs)
+ / kSlowFrameBucketIntervalMs;
+ framebucket = std::min(framebucket,
+ static_cast<uint32_t>(mData->slowFrameCounts.size() - 1));
+ framebucket = std::max(framebucket, 0u);
+ mData->slowFrameCounts[framebucket]++;
+ }
+
mData->jankFrameCount++;
for (int i = 0; i < NUM_BUCKETS; i++) {
@@ -298,14 +267,18 @@
dprintf(fd, "\n90th percentile: %ums", findPercentile(data, 90));
dprintf(fd, "\n95th percentile: %ums", findPercentile(data, 95));
dprintf(fd, "\n99th percentile: %ums", findPercentile(data, 99));
- dprintf(fd, "\nSlowest frames over last 24h: ");
- for (auto& slowFrame : data->slowestFrames) {
- if (!slowFrame.frametimeMs) continue;
- dprintf(fd, "%ums ", slowFrame.frametimeMs);
- }
for (int i = 0; i < NUM_BUCKETS; i++) {
dprintf(fd, "\nNumber %s: %u", JANK_TYPE_NAMES[i], data->jankTypeCounts[i]);
}
+ dprintf(fd, "\nHISTOGRAM:");
+ for (size_t i = 0; i < data->frameCounts.size(); i++) {
+ dprintf(fd, " %ums=%u", frameTimeForFrameCountIndex(i),
+ data->frameCounts[i]);
+ }
+ for (size_t i = 0; i < data->slowFrameCounts.size(); i++) {
+ dprintf(fd, " %zums=%u", (i * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs,
+ data->slowFrameCounts[i]);
+ }
dprintf(fd, "\n");
}
@@ -323,6 +296,12 @@
uint32_t JankTracker::findPercentile(const ProfileData* data, int percentile) {
int pos = percentile * data->totalFrameCount / 100;
int remaining = data->totalFrameCount - pos;
+ for (int i = data->slowFrameCounts.size() - 1; i >= 0; i--) {
+ remaining -= data->slowFrameCounts[i];
+ if (remaining <= 0) {
+ return (i * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs;
+ }
+ }
for (int i = data->frameCounts.size() - 1; i >= 0; i--) {
remaining -= data->frameCounts[i];
if (remaining <= 0) {
diff --git a/libs/hwui/JankTracker.h b/libs/hwui/JankTracker.h
index 1a4a489..84b8c3f 100644
--- a/libs/hwui/JankTracker.h
+++ b/libs/hwui/JankTracker.h
@@ -39,23 +39,18 @@
NUM_BUCKETS,
};
-struct SlowFrame {
- uint16_t whenHours; // When this occurred in CLOCK_MONOTONIC in hours
- uint16_t frametimeMs; // How long the frame took in ms
-};
-
// 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, 55> frameCounts;
+ std::array<uint32_t, 57> frameCounts;
+ // Holds a histogram of frame times in 50ms increments from 150ms to 5s
+ std::array<uint16_t, 97> slowFrameCounts;
uint32_t totalFrameCount;
uint32_t jankFrameCount;
nsecs_t statStartTime;
-
- std::array<SlowFrame, 10> slowestFrames;
};
// TODO: Replace DrawProfiler with this
@@ -78,7 +73,6 @@
private:
void freeData();
void setFrameInterval(nsecs_t frameIntervalNanos);
- void updateSlowest(const FrameInfo& frame);
static uint32_t findPercentile(const ProfileData* data, int p);
static void dumpData(const ProfileData* data, int fd);
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 04223a7..16dd108 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -56,6 +56,7 @@
enum {
FrameStats = 1 << 0,
Reset = 1 << 1,
+ JankStats = 1 << 2,
};
};
@@ -415,7 +416,6 @@
CREATE_BRIDGE4(dumpProfileInfo, CanvasContext* context, RenderThread* thread,
int fd, int dumpFlags) {
args->context->profiler().dumpData(args->fd);
- args->thread->jankTracker().dump(args->fd);
if (args->dumpFlags & DumpFlags::FrameStats) {
args->context->dumpFrames(args->fd);
}
diff --git a/services/core/java/com/android/server/GraphicsStatsService.java b/services/core/java/com/android/server/GraphicsStatsService.java
index e29515f..ecbe1ca 100644
--- a/services/core/java/com/android/server/GraphicsStatsService.java
+++ b/services/core/java/com/android/server/GraphicsStatsService.java
@@ -51,7 +51,7 @@
* 2) ASHMEM_SIZE (for scratch space used during dumping)
* 3) ASHMEM_SIZE * HISTORY_SIZE
*
- * This is currently under 16KiB total memory in the worst case of
+ * This is currently under 20KiB total memory in the worst case of
* 20 processes in history + 10 unique active processes.
*
* @hide */
@@ -59,7 +59,7 @@
public static final String GRAPHICS_STATS_SERVICE = "graphicsstats";
private static final String TAG = "GraphicsStatsService";
- private static final int ASHMEM_SIZE = 296;
+ private static final int ASHMEM_SIZE = 464;
private static final int HISTORY_SIZE = 20;
private final Context mContext;