Android客户端性能工具1:JankTracker(graphicsstats和gfxinfo部分信息)分析

JankTracker有两个 一个是在CanvasContext 一个是在RenderThread中,用于dumpsys graphicsstats的输出
初始化函数,先分析hwc1,hwc2的fence机制有些不同

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.
    mData = new ProfileData;
    reset();
    nsecs_t frameIntervalNanos = static_cast<nsecs_t>(1_s / displayInfo.fps); 
  setFrameInterval(frameIntervalNanos);
}

构造函数,创建mData = new ProfileData;用于统计绘制信息,由于这里是初始化函数,先用reset重置统计,ProfileData的数据结构如下

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 JankType {
kMissedVsync = 0,
kHighInputLatency,
kSlowUI,
kSlowSync,
kSlowRT,

// must be last
NUM_BUCKETS,

};
首先jankTypeCounts用于记录上面各种JankType的次数.上面的含义很好理解.

frameCounts用于记录不算特别早槽的帧的统计信息,主要就是绘制时间的统计信息,slowFrmeCounts就是用于统计耗时比较长的帧.
要了解frameCounts和slowFrmeCounts的含义还需要看下frameCountIndexForFrameTime()这个函数,参数frameTime代表绘制当前一帧所花的时间

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;


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

这个算法很有意思,根据三个阀值计算frameCounts数组每个元素代表的实践步长,其中当frameTime

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

}

也就是说HighInputLatency,kSlowUI,kSlowSync,kSlowRT的伐值分别定义为1.5帧,0.5帧,0.2帧,0.75帧,后面我们会看到这几种type分别代表什么含义

除了构造函数,主要暴露的接口就是void JankTracker::addFrame(const FrameInfo& frame) 接口,接下来的重点就是分析这个接口如何统计信息

void JankTracker::addFrame(const FrameInfo& frame) {
    mData->totalFrameCount++;
    // Fast-path for jank-free frames
    int64_t totalDuration = frame.duration(sFrameStart, FrameInfoIndex::FrameCompleted);
    if (mDequeueTimeForgiveness
            && frame[FrameInfoIndex::DequeueBufferDuration] > 500_us) {
        nsecs_t expectedDequeueDuration =
                mDequeueTimeForgiveness + frame[FrameInfoIndex::Vsync]
                - frame[FrameInfoIndex::IssueDrawCommandsStart];
        if (expectedDequeueDuration > 0) {
            // Forgive only up to the expected amount, but not more than
            // the actual time spent blocked.
            nsecs_t forgiveAmount = std::min(expectedDequeueDuration,
                    frame[FrameInfoIndex::DequeueBufferDuration]);
            totalDuration -= forgiveAmount;
        }
    }
    uint32_t framebucket = frameCountIndexForFrameTime(totalDuration);
    // Keep the fast path as fast as possible.
    if (CC_LIKELY(totalDuration < mFrameInterval)) {
        mData->frameCounts[framebucket]++;
        return;
    }

    // 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++) {
        int64_t delta = frame.duration(COMPARISONS[i].start, COMPARISONS[i].end);
        if (delta >= mThresholds[i] && delta < IGNORE_EXCEEDING) {
            mData->jankTypeCounts[i]++;
        }
    }
}

每到来一帧首先增加mData->totalFrameCount计数,
frame.duration(sFrameStart, FrameInfoIndex::FrameCompleted) 函数用于计算从开始处理vsync到处理完成的时间,计算结果保存在totalDuration中
另外mDequeueTimeForgiveness是用于hwc2的,我们先跳过if (mDequeueTimeForgiveness
&& frame[FrameInfoIndex::DequeueBufferDuration] > 500_us) 条件成立的这段.
frameCountIndexForFrameTime这个函数我们前面已经介绍过它的原理.这里如果一帧的处理时间少于mFrameInterval(一般是16ms)就是正常的,直接给frameCounts中对应计数器+1,返回.

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

这一段我们重点看一下framebucket超过frameCounts能记录的情况,这种情况就要记录到slowFrameCounts中,这个数据每个元素记录50ms范围.
之后mData->jankFrameCount计数加1,代表有出现了一个jankFrame

static const Comparison COMPARISONS[] = {
        {FrameInfoIndex::IntendedVsync, FrameInfoIndex::Vsync},
        {FrameInfoIndex::OldestInputEvent, FrameInfoIndex::Vsync},
        {FrameInfoIndex::Vsync, FrameInfoIndex::SyncStart},
        {FrameInfoIndex::SyncStart, FrameInfoIndex::IssueDrawCommandsStart},
        {FrameInfoIndex::IssueDrawCommandsStart, FrameInfoIndex::FrameCompleted},
};

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

最后这段就是计算如上这些段的时间,然后统计到jankTypeCounts中.
这几段时间差值分别代表
1 main thread收到的vsync的时间到开始处理vsync的时间(发生在main thread)
2 最早的事件和开始处理vsync的时间 (发生在main thread)
3 main thread处理vsync到render thread开始同步的间隔(这里要减去发送命令给reader thread到开始同步的时间,也就是main thread真正处理的时间) (发生在main thread 和 render thread)
4 render thread开始同步到开始draw的时间(发生在main thread(同步完成后main thread基本会返回) 和 render thread)
5 render thread开始绘制到绘制完成的时间(发生在render thread中 极少情况main thread会阻塞在这里)

 在回来和JankType对比一下

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

    // must be last
    NUM_BUCKETS, };

再来看看阀值,细细品味
 

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

到这里JankTracker的逻辑就分析完了
明天分析FrameInfoVisualizer

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值