This reverts commit a6d8fbf4ea634f5f605b2b7db3ca98975f8625b5. Fixes an out-of-bounds read in COMPARISONS by switching up how comparisons works. Instead of requiring all jank types to have an associated COMPARISON's entry, which kHighInputLatency and kMissedDeadline don't, instead have each COMPARISON indicate which JankType it applies to so it can be independently sized from JankTypes. Bug: 70220906 Bug: 75566601 Test: launching & using maps works Change-Id: I7fd90daeb320b4627e42c3418c89726d860998c1
170 lines
6.6 KiB
C++
170 lines
6.6 KiB
C++
/*
|
|
* Copyright (C) 2017 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "ProfileData.h"
|
|
|
|
#include <cinttypes>
|
|
|
|
namespace android {
|
|
namespace uirenderer {
|
|
|
|
static const char* JANK_TYPE_NAMES[] = {
|
|
"Missed Vsync", "High input latency", "Slow UI thread",
|
|
"Slow bitmap uploads", "Slow issue draw commands", "Frame deadline missed"};
|
|
|
|
// The bucketing algorithm controls so to speak
|
|
// If a frame is <= to this it goes in bucket 0
|
|
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
|
|
static const uint32_t kBucket4msIntervals = 48;
|
|
|
|
// 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 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;
|
|
}
|
|
|
|
// Only called when dumping stats, less performance sensitive
|
|
uint32_t ProfileData::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;
|
|
}
|
|
|
|
uint32_t ProfileData::frameTimeForSlowFrameCountIndex(uint32_t index) {
|
|
return (index * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs;
|
|
}
|
|
|
|
void ProfileData::mergeWith(const ProfileData& other) {
|
|
// Make sure we don't overflow Just In Case
|
|
uint32_t divider = 0;
|
|
if (mTotalFrameCount > (1 << 24)) {
|
|
divider = 4;
|
|
}
|
|
for (size_t i = 0; i < other.mJankTypeCounts.size(); i++) {
|
|
mJankTypeCounts[i] >>= divider;
|
|
mJankTypeCounts[i] += other.mJankTypeCounts[i];
|
|
}
|
|
for (size_t i = 0; i < other.mFrameCounts.size(); i++) {
|
|
mFrameCounts[i] >>= divider;
|
|
mFrameCounts[i] += other.mFrameCounts[i];
|
|
}
|
|
mJankFrameCount >>= divider;
|
|
mJankFrameCount += other.mJankFrameCount;
|
|
mTotalFrameCount >>= divider;
|
|
mTotalFrameCount += other.mTotalFrameCount;
|
|
if (mStatStartTime > other.mStatStartTime || mStatStartTime == 0) {
|
|
mStatStartTime = other.mStatStartTime;
|
|
}
|
|
}
|
|
|
|
void ProfileData::dump(int fd) const {
|
|
dprintf(fd, "\nStats since: %" PRIu64 "ns", mStatStartTime);
|
|
dprintf(fd, "\nTotal frames rendered: %u", mTotalFrameCount);
|
|
dprintf(fd, "\nJanky frames: %u (%.2f%%)", mJankFrameCount,
|
|
(float)mJankFrameCount / (float)mTotalFrameCount * 100.0f);
|
|
dprintf(fd, "\n50th percentile: %ums", findPercentile(50));
|
|
dprintf(fd, "\n90th percentile: %ums", findPercentile(90));
|
|
dprintf(fd, "\n95th percentile: %ums", findPercentile(95));
|
|
dprintf(fd, "\n99th percentile: %ums", findPercentile(99));
|
|
for (int i = 0; i < NUM_BUCKETS; i++) {
|
|
dprintf(fd, "\nNumber %s: %u", JANK_TYPE_NAMES[i], mJankTypeCounts[i]);
|
|
}
|
|
dprintf(fd, "\nHISTOGRAM:");
|
|
histogramForEach([fd](HistogramEntry entry) {
|
|
dprintf(fd, " %ums=%u", entry.renderTimeMs, entry.frameCount);
|
|
});
|
|
}
|
|
|
|
uint32_t ProfileData::findPercentile(int percentile) const {
|
|
int pos = percentile * mTotalFrameCount / 100;
|
|
int remaining = mTotalFrameCount - pos;
|
|
for (int i = mSlowFrameCounts.size() - 1; i >= 0; i--) {
|
|
remaining -= mSlowFrameCounts[i];
|
|
if (remaining <= 0) {
|
|
return (i * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs;
|
|
}
|
|
}
|
|
for (int i = mFrameCounts.size() - 1; i >= 0; i--) {
|
|
remaining -= mFrameCounts[i];
|
|
if (remaining <= 0) {
|
|
return frameTimeForFrameCountIndex(i);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void ProfileData::reset() {
|
|
mJankTypeCounts.fill(0);
|
|
mFrameCounts.fill(0);
|
|
mSlowFrameCounts.fill(0);
|
|
mTotalFrameCount = 0;
|
|
mJankFrameCount = 0;
|
|
mStatStartTime = systemTime(CLOCK_MONOTONIC);
|
|
}
|
|
|
|
void ProfileData::reportFrame(int64_t duration) {
|
|
mTotalFrameCount++;
|
|
uint32_t framebucket = frameCountIndexForFrameTime(duration);
|
|
if (framebucket <= mFrameCounts.size()) {
|
|
mFrameCounts[framebucket]++;
|
|
} else {
|
|
framebucket = (ns2ms(duration) - kSlowFrameBucketStartMs) / kSlowFrameBucketIntervalMs;
|
|
framebucket = std::min(framebucket, static_cast<uint32_t>(mSlowFrameCounts.size() - 1));
|
|
mSlowFrameCounts[framebucket]++;
|
|
}
|
|
}
|
|
|
|
void ProfileData::histogramForEach(const std::function<void(HistogramEntry)>& callback) const {
|
|
for (size_t i = 0; i < mFrameCounts.size(); i++) {
|
|
callback(HistogramEntry{frameTimeForFrameCountIndex(i), mFrameCounts[i]});
|
|
}
|
|
for (size_t i = 0; i < mSlowFrameCounts.size(); i++) {
|
|
callback(HistogramEntry{frameTimeForSlowFrameCountIndex(i), mSlowFrameCounts[i]});
|
|
}
|
|
}
|
|
|
|
} /* namespace uirenderer */
|
|
} /* namespace android */ |