Merge "Expand JankTracker" into nyc-dev
am: c48dd5d
* commit 'c48dd5d81e2fadf33314f0e738fd9fc406c6c533':
Expand JankTracker
Change-Id: I2c3c5a8e5a8d279f1457be6050674d5208135727
This commit is contained in:
@@ -56,24 +56,17 @@ static const Comparison COMPARISONS[] = {
|
|||||||
static const int64_t IGNORE_EXCEEDING = seconds_to_nanoseconds(10);
|
static const int64_t IGNORE_EXCEEDING = seconds_to_nanoseconds(10);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Frames that are exempt from jank metrics.
|
* We don't track direct-drawing via Surface:lockHardwareCanvas()
|
||||||
* 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
|
* for now
|
||||||
*
|
*
|
||||||
* TODO: kSurfaceCanvas can negatively impact other drawing by using up
|
* TODO: kSurfaceCanvas can negatively impact other drawing by using up
|
||||||
* time on the RenderThread, figure out how to attribute that as a jank-causer
|
* time on the RenderThread, figure out how to attribute that as a jank-causer
|
||||||
*/
|
*/
|
||||||
static const int64_t EXEMPT_FRAMES_FLAGS
|
static const int64_t EXEMPT_FRAMES_FLAGS = FrameInfoFlags::SurfaceCanvas;
|
||||||
= FrameInfoFlags::WindowLayoutChanged
|
|
||||||
| FrameInfoFlags::SurfaceCanvas;
|
|
||||||
|
|
||||||
// The bucketing algorithm controls so to speak
|
// The bucketing algorithm controls so to speak
|
||||||
// If a frame is <= to this it goes in bucket 0
|
// 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
|
// If a frame is > this, start counting in increments of 2ms
|
||||||
static const uint32_t kBucket2msIntervals = 32;
|
static const uint32_t kBucket2msIntervals = 32;
|
||||||
// If a frame is > this, start counting in increments of 4ms
|
// If a frame is > this, start counting in increments of 4ms
|
||||||
@@ -84,9 +77,14 @@ static const uint32_t kBucket4msIntervals = 48;
|
|||||||
// and filter it out of the frame profile data
|
// and filter it out of the frame profile data
|
||||||
static FrameInfoIndex sFrameStart = FrameInfoIndex::IntendedVsync;
|
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
|
// This will be called every frame, performance sensitive
|
||||||
// Uses bit twiddling to avoid branching while achieving the packing desired
|
// 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));
|
uint32_t index = static_cast<uint32_t>(ns2ms(frameTime));
|
||||||
// If index > kBucketMinThreshold mask will be 0xFFFFFFFF as a result
|
// If index > kBucketMinThreshold mask will be 0xFFFFFFFF as a result
|
||||||
// of negating 1 (twos compliment, yaay) else mask will be 0
|
// of negating 1 (twos compliment, yaay) else mask will be 0
|
||||||
@@ -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
|
// be a pretty garbage value right now. However, mask is 0 so we'll end
|
||||||
// up with the desired result of 0.
|
// up with the desired result of 0.
|
||||||
index = (index - kBucketMinThreshold) & mask;
|
index = (index - kBucketMinThreshold) & mask;
|
||||||
return index < max ? index : max;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only called when dumping stats, less performance sensitive
|
// Only called when dumping stats, less performance sensitive
|
||||||
@@ -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) {
|
void JankTracker::addFrame(const FrameInfo& frame) {
|
||||||
mData->totalFrameCount++;
|
mData->totalFrameCount++;
|
||||||
// Fast-path for jank-free frames
|
// Fast-path for jank-free frames
|
||||||
int64_t totalDuration =
|
int64_t totalDuration =
|
||||||
frame[FrameInfoIndex::FrameCompleted] - frame[sFrameStart];
|
frame[FrameInfoIndex::FrameCompleted] - frame[sFrameStart];
|
||||||
uint32_t framebucket = frameCountIndexForFrameTime(
|
uint32_t framebucket = frameCountIndexForFrameTime(totalDuration);
|
||||||
totalDuration, mData->frameCounts.size() - 1);
|
|
||||||
// Keep the fast path as fast as possible.
|
// Keep the fast path as fast as possible.
|
||||||
if (CC_LIKELY(totalDuration < mFrameInterval)) {
|
if (CC_LIKELY(totalDuration < mFrameInterval)) {
|
||||||
mData->frameCounts[framebucket]++;
|
mData->frameCounts[framebucket]++;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For slowest frames we are still interested in frames that are otherwise
|
// Only things like Surface.lockHardwareCanvas() are exempt from tracking
|
||||||
// exempt (such as first-draw). Although those frames don't directly impact
|
|
||||||
// smoothness, they do impact responsiveness.
|
|
||||||
updateSlowest(frame);
|
|
||||||
|
|
||||||
if (frame[FrameInfoIndex::Flags] & EXEMPT_FRAMES_FLAGS) {
|
if (frame[FrameInfoIndex::Flags] & EXEMPT_FRAMES_FLAGS) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mData->frameCounts[framebucket]++;
|
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++;
|
mData->jankFrameCount++;
|
||||||
|
|
||||||
for (int i = 0; i < NUM_BUCKETS; i++) {
|
for (int i = 0; i < NUM_BUCKETS; i++) {
|
||||||
@@ -298,14 +267,18 @@ void JankTracker::dumpData(const ProfileData* data, int fd) {
|
|||||||
dprintf(fd, "\n90th percentile: %ums", findPercentile(data, 90));
|
dprintf(fd, "\n90th percentile: %ums", findPercentile(data, 90));
|
||||||
dprintf(fd, "\n95th percentile: %ums", findPercentile(data, 95));
|
dprintf(fd, "\n95th percentile: %ums", findPercentile(data, 95));
|
||||||
dprintf(fd, "\n99th percentile: %ums", findPercentile(data, 99));
|
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++) {
|
for (int i = 0; i < NUM_BUCKETS; i++) {
|
||||||
dprintf(fd, "\nNumber %s: %u", JANK_TYPE_NAMES[i], data->jankTypeCounts[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");
|
dprintf(fd, "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,6 +296,12 @@ void JankTracker::reset() {
|
|||||||
uint32_t JankTracker::findPercentile(const ProfileData* data, int percentile) {
|
uint32_t JankTracker::findPercentile(const ProfileData* data, int percentile) {
|
||||||
int pos = percentile * data->totalFrameCount / 100;
|
int pos = percentile * data->totalFrameCount / 100;
|
||||||
int remaining = data->totalFrameCount - pos;
|
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--) {
|
for (int i = data->frameCounts.size() - 1; i >= 0; i--) {
|
||||||
remaining -= data->frameCounts[i];
|
remaining -= data->frameCounts[i];
|
||||||
if (remaining <= 0) {
|
if (remaining <= 0) {
|
||||||
|
|||||||
@@ -39,23 +39,18 @@ enum JankType {
|
|||||||
NUM_BUCKETS,
|
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
|
// Try to keep as small as possible, should match ASHMEM_SIZE in
|
||||||
// GraphicsStatsService.java
|
// GraphicsStatsService.java
|
||||||
struct ProfileData {
|
struct ProfileData {
|
||||||
std::array<uint32_t, NUM_BUCKETS> jankTypeCounts;
|
std::array<uint32_t, NUM_BUCKETS> jankTypeCounts;
|
||||||
// See comments on kBucket* constants for what this holds
|
// 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 totalFrameCount;
|
||||||
uint32_t jankFrameCount;
|
uint32_t jankFrameCount;
|
||||||
nsecs_t statStartTime;
|
nsecs_t statStartTime;
|
||||||
|
|
||||||
std::array<SlowFrame, 10> slowestFrames;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Replace DrawProfiler with this
|
// TODO: Replace DrawProfiler with this
|
||||||
@@ -78,7 +73,6 @@ public:
|
|||||||
private:
|
private:
|
||||||
void freeData();
|
void freeData();
|
||||||
void setFrameInterval(nsecs_t frameIntervalNanos);
|
void setFrameInterval(nsecs_t frameIntervalNanos);
|
||||||
void updateSlowest(const FrameInfo& frame);
|
|
||||||
|
|
||||||
static uint32_t findPercentile(const ProfileData* data, int p);
|
static uint32_t findPercentile(const ProfileData* data, int p);
|
||||||
static void dumpData(const ProfileData* data, int fd);
|
static void dumpData(const ProfileData* data, int fd);
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ namespace DumpFlags {
|
|||||||
enum {
|
enum {
|
||||||
FrameStats = 1 << 0,
|
FrameStats = 1 << 0,
|
||||||
Reset = 1 << 1,
|
Reset = 1 << 1,
|
||||||
|
JankStats = 1 << 2,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -415,7 +416,6 @@ void RenderProxy::notifyFramePending() {
|
|||||||
CREATE_BRIDGE4(dumpProfileInfo, CanvasContext* context, RenderThread* thread,
|
CREATE_BRIDGE4(dumpProfileInfo, CanvasContext* context, RenderThread* thread,
|
||||||
int fd, int dumpFlags) {
|
int fd, int dumpFlags) {
|
||||||
args->context->profiler().dumpData(args->fd);
|
args->context->profiler().dumpData(args->fd);
|
||||||
args->thread->jankTracker().dump(args->fd);
|
|
||||||
if (args->dumpFlags & DumpFlags::FrameStats) {
|
if (args->dumpFlags & DumpFlags::FrameStats) {
|
||||||
args->context->dumpFrames(args->fd);
|
args->context->dumpFrames(args->fd);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ import java.util.ArrayList;
|
|||||||
* 2) ASHMEM_SIZE (for scratch space used during dumping)
|
* 2) ASHMEM_SIZE (for scratch space used during dumping)
|
||||||
* 3) ASHMEM_SIZE * HISTORY_SIZE
|
* 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.
|
* 20 processes in history + 10 unique active processes.
|
||||||
*
|
*
|
||||||
* @hide */
|
* @hide */
|
||||||
@@ -59,7 +59,7 @@ public class GraphicsStatsService extends IGraphicsStats.Stub {
|
|||||||
public static final String GRAPHICS_STATS_SERVICE = "graphicsstats";
|
public static final String GRAPHICS_STATS_SERVICE = "graphicsstats";
|
||||||
|
|
||||||
private static final String TAG = "GraphicsStatsService";
|
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 static final int HISTORY_SIZE = 20;
|
||||||
|
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
|
|||||||
Reference in New Issue
Block a user