Loading libs/hwui/JankTracker.cpp +38 −59 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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<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 Loading @@ -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 Loading Loading @@ -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<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; } 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++) { Loading Loading @@ -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"); } Loading @@ -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) { Loading libs/hwui/JankTracker.h +3 −9 Original line number Diff line number Diff line Loading @@ -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<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 Loading @@ -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); Loading libs/hwui/renderthread/RenderProxy.cpp +1 −1 Original line number Diff line number Diff line Loading @@ -56,6 +56,7 @@ namespace DumpFlags { enum { FrameStats = 1 << 0, Reset = 1 << 1, JankStats = 1 << 2, }; }; Loading Loading @@ -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); } Loading services/core/java/com/android/server/GraphicsStatsService.java +2 −2 Original line number Diff line number Diff line Loading @@ -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 */ Loading @@ -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; Loading Loading
libs/hwui/JankTracker.cpp +38 −59 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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<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 Loading @@ -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 Loading Loading @@ -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<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; } 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++) { Loading Loading @@ -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"); } Loading @@ -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) { Loading
libs/hwui/JankTracker.h +3 −9 Original line number Diff line number Diff line Loading @@ -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<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 Loading @@ -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); Loading
libs/hwui/renderthread/RenderProxy.cpp +1 −1 Original line number Diff line number Diff line Loading @@ -56,6 +56,7 @@ namespace DumpFlags { enum { FrameStats = 1 << 0, Reset = 1 << 1, JankStats = 1 << 2, }; }; Loading Loading @@ -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); } Loading
services/core/java/com/android/server/GraphicsStatsService.java +2 −2 Original line number Diff line number Diff line Loading @@ -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 */ Loading @@ -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; Loading