
Perfetto heapprofd 深度指南從采樣原理到火焰圖與 SQL 分析【免費(fèi)下載鏈接】perfettoProduction-grade client-side tracing, profiling, and analysis for complex software systems.項目地址: https://gitcode.com/GitHub_Trending/pe/perfetto進(jìn)程 RSS 持續(xù)爬升heap dump 里卻找不到大對象這時你需要把內(nèi)存占用歸因到具體調(diào)用棧callstack。Perfetto 的 heapprofd 干的就是這件事hook 住malloc/free按采樣間隔記錄誰分配了多少、有沒有釋放最終變成可查詢的火焰圖。本文覆蓋從一條命令跑通到采樣機(jī)制、SQL 后處理與排錯的完整閉環(huán)。五分鐘跑通一條命令拿到調(diào)用?;鹧鎴D最省事的路徑是tools/heap_profile腳本它會替你完成配置下發(fā)、profiling、拉回 trace、符號化、轉(zhuǎn) pprof 全套動作tools/heap_profile android -n com.example.myapp按包名指定目標(biāo)profiling 持續(xù)到按CtrlC為止。結(jié)束后輸出Wrote profiles to /tmp/heap_profile-XXXXXX (symlink /tmp/heap_profile-latest) The raw-trace and heap_dump.* (pprof) files can be visualized with https://ui.perfetto.dev.把raw-trace拖進(jìn) Perfetto UI在進(jìn)程軌道找到 Native heap profile 軌道點(diǎn)擊 slice 即彈出火焰圖。上面這條命令等于sampling_interval_bytes: 4096默認(rèn)采樣間隔 按名稱匹配 運(yùn)行中與新啟動進(jìn)程都分析 block_client模式。常用參數(shù)速查完整清單以腳本--help為準(zhǔn)參數(shù)類型 / 取值作用備注-n/--name逗號分隔的進(jìn)程名按名稱匹配同時覆蓋運(yùn)行中與新啟動進(jìn)程-p/--pid逗號分隔的 PID按 PID 匹配用于 watermark 觸發(fā)或本地調(diào)試-i/--intervalint默認(rèn) 4096采樣間隔字節(jié)設(shè) 1 即全量記錄-c/--continuous-dumpintms周期轉(zhuǎn)儲間隔0 為禁用-d/--durationintmsprofiling 時長0 為持續(xù)到 CtrlC--heaps逗號分隔如com.android.art指定要采樣的堆僅 Android 12--shmem-sizeint字節(jié)共享內(nèi)存緩沖大小須為 4096 的倍數(shù)、≥8192、2 的冪--no-startup/--no-runningflag只匹配已運(yùn)行 / 只匹配新啟動進(jìn)程Android 11二者互斥--print-configflag只打印生成的 trace config調(diào)試配置時有用不想走腳本時也可以在 trace config 里手寫HeapprofdConfig完整字段見 heapprofd 配置定義好處是能與其他數(shù)據(jù)源CPU、調(diào)度、日志并行采集data_sources { config { name: android.heapprofd heapprofd_config { sampling_interval_bytes: 4096 process_cmdline: com.example.myapp } } }效果與上面第一條命令等價每 4096 字節(jié)采樣一次按名稱匹配目標(biāo)。機(jī)制拆解采樣、緩沖、轉(zhuǎn)儲時機(jī)分別發(fā)生了什么采樣把分配流按 1/n 抽稀Heapprofd hookmalloc/free與 C 的operator new/delete間隔n字節(jié)意味著平均每分配n字節(jié)記錄一次從而壓低對目標(biāo)進(jìn)程的性能開銷。可以想象成一條 1 字節(jié)的分配流每個字節(jié)以 1/n 概率被選中被選中的字節(jié)記上完整的n字節(jié)——期望上總量是準(zhǔn)的但單條記錄有噪聲。大于采樣間隔的分配繞過采樣邏輯按真實(shí)大小記錄大對象不會低估。sampling_interval_bytes必須為非零值否則 producer 不啟動設(shè) 1 即全量精度。類比每 4096 筆流水抽查一筆并放大 4096 倍月底對賬時總量吻合但單月波動看不準(zhǔn)。緩沖shmem 是分配與消費(fèi)之間的蓄水池目標(biāo)進(jìn)程把分配樣本寫進(jìn)客戶端與 heapprofd 之間的共享內(nèi)存緩沖shmem_size_bytes默認(rèn) 8 MiB超過 500 MiB 會被截斷heapprofd 再異步消費(fèi)。緩沖寫滿且block_client: true腳本默認(rèn)時客戶端阻塞等待空間分配不丟但目標(biāo)進(jìn)程明顯變慢可配block_client_timeout_us設(shè)超時Android 11須 100us。--no-block-client則相反緩沖滿時直接提前結(jié)束會話用丟數(shù)據(jù)換目標(biāo)進(jìn)程速度。proto 里還有自適應(yīng)采樣剩余空間低于adaptive_sampling_shmem_threshold時采樣間隔翻倍封頂adaptive_sampling_max_sampling_interval_bytes兩者為 0 時禁用。原理細(xì)節(jié)見 heapprofd 采樣設(shè)計文檔。轉(zhuǎn)儲三個寫入 trace 的時機(jī)堆快照在三個時機(jī)寫入 trace會話結(jié)束時默認(rèn)必然發(fā)生配置了continuous_dump_config時按周期dump_phase_ms首轉(zhuǎn)儲延遲、dump_interval_ms間隔隨時手動觸發(fā)對所有正在分析的進(jìn)程做一次快照adb shell killall -USR1 heapprofd手動轉(zhuǎn)儲疊加在會話結(jié)束轉(zhuǎn)儲之上可多次觸發(fā)——適合讓目標(biāo)處于某個特定狀態(tài)此刻留個底的實(shí)驗(yàn)。啟動分析與運(yùn)行時分析的生效時機(jī)差按名稱而非 PID指定目標(biāo)時新啟動且匹配的進(jìn)程從啟動起就被分析。Android 上應(yīng)用并非從零exec()而是 zygote fork 后特化specialize匹配時分析在特化階段啟用所以 profile 覆蓋特化點(diǎn) → 會話結(jié)束特化最早的一小段分配不計入。按名稱或 PID 請求時已有進(jìn)程會被立即枚舉并通知但分析要等下一次分配發(fā)生后才真正生效——目標(biāo)若當(dāng)時空閑隨后的分配突發(fā)可能漏掉。另一個硬邊界同一目標(biāo)被多個會話指定時只有第一個會話能分析其余會話的ProcessHeapSamples內(nèi)容為空且rejected_concurrent為 true轉(zhuǎn)換 pprof 時會報進(jìn)程已被分析過。數(shù)據(jù)模型分配數(shù)據(jù)落在哪四張表所有數(shù)據(jù)經(jīng) Trace Processor 入庫堆內(nèi)存分配調(diào)用棧查詢就圍繞這四張表表內(nèi)容heap_profile_allocation分配事件callsite_id、size釋放為負(fù)、count、ts、upidstack_profile_mapping二進(jìn)制/庫build_id、namestack_profile_frame幀name、rel_pcstack_profile_callsite調(diào)用點(diǎn)parent_id構(gòu)成調(diào)用樹heap_profile_allocation在 views.sql 中是對 intrinsic 表__intrinsic_heap_profile_allocation的視圖封裝離線符號化數(shù)據(jù)在stack_profile_symbol。組合進(jìn)階host 模式 連續(xù)轉(zhuǎn)儲 SQL 后處理三個能力串起來就是完整的本地二進(jìn)制 → 周期快照 → 累計歸因工作流。第一步host 子命令Perfetto v58僅 Linux讓非 Android 進(jìn)程也能被分析tools/heap_profile host -c 5000 -- ./my_binary --some-flag腳本自動下載tracebox與libheapprofd_glibc_preload.so到~/.local/share/perfetto/prebuilts/通過tracebox --system-sockets拉起內(nèi)置traced再以LD_PRELOAD啟動目標(biāo)。關(guān)鍵在于它設(shè)置了PERFETTO_HEAPPROFD_BLOCKING_INIT1默認(rèn) heapprofd 懶初始化、不阻塞主線程會漏掉啟動階段分配該變量讓第一次malloc阻塞到掛接完成每個分配都不丟——這正是 host 場景想要的從進(jìn)程出生就開始采。-c 5000使時間線每 5 秒出現(xiàn)一個 snapshot slice。平臺預(yù)編譯產(chǎn)物缺失時可從源碼構(gòu)建后用--preload-library out/linux_clang_release/libheapprofd_glibc_preload.so傳入構(gòu)建步驟見 構(gòu)建說明。第二步驗(yàn)證配置加--print-config可先看到完整 trace config 而不實(shí)際執(zhí)行例如heapprofd_config { shmem_size_bytes: 8388608 sampling_interval_bytes: 4096 process_cmdline: my_binary block_client: true continuous_dump_config { dump_phase_ms: 0 dump_interval_ms: 5000 } }第三步SQL 后處理?;鹧鎴D看形狀SQL 看數(shù)字。對raw-trace運(yùn)行標(biāo)準(zhǔn)庫模塊一步得到按函數(shù)累積的未釋放量INCLUDE PERFETTO MODULE android.memory.heap_profile.summary_tree; SELECT name, -- 該調(diào)用棧中幀的函數(shù)名 mapping_name AS map_name, -- 原生二進(jìn)制、庫、JAR 或 APK cumulative_size -- 函數(shù)出現(xiàn)在棧任意位置時分配且未釋放的字節(jié)數(shù) FROM android_heap_profile_summary_tree ORDER BY abs(cumulative_size) DESC;模塊實(shí)現(xiàn)在 summary_tree.sql先按 callsite 聚合self_size/self_alloc_size再沿parent_id向上累加出cumulative_size。同目錄的intervals.sql支持按時間窗口聚合。這樣組合的理由火焰圖回答分配發(fā)生在哪條路徑summary_tree 回答誰累計持有最多且沒還兩者互證。數(shù)據(jù)解讀四種火焰圖視圖與 RSS 的口徑差圖中是 UI 火焰圖視圖頂部下拉可切換四種指標(biāo)重點(diǎn)關(guān)注左側(cè)軌道選中的 sliceUnreleased malloc size該棧分配但未釋放的字節(jié)數(shù)排查泄漏的主視圖Total malloc size該棧分配的字節(jié)總數(shù)含已配對釋放Unreleased / Total malloc count對應(yīng)的次數(shù)版本。對 ART 分配采樣churn profiling則只有兩個視圖Total allocation size / count——只記對象創(chuàng)建時的棧不跟蹤 GC 回收用于判斷大額分配應(yīng)歸因于哪段代碼。 分析應(yīng)用時給過濾器加Hide Frame: libart.so濾掉 ART 運(yùn)行時內(nèi)部幀火焰圖聚焦業(yè)務(wù)代碼如上圖頂部。UI 錄制頁#!/record/memory把同樣的參數(shù)做成了面板目標(biāo)進(jìn)程、采樣間隔Sampling interval、轉(zhuǎn)儲周期Continuous dump interval、共享內(nèi)存緩沖Shared memory buffer須 4096 的倍數(shù)且為 2 的冪與 Block client 開關(guān)。native heap profiling 怎么配到這里勾選即可不用手寫 config。別拿錯尺子。heapprofd、malloc_info、RSS 三者口徑不同維度heapprofdmalloc_infoRSS含義程序向默認(rèn)分配器請求的字節(jié)數(shù)分配器自報userdebug 下am dumpheap -m PID /data/local/tmp/heap.txt觸發(fā)分配器向 OS 拿到的內(nèi)存dumpsys meminfo的 Private Dirtyzygote 初始化前的分配???線程緩存jemalloc thread cache???頁對齊與碎片化???ZRAM 換出不受影響不受影響可能小于前兩者從 zygote 啟動的 Java 應(yīng)用早期初始化分配對 heapprofd 不可見所以 heapprofd 數(shù)字系統(tǒng)性偏小屬正常反過來高 RSS 而 heapprofd 對不上時要懷疑分配器碎片化。排錯鏈路從癥狀到解法Profile 為空→ 目標(biāo)無資格被分析 → 查下文版本/構(gòu)建類型表確認(rèn) manifest 帶debuggable或profileable android:shelltrue/放在application段內(nèi)。會話提前結(jié)束buffer overrun→ 分配速率超過 heapprofd 消費(fèi)能力 → 若為短暫尖峰加大--shmem-size否則加大-i如--interval16000犧牲精度。調(diào)用棧邏輯上不可能→ DEDUPED 幀或 ICF 鏈接 → 方法名含[DEDUPED]說明多方法共享同一份代碼顯示的不一定是實(shí)際調(diào)用的方法若鏈接器傳過-Wl,--icf...平凡函數(shù)常見是構(gòu)造/析構(gòu)函數(shù)會被別名到不相干類的等價函數(shù)。符號顯示為原始地址 / could not find library / 只有一幀→ 離線符號化問題 → 運(yùn)行trace_processor bundle生成增強(qiáng)歸檔或走PERFETTO_BINARY_PATH/PERFETTO_PROGUARD_MAP傳統(tǒng)方式詳見 符號化文檔。提示進(jìn)程已被分析過→ 存在并發(fā)會話 →adb shell killall perfetto清掉殘留后重試。平臺與版本限制heapprofd 要求 Android 10Java 分配分析要求 Android 12能力 / 限制版本邊界heapprofd 基本功能Android 10heaps/all_heaps/target_installed_byJava 分配分析Android 12no_startup/no_running/dump_at_max/min_anonymous_memory_kbAndroid 11process_cmdline的*通配符僅匹配運(yùn)行中進(jìn)程需同時設(shè)no_startupPerfetto v57tools/heap_profile host子命令Perfetto v58僅 Linuxuser 構(gòu)建可分析范圍僅debuggable/profileable應(yīng)用Android 10/11 的sampling_interval_bytes: 0會崩潰目標(biāo)進(jìn)程非法配置Android 11 64 位設(shè)備分析 32 位程序不支持Android 10 x86 / x86_64含 Cuttlefish不支持??all: true分析全部合格進(jìn)程在未修改的 userdebug 構(gòu)建上會引發(fā)系統(tǒng)崩潰zygote 啟動新進(jìn)程時遇到意外的 heapprofd socket。收尾本文覆蓋的工作流邊界heap_profile腳本 / UI 錄制 / 手寫 config 三種啟動方式采樣與 shmem 緩沖機(jī)制啟動 vs 運(yùn)行時分析、連續(xù)與手動轉(zhuǎn)儲四張堆 profile 表與 summary_tree SQL 后處理host 模式本地分析以及按癥狀檢索的排錯鏈。延伸閱讀heapprofd 完整文檔、采樣設(shè)計文檔、heap_profile CLI 參考。【免費(fèi)下載鏈接】perfettoProduction-grade client-side tracing, profiling, and analysis for complex software systems.項目地址: https://gitcode.com/GitHub_Trending/pe/perfetto創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考