b36016c65f
Change-Id: I5ad5e3b8fe55b1528f2e20c63e5abe51d9e40ff1
160 lines
5.4 KiB
C++
160 lines
5.4 KiB
C++
/*
|
|
* Copyright (C) 2015 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 "JankTracker.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstdio>
|
|
#include <inttypes.h>
|
|
|
|
namespace android {
|
|
namespace uirenderer {
|
|
|
|
static const char* JANK_TYPE_NAMES[] = {
|
|
"Missed Vsync",
|
|
"High input latency",
|
|
"Slow UI thread",
|
|
"Slow bitmap uploads",
|
|
"Slow draw",
|
|
};
|
|
|
|
struct Comparison {
|
|
FrameInfoIndex start;
|
|
FrameInfoIndex end;
|
|
};
|
|
|
|
static const Comparison COMPARISONS[] = {
|
|
{FrameInfoIndex::kIntendedVsync, FrameInfoIndex::kVsync},
|
|
{FrameInfoIndex::kOldestInputEvent, FrameInfoIndex::kVsync},
|
|
{FrameInfoIndex::kVsync, FrameInfoIndex::kSyncStart},
|
|
{FrameInfoIndex::kSyncStart, FrameInfoIndex::kIssueDrawCommandsStart},
|
|
{FrameInfoIndex::kIssueDrawCommandsStart, FrameInfoIndex::kFrameCompleted},
|
|
};
|
|
|
|
// If the event exceeds 10 seconds throw it away, this isn't a jank event
|
|
// it's an ANR and will be handled as such
|
|
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()
|
|
* 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::kWindowLayoutChanged
|
|
| FrameInfoFlags::kSurfaceCanvas;
|
|
|
|
JankTracker::JankTracker(nsecs_t frameIntervalNanos) {
|
|
reset();
|
|
setFrameInterval(frameIntervalNanos);
|
|
}
|
|
|
|
void JankTracker::setFrameInterval(nsecs_t frameInterval) {
|
|
mFrameInterval = frameInterval;
|
|
mThresholds[kMissedVsync] = 1;
|
|
/*
|
|
* Due to interpolation and sample rate differences between the touch
|
|
* panel and the display (example, 85hz touch panel driving a 60hz display)
|
|
* we call high latency 1.5 * frameinterval
|
|
*
|
|
* NOTE: Be careful when tuning this! A theoretical 1,000hz touch panel
|
|
* on a 60hz display will show kOldestInputEvent - kIntendedVsync of being 15ms
|
|
* Thus this must always be larger than frameInterval, or it will fail
|
|
*/
|
|
mThresholds[kHighInputLatency] = static_cast<int64_t>(1.5 * frameInterval);
|
|
|
|
// Note that these do not add up to 1. This is intentional. It's to deal
|
|
// with variance in values, and should be sort of an upper-bound on what
|
|
// is reasonable to expect.
|
|
mThresholds[kSlowUI] = static_cast<int64_t>(.5 * frameInterval);
|
|
mThresholds[kSlowSync] = static_cast<int64_t>(.2 * frameInterval);
|
|
mThresholds[kSlowRT] = static_cast<int64_t>(.75 * frameInterval);
|
|
|
|
}
|
|
|
|
void JankTracker::addFrame(const FrameInfo& frame) {
|
|
mTotalFrameCount++;
|
|
// 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());
|
|
// Keep the fast path as fast as possible.
|
|
if (CC_LIKELY(totalDuration < mFrameInterval)) {
|
|
mFrameCounts[framebucket]++;
|
|
return;
|
|
}
|
|
|
|
if (frame[FrameInfoIndex::kFlags] & EXEMPT_FRAMES_FLAGS) {
|
|
return;
|
|
}
|
|
|
|
mFrameCounts[framebucket]++;
|
|
mJankFrameCount++;
|
|
|
|
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++;
|
|
}
|
|
}
|
|
}
|
|
|
|
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));
|
|
for (int i = 0; i < NUM_BUCKETS; i++) {
|
|
fprintf(file, "\n Number %s: %u", JANK_TYPE_NAMES[i], mBuckets[i].count);
|
|
}
|
|
fprintf(file, "\n");
|
|
fflush(file);
|
|
}
|
|
|
|
void JankTracker::reset() {
|
|
mBuckets.fill({0});
|
|
mFrameCounts.fill(0);
|
|
mTotalFrameCount = 0;
|
|
mJankFrameCount = 0;
|
|
}
|
|
|
|
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];
|
|
if (remaining <= 0) {
|
|
return i;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
} /* namespace uirenderer */
|
|
} /* namespace android */
|