日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

GraphicsStatsService之1-dump数据的实现

發布時間:2025/3/18 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 GraphicsStatsService之1-dump数据的实现 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文中所有代碼基于Android8.0

不了解dumpsys,可以先看Android dumpsys 實現

1.執行dump

測試android性能,其中幀率很重要,執行adb shell dumpsys graphicsstats,能得到類似如下結果:

... //包名 Package: android //系統開機多久后開始統計的 Stats since: 253812513812ns //總共繪制的幀數 Total frames rendered: 479 //卡頓幀數,16ms沒繪制完的 Janky frames: 25(5.23%) // 50% 90% 95% 99% 的幀數是多長時間繪制完成的 50th percentile: 5ms 90th percentile: 23ms 95th percentile: 35ms 99th percentile: 43ms// 丟失的Vsync信號 Number Missed Vsync: 12 //高輸入導致的 Number High input latency: 0 //ui線程慢 Number Slow UI thread: 7 //上傳繪制bitmap Number Slow bitmap uploads: 3 //繪制命令異常 Number Slow issue draw commands: 15// 這是每個時間對應的繪制幀數 HISTOGRAM: 5ms=4620 6ms=622 7ms=328 8ms=198 9ms=332 ...非常多... 4150ms=0 4200ms=0 4250ms=0 4300ms=0 4350ms=0 4400ms=0 4450ms=0 4500ms=0 4550ms=0 4600ms=0 4650ms=0 4700ms=0 4750ms=0 4800ms=0 4850ms=0 4900ms=0 4950ms=0 ... 復制代碼

2 執行流程

主要用到的類: GraphicsStatsService.java com_android_server_GraphicsStatsService.cpp GraphicsStatsService.cpp

Android dumpsys 實現一文提到如何service是如何dump的,graphicsstats這個對應的服務是GraphicsStatsService,看一下它是如何將數據dump的。 所有的系統服務從binder繼承的dump接口,在GraphicsStatsService.java中:

@Overrideprotected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, fout)) return;//1 解析參數boolean dumpProto = false;for (String str : args) {if ("--proto".equals(str)) {dumpProto = true;break;}}//2 收集buffersArrayList<HistoricalBuffer> buffers;synchronized (mLock) {buffers = new ArrayList<>(mActive.size());for (int i = 0; i < mActive.size(); i++) {try {buffers.add(new HistoricalBuffer(mActive.get(i)));} catch (IOException ex) {// Ignore}}}//3 創建dump對象long dump = nCreateDump(fd.getInt$(), dumpProto);try {synchronized (mFileAccessLock) {//4 dump數據HashSet<File> skipList = dumpActiveLocked(dump, buffers);buffers.clear();dumpHistoricalLocked(dump, skipList);}} finally {// 5 完成dumpnFinishDump(dump);}} 復制代碼

2.1 解析參數,當 dump graphicsstats --proto 時,加了這個參數會按proto格式打印,反正人沒法認出來... 2.2 收集buffer,這步比較重要,看兩個數據結構ActiveBuffer 和 HistoricalBuffer

private final class ActiveBuffer implements DeathRecipient {...MemoryFile mProcessBuffer;ActiveBuffer(IGraphicsStatsCallback token, int uid, int pid, String packageName, int versionCode)throws RemoteException, IOException {...// 創建 MemoryFilemProcessBuffer = new MemoryFile("GFXStats-" + pid, ASHMEM_SIZE);mProcessBuffer.writeBytes(ZERO_DATA, 0, 0, ASHMEM_SIZE);}....}private final class HistoricalBuffer {final BufferInfo mInfo;final byte[] mData = new byte[ASHMEM_SIZE];HistoricalBuffer(ActiveBuffer active) throws IOException {mInfo = active.mInfo;mInfo.endTime = System.currentTimeMillis();//讀取數據active.mProcessBuffer.readBytes(mData, 0, 0, ASHMEM_SIZE);}}復制代碼

ActiveBuffer在創建時,new了一個MemoryFile,也就是共享內存,并且初始化為0值。在繪制時會將數據填充。當收集buffer時,在HistoricalBuffer里,將數據讀到了它的成員mData中,其大小為ASHMEM_SIZE,是通過一個native方法拿到的:

# com_android_server_GraphicsStatsService.cppstatic jint getAshmemSize(JNIEnv*, jobject) {return sizeof(ProfileData); } 復制代碼

是一個名為ProfileData的結構體的大小。(下一篇聊這個結構體,它關乎數據的來源)這樣數據就收集完了。

2.3 創建dump,在底層做了什么呢?

long dump = nCreateDump(fd.getInt$(), dumpProto); 復制代碼

fd.getInt$(),拿到要寫入的fd。 dumpProto 默認是false

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); } 復制代碼

原來是new了一個GrahicsStatsService.cpp里的一個Dump對象,然后返回了它的指針。Dump類如下:

class GraphicsStatsService::Dump { public:Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {}int fd() { return mFd; }DumpType type() { return mType; }service::GraphicsStatsServiceDumpProto& proto() { return mProto; } private:int mFd;DumpType mType;service::GraphicsStatsServiceDumpProto mProto; }; 復制代碼

這個Dump對象主要作用是在底層保存了要寫入的fd 。 2.4 上一步得到了一個底層的指針,接下來就是打印了:

private HashSet<File> dumpActiveLocked(long dump, ArrayList<HistoricalBuffer> buffers) {HashSet<File> skipFiles = new HashSet<>(buffers.size());for (int i = 0; i < buffers.size(); i++) {HistoricalBuffer buffer = buffers.get(i);File path = pathForApp(buffer.mInfo);skipFiles.add(path);nAddToDump(dump, path.getAbsolutePath(), buffer.mInfo.packageName,buffer.mInfo.versionCode, buffer.mInfo.startTime, buffer.mInfo.endTime,buffer.mData);}return skipFiles;} 復制代碼

主要是nAddToDump這個方法:

// 去除了一些打印語句 # com_android_server_GraphicsStatsService.cpp static void addToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath, jstring jpackage,jint versionCode, jlong startTime, jlong endTime, jbyteArray jdata) {std::string path;const ProfileData* data = nullptr;ScopedByteArrayRO buffer{env};if (jdata != nullptr) {buffer.reset(jdata);// 1 轉換成ProfileData結構data = reinterpret_cast<const ProfileData*>(buffer.get());}if (jpath != nullptr) {ScopedUtfChars pathChars(env, jpath);path.assign(pathChars.c_str(), pathChars.size());}ScopedUtfChars packageChars(env, jpackage);GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr);const std::string package(packageChars.c_str(), packageChars.size());// 2 調用GraphicsStatsService.cpp里的addToDumpGraphicsStatsService::addToDump(dump, path, package, versionCode, startTime, endTime, data); } 復制代碼

2.4.1 剛才數據是按ProfileData這個結構體大小讀的,現在將數據轉換成這個結構。 2.4.2 調用native類的addToDump方法:

# GraphicsStatsService.cpp void GraphicsStatsService::addToDump(Dump* dump, const std::string& path, const std::string& package,int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data) {//1 從指定路徑讀數據service::GraphicsStatsProto statsProto;if (!path.empty() && !parseFromFile(path, &statsProto)) {statsProto.Clear();}// 2 合并兩份數據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 {// 3 打印數據,不需要protobuf格式dumpAsTextToFd(&statsProto, dump->fd());} } 復制代碼

2.4.2.a: 從指定路徑讀數據,為何還要讀呢,不是從共享內存取的嗎? 實際上數據在app死亡或者定時器到了時間,會調整數據,會將數據保存到data/system下的。下一篇討論數據來源。

bool GraphicsStatsService::parseFromFile(const std::string& path, service::GraphicsStatsProto* output) {...void* addr = mmap(nullptr, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);...void* data = reinterpret_cast<uint8_t*>(addr) + sHeaderSize;int dataSize = sb.st_size - sHeaderSize;io::ArrayInputStream input{data, dataSize};bool success = output->ParseFromZeroCopyStream(&input);return success; } 復制代碼

核心是用protobuf的接口,將數據專成GraphicsStatsProto結構。

2.4.2.b: merge數據,將兩份數據合并一下,大多數就是持久化在文件里的,跟內存里的做加法。 2.4.2.c: dump數據:

void dumpAsTextToFd(service::GraphicsStatsProto* proto, int fd) {// This isn't a full validation, just enough that we can deref at willif (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: %d", 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, "\nHISTOGRAM:");for (const auto& it : proto->histogram()) {dprintf(fd, " %dms=%d", it.render_millis(), it.frame_count());}dprintf(fd, "\n"); } 復制代碼

至此,我們看到,將數據寫入到了從java傳過來的fd中,也是在Android dumpsys 實現一文中提到的,pipe創建的管道的寫入端。這樣結合dumpsys實現,看到了數據最終輸出到了終端。

下一篇討論數據來源。

總結

以上是生活随笔為你收集整理的GraphicsStatsService之1-dump数据的实现的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。