Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6fa7b461 authored by John Reck's avatar John Reck Committed by android-build-merger
Browse files

Merge "Expand JankTracker" into nyc-dev

am: c48dd5d8

* commit 'c48dd5d8':
  Expand JankTracker

Change-Id: I2c3c5a8e5a8d279f1457be6050674d5208135727
parents 13041985 c48dd5d8
Loading
Loading
Loading
Loading
+38 −59
Original line number Diff line number Diff line
@@ -56,24 +56,17 @@ static const Comparison COMPARISONS[] = {
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()
 * 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::WindowLayoutChanged
        | FrameInfoFlags::SurfaceCanvas;
static const int64_t EXEMPT_FRAMES_FLAGS = FrameInfoFlags::SurfaceCanvas;

// The bucketing algorithm controls so to speak
// 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
static const uint32_t kBucket2msIntervals = 32;
// 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
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
// 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));
    // If index > kBucketMinThreshold mask will be 0xFFFFFFFF as a result
    // 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
    // up with the desired result of 0.
    index = (index - kBucketMinThreshold) & mask;
    return index < max ? index : max;
    return index;
}

// 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) {
    mData->totalFrameCount++;
    // Fast-path for jank-free frames
    int64_t totalDuration =
            frame[FrameInfoIndex::FrameCompleted] - frame[sFrameStart];
    uint32_t framebucket = frameCountIndexForFrameTime(
            totalDuration, mData->frameCounts.size() - 1);
    uint32_t framebucket = frameCountIndexForFrameTime(totalDuration);
    // Keep the fast path as fast as possible.
    if (CC_LIKELY(totalDuration < mFrameInterval)) {
        mData->frameCounts[framebucket]++;
        return;
    }

    // For slowest frames we are still interested in frames that are otherwise
    // exempt (such as first-draw). Although those frames don't directly impact
    // smoothness, they do impact responsiveness.
    updateSlowest(frame);

    // Only things like Surface.lockHardwareCanvas() are exempt from tracking
    if (frame[FrameInfoIndex::Flags] & EXEMPT_FRAMES_FLAGS) {
        return;
    }

    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++;

    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, "\n95th percentile: %ums", findPercentile(data, 95));
    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++) {
        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");
}

@@ -323,6 +296,12 @@ void JankTracker::reset() {
uint32_t JankTracker::findPercentile(const ProfileData* data, int percentile) {
    int pos = percentile * data->totalFrameCount / 100;
    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--) {
        remaining -= data->frameCounts[i];
        if (remaining <= 0) {
+3 −9
Original line number Diff line number Diff line
@@ -39,23 +39,18 @@ enum JankType {
    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
// GraphicsStatsService.java
struct ProfileData {
    std::array<uint32_t, NUM_BUCKETS> jankTypeCounts;
    // 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 jankFrameCount;
    nsecs_t statStartTime;

    std::array<SlowFrame, 10> slowestFrames;
};

// TODO: Replace DrawProfiler with this
@@ -78,7 +73,6 @@ public:
private:
    void freeData();
    void setFrameInterval(nsecs_t frameIntervalNanos);
    void updateSlowest(const FrameInfo& frame);

    static uint32_t findPercentile(const ProfileData* data, int p);
    static void dumpData(const ProfileData* data, int fd);
+1 −1
Original line number Diff line number Diff line
@@ -56,6 +56,7 @@ namespace DumpFlags {
    enum {
        FrameStats = 1 << 0,
        Reset      = 1 << 1,
        JankStats  = 1 << 2,
    };
};

@@ -415,7 +416,6 @@ void RenderProxy::notifyFramePending() {
CREATE_BRIDGE4(dumpProfileInfo, CanvasContext* context, RenderThread* thread,
        int fd, int dumpFlags) {
    args->context->profiler().dumpData(args->fd);
    args->thread->jankTracker().dump(args->fd);
    if (args->dumpFlags & DumpFlags::FrameStats) {
        args->context->dumpFrames(args->fd);
    }
+2 −2
Original line number Diff line number Diff line
@@ -51,7 +51,7 @@ import java.util.ArrayList;
 * 2) ASHMEM_SIZE (for scratch space used during dumping)
 * 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.
 *
 *  @hide */
@@ -59,7 +59,7 @@ public class GraphicsStatsService extends IGraphicsStats.Stub {
    public static final String GRAPHICS_STATS_SERVICE = "graphicsstats";

    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 final Context mContext;