Facebook一直致力于不断提高Android应用的运行速度。虽然他们内部已经有类似CTScan这样的性能跟踪系统,但Android生态系统的多样性使他们无法在实验室中测试每一种可能。因此,他们希望通过遥测技术从人们真实使用的Android手机中收集性能信息来补充测试数据。近日,Facebook工程师Delyan Kratunov撰文介绍了他们收集Android应用远程性能检测数据的方法。
很长一段时间以来,遥测技术都仅限于费力地插入代码,标识动作的起点和终点。这种方法有诸多弊端:
同时,Delyan还指出,他们也不希望使用下面这两种方法:
他们所采用的方法,灵感来自于该领域先前的研究,核心是一个基于规则的字节码重写器(基于ASM库)。该重写器可以匹配代码位置,然后插入或操作代码。就是说,在Java代码经javac编译成Java虚拟机字节码之后,但是在传递给dx转换成Dalvik VM格式之前,它会介入修改JVM字节码。
作为构建系统的一部分,该字节码重写器会在Android应用的全部Java字节码上运行,执行少数几个简单的转换,产生大量发生过重写的代码位置。例如,下面的规则将在特定方法的入口和出口处插入代码:
new EntryExitRule.Builder() .setMatcherConfiguration( subclassesOf( getObjectType("android/app/Activity") ).withMethods( getMethod("void onCreate(android.os.Bundle)"), getMethod("void onRestart()"), getMethod("void onStart()"), getMethod("void onResume()"), getMethod("void onPause()"), getMethod("void onStop()"), getMethod("void onDestroy()"))) .setDetourType(LOG_UTILS_TYPE) .setDetourMethodEntry(LOG_METHOD_ACTIVITY_START) .setDetourMethodExit(LOG_METHOD_ACTIVITY_END) .setCategory(Categories.LIFECYCLE) .build()
在运行时,这些方法会在日志中记录一个或多个检测事件,并且,这些事件可以组合到一个单独的跟踪文件中。他们的检测粒度是框架调用和回调层。就是说,检测应用如何同Android框架交互以及框架反过来如何调用应用。这非常有用,因为应用组件不同生命周期之间的交互对运行时性能有重大影响。而且,由于检测点插入是自动完成的,所以无需担心代码变化会影响检测点。
在字节码中插入检测点还有一个好处,就是让他们能够透明地处理异步跟踪。也就是说,他们可以在线程之间自动传递足够的上下文信息。这样,他们就能将逻辑控制流串连起来。例如,下面的规则是检测Handler API的:
RedirectionRule.builder() .setMatcherConfiguration( subclassesOf( getObjectType("android/os/Handler") ).withMethods( getMethod("boolean post(Runnable)"), getMethod("boolean postAtFrontOfQueue(Runnable)"), getMethod("boolean postAtTime(Runnable, Object, long)"), getMethod("boolean postAtTime(Runnable, long)"), getMethod("boolean postDelayed(Runnable, long)"), getMethod("void removeCallbacks(Runnable)"))) .setDetourClass("com/facebook/tools/dextr/runtime/detour/HandlerDetour") .setCategory(Categories.ASYNC) .build()
虽然有无数种在线程之间切换控制的方法,但实际上,一个很小的规则集合就可以覆盖应用中大多数异步代码。总的来说,这种跨线程跟踪能力让他们对应用执行流程有了更深入的了解,可以暴露出一些难以捉摸的性能缺陷,如调度延迟和不必要的异步跳转。
此外,在实现该方法的过程中,他们还遇到了其它一些需要克服的问题。比如,仅使用基本数据类型。当字节码重写器操作应用代码时,它会在每个代码位置插入一个唯一标识。在应用构建时,它会生成一个标识与代码位置的映射。在运行时,他们只记录32位的整型标识,然后在服务器端转换成代码位置。这样,事件大小就可以固定,而且非常小。同时,这也缩小了跟踪文件,减少了运行时开销。此处仅举一例,更多信息请查看原文。
感谢郭蕾对本文的审校。
给InfoQ中文站投稿或者参与内容翻译工作,请邮件至[email protected]。也欢迎大家通过新浪微博(@InfoQ,@丁晓昀),微信(微信号:InfoQChina)关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入InfoQ读者交流群)。