blob: 46b094556ad7ed8d0cc58c49fa164918ff8b5e8d [file] [log] [blame]
/*
* 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 sizeof(mFrameCounts)>(ns2ms(totalDuration)),
sizeof(mFrameCounts) / sizeof(mFrameCounts[0]));
// 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() {
memset(mBuckets, 0, sizeof(mBuckets));
memset(mFrameCounts, 0, sizeof(mFrameCounts));
mTotalFrameCount = 0;
mJankFrameCount = 0;
}
uint32_t JankTracker::findPercentile(int percentile) {
int pos = percentile * mTotalFrameCount / 100;
int remaining = mTotalFrameCount - pos;
for (int i = sizeof(mFrameCounts) / sizeof(mFrameCounts[0]) - 1; i >= 0; i--) {
remaining -= mFrameCounts[i];
if (remaining <= 0) {
return i;
}
}
return 0;
}
} /* namespace uirenderer */
} /* namespace android */