diff options
Diffstat (limited to 'libs/hwui/JankTracker.cpp')
| -rw-r--r-- | libs/hwui/JankTracker.cpp | 177 |
1 files changed, 147 insertions, 30 deletions
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp index 7df61f2..cc5c403 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,118 @@ static const int64_t EXEMPT_FRAMES_FLAGS = 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, static_cast<unsigned int>(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; + if (newData->statStartTime > mData->statStartTime + || newData->statStartTime == 0) { + newData->statStartTime = mData->statStartTime; + } + + freeData(); + mData = newData; + mIsMapped = true; +} + void JankTracker::setFrameInterval(nsecs_t frameInterval) { mFrameInterval = frameInterval; mThresholds[kMissedVsync] = 1; @@ -92,16 +203,15 @@ void JankTracker::setFrameInterval(nsecs_t frameInterval) { } 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 +219,54 @@ void JankTracker::addFrame(const FrameInfo& frame) { 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)); +void JankTracker::dumpBuffer(const void* buffer, size_t bufsize, int fd) { + if (bufsize < sizeof(ProfileData)) { + return; + } + const ProfileData* data = reinterpret_cast<const ProfileData*>(buffer); + dumpData(data, fd); +} + +void JankTracker::dumpData(const ProfileData* data, int fd) { + dprintf(fd, "\nStats since: %" PRIu64 "ns", data->statStartTime); + 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++) { - fprintf(file, "\n Number %s: %u", JANK_TYPE_NAMES[i], mBuckets[i].count); + dprintf(fd, "\nNumber %s: %u", JANK_TYPE_NAMES[i], data->jankTypeCounts[i]); } - fprintf(file, "\n"); - fflush(file); + 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; + mData->statStartTime = systemTime(CLOCK_MONOTONIC); } -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; |
