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

Commit ac8d53a9 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Split out jank data from policy"

parents 9f455b3d 7075c792
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -201,6 +201,7 @@ cc_defaults {
        "PathParser.cpp",
        "PathTessellator.cpp",
        "PixelBuffer.cpp",
        "ProfileData.cpp",
        "ProfileRenderer.cpp",
        "Program.cpp",
        "ProgramCache.cpp",
+7 −145
Original line number Diff line number Diff line
@@ -34,14 +34,6 @@
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",
};

struct Comparison {
    FrameInfoIndex start;
    FrameInfoIndex end;
@@ -68,65 +60,11 @@ static const int64_t IGNORE_EXCEEDING = seconds_to_nanoseconds(10);
 */
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 = 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;

// For testing purposes to try and eliminate test infra overhead we will
// consider any unknown delay of frame start as part of the test infrastructure
// 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 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
int32_t JankTracker::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;
}

int32_t JankTracker::frameTimeForSlowFrameCountIndex(uint32_t index) {
    return (index * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs;
}

JankTracker::JankTracker(const DisplayInfo& displayInfo) {
    // By default this will use malloc memory. It may be moved later to ashmem
    // if there is shared space for it and a request comes in to do that.
@@ -199,29 +137,7 @@ void JankTracker::switchStorageToAshmem(int ashmemfd) {
        return;
    }

    // The new buffer may have historical data that we want to build on top of
    // But let's make sure we don't overflow Just In Case
    uint32_t divider = 0;
    if (newData->totalFrameCount > (1 << 24)) {
        divider = 4;
    }
    for (size_t i = 0; i < mData->jankTypeCounts.size(); i++) {
        newData->jankTypeCounts[i] >>= divider;
        newData->jankTypeCounts[i] += mData->jankTypeCounts[i];
    }
    for (size_t i = 0; i < mData->frameCounts.size(); i++) {
        newData->frameCounts[i] >>= divider;
        newData->frameCounts[i] += mData->frameCounts[i];
    }
    newData->jankFrameCount >>= divider;
    newData->jankFrameCount += mData->jankFrameCount;
    newData->totalFrameCount >>= divider;
    newData->totalFrameCount += mData->totalFrameCount;
    if (newData->statStartTime > mData->statStartTime
            || newData->statStartTime == 0) {
        newData->statStartTime = mData->statStartTime;
    }

    newData->mergeWith(*mData);
    freeData();
    mData = newData;
    mIsMapped = true;
@@ -251,7 +167,6 @@ void JankTracker::setFrameInterval(nsecs_t frameInterval) {
}

void JankTracker::addFrame(const FrameInfo& frame) {
    mData->totalFrameCount++;
    // Fast-path for jank-free frames
    int64_t totalDuration = frame.duration(sFrameStart, FrameInfoIndex::FrameCompleted);
    if (mDequeueTimeForgiveness
@@ -271,11 +186,10 @@ void JankTracker::addFrame(const FrameInfo& frame) {
        }
    }
    LOG_ALWAYS_FATAL_IF(totalDuration <= 0, "Impossible totalDuration %" PRId64, totalDuration);
    uint32_t framebucket = frameCountIndexForFrameTime(totalDuration);
    LOG_ALWAYS_FATAL_IF(framebucket < 0, "framebucket < 0 (%u)", framebucket);
    mData->reportFrame(totalDuration);

    // Keep the fast path as fast as possible.
    if (CC_LIKELY(totalDuration < mFrameInterval)) {
        mData->frameCounts[framebucket]++;
        return;
    }

@@ -284,22 +198,12 @@ void JankTracker::addFrame(const FrameInfo& frame) {
        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));
        mData->slowFrameCounts[framebucket]++;
    }

    mData->jankFrameCount++;
    mData->reportJank();

    for (int i = 0; i < NUM_BUCKETS; i++) {
        int64_t delta = frame.duration(COMPARISONS[i].start, COMPARISONS[i].end);
        if (delta >= mThresholds[i] && delta < IGNORE_EXCEEDING) {
            mData->jankTypeCounts[i]++;
            mData->reportJankType((JankType) i);
        }
    }
}
@@ -320,58 +224,16 @@ void JankTracker::dumpData(int fd, const ProfileDataDescription* description, co
    if (sFrameStart != FrameInfoIndex::IntendedVsync) {
        dprintf(fd, "\nNote: Data has been filtered!");
    }
    dprintf(fd, "\nStats since: %" PRIu64 "ns", data->statStartTime);
    dprintf(fd, "\nTotal frames rendered: %u", data->totalFrameCount);
    dprintf(fd, "\nJanky frames: %u (%.2f%%)", data->jankFrameCount,
            (float) data->jankFrameCount / (float) data->totalFrameCount * 100.0f);
    dprintf(fd, "\n50th percentile: %ums", findPercentile(data, 50));
    dprintf(fd, "\n90th percentile: %ums", findPercentile(data, 90));
    dprintf(fd, "\n95th percentile: %ums", findPercentile(data, 95));
    dprintf(fd, "\n99th percentile: %ums", findPercentile(data, 99));
    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, " %ums=%u", frameTimeForSlowFrameCountIndex(i),
                data->slowFrameCounts[i]);
    }
    data->dump(fd);
    dprintf(fd, "\n");
}

void JankTracker::reset() {
    mData->jankTypeCounts.fill(0);
    mData->frameCounts.fill(0);
    mData->slowFrameCounts.fill(0);
    mData->totalFrameCount = 0;
    mData->jankFrameCount = 0;
    mData->statStartTime = systemTime(CLOCK_MONOTONIC);
    mData->reset();
    sFrameStart = Properties::filterOutTestOverhead
            ? FrameInfoIndex::HandleInputStart
            : FrameInfoIndex::IntendedVsync;
}

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) {
            return frameTimeForFrameCountIndex(i);
        }
    }
    return 0;
}

} /* namespace uirenderer */
} /* namespace android */
+2 −29
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
#define JANKTRACKER_H_

#include "FrameInfo.h"
#include "ProfileData.h"
#include "renderthread/TimeLord.h"
#include "utils/RingBuffer.h"

@@ -29,31 +30,6 @@
namespace android {
namespace uirenderer {

enum JankType {
    kMissedVsync = 0,
    kHighInputLatency,
    kSlowUI,
    kSlowSync,
    kSlowRT,

    // must be last
    NUM_BUCKETS,
};

// 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, 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;
};

enum class JankTrackerType {
    // The default, means there's no description set
    Generic,
@@ -88,15 +64,12 @@ public:
    void rotateStorage();
    void switchStorageToAshmem(int ashmemfd);

    uint32_t findPercentile(int p) { return findPercentile(mData, p); }
    static int32_t frameTimeForFrameCountIndex(uint32_t index);
    static int32_t frameTimeForSlowFrameCountIndex(uint32_t index);
    uint32_t findPercentile(int p) { return mData->findPercentile(p); }

private:
    void freeData();
    void setFrameInterval(nsecs_t frameIntervalNanos);

    static uint32_t findPercentile(const ProfileData* data, int p);
    static void dumpData(int fd, const ProfileDataDescription* description, const ProfileData* data);

    std::array<int64_t, NUM_BUCKETS> mThresholds;
+177 −0
Original line number Diff line number Diff line
/*
 * 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",
};

// 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 */
 No newline at end of file
+109 −0
Original line number Diff line number Diff line
/*
 * 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.
 */

#pragma once

#include "utils/Macros.h"

#include <utils/Timers.h>

#include <array>
#include <functional>
#include <tuple>

namespace android {
namespace uirenderer {

enum JankType {
    kMissedVsync = 0,
    kHighInputLatency,
    kSlowUI,
    kSlowSync,
    kSlowRT,

    // must be last
    NUM_BUCKETS,
};

// For testing
class MockProfileData;

// Try to keep as small as possible, should match ASHMEM_SIZE in
// GraphicsStatsService.java
class ProfileData {
    PREVENT_COPY_AND_ASSIGN(ProfileData);

public:
    ProfileData() { reset(); }

    void reset();
    void mergeWith(const ProfileData& other);
    void dump(int fd) const;
    uint32_t findPercentile(int percentile) const;

    void reportFrame(int64_t duration);
    void reportJank() { mJankFrameCount++; }
    void reportJankType(JankType type) { mJankTypeCounts[static_cast<int>(type)]++; }

    uint32_t totalFrameCount() const { return mTotalFrameCount; }
    uint32_t jankFrameCount() const { return mJankFrameCount; }
    nsecs_t statsStartTime() const { return mStatStartTime; }
    uint32_t jankTypeCount(JankType type) const { return mJankTypeCounts[static_cast<int>(type)]; }

    struct HistogramEntry {
        uint32_t renderTimeMs;
        uint32_t frameCount;
    };
    void histogramForEach(const std::function<void(HistogramEntry)>& callback) const;

    constexpr static int HistogramSize() {
        return std::tuple_size<decltype(ProfileData::mFrameCounts)>::value
                + std::tuple_size<decltype(ProfileData::mSlowFrameCounts)>::value;
    }

    // Visible for testing
    static uint32_t frameTimeForFrameCountIndex(uint32_t index);
    static uint32_t frameTimeForSlowFrameCountIndex(uint32_t index);

private:
    // Open our guts up to unit tests
    friend class MockProfileData;

    std::array <uint32_t, NUM_BUCKETS> mJankTypeCounts;
    // See comments on kBucket* constants for what this holds
    std::array<uint32_t, 57> mFrameCounts;
    // Holds a histogram of frame times in 50ms increments from 150ms to 5s
    std::array<uint16_t, 97> mSlowFrameCounts;

    uint32_t mTotalFrameCount;
    uint32_t mJankFrameCount;
    nsecs_t mStatStartTime;
};

// For testing
class MockProfileData : public ProfileData {
public:
    std::array<uint32_t, NUM_BUCKETS>& editJankTypeCounts() { return mJankTypeCounts; }
    std::array<uint32_t, 57>& editFrameCounts() { return mFrameCounts; }
    std::array<uint16_t, 97>& editSlowFrameCounts() { return mSlowFrameCounts; }
    uint32_t& editTotalFrameCount() { return mTotalFrameCount; }
    uint32_t& editJankFrameCount() { return mJankFrameCount; }
    nsecs_t& editStatStartTime() { return mStatStartTime; }
};

} /* namespace uirenderer */
} /* namespace android */
Loading