1 簡介
https://www.lizenghai.com/archives/26061.html
2 用法
用命令行啟動Systrace抓取采樣:《Capture a system trace on the command line》
自定義采樣的方法:《Define custom events》這個方法確實要仔細看,尤其是采樣命令的描述,強調了要使用 -a 參數,之前我忽略了這一點,導致自定義的標簽總也顯示不出來。
python systrace.py -a com.autonavi.amapauto -b 16384 -o my_systrace_report.html sched freq idle am wm gfx view binder_driver hal dalvik camera input res
【坑】采用Android Device Monitor來抓取Systrace時有一個坑,就是經常出現Java heap error(因為我自動插樁所有函數,導致Systrace標簽數量龐大),后來發現原因在於Monitor配置的Java堆最大值太小了,收集trace數據時需要大量的堆(懷疑是做數據合並加工)引發堆空間錯誤。解決方法如下:
1. 找到Monitor腳本所在目錄,里面有lib\monitor-x86,例如:C:\Users\liuheng.klh\AppData\Local\Android\Sdk\tools\lib\monitor-x86(或monitor-x86_64) 2. 打開monitor.ini 配置文件,修改如下三項空間配置(盡可能大點就行): -XX:MaxPermSize=1024m -Xms2048m -Xmx4096m
(1)實現自定義標簽
在Android 6.0(API 23)中包含trace.h,可以在Native層直接進行引用,獲取函數指針進行調用。用 ATRACE_CALL 宏直接在需要插樁的函數入口調用一下即可。關鍵代碼:

1 #include <android/trace.h> 2 3 ///////////////////////////////////////////////////////////////////// 4 // utils.h 5 extern void *(*_ATrace_beginSection) (const char* sectionName) __attribute__ ((no_instrument_function)); 6 extern void *(*_ATrace_endSection) (void) __attribute__ ((no_instrument_function)); 7 extern bool *(*_ATrace_isEnabled) (void) __attribute__ ((no_instrument_function)); 8 9 // SysTrace 10 class ScopedTrace { 11 const char* _name; 12 public: 13 static bool _bInit; 14 static void init() __attribute__ ((no_instrument_function)); 15 16 typedef void *(*fp_ATrace_beginSection) (const char* sectionName); 17 typedef void *(*fp_ATrace_endSection) (void); 18 typedef bool *(*fp_ATrace_isEnabled) (void); 19 20 public: 21 inline ScopedTrace(const char* name) __attribute__ ((no_instrument_function)) { 22 if (!_bInit) { 23 init(); 24 } 25 _ATrace_beginSection(name) ; 26 27 _name = name; 28 //LOGD("_ATrace_beginSection(%s) _ATrace_isEnabled()=%d", name, _ATrace_isEnabled()); 29 } 30 31 inline ~ScopedTrace() __attribute__ ((no_instrument_function)) { 32 _ATrace_endSection(); 33 //LOGD("_ATrace_endSection(%s) _ATrace_isEnabled()=%d", _name, _ATrace_isEnabled()); 34 } 35 }; 36 37 #define ATRACE_NAME(name) ScopedTrace ___tracer(name) 38 #define ATRACE_CALL() ATRACE_NAME(__FUNCTION__) 39 40 ///////////////////////////////////////////////////////////////////// 41 // utils.cpp 42 void *(*_ATrace_beginSection) (const char* sectionName) = NULL; 43 void *(*_ATrace_endSection) (void) = NULL; 44 bool *(*_ATrace_isEnabled) (void) = NULL; 45 46 bool ScopedTrace::_bInit = false; 47 void ScopedTrace::init() { 48 if (_bInit) { 49 return; 50 } 51 52 // Retrieve a handle to libandroid. 53 void *lib = dlopen("libandroid.so", RTLD_NOW || RTLD_LOCAL); 54 55 // Access the native tracing functions. 56 if (lib != NULL) { 57 // Use dlsym() to prevent crashes on devices running Android 5.1 58 // (API level 22) or lower. 59 _ATrace_beginSection = reinterpret_cast<fp_ATrace_beginSection>( 60 dlsym(lib, "ATrace_beginSection")); 61 _ATrace_endSection = reinterpret_cast<fp_ATrace_endSection>( 62 dlsym(lib, "ATrace_endSection")); 63 _ATrace_isEnabled = reinterpret_cast<fp_ATrace_isEnabled>( 64 dlsym(lib, "ATrace_isEnabled")); 65 } 66 67 LOGI("lib=%p, _ATrace_beginSection=%p, _ATrace_endSection=%p, _ATrace_isEnabled=%p, ATrace_isEnabled()=%d", 68 lib, _ATrace_beginSection, _ATrace_endSection, _ATrace_isEnabled, _ATrace_isEnabled()); 69 70 _bInit = true; 71 }
(2)一個巧妙的利用——編譯器自動插樁
利用gcc的 “-finstrument-functions” 可以實現自動化插樁。關鍵代碼如下:

1 // utils.h 2 extern "C" { 3 4 void __cyg_profile_func_enter( void *, void * ) __attribute__ ((no_instrument_function)); 5 void __cyg_profile_func_exit( void *, void * ) __attribute__ ((no_instrument_function)); 6 7 } 8 9 // utils.cpp 10 #define DUMP(func, call) LOGI("[KLH]%s: func = %p, called by = %p\n", __FUNCTION__, func, call) 11 12 extern "C" { 13 14 void __cyg_profile_func_enter(void *this_func, void *call_site) { 15 //DUMP(this_func, call_site); 16 char addr[32]; 17 snprintf(addr, 32, "%p", this_func); 18 19 ScopedTrace::init(); 20 21 if (0 != _ATrace_isEnabled && _ATrace_isEnabled()) { 22 _ATrace_beginSection(addr); 23 } 24 } 25 26 void __cyg_profile_func_exit(void *this_func, void *call_site) { 27 //DUMP(this_func, call_site); 28 29 if (0 != _ATrace_isEnabled && _ATrace_isEnabled()) { 30 _ATrace_endSection(); 31 } 32 } 33 34 }
這里關鍵的點是需要extern "C"聲明原型。
(3)編寫一套Python(其他語言也行)工具來翻譯加工自動插樁的函數(運行時)地址為函數名。翻譯的方法為:
1. 獲取進程pid: adb shell ps | adb shell grep [進程名] 分解字符串第一個即是 2. 獲取進程maps adb shell cat /proc/[pid]/maps 3. 提取trace.html(Systrace報告)中所有運行時地址列表,准備翻譯 4. 提取所有so的地址段,以此來確定運行時地址所屬so及內部相對地址 5. 提取so內部相對地址與符號映射表 readelf -sW libXXX.so 6. 根據4、5翻譯出運行時地址對應的函數符號,並翻譯出函數名 c++filt -n [symbol] 7. trace.html中線程名缺失的問題可以進一步補充(如果線程中途退出則可能遺漏): adb shell cat /proc/pid/task [foreach] tid adb shell cat /proc/pid/task/tid/status 得到Name:字段就是線程名稱
3 原理