Android UI性能测试——使用 Systrace 查找问题

一 官方文档翻译

官文地址:https://developer.android.com/studio/command-line/systrace

systrace命令允许您在系统级别上收集和检查所有运行在设备上的进程的耗时信息。它结合了来自Android内核的数据,例如CPU调度程序,磁盘活动和app线程,最后生成HTML报告,类似于图1中所示。

图1:systrace HTML示例报告,程序默认抓取5秒内应用和系统的消耗。该报告突出显示了systrace认为的异常帧。

此报告提供了在给定时间段内Android设备的系统进程的总体情况。它还检查捕获的跟踪信息以突出显示它所发现的问题,例如在显示动作或动画时的UI jank,并提供有关如何修复它们的建议。但是, systrace工具不会收集有关app进程中代码执行的信息。有关app正在执行的方法以及它使用的CPU资源的更多详细信息,请使用Android Studio自带的的CPU profiler,您还可以生成跟踪日志并使用CPU profiler导入和检查它们。

本文档说明如何从命令行通过systrace生成报告,查看生成的跟踪文件,并使用它们分析和改进app用户界面(UI)的性能。

注意:在运行Android 9(API级别28)或更高版本的设备上,您还可以使用名为“系统跟踪”的系统应用程序来记录设备上的系统跟踪。

要运行systrace,请完成以下步骤

1.从Android Studio下载并安装最新的Android SDK工具。
2.安装Python并配置环境变量。
3.使用USB调试将运行Android 4.3(API级别18)或更高版本的设备连接到开发系统。

Systrace是Android SDK中自带的一个命令工具,位于android-sdk/platform-tools/systrace/

语法

要为app生成HTML报告,您需要使用systrace的以下语法从命令行运行:

$ python systrace.py [options] [categories]

例如,以下命令调用systrace记录设备活动并生成名为mynewtrace.html的HTML报告。对于大多数设备来说,此类别列表是合理的默认列表。

$ python systrace.py -o mynewtrace.html sched freq idle am wm gfx view \
binder_driver hal dalvik camera input res

提示:如果要在跟踪输出中查看任务的名称,则必须在命令参数中包含sched。

要查看已连接设备支持的类别列表,请运行以下命令:

$ python systrace.py --list-categories

如果未指定任何类别或选项,systrace默认会生成包含所有可用类别并使用默认设置的报告。可用的类别取决于您连接的设备。

全局选项

命令和命令选项

调查UI性能问题

systrace对于检测app的UI性能特别有用,因为它可以分析您的代码和帧率,以识别问题所在并提出可能的解决方案或建议。首先,按以下步骤操作:

1.在连接的设备上运行您的应用。

2.systrace使用以下命令运行(此命令会跟踪您的应用10秒钟)

$ python systrace.py -t 10 [other-options] [categories]

3.在systrace运行的同时与您的应用互动。

4.在您定义的时间限制结束后(本次示例是10秒),systrace生成HTML报告。

5.使用Web浏览器打开HTML报告。

通过此报告,您可以检测设备CPU在记录期间的使用情况。

以下部分介绍了如何查阅报告中的信息以发现和修复UI性能问题。

查看帧和警告

如图2所示,该报告列出了每个进程沿时间轴的每个渲染帧。绿色帧表示在16.6毫秒内渲染并保持每秒60帧稳定帧率的帧,黄色或红色帧表渲染时间超过16.6毫秒的帧。

图2:Systrace报告显示放大耗时长的帧。

注意:在运行Android 5.0(API级别21)或更高版本的设备上,渲染帧的工作分配给了UI Thread和RenderThread。在以前的版本中,创建帧的所有工作都被UI Thread完成。

单击帧会突出显示它,并显示有关系统完成该帧所做工作的其他信息,包括警告。它还会向您显示系统在渲染该帧时执行的方法,因此您可以调查这些方法以获取UI jank的原因。

图3.单击异常帧,跟踪报告下方会出现一个警告,用于识别问题。

选择慢速帧后,您会在报告的底部窗格中看到警告。图3中显示的警告指出此帧的主要问题是在ListView回收和重新绑定中花费了太多时间 。点击跟踪中相关事件的链接,可以更详细地查看系统在此期间所执行的操作。

要查看Systrace工具在跟踪中发现的每个警告,以及设备触发每个警告的次数,请单击窗口最右侧的Alert按钮,如图4所示。警告面板可帮助您查看在跟踪中发生的问题,以及它们对jank的贡献频率。可以将警告面板视为要修复的错误列表。通常一个区域的微小变化或改进可以消除应用程序中的整个警告类别。

图4:单击右侧的Alert按钮显示警告面板。

如果你发现UI线程做了很多的工作,你需要找出具体是哪些方法消耗了大多数CPU时间。一种方法是将跟踪标记添加到您认为导致这些瓶颈的方法中,然后在systrace中查看这些函数调用。如果您不确定哪些方法可能导致UI线程出现瓶颈,请使用Android Studio的CPU Profiler。您可以使用CPU Profiler生成跟踪日志并导入和检查它们。

HTML报告的键盘快捷键

下表列出了查看systrace HTML报告时可用的键盘快捷键。

检查应用代码

因为systrace仅在系统级别显示有关进程的信息,所以在HTML报告中很难了解你的APP在给定时间内具体执行的方法。在Android 4.3(API级别18)及更高版本中,您可以使用代中的Trace类来标记HTML报告中的执行事件,您不需要通过检查代码来记录跟踪 。但是检查代码可以帮助您了解app代码的哪些部分可能会导致线程挂起或UI jank。这种方法与使用Debug类不同——Trace类只是向systrace报告添加标记,而Debug 类是通过生成.trace文件来帮助您详细检查app的CPU使用情况。

要生成包含已检测跟踪事件的systrace HTML报告,您需要使用systrace的-a或--app选项运行命令行并指定app的包名称。

下面的示例代码演示如何使用Trace类标记一个方法的执行,包括该方法中的两个嵌套代码块:

public class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {
    ...
    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        Trace.beginSection(&quot;MyAdapter.onCreateViewHolder&quot;);
        MyViewHolder myViewHolder;
        try {
            myViewHolder = MyViewHolder.newInstance(parent);
        } finally {
            // In &#39;try...catch&#39; statements, always call <code><a href="/reference/android/os/Trace.html#endSection()">endSection()</a></code>
            // in a &#39;finally&#39; block to ensure it is invoked even when an exception
            // is thrown.
            Trace.endSection();
        }
        return myViewHolder;
    }</p>

<p>@Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        Trace.beginSection(&quot;MyAdapter.onBindViewHolder&quot;);
        try {
            try {
                Trace.beginSection(&quot;MyAdapter.queryDatabase&quot;);
                RowItem rowItem = queryDatabase(position);
                mDataset.add(rowItem);
            } finally {
                Trace.endSection();
            }
            holder.bind(mDataset.get(position));
        } finally {
            Trace.endSection();
        }
    }
...
}

注意:当您多次调用beginSection(String)方法时,每次会调用离beginSection(String)方法最近的endSection()方法。因此,对于嵌套调用,例如上面示例中的调用,您需要确保将每个beginSection()方法的调用都正确匹配一个endSection()方法的调用。此外,在一个线程中你不能只调用beginSection()方法,您必须在同一线程中调用endSection()方法来结束它。

二 补充(摘自《大话APP测试2.0》)

Systrace是类似于Memory Monitor的一个缩小问题访问、初步判断问题的工具。但它也不是万能的,需要TraceView的配合才能最终定位问题。
实战:假设我们现在面对着一个比较卡的列表界面,应该按照如下的步骤来执行。
1)保证当前环境下测试的应用版本都是正确和稳定的。
2)在终端开启Systrace监听命令(一般开10s就足够了)。
3)在终端显示当前正在搜集Systrace时开始进行你认为卡顿的那些操作,比如滚动ListView、切换Tab等。
4)看到终端显示capture trace时意味着Systrace搜集trace已经结束,此时等待html报告生成即可。
打开Systrace报告,如果看到SurfaceFlinger这一项的色条间隔很大并且很不规则,这说明我们真实滚动列表时掉帧很厉害,从这个数据上面我们可以看出该ListView的功能体验很不好,并且能够计算出掉帧率。
知道掉帧率以及的确卡顿后,我们可以通过“W”、“A”、“S”、“D”把结果放大,然后查看在掉帧的时间段内应用到底做了什么,这样就能够大概定位问题出在什么地方了。
我们来总结一下,通过Systrace得到的信息有:
1)应用是否真的卡顿。
2)掉帧率是多少。
3)掉帧时应用到底在干什么。
4)单击Systrace报告中的警告或者错误的标签,看到对应的修改建议。
5)Kernel层CPU的使用情况。
……
Systrace也并不是所有的模块或功能都支持的,那么对于一些不支持但又要进行测试的功能应该怎么办?在Android4.3或者更高的版本中Android本身提供了Systrace类,可以进行相关的引用从而最终能够在报告中查看到。如下是官方文档中所提供的一段代码,大家可以参考。

public void ProcessPeople(){
    
    Trace.beginSection("ProcessPeople");        
    try{            
        Trace.beginSection("Processing Jane");            
        try {                
            //code for Jane task...            
        }finish{                
            Trace.endSection();//ends "Processing Jane"            
        }                        

        Trace.beginSection("Processing John");                
        try {                
            //code for John task...            
        }finish{                
            Trace.endSection();//ends "Processing John"            
        }        
    }finish{            
        Trace.endSection();
        //ends "ProcessPeople"        
    }    

}

三 我的实战

C:\Users\Administrator>adb devices # 查看设备
List of devices attached
GSL7N16B10000581        device

C:\Users\Administrator>D: # 进入D盘

D:\>cd software\Android\SDK\platform-tools\systrace # 进入Systrace目录

D:\software\Android\SDK\platform-tools\systrace>dir # 查看该目录:可看到有systrace.py文件
 驱动器 D 中的卷是 软件
 卷的序列号是 A80B-3BC5

 D:\software\Android\SDK\platform-tools\systrace 的目录

2018\11\30 周五  14:25    <DIR>          .
2018\11\30 周五  14:25    <DIR>          ..
2018\11\22 周四  10:32    <DIR>          catapult
2018\11\22 周四  10:32            11,738 NOTICE
2018\11\22 周四  10:32             1,456 systrace.py
2018\11\22 周四  10:32                41 UPSTREAM_REVISION
               3 个文件         13,235 字节
               3 个目录 28,734,406,656 可用字节


D:\software\Android\SDK\platform-tools\systrace>python systrace.py -l #查看设备支持的类别
         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
          pm - Package Manager
          ss - System Server
    database - Database
     network - Network
         adb - ADB
       sched - CPU Scheduling
        freq - CPU Frequency
        idle - CPU Idle
        load - CPU Load
  memreclaim - Kernel Memory Reclaim
  binder_driver - Binder Kernel driver
  binder_lock - Binder global lock trace

NOTE: more categories may be available with adb root



D:\software\Android\SDK\platform-tools\systrace>python systrace.py -o mynewtrace.html -t 10 sched freq idle am wm gfx view binder_driver hal dalvik camera input res #在honor 8 Android 8.0设备上运行,报如下错误

Exception in thread Thread-11:
Traceback (most recent call last):
  File "D:\software\Python27\lib\threading.py", line 801, in __bootstrap_inner
    self.run()
  File "D:\software\Python27\lib\threading.py", line 754, in run
    self.__target(*self.__args, **self.__kwargs)
  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\tracing_agents\atrace_agent.py", line 196, in _collect_and_preprocess
    trace_data = self._collect_trace_data()
  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\tracing_agents\atrace_agent.py", line 256, in _collect_trace_data
    result = self._stop_collect_trace()
  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\tracing_agents\atrace_agent.py", line 243, in _stop_collect_trace
    large_output=True, check_return=True, timeout=ADB_LARGE_OUTPUT_TIMEOUT)
  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\..\..\devil\devil\android\decorators.py", line 57, in timeout_retry_wrapper
    retry_if_func=retry_if_func)
  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\..\..\devil\devil\utils\timeout_retry.py", line 157, in Run
    error_log_func=error_log_func)
  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\..\..\devil\devil\utils\reraiser_thread.py", line 186, in JoinAll
    self._JoinAll(watcher, timeout)
  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\..\..\devil\devil\utils\reraiser_thread.py", line 151, in _JoinAll
    (len(alive_threads), len(self._threads)))
CommandTimeoutError: Timed out waiting for 1 of 1 threads.

Outputting Systrace results...
Tracing complete, writing results
Traceback (most recent call last):
  File "systrace.py", line 49, in <module>
    sys.exit(run_systrace.main())
  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\run_systrace.py", line 201, in main
    main_impl(sys.argv)
  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\run_systrace.py", line 198, in main_impl
    controller.OutputSystraceResults(write_json=options.write_json)
  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\systrace_runner.py", line 69, in OutputSystraceResults
    self._out_filename)
  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\output_generator.py", line 99, in GenerateHTMLOutput
    html_file.write(_ConvertToHtmlString(result.raw_data))
  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\output_generator.py", line 121, in _ConvertToHtmlString
    raise ValueError('Invalid trace result format for HTML output')
ValueError: Invalid trace result format for HTML output

D:\software\Android\SDK\platform-tools\systrace>python systrace.py -o mynewtrace.html -t 10 sched freq idle am wm gfx view hal dalvik camera input res # 在huawei M2-803L android 5.0.1 EMUI 3.1设备上运行报如下错误
Starting tracing (10 seconds)
Tracing completed. Collecting output...
CRITICAL:root:(TimeoutThread-1-for-Thread-11) Exception on ReadFile(YVF6R1741500
0417, /sys/kernel/debug/tracing/tracing_on, retries=3, timeout=30), attempt 1 of
 4: AdbCommandFailedError("(device: YVF6R17415000417) adb pull /sys/kernel/debug
/tracing/tracing_on 'c:\\users\\admini~1\\appdata\\local\\temp\\tmppdnptu\\tmp_R
eadFileWithPull': failed with exit status 1 and output:\n- adb: error: remote ob
ject '/sys/kernel/debug/tracing/tracing_on' does not exist\n",)
CRITICAL:root:(TimeoutThread-2-for-Thread-11) Exception on ReadFile(YVF6R1741500
0417, /sys/kernel/debug/tracing/tracing_on, retries=3, timeout=30), attempt 2 of
 4: AdbCommandFailedError("(device: YVF6R17415000417) adb pull /sys/kernel/debug
/tracing/tracing_on 'c:\\users\\admini~1\\appdata\\local\\temp\\tmpepvljn\\tmp_R
eadFileWithPull': failed with exit status 1 and output:\n- adb: error: remote ob
ject '/sys/kernel/debug/tracing/tracing_on' does not exist\n",)
CRITICAL:root:(TimeoutThread-3-for-Thread-11) Exception on ReadFile(YVF6R1741500
0417, /sys/kernel/debug/tracing/tracing_on, retries=3, timeout=30), attempt 3 of
 4: AdbCommandFailedError("(device: YVF6R17415000417) adb pull /sys/kernel/debug
/tracing/tracing_on 'c:\\users\\admini~1\\appdata\\local\\temp\\tmpiyef2y\\tmp_R
eadFileWithPull': failed with exit status 1 and output:\n- adb: error: remote ob
ject '/sys/kernel/debug/tracing/tracing_on' does not exist\n",)
Exception in thread Thread-11:
Traceback (most recent call last):
  File "D:\software\Python27\lib\threading.py", line 801, in __bootstrap_inner
    self.run()
  File "D:\software\Python27\lib\threading.py", line 754, in run
    self.__target(*self.__args, **self.__kwargs)
  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\tracing_agents\atrace_agent.py", line 196, in _collect_and_preprocess
    trace_data = self._collect_trace_data()
  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\tracing_agents\atrace_agent.py", line 256, in _collect_trace_data
    result = self._stop_collect_trace()
  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\tracing_agents\atrace_agent.py", line 247, in _stop_collect_trace
    if int(self._device_utils.ReadFile(is_trace_enabled_file)):
  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\..\..\devil\devil\android\decorators.py", line 57, in timeout_retry_wrapper
    retry_if_func=retry_if_func)
  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\..\..\devil\devil\utils\timeout_retry.py", line 157, in Run
    error_log_func=error_log_func)
  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\..\..\devil\devil\utils\reraiser_thread.py", line 186, in JoinAll
    self._JoinAll(watcher, timeout)
  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\..\..\devil\devil\utils\reraiser_thread.py", line 158, in _JoinAll
    thread.ReraiseIfException()
  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\..\..\devil\devil\utils\reraiser_thread.py", line 81, in run
    self._ret = self._func(*self._args, **self._kwargs)
  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\..\..\devil\devil\utils\timeout_retry.py", line 150, in <lambda>
    child_thread = reraiser_thread.ReraiserThread(lambda: func(*args, **kwargs),

  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\..\..\devil\devil\android\decorators.py", line 47, in impl
    return f(*args, **kwargs)
  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\..\..\devil\devil\android\device_utils.py", line 1781, in ReadFile
    return self._ReadFileWithPull(device_path)
  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\..\..\devil\devil\android\device_utils.py", line 1733, in _ReadFileWithPull
    self.adb.Pull(device_path, host_temp_path)
  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\..\..\devil\devil\android\sdk\adb_wrapper.py", line 474, in Pull
    self._RunDeviceAdbCmd(cmd, timeout, retries)
  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\..\..\devil\devil\android\sdk\adb_wrapper.py", line 301, in _RunDeviceAdbCmd
    check_error=check_error)
  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\..\..\devil\devil\android\decorators.py", line 51, in timeout_retry_wrapper
    return impl()
  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\..\..\devil\devil\android\decorators.py", line 47, in impl
    return f(*args, **kwargs)
  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\..\..\devil\devil\android\sdk\adb_wrapper.py", line 281, in _RunAdbCmd
    args, output, status, device_serial)
AdbCommandFailedError: (device: YVF6R17415000417) adb pull /sys/kernel/debug/tra
cing/tracing_on 'c:\users\admini~1\appdata\local\temp\tmp_ygea9\tmp_ReadFileWith
Pull': failed with exit status 1 and output:
- adb: error: remote object '/sys/kernel/debug/tracing/tracing_on' does not exis
t


Outputting Systrace results...
Tracing complete, writing results
Traceback (most recent call last):
  File "systrace.py", line 49, in <module>
    sys.exit(run_systrace.main())
  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\run_systrace.py", line 201, in main
    main_impl(sys.argv)
  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\run_systrace.py", line 198, in main_impl
    controller.OutputSystraceResults(write_json=options.write_json)
  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\systrace_runner.py", line 69, in OutputSystraceResults
    self._out_filename)
  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\output_generator.py", line 99, in GenerateHTMLOutput
    html_file.write(_ConvertToHtmlString(result.raw_data))
  File "D:\software\Android\SDK\platform-tools\systrace\catapult\systrace\systra
ce\output_generator.py", line 121, in _ConvertToHtmlString
    raise ValueError('Invalid trace result format for HTML output')
ValueError: Invalid trace result format for HTML output
systrace-example-error

实战总结:

1.测试发现目前运行Systrace命令只能使用Python2.7而不能使用Python3.X。

2.第一次运行Systrace命令时报错:ImportError: No module named win32con。
解决方法:通过命令pip install pypiwin32或者python -m pip install pypiwin32 来安装pypiwin32。
如果你没有pip,请参考下面步骤:
下载(https://pypi.python.org/pypi/pip);
解压;
安装(Python setup.py install);
添加环境变量:D:\software\Python27\Scripts。
PS:我的电脑是装了2个Python,一个Python2.7,一个Python3.5,结果执行pip install pypiwin32命令时把模块安装到了Python3.5里,于是通过Python2.7执行Systrace命令时发现依然报错。所以请确认你的pypiwin32确实安装到了Python2.7里(最好的办法就是安装前先卸载Python3.5)

3.解决了上面报错后,发现又报错:ValueError: Invalid trace result format for HTML output。查看stackoverflow后也没找到合适的解决方案。

posted @ 2019-05-17 15:51  zhengna  阅读(2688)  评论(0编辑  收藏  举报