diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp index 47d01088f6f74..d1ff67027d078 100644 --- a/libs/hwui/JankTracker.cpp +++ b/libs/hwui/JankTracker.cpp @@ -56,24 +56,17 @@ static const Comparison COMPARISONS[] = { 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 @@ static const uint32_t kBucket4msIntervals = 48; // 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(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 @@ static uint32_t frameCountIndexForFrameTime(nsecs_t frameTime, uint32_t max) { // 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 @@ void JankTracker::setFrameInterval(nsecs_t frameInterval) { } -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(std::min( - ns2ms(frame[FrameInfoIndex::FrameCompleted] - frame[FrameInfoIndex::IntendedVsync]), - static_cast(std::numeric_limits::max()))); - uint16_t startHours = static_cast(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(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 @@ void JankTracker::dumpData(const ProfileData* data, int fd) { 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 @@ void JankTracker::reset() { 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 1a4a4897f4960..84b8c3f3f1553 100644 --- a/libs/hwui/JankTracker.h +++ b/libs/hwui/JankTracker.h @@ -39,23 +39,18 @@ enum JankType { 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 jankTypeCounts; // See comments on kBucket* constants for what this holds - std::array frameCounts; + std::array frameCounts; + // Holds a histogram of frame times in 50ms increments from 150ms to 5s + std::array slowFrameCounts; uint32_t totalFrameCount; uint32_t jankFrameCount; nsecs_t statStartTime; - - std::array slowestFrames; }; // TODO: Replace DrawProfiler with this @@ -78,7 +73,6 @@ public: 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 04223a7d51889..16dd108488b24 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -56,6 +56,7 @@ namespace DumpFlags { enum { FrameStats = 1 << 0, Reset = 1 << 1, + JankStats = 1 << 2, }; }; @@ -415,7 +416,6 @@ void RenderProxy::notifyFramePending() { 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 e29515fc36ae9..ecbe1ca98911f 100644 --- a/services/core/java/com/android/server/GraphicsStatsService.java +++ b/services/core/java/com/android/server/GraphicsStatsService.java @@ -51,7 +51,7 @@ import java.util.ArrayList; * 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 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 = 296; + private static final int ASHMEM_SIZE = 464; private static final int HISTORY_SIZE = 20; private final Context mContext;