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引數,只有--user
和process
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-profiler
和pm dump-profiles
,實現上一樣。UI介面的直接看AndroidStudio官方文件Inspect CPU activity with CPU Profiler
記錄的是java程序中所有執行緒的trace,native的可以參考【譯】Simpleperf分析之Android系統篇。
檔案大小預設8M,可通過屬性調節debug.traceview-buffer-size-mb