Android9.0 性能问题统计分析工具JankTracker

本文宗旨是围绕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;
}

说到这里是不是有点乱了,哪来这么多断点参数啊~好,来个图跟踪下会清晰很多:


FrameInfoIndex.png

挑战下:下面 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:详细数据展示

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容