Traceview和dmtracedump分析工具


Traceview是查看程序运行时log的图形化查看工具,在代码中用Debug类记录跟踪信息并生成log文件。用Traceview工具可以帮助调试程序和分析程序性能。



Traceview的界面布局


如果你有一份Trace log文件(可以在程序中添加跟踪代码生成,也可以由ddms生成),那么就可以用Traceview载入log文件,Traceview会在窗口中用两个面板来形象化


的展示程序相关信息:


  • 时间线面板    ----    描述每个线程和方法何时起止
  • 分析面板        ----    提供每个方法的情况概要


时间线面板


下图是时间轴面板的一个特写。每一行显示一个线程的运行情况,时间从左到右增加。每一个方法用一种颜色表示(从表示最长时间段的那个颜色开始,颜色会被循环重用,注:待考)。第一行下面的细线指示了,对选中方法的所有的调用的时间范围(进入到退出),图片所示的方法是LoadListener.nativeFinished(),这个方法在分析视图中被选中。






图一,Traceview的时间线面板



分析面板


图二显示的是分析面板,方法中所有时间消耗的一个概要。该表同时显示inclusive 时间和exclusive时间(用与总时间的百分比表示)。Exclusive时间是方法本身消耗的时间,Inclusive时间是方法本身消耗的时间,再加上方法中所调用的其他任何方法所消耗的时间之和。我们称调用方法为“parents”,被调用的方法称为“children”。当一个方法被选中时(单击就会选中),它会展开显示“parents”和“children”。“parents”用紫色背景显示,“children”用黄色背景显示。表的最后一列显示对本方法的调用数量加上递归调用数量。最后一栏显示对那个方法的调用总数中的调用数量(注:待考) 。由图中,我们可以看到对LoadListener.nativeFinished()方法有14次调用;同时看时间线视图,会 发现这些调用中有一个占用了异常长的时间。





图二,Traceview的分析面板



Traceview文件格式


Tracing创建了输出中两个截然不同的部分:一个data文件,包含了跟踪数据,另外还有一个key文件,提供二进制标识符到线程和方法名字的映射。当Tracing结束时,会将它们串联为单个.trace文件。



注意:之前版本的Traceview没有自动串联这些文件,如果你有旧的key和data文件需要继续跟踪,你可以手动串联,用如下命令:


cat mytrace.key mytrace.data > mytrace.trace



data文件格式


data文件是二进制格式的,结构如下(所有值都以little-endian顺序存储)


注:具体内容就不翻译了,不影响我们使用这个工具)



key文件格式


key文件是分为三部分的纯文本文件,每一部分都用星号“*”开头,如果你在行首看到了星号“*”字符,那么恭喜你,你找到了一个新部分的开头了。


注:文件格式的详细内容不翻译,不影响使用)



创建跟踪文件


要使用Traceview,你必须要生成包含你想要分析的跟踪信息的log文件。



有两种方法来生成跟踪log:


  • 在代码中,使用Debug类,调用这个类的方法来开始和结束跟踪,并将跟踪信息写入到磁盘。这种方式很精确,因为你可以在代码中精确的指定在什么位置开始和结束跟踪数据。
  • 使用DDMS的方法分析特性。这种方法不那么精确,因为你没有更改代码,而只是通过DDMS指定什么时候开始和结束log。尽管这种方式,不能精确控制log的地方,但是如果你无法接触程序代码,或者不需要第一种方式那么精确时,这仍然是一种很有用的方法。


生成trace log须知:


  • 如果你使用Debug类,你的设备或者模拟器需要有SD卡,并且程序有写入SD卡的权限。
  • 如果你使用DDMS,Android 1.5的设备是不支持的。
  • 如果你使用DDMS,Android2.1或之前的设备,必须有SD卡,并且程序有写入SD卡的权限。
  • 如果你使用DDMS,Android2.2或之后的设备,不需要SD卡,trace log文件直接传到了你的开发机上。

本文关注如何用Debug类生成跟踪数据,用DDMS来生成跟踪数据的话,可参考这一节: Using the Dalvik Debug Monitor Server



为生成跟踪数据,首先引入Debug类,之后调用几个startMethodTracing()方法中的一个,调用时,指定系统生成的log文件的基本名。调用stopMethodTracing()方法来结束log。这两个方法启动和结束方法跟踪,这种方法跟踪可以跨越整个虚拟机。例如,你可以在Activity的onCreate()方法中调用startMethodTracing()方法,在Activity的onDestroy()中调用stopMethodTracing()方法。


<span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(0, 0, 0);"> <wbr>  <wbr> </wbr></wbr></span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(136, 0, 0);">// start tracing to "/sdcard/calc.trace"</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(0, 0, 0);"> <wbr>  <wbr> </wbr></wbr></span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(102, 0, 102);">Debug</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(102, 102, 0);">.</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(0, 0, 0);">startMethodTracing</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(102, 102, 0);">(</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(0, 136, 0);">"calc"</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(102, 102, 0);">);</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(0, 0, 0);"> <wbr>  <wbr> </wbr></wbr></span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(136, 0, 0);">// ...</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(0, 0, 0);">
 <wbr>  <wbr> </wbr></wbr></span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(136, 0, 0);">// stop tracing</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(0, 0, 0);">
 <wbr>  <wbr> </wbr></wbr></span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(102, 0, 102);">Debug</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(102, 102, 0);">.</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(0, 0, 0);">stopMethodTracing</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(102, 102, 0);">();</span>

程序调用startMethodTracing()方法,系统会创建名为.trace的log文件。这个文件包含二进制的方法跟踪数据和线程方法名称映射表。



之后系统开始缓存生成的跟踪数据,直到程序调用stopMethodTracing()方法,此时,就会将缓存的数据写入到输出文件。如果在调用stopMethodTracing()方法之前,系统到达了最大缓冲尺寸,系统会停止跟踪并发送一个消息到控制台。



当启用分析时,解释性代码会运行得更慢一些,不要试图从分析结果中生成绝对时序(例如:函数X耗时2.5秒),这些时间只有相对于其他的分析输出才有用,所以你可以通过时间比较,判断所做更改是使代码变得更快了,还是更慢了。



当使用android模拟器时,在创建AVD时,必须指定SD卡,因为tracing文件要写入到SD卡中,同时程序必须有写入SD卡的权限。



注:当然,我没有全部翻译)。



复制跟踪文件到主机


程序已经运行并且系统已经在设备或模拟器上生成.trace文件后,你需要将log文件复制到你的开发主机上,可以使用adb pull命令来复制文件。下面这个例子,从模拟器的默认位置复制calc.trace文件到模拟器主机的/temp目录下:


<span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(0, 0, 0);">adb pull </span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(102, 102, 0);">/</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(0, 0, 0);">sdcard</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(102, 102, 0);">/</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(0, 0, 0);">calc</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(102, 102, 0);">.</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(0, 0, 0);">trace </span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(102, 102, 0);">/</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(0, 0, 0);">tmp</span>



用Traceview查看trace文件


运行traceview来查看trace文件,输入traceview 。例如,运行Traceview查看上节复制的例子文件:


<span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(0, 0, 0);">traceview </span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(102, 102, 0);">/</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(0, 0, 0);">tmp</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(102, 102, 0);">/</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(0, 0, 0);">calc</span>

注意:如果你要查看那些使用了Proguard的程序(Release模式编译)生成的log文件,一些方法和成员名称可能会被混淆。你可以使用Proguard的mapping.txt来计算出原始的未混淆的名称。关于该文件的更多信息,请查看 Proguard文档。



使用dmtracdedump


dmtracdedump是让你从跟踪日志文件中,生成图形化调用栈的工具。该工具使用Graphviz Dot utility来生成图形化输出,所以你在运行dmtracdedump之前,必须安装Graphviz。




dmtracdedump能够将调用栈数据生成为树形图,每个调用表示为一个节点。它使用箭头表示调用流程(从parent节点到child节点)。下图是一个输出示例。







图三,dmtracedump截图





  •   ----    调用的参考编号,trace log中用到的。
  •   ----    Inclusive消耗时间(方法,包括所有child方法消耗的毫秒数)
  •   ----    Exclusive消耗时间(方法,不包括任何child方法消耗的毫秒数)
  •   ----    调用数


dmtracedump 的用法:


<span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(0, 0, 0);">dmtracedump </span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(102, 102, 0);">[-</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(0, 0, 0);">ho</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(102, 102, 0);">]</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(0, 0, 0);"> </span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(102, 102, 0);">[-</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(0, 0, 0);">s sortable</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(102, 102, 0);">]</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(0, 0, 0);"> </span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(102, 102, 0);">[-</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(0, 0, 0);">d trace</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(102, 102, 0);">-</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(0, 0, 136);">base</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(102, 102, 0);">-</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(0, 0, 0);">name</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(102, 102, 0);">]</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(0, 0, 0);"> </span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(102, 102, 0);">[-</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(0, 0, 0);">g outfile</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(102, 102, 0);">]</span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(0, 0, 0);"> </span><span style="word-wrap: normal; word-break: normal; line-height: 25.19999885559082px; color: rgb(102, 102, 0);"><</span><span style="word-wrap: normal; word-break: normal; color: rgb(0, 0, 0);">trace</span><span style="word-wrap: normal; word-break: normal;">-</span><span style="word-wrap: normal; word-break: normal; color: rgb(0, 0, 136);">base</span><span style="word-wrap: normal; word-break: normal;">-</span><span style="word-wrap: normal; word-break: normal; color: rgb(0, 0, 0);">name</span><span style="word-wrap: normal; word-break: normal;">></span></span>

dmtracedump从  .data和 .key文件中载入trace log数据。下表列出了dmtracedump的选项。


Option

Description

-d 

Diff with this trace name

-g 

Generate output to

-h

Turn on HTML output

-o

Dump the trace file instead of profiling

-d 

URL base to the location of the sortable javascript file

-t 

Minimum threshold for including child nodes in the graph (child's inclusive time as a percentage of parent inclusive time). If this option is not used, the default threshold is 20%.

注:上面这个原始表明显是有误的,第一项和第五项相同,描述却不相同,第五项应为-s。下面是我运行这个工具,所提示的用法:

cly@topgun /cygdrive/g/android-sdk-windows/tools
$ dmtracedump.exe
Copyright (C) 2006 The Android Open Source Project

usage: G:\android-sdk-windows\tools\dmtracedump.exe [-ho] [-s sortable] [-d trace-file-name] [-g outfile] trace-file-name
  -d trace-file-name  - Diff with this trace
  -g outfile          - Write graph to 'outfile'
  -k                  - When writing a graph, keep the intermediate DOT file
  -h                  - Turn on HTML output
  -o                  - Dump the dmtrace file instead of profiling
  -s                  - URL base to where the sortable javascript file
  -t threshold        - Threshold percentage for including nodes in the graph




Traceview的已知问题


线程


Traceview log没有处理线程,会导致下面两个问题:


  1. 如果在分析期间,线程退出,那么该线程名字不会出现。
  2. VM重用线程ID。如果一个线程退出,又起了另一个线程,它们的ID可能相同。



注:我的ADT版本是21.1,使用了下traceview.bat,发现这个工具已经不能单独使用了,而用一个综合工具 Android Device Monitor代替了


cly@topgun /cygdrive/c/Users/cly/Desktop
 $ traceview.bat ./ddms61735.trace
 The standalone version of traceview is deprecated.
 Please use Android Device Monitor (tools/monitor) instead. 
 trace file './ddms61735.trace' not found


使用monitor


cly@topgun /cygdrive/c/Users/cly/Desktop
$ monitor.bat


会开启一个界面,选择File菜单,然后Open file,打开你的trace文件即可,双击这个trace的标签页,即可最大化显示,截图如下。




界面截图如下:,




android traceview性能调试_数据




图四,新版本的traceview视图



关于Traceview文件格式请参照 http://developer.android.com/guide/developing/debugging/debugging-tracing.html


在Android系统中,Java层和c++/c层都可以创建线程。不过Java层创建的线程最终都由c++/c层来实现。使用ddms这个工具可以查看虚拟机实例进程(除zygote本身和由nativecode创建的进程,比如vold外)所包含的线程大致信息。比如线程名字,id号,状态,user time和systemtime等。
大家可以看到一般一个虚拟机实例进程都包含有如下7个通用的线程,下面大致讲一下这些线程的作用和创建流程。
1. main
这个就是主线程了。具体流程待细述。
2. HeapWorker
一个异步的工作线程,处理那些需要在单独线程里面做的避免同步问题的堆操作。其源代码在dalvik/vm/alloc/HeapWorker.*部分。
3. Signal Catcher
这个线程是用来捕获linux信号和做一些后续处理的。比如说,当一个SIGQUIT(Ctrl-\)信号到达后,这个线程就会挂起虚拟机,并且将所有线程的状态信息输出到log。其源代码在dalvik/vm/SignalCatcher.*部分。
4. JDWP
这个线程是用来实现Java Debug WireProtocol的。如果命令行调试器的参数为"suspend=y",这样会暂停虚拟机。这个估计和eclipse的调试和ddms等调试工具相关。其源代码在dalvik/vm/jdwp/*部分。
5. Stdio Converter
这个线程从标准输出和标准错误输出读取信息并将它们转换为log信息。其源代码在dalvik/vm/StdioConverter.*部分。
6. Compiler
Android'sJit独立于目标平台的部分。其源代码在dalvik/vm/compiler/Compiler.*和dalvik/vm/interp/Jit.*等部分。
7. Binder Thread #%d
使用binder进行通讯时用到的线程。其源代码在frameworks/base/libs/binder/*等部分。
以下的线程属于system_server和应用程序专有线程,视具体应用的需求而定。
8. system_server专有
android.server.ServerThread
ActivityManager
ProcessStats
PackageManager
FileObserver
AccountManagerService
SyncHandlerThread
UEventObserver
PowerManagerService
AlarmManager
WindowManager
InputDeviceReader
WindowManagerPolicy
InputDispatcher
ConnectivityThread
WifiService
WifiWatchdogThread
LocationManagerService
AudioService
GpsEventThread
GpsNetworkThread
android.hardware.SensorManager$SensorThread
watchdog
Wallpaper
com.android.server.MountListener
9. misc
其他部分线程由java层的api提供,Thread等。