性能工具设计简介

发表于:2014-11-21来源:uml.org.cn作者:hoboss点击数: 标签:性能工具设计
客户端的性能是用户体验的一个非常重要的方面。在需求设计时要考虑到可能的性能问题;在不断的迭代开发新功能的同时要保持性能的稳定;在运营过程中要能追踪并解决历史遗留的性能

  客户端的性能是用户体验的一个非常重要的方面。在需求设计时要考虑到可能的性能问题;在不断的迭代开发新功能的同时要保持性能的稳定;在运营过程中要能追踪并解决历史遗留的性能问题。工欲善其事,必先利其器。虽然有不少现成工具可以使用,但自己设计性能工具更灵活,更有针对性。性能工具的主要功能是监控目标程序获取相关数据,组织数据并对其加以分析,帮助我们定位问题、找出性能瓶颈。

  需要哪些数据

  常见的性能问题主要有:界面卡、反应慢、CPU高、内存大等几个方面。我们针对这些情况来考虑需要收集哪些数据,以及如何来记录这些数据(针对windows客户端)。

  1、界面卡、反应慢

  界面卡、反应慢主要是指程序的响应速度过慢,包括:界面对鼠标键盘输入的响应速度;一些比较耗时的系统API的处理速度,比如读写文件等;以及目标程序的一些关键路径的执行速度。

  界面响应速度,这里我们可以用每次消息循环的执行的时间点以及时间消耗来判断。对于大多数windows程序来说hook GetMessage并记录下来每次调用GetMessage的时间点就可以了。但也有些客户端自己实现的消息循环,这里我们就需要hook对应的函数(比如PeekMessage)或是在消息循环里加上log。另外我们也可以向目标程序发送WM_NULL消息跟据其响应时间来判断响应速度。在这里,我推荐以两次消息循环的时间间隔或是对WM_NULL消息的响应速度小于50ms做为一个判断标准,大于这个值则认为“卡”。

  系统API执行速度,用hook的方法记录调用的线程ID、时间点、消耗时间以及一些必要的参数(比如读写的是哪个文件)。通常需要关注的系统API有:CreateFile、ReadFile、WriteFile、BitBlt和StrenchBlt等等。

  对于一些关键路径,我们需要记录下来执行时的线程ID、时间点和时间消耗。这一点针对性比较强,直接在目标程序加入log就可以了。当然也可以用hook 的方法,但不推荐。因为Hook的实现相对比较复杂,需要实现一个跳板函数,在跳板函数中记录相关信息。Hook更适应于记录一些调用次数频繁的系统级 API。而一些关键路径往往与目标程序逻辑密切相关,整个过程只有几次调用,甚至可能只有一次或完全不调用。这样就完全没有必要用hook的方法。

  系统API和关键路径(函数)的耗时,推荐记录开始时间和结束时间两个数据。这样在有嵌套的情况下,比单纯记录消耗时长,更直观,更便于分析问题。

  怎么hook?这里推荐参考微软的detours库。detours2.1版本之前是开源的。其基本原理是这样的:1. 把目标函数的前几行指令拷贝到一个地方,然后在原来的位置替换成一条跳转指令。2. 通过跳转指令跳转到自己实现的跳板函数,在跳板函数就可加入自己的代码了。如下:

  2、CPU高、内存大

  针对CPU高、内存大,我们按时间顺序以固定的时间间隔记录下来目标进程的CPU和内存的消耗就可以了。在CPU占用方面我们也可以更进一步记录下每个线程的CPU消耗

  3、有了上面的数据就够了吗?

  不够。比如,当我们收集到一组数据,分析后发现目标进程在启动后35秒-40秒的这段时间对WM_NULL消息无响应(界面卡住)。然后呢?然后我们仍然没有办法知道为什么会卡,以及卡在哪里。所以我们还需要目标的调用堆栈。同样按时间顺序以固定的时间间隔记录下堆栈信息。这样当我们发现问题的时间点后,便能得到当时一个堆栈执行情况。接下来就可以通过review代码来解决问题。

  至于获取调用堆栈。微软提供的 StackWalk64 就可以了,具体请查阅MSDN。当然如果你喜欢自己通过堆来还原调用堆栈,那么可以使用NtQueryInformationThread在取得线程信息后,根据ebp esp来回溯吧。

  4、还需要哪些数据?

  任何你关注的数据都可以记录下来。假如你关注IO对性能的影响,那么就记录下IO量和页面错误的数据;你怀疑是不是有其它模块的注入影响了你的程序,那么你也可以记录下来目标进程的模块信息……

  组织分析数据

  把数据初步整理,以图形的方式直观的表现出来。下面举几个例子。

  用树表示关键函数的执行所花的时间。

原文转自:http://www.uml.org.cn/Test/201302212.asp