性能测试工具 [腾讯 TMQ] 运用 AOP 思想更优雅地进行性能调优

匿名 · 2016年10月11日 · 最后由 重小生 回复于 2016年11月30日 · 2531 次阅读

作者:李泽玲
在软件测试中,如果想在一个耗时严重的操作中找出其耗时的瓶颈时,一般采用的方法是在每个被调用的函数中写进测试代码,在运行时打出日志。如果该操作涉及到的业务逻辑特别复杂时,插入这些测试代码不仅工作量十分巨大,而且难以维护。如果后期剔除不干净,不仅增加了无关的代码量,还会在执行时造成不必要的资源浪费。

像在手机管家的清理加速模块中,垃圾扫描这个功能的耗时是性能优化的重点,如何快速测试和分析扫描过程中的函数耗时一直是性能测试想克服的难题。但是在数以千计的函数中插入测试代码简直是一场恶梦,所以优化过程一直是不知道从何开始从何结束。这时候好想有个脚本可以跟踪调用关系,并且可以根据规则自动插入测试代码。

像在手机管家的清理加速模块中,垃圾扫描这个功能的耗时是性能优化的重点,如何快速测试和分析扫描过程中的函数耗时一直是性能测试想克服的难题。但是在数以千计的函数中插入测试代码简直是一场恶梦,所以优化过程一直是不知道从何开始从何结束。这时候好想有个脚本可以跟踪调用关系,并且可以根据规则自动插入测试代码。

它是一种可以通过预编译方式和运行期间的动态代理实现的编程技术,在不修改源代码的情况下给程序统一添加某种功能,共享一个行为,主要可用来作为日志记录,性能统计,安全控制和事务处理等等。而且最重要的是,打包时可以通过不同的编译选项选择插入或者剔除测试代码,真正做到无损插桩!啊!果然很适合这种性能调优场景,赶紧学起来!

开始了解 AOP

面向切面编程思想中有两个核心的步骤:

1.在软件中挑选出来需要关注的切面关注点(pointcut),即程序中我们选择出来的执行点的集合,选择出来之后就可以对其进行后续操作,比如性能调优这里选择了手机管家中的 sdcard 卡扫描这个关注点,这里可以通过通配符组合各种规则来选取一系列的函数;

2.在切面上增加一些需要统一执行的操作(advice),比如统一给切面选取到的函数添加统计耗时的代码, 这里主要有三种类型,分别是:

  • (1)before : 在执行点的代码执行之前进行操作
  • (2)after : 在执行点的代码执行之后进行操作
  • (3)around :将 before 和 after 拼接在同一个执行点上,就是 around 操作

具体示意图如下,看不懂也没关系,下面还会有具体代码的演示。

所以怎么应用到具体工程上

AOP 这么厉害,所以到底怎么应用到具体工程上。对于 java、C++ 都已经有了对应的 AOP 支持版本,aspectj 就是基于 java 易用的、功能强大的 aop 编程语言。在 eclipse 中安装 AJDT 插件就可以快速对工程进行插桩。

aspectJ 插桩实战

在刚刚简介的基础上了解 AOP 的基本思想,接下来通过 aspectJ 实战脚本的例子深入了解下如何对你的程序进行自动插桩吧。

(1)查看运行函数:

我的程序功能很复杂,在测试 CPU 使用率时总发现一个现象,灭屏时进程的 CPU 使用率会突然升高,现有的 log 无法发现问题,想看看这是灭屏后程序到底有没有做什么异常操作,有没有异常调用其他不相关函数,这时候该怎么办?

用法详解:

  • a. pointcut TestPoint() 表示定义了 TestPoint 这个切点,在切点表达式中使用 execution(deepclean..(..))匹配所有函数名中含有 deepclean 字符的函数,execution 代表在该函数的执行处进行操作;
  • b. before():TestPoint() 操作代表在 TestPoint 这个切点的执行前插入打印函数签名的代码。

用法:打完插桩包后,安装后,打开被插桩过的软件,logcat 中自动输出每个清理相关函数的函数名,此时便可查看是否有异常调用。

(2)定位耗时操作的性能瓶颈:

业务中有个扫描的函数,想查看在扫描执行过程中该函数调用到的每个函数运行耗时,查看耗时瓶颈,以定位最应优化的函数

pointcut 定义详解:

  • a. cflow 表示跟踪 scanAll() 函数被调用的工作流,所以在 scanAll() 中调用的函数都会被我们选取到;
  • b. ! within(CPUTimeTest) 表示不要跟踪 aspectj 脚本测试类中的代码,避免插桩后代码的自循环,其中 CPUTimeTest 是工程中自定义的测试类名(这个是使用 cflow 关键字对函数进行跟踪时的必加项! );
  • c. && TestPoint() 保证跟踪的函数都是 clean 相关的函数,避免引入不必要测试日志,TestPoint() 定义详情参照上个例子。

advice 定义详解:

  • a. 使用 around() 函数将测试函数的函数体包围在测试代码中,被测函数的函数体为 Object obj= proceed();
  • b. 使用安卓自带的 debug 函数获取线程运行耗时:Debug.threadCpuTimeNanos()(debug 类中有许多性能监控获取接口,如内存使用率、CPU 使用率等,可以按照自身需要切换不同监控数据)
  • c. 插桩后代码实际执行顺序为:记录进入函数时的 startTime,运行函数本体,记录函数执行完的 endTime,输出两者之间的时间差

用法:安装插桩包后,通过 logcat 收集日志中各个函数耗时,得到扫描过程中每个函数过程中的耗时和被调用次数,查看扫描瓶颈。如果输出函数过多,还可以使用 HashMap 对每个函数的调用次数和总耗时进行统计,最后输出最终结果。

(3)输出异常情况的调试信息:

测试过程中想监控一个函数,如果其耗时(或者其他指标)超过预定的最大值,则为异常情况,想输出异常情况下函数的调用行数和调用堆栈

用法详解:

  • a. 在上一个例子的基础上,使用 if 逻辑判断耗时是否是异常数值,减少输出日志的数量,更容易定位问题
  • b. 使用 thisJoinPointStaticPart 获取该切点的静态信息,包括被调用的文件名,行数和函数签名,方便进行定位和跟踪

用法:安装插桩包,触发业务逻辑,查看日志输出,定位异常路径

获取 AOP 的更多信息

由于篇幅的局限,这里只是通过一些例子简单地讲解了 AOP 的相关应用。作为一种新的程序设计模式,AOP 还蕴含着更多的能力,不仅仅适用于性能调优上,还能应用在开发上,它能将逻辑代码和处理琐碎事务的代码分离开,减少代码实现和维护的复杂度。

更多的 AOP 和 aspectJ 的学习资料可以参考下面链接:

(1)https://eclipse.org/aspectj/doc/released/adk15notebook/index.html 官方 aspectJ 开发者文档

(2)http://blog.csdn.net/zl3450341/article/details/7673938 详细的 aspectJ 语法中文讲解

本章完~

原文链接:enter link description here


TMQ(腾讯移动品质中心)是腾讯最早专注在移动 APP 测试的团队
我们专注于移动测试技术精华,饱含腾讯多款亿级 APP 的品质秘密,文章皆独家原创,我们不谈虚的,只谈干货!

扫码关注我们

共收到 1 条回复 时间 点赞

这个想法倒是第一次看到,先码再看

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册