Systrace是Android系统中性能分析的主要工具。它记录一段时间内的设备活动,并产生一个HTML格式的Systrace报告。它收集了CPU调度、磁盘操作、应用线程等信息,可以帮助开发者分析系统瓶颈,改进性能。Systrace实际上是一些工具的集合,在设备端使用Atrace来控制用户空间的tracing和设置ftrace,kernel中使用tracing机制进行数据采集。Systrace最终读取ftrace的buffer,然后将数据打包以HTML文件的方式呈现。
Systrace的使用
在Android Studio 3.0之前的版本中可以通过菜单打开DDMS来使用Systrace,但之后的版本中DDMS被移除了,尽管可以通过命令方式启动DDMS,但它正逐渐被抛弃,取而代之的是Android Profiler。其中的CPU Profiler就提供了与Systrace类似的功能,他们使用的都是Kernel tracing的数据。所以我们不再介绍图形化的Systrace,仅谈论命令行模式。
使用Systrace命令前先确保已经安装Android SDK和Python,Systrace命令位于android-sdk/platform-tools/systrace/中。通过Python脚本来运行Systrace,
$ python systrace.py [options] [categories]
其中options支持的选项如下,
options | 描述 |
---|---|
-o < FILE > | 输出的目标文件 |
-t N, –time=N | 执行时间,默认5s |
-b N, –buf-size=N | buffer大小(单位kB),用于限制trace总大小,默认无上限 |
-k < KFUNCS >,–ktrace=< KFUNCS > | 追踪kernel函数,用逗号分隔 |
-a < APP_NAME >,–app=< APP_NAME > | 追踪应用包名,用逗号分隔 |
–from-file=< FROM_FILE > | 从文件中创建互动的systrace |
-e < DEVICE_SERIAL >,–serial=< DEVICE_SERIAL > | 指定设备 |
-l, –list-categories | 列举可用的tags |
categories为需要Tracing的模块,当前系统支持的categories可通过命令获取,
$ python systrace.py --list-categories
常见的categories包括,
gfx - Graphics
input - Input
view - View System
webview - WebView
wm - Window Manager
am - Activity Manager
sm - Sync Manager
audio - Audio
video - Video
camera - Camera
hal - Hardware Modules
app - Application
res - Resource Loading
dalvik - Dalvik VM
rs - RenderScript
bionic - Bionic C Library
power - Power Management
sched - CPU Scheduling
freq - CPU Frequency
idle - CPU Idle
load - CPU Load
Systrace执行的结果会生成一个HTML的报告,例如下面的执行结果会保存在mynewtrace.html中。
$ python systrace.py -o mynewtrace.html sched freq idle am wm gfx view input
使用Chrome打开mynewtrace.html,你tracing的模块将以图形化的方式展示出来。一个图形化的Systrace报告大致的展示如下,
通过快捷键可以调整视图,快捷键列表如下,
Key | Description |
---|---|
W | 缩小时间轴 |
A | 沿时间轴左移 |
S | 放大时间轴 |
D | 沿时间线右轴 |
E | 将当前鼠标位置在时间轴上居中 |
M | 标记当前的选择 |
1 | 选择模式,双击将高亮相同任务 |
2 | 平移模式,拖动平移视图 |
3 | 缩放模式,拖动鼠标实现放大/缩小 |
4 | 时移模式,拖动来创建或移除时间窗口线 |
G | 在当前选定任务的开始处显示网格。 |
Shift + G | 在当前选定任务的结束处显示网格 |
Left Arrow | 在当前时间轴上选择上一个事件。 |
Right Arrow | 在当前时间轴上选择下一个事件。 |
通过查看Systrace视图报告,可以查找并修复性能问题。下面的步骤可以帮助快速确定性能问题点,
- 使用矩形选择感兴趣的时间间隔。
- 使用标尺标记或高亮发生问题的区域。
- 通过”View Options > Highlight VSync“来展示每一个显示刷新操作。
如下图所示,Systrace报告列出了UI显示相关的每个进程,并沿时间轴标记了每一个显示帧。用绿色圆圈表示在16.6毫秒(每秒60帧)内呈现的,能稳定显示的帧。渲染时间超过16.6毫秒的帧用黄色或红色的圆圈表示。
点击帧标识的圆圈使其高亮,它将提供有关系统为渲染该帧所做的工作的其他信息,包括警报。报告还显示了在渲染该帧时系统正在执行的方法,分析这些方法可以确定UI卡顿的潜在原因。
点击异常帧后,可以在报告的底部窗口中看到警报。上图所示的警报表明,该帧的主要问题是ListView回收和重新绑定花费了太多时间。继续跟踪相关事件的链接,可以详细看到系统在这段时间内的工作。
要查看报告中发现的所有警报以及设备触发警报的次数,单击窗口最右侧的Alerts选项卡,如下图所示。Alerts面板可帮助查看跟踪中出现哪些问题,以及它们导致卡顿的频率如何。您可以将此面板视为要修复的问题列表。通常,一个小的修改或优化可以消灭整个一类警报。
如果您看到UI线程上运行了太多工作,可以使用以下方法之一来帮助确定哪些方法消耗了太多的CPU时间:
- 如果您对可能导致瓶颈的方法有所了解,可以在这些方法中添加跟踪标记。参考下诉的”定义自己的tracing“。
- 如果不确定UI瓶颈的根源,可以使用Android Studio中提供的CPU Profiler继续跟踪。通过Debug类生成跟踪日志,然后使用CPU Profiler导入和检查它们。
Atrace用法
Atrace可以认为是Systrace工具的一部分,其运行在设备端,用于控制ftrace。一般情况下,可以不需要关心Atrace,因为Atrace是由Systrace通过ADB进行控制的。我们简单介绍一下Atrace的使用,也许某一天真的用得上。Atrace的使用帮助如下,
usage: atrace [options] [categories...]
options include:
-a appname enable app-level tracing for a comma separated list of cmdlines; * is a wildcard matching any process
-b N use a trace buffer size of N KB
-c trace into a circular buffer
-f filename use the categories written in a file as space-separated
values in a line
-k fname,... trace the listed kernel functions
-n ignore signals
-s N sleep for N seconds before tracing [default 0]
-t N trace for N seconds [default 5]
-z compress the trace dump
--async_start start circular trace and return immediately
--async_dump dump the current contents of circular trace buffer
--async_stop stop tracing and dump the current contents of circular
trace buffer
--stream stream trace to stdout as it enters the trace buffer
Note: this can take significant CPU time, and is best
used for measuring things that are not affected by
CPU performance, like pagecache usage.
--list_categories
list the available tracing categories
-o filename write the trace to the specified file instead
of stdout.
其中许多选项都和Systrace相同,categories也是一样的。在设备端使用下面命令将tracing抓取到文件中,
# atrace -o /data/mynewtrace -t 10 sched freq idle am wm gfx view input
之后可以在PC中使用Systrace转换为HTML报告,和直接使用Systrace抓取的结果相同
$ python systrace.py --from-file mynewtrace
定义自己的tracing
Systrace默认仅仅跟踪系统级别进程的信息,如果想跟踪自己应用相对系统事件的执行状态,需要在代码中增加自己的tracing。在Java代码中使用Trace类来实现,由beginSection()和endSection()来标记跟踪的始末。实例代码如下,
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();
}
}
}
Native中增加跟踪代码包括几个部分,首先定义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符号,通常在构造函数里完成。
// 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"));
}
之后就可以在跟踪的代码前后调用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();
按上述方法修改完代码后,编译生成Debug版本的App。在执行Systrace时,像下例中使用-a来指定App,跟踪的事件将展示在Systrace的报告中。
$ 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
控制Ftrace
Atrace使用起来很简单,和Systrace几乎一样。Atrace通过配置Ftrace来启动tracing,了解Atrace就能简单了解Ftrace的使用。tracing的内容是通过categories指定的,看一下categories在代码中的处理。
/* Tracing categories */
static const TracingCategory k_categories[] = {
{ "gfx", "Graphics", ATRACE_TAG_GRAPHICS, { } },
{ "input", "Input", ATRACE_TAG_INPUT, { } },
{ "view", "View System", ATRACE_TAG_VIEW, { } },
{ "webview", "WebView", ATRACE_TAG_WEBVIEW, { } },
{ "wm", "Window Manager", ATRACE_TAG_WINDOW_MANAGER, { } },
{ "am", "Activity Manager", ATRACE_TAG_ACTIVITY_MANAGER, { } },
{ "sm", "Sync Manager", ATRACE_TAG_SYNC_MANAGER, { } },
{ "audio", "Audio", ATRACE_TAG_AUDIO, { } },
{ "video", "Video", ATRACE_TAG_VIDEO, { } },
{ "camera", "Camera", ATRACE_TAG_CAMERA, { } },
{ "hal", "Hardware Modules", ATRACE_TAG_HAL, { } },
{ "app", "Application", ATRACE_TAG_APP, { } },
{ "res", "Resource Loading", ATRACE_TAG_RESOURCES, { } },
{ "dalvik", "Dalvik VM", ATRACE_TAG_DALVIK, { } },
{ "rs", "RenderScript", ATRACE_TAG_RS, { } },
{ "bionic", "Bionic C Library", ATRACE_TAG_BIONIC, { } },
{ "power", "Power Management", ATRACE_TAG_POWER, { } },
{ "sched", "CPU Scheduling", 0, {
{ REQ, "/sys/kernel/debug/tracing/events/sched/sched_switch/enable" },
{ REQ, "/sys/kernel/debug/tracing/events/sched/sched_wakeup/enable" },
} },
{ "irq", "IRQ Events", 0, {
{ REQ, "/sys/kernel/debug/tracing/events/irq/enable" },
} },
{ "freq", "CPU Frequency", 0, {
{ REQ, "/sys/kernel/debug/tracing/events/power/cpu_frequency/enable" },
{ OPT, "/sys/kernel/debug/tracing/events/power/clock_set_rate/enable" },
} },
{ "membus", "Memory Bus Utilization", 0, {
{ REQ, "/sys/kernel/debug/tracing/events/memory_bus/enable" },
} },
{ "idle", "CPU Idle", 0, {
{ REQ, "/sys/kernel/debug/tracing/events/power/cpu_idle/enable" },
} },
{ "disk", "Disk I/O", 0, {
{ OPT, "/sys/kernel/debug/tracing/events/f2fs/f2fs_sync_file_enter/enable" },
{ OPT, "/sys/kernel/debug/tracing/events/f2fs/f2fs_sync_file_exit/enable" },
{ OPT, "/sys/kernel/debug/tracing/events/f2fs/f2fs_write_begin/enable" },
{ OPT, "/sys/kernel/debug/tracing/events/f2fs/f2fs_write_end/enable" },
{ OPT, "/sys/kernel/debug/tracing/events/ext4/ext4_da_write_begin/enable" },
{ OPT, "/sys/kernel/debug/tracing/events/ext4/ext4_da_write_end/enable" },
{ OPT, "/sys/kernel/debug/tracing/events/ext4/ext4_sync_file_enter/enable" },
{ OPT, "/sys/kernel/debug/tracing/events/ext4/ext4_sync_file_exit/enable" },
{ REQ, "/sys/kernel/debug/tracing/events/block/block_rq_issue/enable" },
{ REQ, "/sys/kernel/debug/tracing/events/block/block_rq_complete/enable" },
} },
{ "mmc", "eMMC commands", 0, {
{ REQ, "/sys/kernel/debug/tracing/events/mmc/enable" },
} },
{ "load", "CPU Load", 0, {
{ REQ, "/sys/kernel/debug/tracing/events/cpufreq_interactive/enable" },
} },
{ "sync", "Synchronization", 0, {
{ REQ, "/sys/kernel/debug/tracing/events/sync/enable" },
} },
{ "workq", "Kernel Workqueues", 0, {
{ REQ, "/sys/kernel/debug/tracing/events/workqueue/enable" },
} },
{ "memreclaim", "Kernel Memory Reclaim", 0, {
{ REQ, "/sys/kernel/debug/tracing/events/vmscan/mm_vmscan_direct_reclaim_begin/enable" },
{ REQ, "/sys/kernel/debug/tracing/events/vmscan/mm_vmscan_direct_reclaim_end/enable" },
{ REQ, "/sys/kernel/debug/tracing/events/vmscan/mm_vmscan_kswapd_wake/enable" },
{ REQ, "/sys/kernel/debug/tracing/events/vmscan/mm_vmscan_kswapd_sleep/enable" },
} },
{ "regulators", "Voltage and Current Regulators", 0, {
{ REQ, "/sys/kernel/debug/tracing/events/regulator/enable" },
} },
};
对于Kernel的tracing就是直接设置sysfs中的开关,写1就打开tracing,写0就关闭。对于用户空间的tracing则使用了ATRACE_TAG,每一个category代表trace tags中的一位,使用0/1来表示使能/关闭。生成的trace tags被保存的属性"debug.atrace.tags.enableflags"中。在具体模块在使用tracing时,先定义本模块的ATRACE_TAG,然后在具体的函数入口增加ATRACE_CALL()来进行跟踪。例如在SurfaceFlinger的代码中可以看到,
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
......
void SurfaceFlinger::eventControl(int disp, int event, int enabled) {
ATRACE_CALL();
getHwComposer().eventControl(disp, event, enabled);
}
void SurfaceFlinger::onMessageReceived(int32_t what) {
ATRACE_CALL();
......
}
ATRACE_CALL()的实现很简单,就是在构造和析构时调用了atrace_begin()和atrace_end()。
#define ATRACE_NAME(name) android::ScopedTrace ___tracer(ATRACE_TAG, name)
// ATRACE_CALL is an ATRACE_NAME that uses the current function name.
#define ATRACE_CALL() ATRACE_NAME(__FUNCTION__)
namespace android {
class ScopedTrace {
public:
inline ScopedTrace(uint64_t tag, const char* name)
: mTag(tag) {
atrace_begin(mTag,name);
}
inline ~ScopedTrace() {
atrace_end(mTag);
}
private:
uint64_t mTag;
};
}; // namespace android
atrace_begin()实现就是写入一条开始信息到Ftrace的trace_marker中,其中包含进程id、trace名字等信息。trace_marker位于"/sys/kernel/debug/tracing/trace_marker"。atrace_end()写入一条结束信息。
#define ATRACE_BEGIN(name) atrace_begin(ATRACE_TAG, name)
static inline void atrace_begin(uint64_t tag, const char* name)
{
if (CC_UNLIKELY(atrace_is_tag_enabled(tag))) {
char buf[ATRACE_MESSAGE_LENGTH];
size_t len;
len = snprintf(buf, ATRACE_MESSAGE_LENGTH, "B|%d|%s", getpid(), name);
write(atrace_marker_fd, buf, len);
}
}
#define ATRACE_END() atrace_end(ATRACE_TAG)
static inline void atrace_end(uint64_t tag)
{
if (CC_UNLIKELY(atrace_is_tag_enabled(tag))) {
char c = 'E';
write(atrace_marker_fd, &c, 1);
}
}
配置Ftrace
除了上述Ftrace的控制开关,还需要在tracing之前在用户空间对Ftrace进行配置。我们使用Atrace的源码来看一下配置过程,Atrace中涉及的配置如下。
// 配置tracing使用的时钟,"global":全局时钟,"local":per-CPU本地时钟。默认使用全局时钟。
static const char* k_traceClockPath =
"/sys/kernel/debug/tracing/trace_clock";
// 配置tracing缓存的大小,默认为2048kb。在Systrace和Atrace命令中可以通过"-b"指定。
static const char* k_traceBufferSizePath =
"/sys/kernel/debug/tracing/buffer_size_kb";
// 设置tracing缓存是否可以被覆盖,默认是false。在Atrace命令中可以通过"-c"设置。
static const char* k_tracingOverwriteEnablePath =
"/sys/kernel/debug/tracing/options/overwrite";
// 设置使用的tracer,默认为"function_graph"。
static const char* k_currentTracerPath =
"/sys/kernel/debug/tracing/current_tracer";
// tracing中是否打印tgdi,默认为true。
static const char* k_printTgidPath =
"/sys/kernel/debug/tracing/options/print-tgid";
// 使用"function_graph"时是否显示绝对时间戳,默认为true。
static const char* k_funcgraphAbsTimePath =
"/sys/kernel/debug/tracing/options/funcgraph-abstime";
// 使用"function_graph"时是否显示所在的CPU,默认为true。
static const char* k_funcgraphCpuPath =
"/sys/kernel/debug/tracing/options/funcgraph-cpu";
// 使用"function_graph"时是否显示进程的command line,默认为true。
static const char* k_funcgraphProcPath =
"/sys/kernel/debug/tracing/options/funcgraph-proc";
// 使用"function_graph"时是否显示为平面列表,默认为true。
static const char* k_funcgraphFlatPath =
"/sys/kernel/debug/tracing/options/funcgraph-flat";
// 使用"function_graph"时是否显示函数执行时间,未设置。
static const char* k_funcgraphDurationPath =
"/sys/kernel/debug/tracing/options/funcgraph-duration";
// 指定tracing的kernel函数,Systrace和Atrace命令可以通过"-k"来指定。
static const char* k_ftraceFilterPath =
"/sys/kernel/debug/tracing/set_ftrace_filter";
// 开始或停止tracing,实际上是控制tracing缓存是否可以被写入。
static const char* k_tracingOnPath =
"/sys/kernel/debug/tracing/tracing_on";
// tracing输出到这个节点,读取这个节点来dump输出。
static const char* k_tracePath =
"/sys/kernel/debug/tracing/trace";
// 用户写这个节点来增加自己的tracing。
static const char* k_traceMarkerPath =
"/sys/kernel/debug/tracing/trace_marker";
Atrace设置Ftrace在setUpTrace()完成,之后会调用startTrace()开始使能跟踪。
static bool setKernelTraceFuncs(const char* funcs)
{
bool ok = true;
if (funcs == NULL || funcs[0] == '\0') {
// Disable kernel function tracing.
if (fileIsWritable(k_currentTracerPath)) {
ok &= writeStr(k_currentTracerPath, "nop"); //设置current_tracer
}
if (fileIsWritable(k_ftraceFilterPath)) {
ok &= truncateFile(k_ftraceFilterPath);
}
} else {
// Enable kernel function tracing.
ok &= writeStr(k_currentTracerPath, "function_graph"); //设置current_tracer
ok &= setKernelOptionEnable(k_funcgraphAbsTimePath, true); //设置funcgraph-abstime
ok &= setKernelOptionEnable(k_funcgraphCpuPath, true); //设置funcgraph-cpu
ok &= setKernelOptionEnable(k_funcgraphProcPath, true); //设置funcgraph-proc
ok &= setKernelOptionEnable(k_funcgraphFlatPath, true); //设置funcgraph-flat
// Set the requested filter functions.
......
// Verify that the set functions are being traced.
if (ok) {
ok &= verifyKernelTraceFuncs(funcs); //设置set_ftrace_filter
}
}
return ok;
}
static bool setUpTrace()
{
bool ok = true;
// Set up the tracing options.
ok &= setTraceOverwriteEnable(g_traceOverwrite); //设置overwrite
ok &= setTraceBufferSizeKB(g_traceBufferSizeKB); //设置buffer_size_kb
ok &= setGlobalClockEnable(true); //设置trace_clock
ok &= setPrintTgidEnableIfPresent(true); //设置print-tgid
ok &= setKernelTraceFuncs(g_kernelTraceFuncs);
// Set up the tags property.
...... // 设置property
// Enable all the sysfs enables that are in an enabled category.
...... // 通过sysfs使能kernel中的categories
return ok;
}
Atrace的大致流程就是:配置Ftrace的参数,然后使能tracing,结束后抓取tracing数据。
Ftrace的使用
上面介绍了使用Systrace和Atrace进行系统跟踪的方法,它们本质上最终还是使用Ftrace来实现。所以,我们完全可以直接通过sysfs来配置Ftrace来进行系统跟踪,但这种方式仅仅可以跟踪kernel的调用。使用过程与Atrace的实现是类似,一个简单的Ftrace使用过程如下。
-
使能需要tracing的kernel事件,例如:
$ echo 1 > /sys/kernel/debug/tracing/events/cpufreq_interactive/enable
-
选着tracer,
$ echo function_graph > /sys/kernel/debug/tracing/current_tracer
-
配置缓存的大小,
$ echo 2048 > /sys/kernel/debug/tracing/buffer_size_kb
-
使能tracing,
$ echo 1 > /sys/kernel/debug/tracing/tracing_on
-
运行测试程序,然后停止tracing,
$ echo 0 > /sys/kernel/debug/tracing/tracing_on
-
导出tracing数据,
$ cat /sys/kernel/debug/tracing/trace > /data/tmp/trace_output
原始的tracing数据可读性较差,大致的样子如下。
TRACE:
# tracer: nop
#
# entries-in-buffer/entries-written: 1701/1701 #P:2
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID TGID CPU# |||| TIMESTAMP FUNCTION
# | | | | |||| | |
<...>-7844 (-----) [000] ...1 886.340626: tracing_mark_write: B|3073|queueBuffer
<...>-4061 (-----) [000] ...1 886.340908: tracing_mark_write: B|3049|queueBuffer
<...>-4061 (-----) [000] ...1 886.340932: tracing_mark_write: B|3049|SurfaceView: 2
分析这个tracing数据时只能通过搜索去寻找感兴趣的方法,然后再进行数据对比。可以通过工具”Catapult“来提供可读性,Catapult可以将文本格式的数据转化为网页格式。通过GitHub来获取Catapult,然后运行trace2html生成网页文件。
$ catapult/tracing/bin/trace2html ~/path/to/trace_file
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。