本文宗旨是围绕JankTracker分析下不同阶段耗时的计算方法,既然是说JankTracker,那么有必要看下JankTracker使用的地方,跟踪源码发现有3处会用到,下面就每一处的使用做详细的分析:
一. 初始化过程
该环节比较简单,主要完成mGlobalData和mFrameInterval的赋值工作,后面会使用这两个成员变量。
frameworks/base/libs/hwui/renderthread/CanvasContext.h
JankTracker mJankTracker;
FrameInfo* mCurrentFrameInfo = nullptr;
frameworks/base/libs/hwui/renderthread/CanvasContext.cpp
CanvasContext::CanvasContext(RenderThread& thread, bool translucent,
RenderNode* rootRenderNode, IContextFactory* contextFactory,
std::unique_ptr<IRenderPipeline> renderPipeline)
...
, mJankTracker(&thread.globalProfileData(), thread.mainDisplayInfo())
frameworks/base/libs/hwui/JankTracker.cpp
JankTracker::JankTracker(ProfileDataContainer* globalData, const DisplayInfo& displayInfo) {
mGlobalData = globalData;
nsecs_t frameIntervalNanos = static_cast<nsecs_t>(1_s / displayInfo.fps);
...
setFrameInterval(frameIntervalNanos);
}
void JankTracker::setFrameInterval(nsecs_t frameInterval) {
mFrameInterval = frameInterval;
二.prepareTree阶段
该阶段主要完成FrameInfoIndex::SyncQueued和FrameInfoIndex::SyncStart过程,而SyncQueued是在postAndWait之前设置,意思就是在DrawFrameTask run起来之前记录下时间点。
frameworks/base/libs/hwui/renderthread/CanvasContext.cpp
void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued,
RenderNode* target) {
mRenderThread.removeFrameCallback(this);
// If the previous frame was dropped we don't need to hold onto it, so
// just keep using the previous frame's structure instead
if (!wasSkipped(mCurrentFrameInfo)) { //判断是否有skip_flag
mCurrentFrameInfo = mJankTracker.startFrame(); //拿出RingBuffer中每一个FrameInfo,总共120个一个循环
}
mCurrentFrameInfo->importUiThreadInfo(uiFrameInfo); //将uiFrameInfo内存拷贝到mCurrentFrameInfo的成员mFrameInfo
mCurrentFrameInfo->set(FrameInfoIndex::SyncQueued) = syncQueued; //设置FrameInfoIndex::SyncQueued,分析见下
mCurrentFrameInfo->markSyncStart(); //设置FrameInfoIndex::SyncStart
...
if (CC_UNLIKELY(!mNativeSurface.get())) {
mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); //如果没有window的话就设置FrameInfoIndex::Flags为FrameInfoFlags::SkippedFrame
info.out.canDrawThisFrame = false;
return;
}
...
if (!info.out.canDrawThisFrame) {
mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); //如果无需draw也设置FrameInfoIndex::Flags为FrameInfoFlags::SkippedFrame
}
在prepareTree阶段主要看下下面几个过程:
1.startFrame
取走RingBuffer中的mBuffer数据地址给到mCurrentFrameInfo,之后直接操作mCurrentFrameInfo即可。
frameworks/base/libs/hwui/JankTracker.h
RingBuffer<FrameInfo, 120> mFrames;
FrameInfo* startFrame() { return &mFrames.next(); }
2. FrameInfoIndex
接下来就是完成一些FrameInfoIndex的标志的设置,下面会有详细的分析:
frameworks/base/libs/hwui/FrameInfo.h
int64_t mFrameInfo[static_cast<int>(FrameInfoIndex::NumIndexes)];
void markSyncStart() { set(FrameInfoIndex::SyncStart) = systemTime(CLOCK_MONOTONIC); }
void addFlag(int frameInfoFlag) {
set(FrameInfoIndex::Flags) |= static_cast<uint64_t>(frameInfoFlag);
}
FrameInfoIndex的每个成员下面代码中均有解释。上面一步已经完成了FrameInfoIndex::SyncQueued和FrameInfoIndex::SyncStart标志的设置。
enum class FrameInfoIndex {
Flags = 0, //prepareTree设置FrameInfoFlags::SkippedFrame
IntendedVsync, 期望的vsync时间点//JNI调用syncDraw之前的时间
Vsync, 距离真正执行时最近的vsync时间点//同上
OldestInputEvent, 这一帧之前的输入时间时间点
NewestInputEvent, 上一帧的最新的Input事件时间点
HandleInputStart, //JNI调用syncDraw之前的时间
AnimationStart, //同上
PerformTraversalsStart, preformTraversals开始的时间点//同上
DrawStart, //同上
// End of UI frame info
SyncQueued, syncAndDrawFrame任务入队的时间点//prepareTree传入
SyncStart, 主线程和RenderThread线程资源同步的时间点//prepareTree设置当前时间
IssueDrawCommandsStart, 执行渲染命令的时间点//draw开始设置当前时间
SwapBuffers, swapbuffer开始的时间点//draw swapBuffer开始设置当前时间
FrameCompleted, 渲染结束的时间点//draw结束设置当前时间
DequeueBufferDuration, //draw
QueueBufferDuration, //draw
// Must be the last value!
// Also must be kept in sync with FrameMetrics.java#FRAME_STATS_COUNT
NumIndexes
};
而上面遗漏了一点:SyncQueued是在postAndWait之前设置的,也就是还没有执行DrawFrame动作。
int DrawFrameTask::drawFrame() {
LOG_ALWAYS_FATAL_IF(!mContext, "Cannot drawFrame with no CanvasContext!");
mSyncResult = SyncResult::OK;
mSyncQueued = systemTime(CLOCK_MONOTONIC);
postAndWait();
return mSyncResult;
}
bool DrawFrameTask::syncFrameState(TreeInfo& info) {
...
mContext->prepareTree(info, mFrameInfo, mSyncQueued, mTargetNode);
...
}
三.draw
详细分析见代码中介绍:
frameworks/base/libs/hwui/renderthread/CanvasContext.cpp
void CanvasContext::draw() {
...
mCurrentFrameInfo->markIssueDrawCommandsStart(); //设置FrameInfoIndex::IssueDrawCommandsStart
...
bool didSwap =
mRenderPipeline->swapBuffers(frame, drew, windowDirty, mCurrentFrameInfo, &requireSwap); //设置FrameInfoIndex::SwapBuffers
...
nsecs_t dequeueStart = mNativeSurface->getLastDequeueStartTime();
if (dequeueStart < mCurrentFrameInfo->get(FrameInfoIndex::SyncStart)) { //Surface::dequeueBuffer < prepareTree开始时
swap.dequeueDuration = 0;
}
mNativeSurface->query(NATIVE_WINDOW_LAST_QUEUE_DURATION, &durationUs);
swap.queueDuration = us2ns(durationUs);
mCurrentFrameInfo->set(FrameInfoIndex::DequeueBufferDuration) = swap.dequeueDuration; //设置FrameInfoIndex::DequeueBufferDuration,即surfce到bqp间隔
mCurrentFrameInfo->set(FrameInfoIndex::QueueBufferDuration) = swap.queueDuration; //设置FrameInfoIndex::QueueBufferDuration,即surfce到bqp间隔
mCurrentFrameInfo->markFrameCompleted(); //设置FrameInfoIndex::FrameCompleted
//看下这种统计方式,其实原理很简单,10帧做一次统计输出,每次的sBenchMma计算方法是假设前面9帧都是和之前一帧是一样的,然后加上当前帧,最后除以10就是当前的平均帧率
#if LOG_FRAMETIME_MMA
float thisFrame = mCurrentFrameInfo->duration(FrameInfoIndex::IssueDrawCommandsStart,
FrameInfoIndex::FrameCompleted) /
NANOS_PER_MILLIS_F;
if (sFrameCount) {
sBenchMma = ((9 * sBenchMma) + thisFrame) / 10;
} else {
sBenchMma = thisFrame;
}
if (++sFrameCount == 10) {
sFrameCount = 1;
ALOGD("Average frame time: %.4f", sBenchMma);
}
#endif
mJankTracker.finishFrame(*mCurrentFrameInfo);
if (CC_UNLIKELY(mFrameMetricsReporter.get() != nullptr)) {
mFrameMetricsReporter->reportFrameMetrics(mCurrentFrameInfo->data());
}
1.部分FrameInfoIndex设置
frameworks/base/libs/hwui/FrameInfo.h
void markIssueDrawCommandsStart() {
set(FrameInfoIndex::IssueDrawCommandsStart) = systemTime(CLOCK_MONOTONIC);
}
void markSwapBuffers() { set(FrameInfoIndex::SwapBuffers) = systemTime(CLOCK_MONOTONIC); }
void markFrameCompleted() { set(FrameInfoIndex::FrameCompleted) = systemTime(CLOCK_MONOTONIC); }
frameworks/base/libs/hwui/renderthread/OpenGLPipeline.cpp
bool OpenGLPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty,
FrameInfo* currentFrameInfo, bool* requireSwap) {
...
currentFrameInfo->markSwapBuffers();
再看看JNI处设置的一些FrameInfoIndex,会面的统计中都会用到。
frameworks/base/core/jni/android_view_Surface.cpp
static void draw(JNIEnv* env, jclass clazz, jlong rendererPtr) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(rendererPtr);
nsecs_t vsync = systemTime(CLOCK_MONOTONIC);
UiFrameInfoBuilder(proxy->frameInfo())
.setVsync(vsync, vsync) //vsync设置的都是一样的,即当前时间
.addFlag(FrameInfoFlags::SurfaceCanvas);
proxy->syncAndDrawFrame();
}
frameworks/base/libs/hwui/FrameInfo.h
UiFrameInfoBuilder& setVsync(nsecs_t vsyncTime, nsecs_t intendedVsync) {
set(FrameInfoIndex::Vsync) = vsyncTime; //设置FrameInfoIndex::Vsync
set(FrameInfoIndex::IntendedVsync) = intendedVsync; //设置FrameInfoIndex::IntendedVsync
// Pretend the other fields are all at vsync, too, so that naive
// duration calculations end up being 0 instead of very large
set(FrameInfoIndex::HandleInputStart) = vsyncTime; //设置FrameInfoIndex::HandleInputStart
set(FrameInfoIndex::AnimationStart) = vsyncTime; //设置FrameInfoIndex::AnimationStart
set(FrameInfoIndex::PerformTraversalsStart) = vsyncTime; //设置FrameInfoIndex::PerformTraversalsStart
set(FrameInfoIndex::DrawStart) = vsyncTime; //设置FrameInfoIndex::DrawStart
return *this;
}
说到这里是不是有点乱了,哪来这么多断点参数啊~好,来个图跟踪下会清晰很多:
挑战下:下面 1)是不是就代表sync任务从入队到执行的时间; 2)是不是就代表swapbuffer的耗时。
1){FrameInfoIndex::SyncQueued, FrameInfoIndex::SyncStart}
2){FrameInfoIndex::SwapBuffers, FrameInfoIndex::FrameCompleted}
2.finishFrame
最主要目的是向ProfileData中送数据,方便后面的dump,详细分析见代码:
frameworks/base/libs/hwui/JankTracker.h
ProfileDataContainer mData;
ProfileDataContainer* mGlobalData;
frameworks/base/libs/hwui/JankTracker.cpp
static FrameInfoIndex sFrameStart = FrameInfoIndex::IntendedVsync;
void JankTracker::finishFrame(const FrameInfo& frame) {
// Fast-path for jank-free frames
//计算FrameInfoIndex::FrameCompleted - FrameInfoIndex::IntendedVsync时间差,其实就是整个RT的draw时间
int64_t totalDuration = frame.duration(sFrameStart, FrameInfoIndex::FrameCompleted);
...
//report到ProfileData中去
mData->reportFrame(totalDuration);
(*mGlobalData)->reportFrame(totalDuration);
// Only things like Surface.lockHardwareCanvas() are exempt from tracking
//flag 类型为FrameInfoFlags::SurfaceCanvas直接返回
if (CC_UNLIKELY(frame[FrameInfoIndex::Flags] & EXEMPT_FRAMES_FLAGS)) {
return;
}
//如果RT draw时间超了16.67ms的话依旧向ProfileData中去report
if (totalDuration > mFrameInterval) {
mData->reportJank();
(*mGlobalData)->reportJank();
}
bool isTripleBuffered = mSwapDeadline > frame[FrameInfoIndex::IntendedVsync];
mSwapDeadline = std::max(mSwapDeadline + mFrameInterval,
frame[FrameInfoIndex::IntendedVsync] + mFrameInterval);
// If we hit the deadline, cool!
//如果没有超过deadline的话就直接返回没有丢帧
if (frame[FrameInfoIndex::FrameCompleted] < mSwapDeadline) {
if (isTripleBuffered) { //向ProfileData中report输入延迟
mData->reportJankType(JankType::kHighInputLatency);
(*mGlobalData)->reportJankType(JankType::kHighInputLatency);
}
return;
}
//向ProfileData中report kMissedDeadline
mData->reportJankType(JankType::kMissedDeadline);
(*mGlobalData)->reportJankType(JankType::kMissedDeadline);
// Janked, reset the swap deadline
nsecs_t jitterNanos = frame[FrameInfoIndex::FrameCompleted] - frame[FrameInfoIndex::Vsync];
nsecs_t lastFrameOffset = jitterNanos % mFrameInterval;
mSwapDeadline = frame[FrameInfoIndex::FrameCompleted] - lastFrameOffset + mFrameInterval;
for (auto& comparison : COMPARISONS) { //COMPARISONS统计分析见下
int64_t delta = frame.duration(comparison.start, comparison.end);
if (delta >= mThresholds[comparison.type] && delta < IGNORE_EXCEEDING) {
mData->reportJankType(comparison.type);
(*mGlobalData)->reportJankType(comparison.type);
}
}
// Log daveys since they are weird and we don't know what they are (b/70339576)
//RT draw超过700ms问题的统计打印
if (totalDuration >= 700_ms) {
static int sDaveyCount = 0;
std::stringstream ss;
ss << "Davey! duration=" << ns2ms(totalDuration) << "ms; ";
for (size_t i = 0; i < static_cast<size_t>(FrameInfoIndex::NumIndexes); i++) {
ss << FrameInfoNames[i] << "=" << frame[i] << ", ";
}
ALOGI("%s", ss.str().c_str());
// Just so we have something that counts up, the value is largely irrelevant
ATRACE_INT(ss.str().c_str(), ++sDaveyCount);
}
}
那么mData->reportFrame或者 (*mGlobalData)->reportFrame如何进入到ProfileData中去的呢?
frameworks/base/libs/hwui/ProfileDataContainer.h
ProfileData* operator->() { return mData; }
ProfileData* mData = new ProfileData;
frameworks/base/libs/hwui/ProfileData.cpp
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]++;
}
}
因此,mData->reportFrame(totalDuration);因为ProfileDataContainer重载了->运算符,所以其实就是调用ProfileData的reportFrame函数。下面会统一分析ProfileData:
四.ProfileData
1.定义
先看下一些基本定义,Comparison在android8.0和android9.0之间的区别如下:
//android O版本
frameworks/base/libs/hwui/JankTracker.cpp
static const Comparison COMPARISONS[] = {
{FrameInfoIndex::IntendedVsync, FrameInfoIndex::Vsync}, ->kMissedVsync
{FrameInfoIndex::OldestInputEvent, FrameInfoIndex::Vsync}, ->kHighInputLatency
{FrameInfoIndex::Vsync, FrameInfoIndex::SyncStart}, ->kSlowUI
{FrameInfoIndex::SyncStart, FrameInfoIndex::IssueDrawCommandsStart}, ->kSlowSync
{FrameInfoIndex::IssueDrawCommandsStart, FrameInfoIndex::FrameCompleted}, ->kSlowRT
};
//android P版本
frameworks/base/libs/hwui/JankTracker.cpp
static const std::array<Comparison, 4> COMPARISONS{
Comparison{JankType::kMissedVsync, [](nsecs_t) { return 1; }, FrameInfoIndex::IntendedVsync,
FrameInfoIndex::Vsync},
Comparison{JankType::kSlowUI,
[](nsecs_t frameInterval) { return static_cast<int64_t>(.5 * frameInterval); },
FrameInfoIndex::Vsync, FrameInfoIndex::SyncStart},
Comparison{JankType::kSlowSync,
[](nsecs_t frameInterval) { return static_cast<int64_t>(.2 * frameInterval); },
FrameInfoIndex::SyncStart, FrameInfoIndex::IssueDrawCommandsStart},
Comparison{JankType::kSlowRT,
[](nsecs_t frameInterval) { return static_cast<int64_t>(.75 * frameInterval); },
FrameInfoIndex::IssueDrawCommandsStart, FrameInfoIndex::FrameCompleted},
};
再看下ProfileData中的JankType定义:
frameworks/base/libs/hwui/ProfileData.h
enum JankType {
kMissedVsync = 0,
kHighInputLatency,
kSlowUI, ->prepareDraw ~ prepareTree
kSlowSync, ->CanvasContext::prepareTree
kSlowRT, ->CanvasContext::draw
// must be last
NUM_BUCKETS,
};
2.统计过程
好了说了定义后再看下数据是怎么被赋值的,后面方便dump数据。
set完成数据的写入,get完成数据的写出:
//set方法
frameworks/base/libs/hwui/ProfileData
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 reportJank() { mJankFrameCount++; }
void reportJankType(JankType type) { mJankTypeCounts[static_cast<int>(type)]++; }
//get方法
uint32_t totalFrameCount() const { return mTotalFrameCount; }
uint32_t jankFrameCount() const { return mJankFrameCount; }
uint32_t jankTypeCount(JankType type) const { return mJankTypeCounts[static_cast<int>(type)]; }
nsecs_t statsStartTime() const { return mStatStartTime; }
也就是:
reportFrame() -> mTotalFrameCount++ <-totalFrameCount()
reportJank() ->mJankFrameCount++ <-jankFrameCount()
reportJankType() ->mJankTypeCounts[xxx]++ <-jankTypeCount()
mStatStartTime <-statsStartTime()
好了,说到现在是不是应该写点dumpsys gfxinfo和dumpsys graphicsstats以及profile的东西了,别急啊,会有更新的~
五. dumpsys 过程
根据dumpsys可以知道,当调用的时候会执行后面紧跟着的service的dump,因此,从GraphicsStatsService开始说起
frameworks/base/services/core/java/com/android/server/GraphicsStatsService.java
protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
long dump = nCreateDump(fd.getInt$(), dumpProto);
...
HashSet<File> skipList = dumpActiveLocked(dump, buffers); //都会调用nAddToDump
...
dumpHistoricalLocked(dump, skipList); //都会调用nAddToDump
...
nFinishDump(dump);
进入JNI
frameworks/base/services/core/jni/com_android_server_GraphicsStatsService.cpp
static jlong createDump(JNIEnv*, jobject, jint fd, jboolean isProto) {
GraphicsStatsService::Dump* dump = GraphicsStatsService::createDump(fd, isProto
? GraphicsStatsService::DumpType::Protobuf : GraphicsStatsService::DumpType::Text);
return reinterpret_cast<jlong>(dump);
}
static void finishDump(JNIEnv*, jobject, jlong dumpPtr) {
GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr);
GraphicsStatsService::finishDump(dump);
}
static void addToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath, jstring jpackage,
jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) {
...
GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr);
LOG_ALWAYS_FATAL_IF(!dump, "null passed for dump pointer");
const std::string package(packageChars.c_str(), packageChars.size());
GraphicsStatsService::addToDump(dump, path, package, versionCode, startTime, endTime, data);
}
native层:
frameworks/base/libs/hwui/service/GraphicsStatsService.cpp
GraphicsStatsService::Dump* GraphicsStatsService::createDump(int outFd, DumpType type) {
return new Dump(outFd, type);
}
void GraphicsStatsService::finishDump(Dump* dump) {
if (dump->type() == DumpType::Protobuf) {
FileOutputStreamLite stream(dump->fd());
dump->proto().SerializeToZeroCopyStream(&stream);
}
delete dump;
}
void GraphicsStatsService::addToDump(Dump* dump, const std::string& path,
const std::string& package, int64_t versionCode,
int64_t startTime, int64_t endTime, const ProfileData* data) {
protos::GraphicsStatsProto statsProto;
if (!path.empty() && !parseFromFile(path, &statsProto)) {
statsProto.Clear();
}
if (data &&
!mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data)) {
return;
}
if (!statsProto.IsInitialized()) {
ALOGW("Failed to load profile data from path '%s' and data %p",
path.empty() ? "<empty>" : path.c_str(), data);
return;
}
if (dump->type() == DumpType::Protobuf) {
dump->proto().add_stats()->CopyFrom(statsProto);
} else {
dumpAsTextToFd(&statsProto, dump->fd());
}
}
void GraphicsStatsService::addToDump(Dump* dump, const std::string& path) {
protos::GraphicsStatsProto statsProto;
if (!parseFromFile(path, &statsProto)) {
return;
}
if (dump->type() == DumpType::Protobuf) {
dump->proto().add_stats()->CopyFrom(statsProto);
} else {
dumpAsTextToFd(&statsProto, dump->fd());
}
}
//dump结果显示:
void dumpAsTextToFd(protos::GraphicsStatsProto* proto, int fd) {
// This isn't a full validation, just enough that we can deref at will
if (proto->package_name().empty() || !proto->has_summary()) {
ALOGW("Skipping dump, invalid package_name() '%s' or summary %d",
proto->package_name().c_str(), proto->has_summary());
return;
}
dprintf(fd, "\nPackage: %s", proto->package_name().c_str());
dprintf(fd, "\nVersion: %lld", proto->version_code());
dprintf(fd, "\nStats since: %lldns", proto->stats_start());
dprintf(fd, "\nStats end: %lldns", proto->stats_end());
auto summary = proto->summary();
dprintf(fd, "\nTotal frames rendered: %d", summary.total_frames());
dprintf(fd, "\nJanky frames: %d (%.2f%%)", summary.janky_frames(),
(float)summary.janky_frames() / (float)summary.total_frames() * 100.0f);
dprintf(fd, "\n50th percentile: %dms", findPercentile(proto, 50));
dprintf(fd, "\n90th percentile: %dms", findPercentile(proto, 90));
dprintf(fd, "\n95th percentile: %dms", findPercentile(proto, 95));
dprintf(fd, "\n99th percentile: %dms", findPercentile(proto, 99));
dprintf(fd, "\nNumber Missed Vsync: %d", summary.missed_vsync_count());
dprintf(fd, "\nNumber High input latency: %d", summary.high_input_latency_count());
dprintf(fd, "\nNumber Slow UI thread: %d", summary.slow_ui_thread_count());
dprintf(fd, "\nNumber Slow bitmap uploads: %d", summary.slow_bitmap_upload_count());
dprintf(fd, "\nNumber Slow issue draw commands: %d", summary.slow_draw_count());
dprintf(fd, "\nNumber Frame deadline missed: %d", summary.missed_deadline_count());
dprintf(fd, "\nHISTOGRAM:");
for (const auto& it : proto->histogram()) {
dprintf(fd, " %dms=%d", it.render_millis(), it.frame_count());
}
dprintf(fd, "\n");
}
解释如下:
Stats since:应用统计信息开始的时间戳
Total frames rendered:绘制总帧数
Janky frames:卡顿总帧数
50th percentile:50%的帧在多少ms内绘制的
90th percentile:90%的帧在多少ms内绘制的
Number Missed Vsync:未正常收到的Vsync信号数量
Numer High input latency:高输入导致掉帧
Number Slow Ui Thread:主线程慢导致卡顿
Number Slow bitmap uploads:位图上传慢导致卡顿
Number Slow issue draw commands:绘制命令慢导致卡顿
HISTOGRAM:详细数据展示