1. 程式人生 > 其它 >debug:am profile命令的實現

debug:am profile命令的實現

debug:am profile命令的實現

目錄

一、原始碼分析

程式碼基於android11。am命令的實現見debug:am、cmd命令。書接上文,

system_server程序

ActivityManagerShellCommand#onCommand

frameworks/base/services/core/java/com/android/server/am/ActivityManagerShellCommand.java

 176     @Override
 177     public int onCommand(String cmd) {      
 183             switch (cmd) {
 184                 case "start":
 185                 case "start-activity":
 186                     return runStartActivity(pw);
......
 205                 case "profile":
 206                     return runProfile(pw);
......

走到206行

ActivityManagerShellCommand#runProfile

frameworks/base/services/core/java/com/android/server/am/ActivityManagerShellCommand.java

 815     private int runProfile(PrintWriter pw) throws RemoteException {
 816         final PrintWriter err = getErrPrintWriter();
 817         String profileFile = null;
 818         boolean start = false;
 819         boolean wall = false;
 820         int userId = UserHandle.USER_CURRENT;
 821         int profileType = 0;
 822         mSamplingInterval = 0;
 823         mStreaming = false;
 825         String process = null;
 827         String cmd = getNextArgRequired();
 828 
 829         if ("start".equals(cmd)) {
 830             start = true;
 831             String opt;
 832             while ((opt=getNextOption()) != null) {
 833                 if (opt.equals("--user")) {
 834                     userId = UserHandle.parseUserArg(getNextArgRequired());
 835                 } else if (opt.equals("--wall")) {
 836                     wall = true;
 837                 } else if (opt.equals("--streaming")) {
 838                     mStreaming = true;
 839                 } else if (opt.equals("--sampling")) {
 840                     mSamplingInterval = Integer.parseInt(getNextArgRequired());
 841                 } else {
 842                     err.println("Error: Unknown option: " + opt);
 843                     return -1;
 844                 }
 845             }
 846             process = getNextArgRequired();
 847         } else if ("stop".equals(cmd)) {
 848             String opt;
 849             while ((opt=getNextOption()) != null) {
 850                 if (opt.equals("--user")) {
 851                     userId = UserHandle.parseUserArg(getNextArgRequired());
 852                 } else {
 853                     err.println("Error: Unknown option: " + opt);
 854                     return -1;
 855                 }
 856             }
 857             process = getNextArgRequired();
 858         } else {
 859             // Compatibility with old syntax: process is specified first.
 860             process = cmd;
 861             cmd = getNextArgRequired();
 862             if ("start".equals(cmd)) {
 863                 start = true;
 864             } else if (!"stop".equals(cmd)) {
 865                 throw new IllegalArgumentException("Profile command " + process + " not valid");
 866             }
 867         }
......
 877         if (start) {
 878             profileFile = getNextArgRequired();
 879             fd = openFileForSystem(profileFile, "w");
 880             if (fd == null) {
 881                 return -1;
 882             }
 883             profilerInfo = new ProfilerInfo(profileFile, fd, mSamplingInterval, false, mStreaming,
 884                     null, false);
 885         }
 886 
 887         try {
 888             if (wall) {
 889                 // XXX doesn't work -- this needs to be set before booting.
 890                 String props = SystemProperties.get("dalvik.vm.extra-opts");
 891                 if (props == null || !props.contains("-Xprofile:wallclock")) {
 892                     props = props + " -Xprofile:wallclock";
 893                     //SystemProperties.set("dalvik.vm.extra-opts", props);
 894                 }
 895             } else if (start) {
 896                 //removeWallOption();
 897             }
 898             if (!mInterface.profileControl(process, userId, start, profilerInfo, profileType)) {

832-840行檢查幾個start引數

  • --user:UserHandle裡的id,預設為UserHandle.USER_CURRENT
  • --wall:隱藏引數,虛擬機器配置dalvik.vm.extra-opts : -Xprofile:wallclock
  • --streaming:連續寫檔案
  • --sampling:取樣頻率

847-857行檢查stop引數,只有--userprocess

858-867行是對舊命令引數的相容。

878、879行拿到引數檔案路徑,獲取fd,用於寫profile到檔案

883行,new一個輔助工具類ProfilerInfo,裝載本次profile的一些資訊引數,這裡解釋下建構函式引數

frameworks/base/core/java/android/app/ProfilerInfo.java

 33 public class ProfilerInfo implements Parcelable {
 38     public final String profileFile;//命令列傳的檔案路徑
 41     public ParcelFileDescriptor profileFd;//上面檔案的fd
 44     public final int samplingInterval;//資料取樣間隔
 47     public final boolean autoStopProfiler;//app idle狀態自動停止
 52     public final boolean streamingOutput;//是否連續輸出到檔案
 57     public final String agent;//代理
 66     public final boolean attachAgentDuringBind;//是否bind-application階段或之前接入代理
 68     public ProfilerInfo(String filename, ParcelFileDescriptor fd, int interval, boolean autoStop,
 69             boolean streaming, String agent, boolean attachAgentDuringBind) {    

回來繼續,

888-896行是虛擬器引數wallclock的配置,此處註釋掉了,沒開放。wallclock與realtime是一起的,分別代表現實時間、機器執行時長。

898行是重點,在此方法中開始溝通java程序,開始profile抓取。mInterface還是AMS。

需要注意的是,profile抓取同上篇的am trace-ipc不太一致。profile的寫檔案是start後就開始寫了,stop只是停止寫。而am trace-ipc是先快取在記憶體裡,stop時再寫到檔案裡。

ActivityManagerService.java#profileControl

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

18451     public boolean profileControl(String process, int userId, boolean start,
18452             ProfilerInfo profilerInfo, int profileType) throws RemoteException {
18453 
18454         try {
18455             synchronized (this) {
18456                 // note: hijacking SET_ACTIVITY_WATCHER, but should be changed to
18457                 // its own permission.
18458                 if (checkCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER)
18459                         != PackageManager.PERMISSION_GRANTED) {
18460                     throw new SecurityException("Requires permission "
18461                             + android.Manifest.permission.SET_ACTIVITY_WATCHER);
18462                 }
18468                 ProcessRecord proc = null;
18469                 if (process != null) {
18470                     proc = findProcessLocked(process, userId, "profileControl");
18471                 }
18472 
18477                 if (start) {
18478                     stopProfilerLocked(null, 0);
18479                     setProfileApp(proc.info, proc.processName, profilerInfo);
18480                     mProfileData.setProfileProc(proc);
18481                     mProfileType = profileType;
18482                     ParcelFileDescriptor fd = profilerInfo.profileFd;
18483                     try {
18484                         fd = fd.dup();
18485                     } catch (IOException e) {
18486                         fd = null;
18487                     }
18488                     profilerInfo.profileFd = fd;
18489                     proc.thread.profilerControl(start, profilerInfo, profileType);
18490                     fd = null;
18491                     try {
18492                         mProfileData.getProfilerInfo().profileFd.close();
18493                     } catch (IOException e) {
18494                     }
18495                     mProfileData.getProfilerInfo().profileFd = null;
18496 
18497                     if (proc.pid == MY_PID) {
18502                         profilerInfo = null;
18503                     }
18504                 } else {
18505                     stopProfilerLocked(proc, profileType);
18506                     if (profilerInfo != null && profilerInfo.profileFd != null) {
18507                         try {
18508                             profilerInfo.profileFd.close();
18509                         } catch (IOException e) {

18458行鑑權。android.Manifest.permission.SET_ACTIVITY_WATCHER

18470行拿到ProcessRecord。這個方法入參是string,pid或者包名都是可以的。

18477的分支,需要注意18478行先停止上一次的profile,清空檔案,然後才是本次的記錄。

18479行,有個檢查,需要裝置是debug版本或者app是debug或者app設定了<profileable android:shell=["true" | "false"] android:enable=["true" | "false"] />才允許對該應用抓profile。參見官網連結:manifest/profileable-element

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

 8488     void setProfileApp(ApplicationInfo app, String processName, ProfilerInfo profilerInfo) {
 8490             boolean isDebuggable = "1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0"));
 8491             if (!isDebuggable) {                                                       
 8492                 if (!app.isProfileableByShell()) {
 8493                     throw new SecurityException("Process not debuggable, "
 8494                             + "and not profileable by shell: " + app.packageName);
 8495                 }
 8496             }
 8497             mProfileData.setProfileApp(processName);

對應到apk安裝時的解析程式碼如下:

frameworks/base/core/java/android/content/pm/PackageParser.java

3335     private boolean parseBaseApplication(){
......
3463         if (sa.getBoolean(
3464                 com.android.internal.R.styleable.AndroidManifestApplication_debuggable,
3465                 false)) {
3466             ai.flags |= ApplicationInfo.FLAG_DEBUGGABLE;
3467             // Debuggable implies profileable
3468             ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PROFILEABLE_BY_SHELL;       
3469         }
......
3875             } else if (tagName.equals("profileable")) {
3876                 sa = res.obtainAttributes(parser,
3877                         com.android.internal.R.styleable.AndroidManifestProfileable);
3878                 if (sa.getBoolean(
3879                         com.android.internal.R.styleable.AndroidManifestProfileable_shell, false)) {
3880                     ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PROFILEABLE_BY_SHELL;                                          
3881                 }

回來繼續,18479-18488行設定好程序,fd等資訊

18489行binder ipc溝通java程序開始抓profile。下面轉到對端跟蹤

java程序

ActivityThread.java$ApplicationThread#profilerControl

frameworks/base/core/java/android/app/ActivityThread.java

 947     private class ApplicationThread extends IApplicationThread.Stub {    
1169         @Override
1170         public void profilerControl(boolean start, ProfilerInfo profilerInfo, int profileType) {
1171             sendMessage(H.PROFILER_CONTROL, profilerInfo, start ? 1 : 0, profileType);
1172         }
-------------------------------------------------------------------------
1978                 case PROFILER_CONTROL:
1979                     handleProfilerControl(msg.arg1 != 0, (ProfilerInfo)msg.obj, msg.arg2);
1980                     break;
-------------------------------------------------------------------------
6375     private void handleBindApplication(AppBindData data) {
6394         mProfiler = new Profiler();                                                 
6395         String agent = null;
6396         if (data.initProfilerInfo != null) {
6397             mProfiler.profileFile = data.initProfilerInfo.profileFile;
6398             mProfiler.profileFd = data.initProfilerInfo.profileFd;
6399             mProfiler.samplingInterval = data.initProfilerInfo.samplingInterval;
6400             mProfiler.autoStopProfiler = data.initProfilerInfo.autoStopProfiler;
6401             mProfiler.streamingOutput = data.initProfilerInfo.streamingOutput;
6402             if (data.initProfilerInfo.attachAgentDuringBind) {
6403                 agent = data.initProfilerInfo.agent;
6404             }
6405         }
-------------------------------------------------------------------------
6047     final void handleProfilerControl(boolean start, ProfilerInfo profilerInfo, int profileType) {                                  
6048         if (start) {
6049             try {   
6050                 switch (profileType) {
6051                     default:
6052                         mProfiler.setProfiler(profilerInfo);
6053                         mProfiler.startProfiling();
6054                         break;
6055                 }
6056             } catch (RuntimeException e) {
6057                 Slog.w(TAG, "Profiling failed on path " + profilerInfo.profileFile
6058                         + " -- can the process access this path?");
6059             } finally {
6060                 profilerInfo.closeFd();
6061             }       
6062         } else {    
6063             switch (profileType) {
6064                 default:
6065                     mProfiler.stopProfiling();
6066                     break;

現在走到6047的handleProfilerControl方法,還是分start、stop。都是藉助了6394行初始化的Profiler類。

需要關注的另外一點是6395、6403行這個代理,bindApplication階段初始化的。

6047行的入參profileType上面傳下來預設是0,但是這裡空實現。

6053、6065行跟進

ActivityThread.java$Profiler#startProfiling

 875         public void startProfiling() {
 876             if (profileFd == null || profiling) {
 877                 return;
 878             }
 879             try {
 880                 int bufferSize = SystemProperties.getInt("debug.traceview-buffer-size-mb", 8);
 881                 VMDebug.startMethodTracing(profileFile, profileFd.getFileDescriptor(),
 882                         bufferSize * 1024 * 1024, 0, samplingInterval != 0, samplingInterval,
 883                         streamingOutput);
 884                 profiling = true;
 885             } catch (RuntimeException e) {
-------------------------------------------------------------------------
 895         public void stopProfiling() {
 896             if (profiling) {
 897                 profiling = false;
 898                 Debug.stopMethodTracing();
 899                 if (profileFd != null) {
 900                     try {
 901                         profileFd.close();

876行,沒開啟檔案或者已經在抓了就退出

880行,profile預設抓8M,在AndroidStudio上我們可以改大,然後8.0以上的android沒有限制檔案大小。

所以這裡命令列抓的限制可以設定屬性規避,比如setprop debug.traceview-buffer-size-mb 32

大小限制的官網連結:Create, edit, or view a recording configuration

881行,最終是操作了虛擬機器VMDebug.startMethodTracing。到此結束,虛擬機器的內容就不跟了。

stopProfiling方法在898行同樣是操作的虛擬機器

frameworks/base/core/java/android/os/Debug.java

1370     /**     
1371      * Stop method tracing.
1372      */         
1373     public static void stopMethodTracing() {                                           
1374         VMDebug.stopMethodTracing(); 
1375     }       

二、使用

profile是啥、作用?

簡單的說,一個程序的所有方法呼叫記錄,也就是火焰圖。在Android世界裡,分為java的profile與native的perf。

1、確認程序的動作,走了哪些方法流程

2、確認方法耗時與壓力,哪些方法操作重、呼叫頻繁

本問題,在官方文件上有更詳細的介紹解釋和AndroidStudio profiler操作指導:AndroidStudio Profile

本文的am profile是抓java profile的命令列操作入口。

命令提示

generic_x86_64:/ # am
Activity manager (activity) commands:
......
  profile start [--user <USER_ID> current]
          [--sampling INTERVAL | --streaming] <PROCESS> <FILE>
      Start profiler on a process.  The given <PROCESS> argument
        may be either a process name or pid.  Options are:
      --user <USER_ID> | current: When supplying a process name,
          specify user of process to profile; uses current user if not
          specified.
      --sampling INTERVAL: use sample profiling with INTERVAL microseconds
          between samples.
      --streaming: stream the profiling output to the specified file.
  profile stop [--user <USER_ID> current] <PROCESS>
      Stop profiler on a process.  The given <PROCESS> argument
        may be either a process name or pid.  Options are:
      --user <USER_ID> | current: When supplying a process name,
          specify user of process to profile; uses current user if not
          specified.

使用示例

generic_x86_64:/ # am profile start com.example.myapplication /data/local/tmp/example_profile.trace       
##############做操作##############
generic_x86_64:/ # am profile stop com.example.myapplication     
:~/$ adb pull /data/local/tmp/example_profile.trace 

抓完pull下來之後,用AndroidStudio裡的profiler開啟,或者單獨的SDK裡的profiler工具開啟,操作路徑:profiler-->SESSIONS-->"+"-->Load form file。舊的SDK裡DDMS也是可以開啟解析的。

開啟的效果貼張圖:

每個執行緒一份trace記錄,如果用於流程除錯,主要關注右上角的Flame Chart頁,也就是火焰圖。

三、總結

同之前的am trace-ipc命令類似,也是AMS將命令分發到應用程序。實現上是藉助了以下兩個方法操作虛擬機器,開啟、關閉profile trace記錄:

VMDebug.startMethodTracing()
VMDebug.stopMethodTracing()

需要關注的有

這是一種命令列抓profile的入口,還有其他命令也可以抓:am start [options] intent --start-profilerpm dump-profiles,實現上一樣。UI介面的直接看AndroidStudio官方文件Inspect CPU activity with CPU Profiler

記錄的是java程序中所有執行緒的trace,native的可以參考【譯】Simpleperf分析之Android系統篇

檔案大小預設8M,可通過屬性調節debug.traceview-buffer-size-mb

作者:秋城 | 部落格:https://www.cnblogs.com/houser0323 | 轉載請註明作者出處