方法二:使用命令行抓取 Systrace
参数分为两个部分options和category: options可取值:
category可取值:
[options] 是一些命令参数,[category] 是你感兴趣的系统模块,比如view代表view系统(包含绘制流程),am代表ActivityManager(包含Activity创建过程等);分析不同问题的时候,可以选择不同你感兴趣的模块。需要重复的是,尽可能缩小需要Trace的模块,其一是数据量小易与分析;其二,虽然systrace本身开销很小,但是缩小需要Trace的模块也能减少运行时开销。比如你分析卡顿的时候,power, webview 就几乎是无用的。 方法三:离线抓取 Systrace ① 输入指令:adb root && adb remount ② 输入以下指令开始后台抓取systrace,此时可以断开usb连接线去复现问题: adb shell "atrace -z -b 40000 gfx input view wm am camera hal res dalvik rs sched freq idle disk mmc -t 15 > /data/local/tmp/trace_output &" 参数说明: -a appname enable app-level tracing for a comma separated list of cmdlines -b N use a trace buffer size of N KB -t N trace for N seconds [defualt 5] -z compress the trace dump --list_categories list the available tracing categories The time and buffer size should be long enough to finished the systrace collecting. ③ 复现问题后,重新连接usb线输入如下指令,确认atrace进程是否结束抓取并退出: adb shell ps -A | grep atrace ④ 抓取完成后,取出生成的trace文件,并转换成html格式: adb pull /data/local/tmp/trace_output systrace.py --from-file trace_output -o output.html 然后就可以用谷歌浏览器打开分析了~ 三、trace.html文件分析:使用Google Chrome(其他浏览器很可能打不开)将这个文件打开进行分析,界面如下: 按键操作 作用w 放大,[+shift]速度更快 s 缩小,[+shift]速度更快 a 左移,[+shift]速度更快 d 右移,[+shift]速度更快 f 放大当前选定区域 m 标记当前选定区域 v 高亮VSync g 切换是否显示60hz的网格线0 恢复trace到初始态,这里是数字0而非字母o h 切换是否显示详情/ 搜索关键字 enter 显示搜索结果,可通过← →定位搜索结果 ` 显示/隐藏脚本控制台? 显示帮助功能 (1)分析View性能
放大后可看到时间1420~1500ms中,两个F之间间隔70ms左右,明显是超过16ms的,然后继续放大可以看到具体的任务内容和占用时长: 此时再点击“F”图标会在下栏提示相关内容如下: 提示是listview在recycling/rebinding的时效率低,接着点击Alerts: 点击各项也会相应展开,并给出性能分析结果和优化建议: 如果要查看工具在
如果在 虽然Systrace无法定位到某一行需要优化的代码,但通过Alerts和Frames以根据TraceView分析具体函数花了多长时间来进一步优化代码提高性能。 (2)分析HAL层线程处理性能 ①用chrome打开trace的html文件。 四、代码中添加标记生成 trace log 由于systrace是在系统级显示有关进程的信息,因此很难在HTML报告中的某个特定时间知道您的应用程序正在执行什么方法。 在Android 4.3(API级别18)及更高版本中,您可以使用代码中的Trace类在HTML报告中标记执行事件。 您不需要用代码来记录systrace的跟踪记录,但是这样做可以帮助您查看应用程序代码的哪些部分可能会导致线程挂起或UI断线。这种方法与使用Debug类不同,Trace类简单地将标志添加到systrace报告中,而Debug类可帮助您通过生成.trace文件来检查详细的app CPU使用情况。 (1)Java代码要生成包含已检测的跟踪事件的systrace HTML报告,如果使用指令方式抓取则需要使用-a或--app命令行选项运行systrace,并指定应用程序的包名称。 通常在怀疑引起jank代码地方的开始处添加 Trace.beginSection("defined by yourself"); 结束处添加Trace.endSection(); (注:这两个方法需要在同一个线程中成对出现,否则多次调用beginSection时,调用endSection只会结束最近的beginSection方法),添加方法参考如下: public class MyAdapter extends RecyclerView.Adapter<MyViewHolder> { @Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { Trace.beginSection("MyAdapter.onCreateViewHolder"); MyViewHolder myViewHolder; try { myViewHolder = MyViewHolder.newInstance(parent); } finally { // In try and catch statements, always call "endSection()" in a // "finally" block. That way, the method is invoked even when an // exception occurs. Trace.endSection(); } return myViewHolder; } @Override public void onBindViewHolder(MyViewHolder holder, int position) { Trace.beginSection("MyAdapter.onBindViewHolder"); try { try { Trace.beginSection("MyAdapter.queryDatabase"); RowItem rowItem = queryDatabase(position); dataset.add(rowItem); } finally { Trace.endSection(); } holder.bind(dataset.get(position)); } finally { Trace.endSection(); } } } 生成 trace.html 指令: python systrace.py -a com.example.myapp -b 16384 -o my_systrace_report.html sched freq idle am wm gfx view binder_driver hal dalvik camera input res (2)Native层在Android 6.0 (API level 23)及以上版本支持添加trace,步骤如下: ①为ATrace函数定义函数指针,如下面的代码片段所示: #include <android/trace.h>#include <dlfcn.h>void *(*ATrace_beginSection) (const char* sectionName);void *(*ATrace_endSection) (void); typedef void *(*fp_ATrace_beginSection) (const char* sectionName); typedef void *(*fp_ATrace_endSection) (void); ②加载ATrace_xxx符号,如下面的代码片段所示: // Retrieve a handle to libandroid.void *lib = dlopen("libandroid.so", RTLD_NOW || RTLD_LOCAL);// Access the native tracing functions.if (lib != NULL) { // Use dlsym() to prevent crashes on devices running Android 5.1 // (API level 22) or lower. ATrace_beginSection = reinterpret_cast<fp_ATrace_beginSection>( dlsym(lib, "ATrace_beginSection")); ATrace_endSEction = reinterpret_cast<fp_ATrace_endSection>( dlsym(lib, "ATrace_endSection")); } 注:出于安全考虑,dlopen操作只在debug时使用,另外如果要在Android 4.3 (API级别18)使用trace功能,可以通过JNI调用如上接口。 ③在需要分析的函数开始和结束处分别调用ATrace_beginSection()和ATrace_endSection(): #include <android/trace.h>char *customEventName = new char[32]; sprintf(customEventName, "User tapped %s button", buttonName); ATrace_beginSection(customEventName);// Your app or game's response to the button being pressed.ATrace_endSection(); C++中可以进一步通过宏定义利用构造及析构函数进行封装,便于使用: ①宏定义: #define ATRACE_NAME(name) ScopedTrace ___tracer(name)// ATRACE_CALL is an ATRACE_NAME that uses the current function name.#define ATRACE_CALL() ATRACE_NAME(__FUNCTION__)class ScopedTrace { public: inline ScopedTrace(const char *name) { ATrace_beginSection(name); } inline ~ScopedTrace() { ATrace_endSection(); } }; ②在需要追踪的代码部分开头使用宏定义,即可给当前函数添加trace标记: void myExpensiveFunction() { ATRACE_CALL(); // Code that you want to trace.} >>>补充: (1)集成好trace库的Android平台可以通过如下方法直接使用: #include <utils/Trace.h> #define ATRACE_TAG ATRACE_TAG_ALWAYS ATRACE_CALL(); Android 4.3 以上也可以用如下方式: #include <cutils/trace.h> ATRACE_BEGIN("TEST"); ATRACE_END(); (2)手动开启App的自定义Label的Trace功能要调用一个SDK @hide的函数,需要反射调用,把下面这段代码放在Application的`attachBaseContext`中即可,在非debuggable的版本中也适用! Class<?> trace = Class.forName("android.os.Trace"); Method setAppTracingAllowed = trace.getDeclaredMethod("setAppTracingAllowed", boolean.class); setAppTracingAllowed.invoke(null, true); (3) 通过 Utils_Trace.cpp #include "Utils_Trace.h"#include "stdio.h"#define ATRACE_MESSAGE_LEN 256#ifdef __cplusplusextern "C" {#endifint trace_init(int * phHandle) { int atrace_marker_fd = open("/sys/kernel/debug/tracing/trace_marker", O_WRONLY); if (atrace_marker_fd == -1) { return -1; } *phHandle = atrace_marker_fd; return 0; }void trace_uninit(int hHandle) { if (-1 == hHandle) { return; } close(hHandle); }void trace_begin(int hHandle, const char *name) { if (-1 == hHandle) { return; } char buf[ATRACE_MESSAGE_LEN] = { 0 }; int len = snprintf(buf, ATRACE_MESSAGE_LEN, "B|%d|%s", getpid(), name); write(hHandle, buf, len); }void trace_end(int hHandle,const char *name) { if (-1 == hHandle) { return; } char buf[ATRACE_MESSAGE_LEN] = { 0 }; int len = snprintf(buf, ATRACE_MESSAGE_LEN, "E|%d|%s", getpid(), name); //char c = 'E'; //write(hHandle, &c, 1); write(hHandle, buf, len); }void trace_async_begin(int hHandle, const char *name, const int32_t cookie) { if (-1 == hHandle) { return; } char buf[ATRACE_MESSAGE_LEN] = { 0 }; int len = snprintf(buf, ATRACE_MESSAGE_LEN, "S|%d|%s|%i", getpid(), name, cookie); write(hHandle, buf, len); }void trace_async_end(int hHandle, const char *name, const int32_t cookie) { if (-1 == hHandle) { return; } char buf[ATRACE_MESSAGE_LEN] = { 0 }; int len = snprintf(buf, ATRACE_MESSAGE_LEN, "F|%d|%s|%i", getpid(), name, cookie); write(hHandle, buf, len); }void trace_counter(int hHandle, const char *name, const int value) { if (-1 == hHandle) { return; } char buf[ATRACE_MESSAGE_LEN] = { 0 }; int len = snprintf(buf, ATRACE_MESSAGE_LEN, "C|%d|%s|%i", getpid(), name, value); write(hHandle, buf, len); } #ifdef __cplusplus }#endif Utils_Trace.h #ifndef __UTILS_TRACE___H___#define __UTILS_TRACE___H___#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h> 使用方法: ①. 把Utils_Trace.h、Utils_Trace.cpp放在工作代码中,并编译。 (4)修改线程名: #include <pthread.h>static void *render_scene(void *parm) { // Code for preparing your app or game's visual components.}static void *load_main_menu(void *parm) { // Code that executes your app or game's main logic.}void init_threads() {
pthread_t render_thread, main_thread;
pthread_create(&render_thread, NULL, render_scene, NULL);
pthread_create(&main_thread, NULL, load_main_menu, NULL);
pthread_setname_np(render_thread, "MyRenderer");
pthread_setname_np(main_thread, "MyMainMenu");
} (5)修改进程名: prctl(PR_SET_NAME, “process_name”, NULL, NULL, NULL); 第一个参数是操作类型,指定PR_SET_NAME,即设置进程名。 第二个参数是进程名字符串,长度至多16字节。 五、TraceView的使用
Android SDK自带的Debug类使用方法如下: 在开始记录的点写上代码Debug.startMethodTracing("tracePath");
在终止记录的点写上代码Debug.stopMethodTracing();
通过adb pull /mnt/sdcard/tracePath.trace .将trace导出指定的文件夹中
通过Android studio打开trace文件,界面同CPU Profiler差不多。 提示:可以使用命令行中的 如上图所示,CPU Profiler的视图包括以下内容: 绿色: 线程处于活动状态或准备好使用CPU。也就是说,它处于”运行”或”可运行”状态。 黄色:线程处于活动状态,但是在完成其工作之前,它正在等待I / O操作(如文件或网络I / O)。 灰色:线程正在睡眠,不会消耗任何CPU时间,当线程需要访问尚未可用的资源时,有时会发生这种情况。要么线程进入自愿性睡眠,要么内核使线程休眠,直到所需的资源可用。 ③CPU timeline:列出CPU在App运行过程中CPU使用情况。 橙色:系统方法
蓝色:第三方API(包括java语言的api)
绿色:App自身方法 六、linux进程、线程与cpu的亲和性(affinity) 一、什么是cpu亲和性(affinity) 软亲和性: 就是进程要在指定的 CPU 上尽量长时间地运行而不被迁移到其他处理器,Linux 内核进程调度器天生就具有被称为 软 CPU 亲和性(affinity) 的特性,这意味着进程通常不会在处理器之间频繁迁移。这种状态正是我们希望的,因为进程迁移的频率小就意味着产生的负载小。 硬亲和性:简单来说就是利用linux内核提供给用户的API,强行将进程或者线程绑定到某一个指定的cpu核运行。 解释:在linux内核中,所有的进程都有一个相关的数据结构,称为 task_struct。这个结构非常重要,原因有很多;其中与亲和性(affinity)相关度最高的是 cpus_allowed 位掩码。这个位掩码由 n 位组成,与系统中的 n 个逻辑处理器一一对应。 具有 4 个物理 CPU 的系统可以有 4 位。如果这些 CPU 都启用了超线程,那么这个系统就有一个 8 位的位掩码。 如果为给定的进程设置了给定的位,那么这个进程就可以在相关的 CPU 上运行。因此,如果一个进程可以在任何 CPU 上运行,并且能够根据需要在处理器之间进行迁移,那么位掩码就全是 1。实际上,这就是 Linux 中进程的缺省状态; 二、进程与cpu的绑定 sched_setaffinity可以将某个进程绑定到一个特定的CPU。你比操作系统更了解自己的程序,为了避免调度器愚蠢的调度你的程序,或是为了在多线程程序中避免缓存失效造成的开销,可以自行将当前进程绑定到期望运行的CPU核上。 #define _GNU_SOURCE#include <sched.h>#include <stdio.h>#include <string.h>#include <stdlib.h>#include <unistd.h>#include <errno.h>/* sysconf( _SC_NPROCESSORS_CONF ) 查看cpu的个数;打印用%ld长整。 * sysconf( _SC_NPROCESSORS_ONLN ) 查看在使用的cpu个数;打印用%ld长整 */int main(int argc, char **argv) { int cpus = 0; int i = 0; cpu_set_t mask; cpu_set_t get; cpus = sysconf(_SC_NPROCESSORS_CONF); printf("cpus: %d\n", cpus); CPU_ZERO(&mask); /* 初始化set集,将set置为空*/ CPU_SET(0, &mask); /* 依次将0、1、2、3号cpu加入到集合,前提是你的机器是多核处理器*/ CPU_SET(1, &mask); CPU_SET(2, &mask); CPU_SET(3, &mask); //void CPU_CLR (int cpu, cpu_set_t *set) //这个宏将 指定的 cpu 从 CPU 集 set 中删除 //int CPU_ISSET (int cpu, const cpu_set_t *set) //如果 cpu 是 CPU 集 set 的一员,这个宏就返回一个非零值(true),否则就返回零(false) /*设置cpu 亲和性(affinity)*/ /*sched_setaffinity函数设置进程为pid的这个进程,让它运行在mask所设定的CPU上.如果pid的值为0, *则表示指定的是当前进程,使当前进程运行在mask所设定的那些CPU上. *第二个参数cpusetsize是mask所指定的数的长度.通常设定为sizeof(cpu_set_t). *如果当前pid所指定的进程此时没有运行在mask所指定的任意一个CPU上, *则该指定的进程会从其它CPU上迁移到mask的指定的一个CPU上运行.*/ if (sched_setaffinity(0, sizeof(mask), &mask) == -1) { printf("Set CPU affinity failue, ERROR:%s\n", strerror(errno)); return -1; } usleep(1000); /* 让当前的设置有足够时间生效*/ /*查看当前进程的cpu 亲和性*/ CPU_ZERO(&get); /*sched_getaffinity函数获得pid所指示的进程的CPU位掩码,并将该掩码返回到mask所指向的结构中. *即获得指定pid当前可以运行在哪些CPU上. *同样,如果pid的值为0.也表示的是当前进程*/ if (sched_getaffinity(0, sizeof(get), &get) == -1) { printf("get CPU affinity failue, ERROR:%s\n", strerror(errno)); return -1; } /*查看当前进程的在哪个cpu核上运行*/ for(i = 0; i < cpus; i++) { if (CPU_ISSET(i, &get)) { /*查看cpu i 是否在get 集合当中*/ printf("this process %d of running processor: %d\n", getpid(), i); } } sleep(3); //让程序停在这儿,方便top命令查看 return 0; } 运行结果如下: [root@localhost test]# ./test cpus: 24this process 2848 of running processor: 0this process 2848 of running processor: 1this process 2848 of running processor: 2this process 2848 of running processor: 3 其中syscall是一个系统调用,根据指定的参数number和所有系统调用的接口来确定调用哪个系统调用,用于用户空间跟内核之间的数据交换。下面是syscall函数原型及一些常用的number: //syscall - indirect system callSYNOPSIS #define _GNU_SOURCE /* See feature_test_macros(7) */ #include <unistd.h> #include <sys/syscall.h> /* For SYS_xxx definitions */ int syscall(int number, ...); /* sysconf( _SC_PAGESIZE ); 此宏查看缓存内存页面的大小;打印用%ld长整型。 sysconf( _SC_PHYS_PAGES ) 此宏查看内存的总页数;打印用%ld长整型。 sysconf( _SC_AVPHYS_PAGES ) 此宏查看可以利用的总页数;打印用%ld长整型。 sysconf( _SC_NPROCESSORS_CONF ) 查看cpu的个数;打印用%ld长整。 sysconf( _SC_NPROCESSORS_ONLN ) 查看在使用的cpu个数;打印用%ld长整。 (long long)sysconf(_SC_PAGESIZE) * (long long)sysconf(_SC_PHYS_PAGES) 计算内存大小。 sysconf( _SC_LOGIN_NAME_MAX ) 查看最大登录名长度;打印用%ld长整。 sysconf( _SC_HOST_NAME_MAX ) 查看最大主机长度;打印用%ld长整。 sysconf( _SC_OPEN_MAX ) 每个进程运行时打开的文件数目;打印用%ld长整。 sysconf(_SC_CLK_TCK) 查看每秒中跑过的运算速率;打印用%ld长整。*/ 三、线程与cpu的绑定 线程于进程的绑定方法大体一致,需要注意的是线程绑定于进程的区别是所用函数不一样线程绑定用到下面两个函数,跟进程类似就不做详细说明,将当前线程绑定到0、1、2、3号cpu上示例如下: #define _GNU_SOURCE#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <pthread.h>#include <sched.h>void *testfunc(void *arg) { int i, cpus = 0; cpu_set_t mask; cpu_set_t get; cpus = sysconf(_SC_NPROCESSORS_CONF); printf("this system has %d processor(s)\n", cpus); CPU_ZERO(&mask); for (i = 0; i < 4; i++) { /*将0、1、2、3添加到集合中*/ CPU_SET(i, &mask); } /* 设置cpu 亲和性(affinity)*/ if (pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask) < 0) { fprintf(stderr, "set thread affinity failed\n"); } /* 查看cpu 亲和性(affinity)*/ CPU_ZERO(&get); if (pthread_getaffinity_np(pthread_self(), sizeof(get), &get) < 0) { fprintf(stderr, "get thread affinity failed\n"); } /* 查看当前线程所运行的所有cpu*/ for (i = 0; i < cpus; i++) { if (CPU_ISSET(i, &get)) { printf("this thread %d is running in processor %d\n", (int)pthread_self(), i); } } sleep(3); //查看 pthread_exit(NULL); } int main(int argc, char *argv[]) { pthread_t tid; if (pthread_create(&tid, NULL, (void *)testfunc, NULL) != 0) { fprintf(stderr, "thread create failed\n"); return -1; } pthread_join(tid, NULL); return 0; } 运行结果如下: [root@localhost thread]# ./test this system has 24 processor(s)this thread 2812323584 is running in processor 0this thread 2812323584 is running in processor 1this thread 2812323584 is running in processor 2this thread 2812323584 is running in processor 3 |
|