发布新日志

  • Bugzilla安装指南(Windows)(转)

    2008-11-05 09:30:41

    Bugzilla安装指南(Windows

    1.  准备
    BugzillaWindows下的安装颇为复杂,所以有很多人写了安装指南。但是使用安装的时候发现每个指南写的都有缺陷。这里我仅仅是把我安装的过程记录下来,给大家一个参考。同时还列出了一些我觉得有帮助的参考文章和站点。
    工欲善其事必先利其器,建议你在开始安装之前把所有需要的软件下载齐全,这样可以提高效率和成功率。Bugzilla所需的软件都是开源的,都可以从它们的官方网站上下载到(我个人不喜欢去华军软件园之类的下载网站上找,因为即不安全,找到的也不一定是最新的版本)。下面把所需东西和下载网站罗列一下:
    n         MySQL4.1
    http://dev.mysql.com/downloads/mysql
    n         Perl  5.8.7.815
    http://www.activestate.com/Products/Download/Download.plex?id=ActivePerl
    n         Perl模块
    有两个简单的途径可以获得Bugzilla所需的Perl模块。一个是Bugzilla汉化项目整理的,收集的很全而且比较新,还有一个安装批处理程序,所以推荐大家用这个;另外一个是Bugzilla测试服务器,它也提供了完整的Perl模块集合,但是版本似乎比较老。第三条道路也是有的,但是需要自己去找然后再编译。对于像我一样不懂Perl德人来说是在复杂,因此不推荐大家这样做。
    http://sourceforge.net/project/showfiles.php?group_id=75477
    http://landfill.bugzilla.org/ppm/
    n         Bugzilla2.20
    http://www.bugzilla.org/download/
    n         Bugzilla汉化包(2.20
    http://sourceforge.net/project/showfiles.php?group_id=75477

    2.  安装和配置MySQL
    安装MySQL很简单,只要按照安装程序的提示一步一步的做就可以了,如果有问题可以到MySQL官方网站(http://dev.mysql.com/doc/)上查看在线手册。
    接下来要配置MySQL。有些文章里写道需要手工修改root用户的密码,其实这一步在MySQL安装程序里就已经完成了(可能那些文档写的较早,MySQL的安装程序可能不太好用吧),因此不用再去设置。我们要新建一个Bug数据库和一个Bugzilla访问这个数据库的用户。操作如下:
    C:\mysql\bin>mysql --user=root -p mysql
    Enter password: ********
    Welcome to the MySQL monitor.  Commands end with ; or \g.
    Your MySQL connection id is 15 to server version: 4.0.20a-debug
    Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
    mysql> create database <database_name>;
    Query OK, 1 row affected (0.11 sec)
    mysql> grant all privileges on <database_name>.* to '<user_name>'@'<server_name>' identified by '<password>';
    Query OK, 0 rows affected (0.03 sec)
    mysql> flush privileges;
    Query OK, 0 rows affected (0.00 sec)
    mysql> quit
    Bye
    C:\mysql\bin>

    3.  安装Perl及其模块
    安装Perl也很容易,按照安装程序提示一步一步装就可以了。稍微复杂一点的是安装它的模块。不过有了Bugzilla汉化项目提供的批处理程序,这个步骤也非常简单了。大家只要记住一个简单的命令就可以了:
    ppn install <module_name>
    ppn uninstall <module_name>

    4.  安装Bugzilla
    把下载到压缩包解压到一个文件夹,然后运行Bugzilla的安装检查程序(CheckSetup.pl)。它会自动验证是不是安装了必须的软件。如果没有什么问题它会在Bugzilla目录里生成一个localconfig文件(没有扩展名)。
    用文本编辑器打开localconfig文件,找到下面两段文字。$db_host表示服务器名称,$db_name表示数据库名称,$db_user表示登录用户名,$db_pass表示密码。修改这几个值并保存。
    #
    # How to access the SQL database:
    #
    $db_host = 'localhost';         # where is the database?
    $db_name = 'bugs';              # name of the SQL database
    $db_user = 'bugs';    # user to attach to the SQL database
    #
    # Enter your database password here. It's normally advisable to specify
    # a password for your bugzilla database user.
    # If you use apostrophe (') or a backslash (\) in your password, you'll
    # need to escape it by preceding it with a '\' character. (\') or (\)
    # (Far simpler just not to use those characters.)
    #
    $db_pass = 'bugs@agfa';
    再次运行Bugzilla的安装检查程序(CheckSetup.pl)。这时如果正常它将初始化数据库结构和Demo数据。不过不要高兴得太早,可能会出现“Client does not support authentication protocol requested by server ……”错误信息。这个问题整整困扰了我一个上午,幸亏后来找到Byron Jones写的《Installing Bugzilla on Microsoft Windows》。产生这个错误是因为MySQL 4.1及以后的版本使用了新的密码加密算法,而使用的PerlDBD::MySql模块不够新,不支持新的加密算法。你可以采取两种方式来解决这个问题:一是使用新的DBD::MySql模块,不过需要自己编译;另一种是在MySQL中强制使用兼容老版本的密码加密算法:
    C:\mysql\bin>mysql --user=root -p mysql
    Enter password: ********
    Welcome to the MySQL monitor.  Commands end with ; or \g.
    Your MySQL connection id is 15 to server version: 4.1.11-nt
    Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
    mysql> set password for '<user_name>'@'<server_name>' = OLD_PASSWORD ('<password>');
    Query OK, 0 rows affected (0.00 sec)
    mysql> quit
    Bye
    C:\mysql\bin>
    5.  配置IIS
    打开IIS管理界面。新建一个虚拟路径,指向Bugzilla所在文件夹。
    然后按应用程序设置按钮。增加一个映射,将.cgi文件映射到perl.exe。这里特别注意,有些文档里写成:perl.exe “%s” %s,这样不正确,在运行时出错(又花去一个小时)。正确的配置应该如下:
    <perl完整路径>\perl.exe -x<Bugzilla完整路径> -wT "%s" %s
    例如:
    c:\perl\bin\perl.exe -xc:\bugzilla -wT "%s" %s


    最后,将index.cgi加入到默认文档列表中。最好移到最前面,这样可以加快查询速度。如果不希望/不能把index.cgi加入到默认文档列表中,也可以在安装Bugzilla的时候,将localconfig文件中$index_html的值改为1。这样运行checksetup.pl时,就会生成一个index.html,自动重定向到index.cgi。
    #
    # With the introduction of a configurable index page using the
    # template toolkit, Bugzilla's main index page is now index.cgi.
    # Most web servers will allow you to use index.cgi as a directory
    # index, and many come preconfigured that way, but if yours doesn't
    # then you'll need an index.html file that provides redirection
    # to index.cgi. Setting $index_html to 1 below will allow
    # checksetup.pl to create one for you if it doesn't exist.
    # NOTE: checksetup.pl will not replace an existing file, so if you
    #       wish to have checksetup.pl create one for you, you must
    #       make sure that index.html doesn't already exist
    $index_html = 1;

    6.  配置Bugzilla
    不想多写了,在浏览器中打开http://localhost/bugzilla(根据你的具体情况而定)。如果你的Bugzilla是第一次使用,它会自动转向到Setup页面,按部就班的做就可以了。

    7.  汉化Bugzilla
    最后要做的就是汉化了,不过你不想汉化也没有问题。将汉化包解压解压到cn文件夹,将整个文件目录 cn 拷贝至 Bugzilla 的子目录 template下;然后以管理员身份登录Bugzilla,点击页脚的 Parameters(系统参数设置)链接,将 languages 一项的值改为 cn,保存,则以后见到的Bugzilla页面就是汉语页面了。如果想返回英文界面,将 cn 改回 en 即可。
    为保证向后兼容,汉化的文件全部存为 UTF-8 格式。但不管你是否汉化Bugzilla,为强迫Bugzilla采用UTF-8来处理字符串,避免Bugzilla偶然出现的乱码,强烈建议大家将文件 <Bugzilla安装目录>\Bugzilla\CGI.pm 的第55行改为 $self->charset('UTF-8')。

    8.  总结
    到这里,Bugzilla的安装就基本上搞定了。也许你已经发现了,这篇文档没有说明关于邮件的问题。这时因为我没有配置,不过按照Bugzilla文档的说明,它已经提供了内置的SMTP支持。可是它不支持需要认证的SMTP,可以使用Glob's sendmail wrapper来解决。

    9.  参考
    Bugzilla官方网站http://www.bugzilla.org
    Bugzilla汉化项目http://sourceforge.net/projects/bugzilla-cn
    http://cosoft.org.cn/projects/bugzillchinese/
    Perl官方网站http://www.perl.com
    ActivePerl官方网站http://www.activestate.com/Products/ActivePerl
    MySQL官方网站http://www.mysql.com
    Fake Sendmait for Windows   http://www.glob.com.au/sendmail/
    Installing Bugzilla on Microsoft Windows
    http://www.bugzilla.org/docs/win32install.html
    The Bugzilla Guidehttp://www.bugzilla.org/docs/2.20/html
    Bugzilla windows安装红宝书http://blog.fz0132.com/trackback.asp?tbID=654

    10. 附录
    安装配置Bugzilla的工作清单
    □下载Perl
    □  下载Perl模块
    □  下载MySQL
    □  下载Bugzilla
    □  下载Bugzilla汉化包
    □  安装MySQL
    □  生成Bug数据库
    □  生成Bugzilla数据库用户并分配权限
    □  安装Perl
    □  安装Perl模块
    □  解压Bugzilla压缩包
    □  运行CheckSetup.pl检查安装
    □  修改localconfig文件,设置数据库访问方式
    □  再次运行CheckSetup.pl完成数据库初始化
    □  修改Bugzilla数据库用户密码加密方式(视情况而定)
    □  在IIS管理器中为Bugzilla建立虚拟路径
    □  将.cgi文件映射到perl.exe
    □  将index.cgi加入到默认文档列表中(可选)
    □  配置Bugzilla
    □  汉化Bugzilla
  • LoadRunner设置相关

    2008-07-24 14:49:37

    LoadRunner设置相关

    1>     Run time setting设置中的Browser:‘Simulate a new user on each iteration’选项
    例如:录制了一个脚本,设置了100个VU,每个VU的迭代次数为10次,正确运行时应该在系统中生成100×10条记录,但是,运行后发现这100个VU都只运行了一次,最终生成了100条记录。
    原因是:
    选中了Simulate a new user on each iteration,如果选中这一项,在两次迭代之间LR清除了cookie。
    也可以把‘Clear cache on each iteration’取消选中
    2>     录制脚本时在Recording option中的设置Recording项目
    ¨   HTML-base方式:
    HTML-based 方式对每个页面录制形成一条语句,对LoadRunner来说,在该模式下,访问一个页面,首先会与服务器之间建立一个连接获取页面的内容,然后从页面中分解得到其他的元素(component),然后建立几个连接分别获取相应的元素
    ¨   URL-base方式
    URL-based 方式将每条客户端发出的请求录制成一条语句,对LoadRunner来说,在该模式下,一条语句只建立一个到服务器的连接,LoadRunner提供了web_concurrent_start和web_concurrent_end函数模拟HTML-based的工作方式
    在录制脚本时选择那种方式呢:
    ¨       如果应用是WEB应用,首选是HTML-based方式
    ¨       如果应用是使用HTTP协议的非WEB应用,首选是URL-based方式
    ¨       如果WEB应用中使用了java applet程序,且applet程序与服务器之间存在通讯,选用URL-based方式
    ¨       如果WEB应用中使用的javascrīpt、vbscrīpt脚本与服务器之间存在通讯(调用了服务端组件),选用URL-based方式

    在 LoadRunner 的运行场景中,有一个不大起眼的设置,可能经常会被很多人忽略,它就是 Pacing 。具体设置方式为: Run-Time settings à General à Pacing ,这个设置的功能从字面上就很容易理解,即在场景的两次迭代 (iteration) 之间,加入一个时间间隔(步进)。设置方法也很简单,这里就不赘述了,我在这里想说明的是,这个设置到底有什么作用?为什么要进行这个设置?说实话,虽然我在以前做过的一些性能测试中,偶尔会对这个步进值进行一些设置,但其实对它的真正含义和作用,我还并不十分清楚。
    前段时间,我在对X银行招聘信息系统进行性能测试的时候,发现这个值的设置对于测试的结果有着很大的影响,很遗憾当时没有深入研究这个问题,而只是简单地认为它同脚本中的 thinktime 一样只是为了更真实地模拟实际情况而已。最近在网络上看到一篇题为《调整压力测试工具》的文章,读完之后,再用之前我的测试经历加以印证,真有种豁然开朗的感觉。以下就将我的一些体会与大家分享:
    通常我们在谈到一个软件的“性能”的时候,首先想到的就是“响应时间”和“并发用户数”这两个概念。我们看到的性能需求经常都是这样定义的:
    “要求系统支持 100 个并发用户”
    看到这样的性能需求,我们往往会不假思索地就在测试场景中设置 100 个用户,让它们同时执行某一个测试脚本,然后观察其操作的响应时间,我们都是这样做的,不是吗?我在实际实施性能测试的过程中,也往往都是这样做的。可惜的是,我们中的大多数人很少去更深入地思考一下其中的奥妙,包括我自己。
    事实上,评价一个软件系统的性能,可以从两个不同的视角去看待:客户端视角和服务器视角(也有人把它叫做用户视角和系统视角),与此相对应的,又可以引出两个让初学者很容易混淆的两个概念:“并发用户数”和“每秒请求数”。“并发用户数”是从客户端视角去定义的,而“每秒请求数”则是从服务器视角去定义的。
    因此,上面所描述的做法的局限性就是,它反映的仅仅是客户端的视角。
    对于这个世界上的很多事情,变换不同的角度去看它,往往可以有助于我们得到更正确的结论。现在,我们就转换一下角度,以服务器的视角来看看性能需求应该怎么样定义:
    “要求系统的事务处理能力达到 100 个 / 秒” ( 这里为了理解的方便,假定在测试脚本中的一个事务仅仅包含一次请求 )
    面对以这样方式提出的性能需求,在 LoadRunner 中,我们又该如何去设置它的并发用户数呢?千万不要想当然地以为设置了 100 个并发用户数,它就会每秒向服务器提交 100 个请求,这是两个不同的概念,因为 LoadRunner 模拟客户端向服务器发出请求,必须等待服务器对这个请求做出响应,并且客户端收到这个响应之后,才会重新发出新的请求,而服务器对请求的处理是需要一个时间的。我们换个说法,对于每个虚拟用户来说,它对服务器发出请求的频率将依赖于服务器对这个请求的处理时间。而服务器对请求的处理时间是不可控的,如果我们想要在测试过程中维持一个稳定的每秒请求数( RPS ),只有一个方法,那就是通过增加并发用户数的数量来达到这个目的。这个方法看起来似乎没有什么问题,如果我们在测试场景中只执行一次迭代的话。然而有经验的朋友都会知道,实际情况并不是这样,我们通常会对场景设置一个持续运行时间(即多次迭代),通过多个事务 (transaction) 的取样平均值来保证测试结果的准确性。测试场景以迭代的方式进行,如果不设置步进值的话,那么对于每个虚拟用户来说,每一个发到服务器的请求得到响应之后,会马上发送下一次请求。同时,我们知道, LoadRunner 是以客户端的角度来定义“响应时间”的 ,当客户端请求发出去后, LoadRunner 就开始计算响应时间,一直到它收到服务器端的响应。这个时候问题就产生了:如果此时的服务器端的排队队列已满,服务器资源正处于忙碌的状态,那么该请求会驻留在服务器的线程中,换句话说,这个新产生的请求并不会对服务器端产生真正的负载,但很遗憾的是,该请求的计时器已经启动了,因此我们很容易就可以预见到,这个请求的响应时间会变得很长,甚至可能长到使得该请求由于超时而失败。等到测试结束后,我们查看一下结果,就会发现这样一个很不幸的现象:事务平均响应时间很长,最小响应时间与最大响应时间的差距很大,而这个时候的平均响应时间,其实也就失去了它应有的意义。也就是说,由于客户端发送的请求太快而导致影响了实际的测量结果。
    因此,为了解决这个问题,我们可以在每两个请求之间插入一个间隔时间,这将会降低单个用户启动请求的速度。间歇会减少请求在线程中驻留的时间,从而提供更符合现实的响应时间。这就是我在文章开头所提到的 Pacing 这个值的作用。
    最后再补充一句话:虽然性能测试通常都是从客户端活动的角度定义的,但是它们应该以服务器为中心的视角来看待。请注意这句话,理解它很重要,只有真正理解了这句话,你才会明白为什么我们一直强调做性能测试的时候要保证一个独立、干净的测试环境,以及一个稳定的网络,因为我们希望评价的是软件系统真正的性能,所以必须排除其它一切因素对系统性能造成的影响。

  • LR的性能测试流程图

    2008-07-24 13:39:33

    暂无
  • 要做好性能测试,该掌握些什么?【转】

    2008-07-17 11:29:31

    【转】

    今天有同行在blog上留言,问“想从功能测试转向性能测试,但不知道需要哪些了解哪些知识,及怎样进行一个系统的学习”。这类问题之前也被问到很多次了,所以这次干脆整理一下,发个主题供同行们参考。如果需要补充,也欢迎大家留言一起讨论。

    如果想真的做好性能测试,需要学习的东西还是比较多的。简单列一下吧。

    1. 精通性能测试的基本概念,过程,方法论,了解性能工程;

    2. 精通1个商业性能测试工具+1个开源性能测试工具,知道工具可以做什么,不可以做什么,以及工具使用中常见的问题和解决思路;

    3. 扎实的计算机专业基础知识,包括计算机组成原理、操作系统数据库原理、计算机网络原理;

    4. 熟悉至少1个常用的数据库产品,例如SQL Server或者 Oracle,能进行一般的数据库管理操作,熟悉SQL脚本的使用,熟悉常用的数据调优工具和常用的counter;

    5. 熟悉至少一个操作系统的原理,Windows或者Linux都可以,熟悉操作系统的体系架构、操作系统的重要基础概念,以及内存管理、存储/文件系统、驱动/硬件的管理、网络协议的实现及构成、性能的监控方法和原理,熟悉常用的counter;

    6. 熟悉至少一个web server 产品,例如apache,了解一般的配置和常用的counter;

    7. 熟悉至少一个应用服务器产品,例如tomcat,了解一般的配置,熟悉常用的服务器性能监控方法和原理,熟悉常用的counter;

    8. 至少熟悉TCP/IP协议,熟悉HTTP协议,至少见过并了解三层、四层交换或者路由器的使用和配置。了解常用的与网络性能相关的counter;

    9. 了解一般的大型企业应用的部署架构和应用架构;

    10. 了解知名大型web应用、高并发量、高流量、实时响应要求高的超大规模网站的架构和优化历程;


    11. 熟悉统计学的基础知识、常用分析方法以及实验设计方法,了解数学建模相关的知识;

    12. 熟悉专属行业的业务知识和用户场景,例如电信行业的OSS系统所涉及的业务知识和用户场景,证券交易系统所涉及的业务知识和用户场景;

    13. 大量的实际性能测试及优化经验

    14. 积极的参与到各类圈子、社团的讨论和交流、分享中。


    另外,我之前也整理发布过不少性能测试方面的资料,从入门级的文章到 升级的必读都有一些,有兴趣可以参考。

    资料收集:高并发 高性能 高扩展性 Web 2.0 站点架构设计及优化策略 http://www.cnblogs.com/jackei/archive/2007/10/07/915931.html

    最全,最强的软件测试资料汇总 (性能测试,性能调优,功能测试,自动化测试,测试管理,测试工具,测试用例设计,缺陷分析预防,前沿测试技术...)
    http://www.cnblogs.com/jackei/archive/2007/02/06/641647.html

    好东西大家共分享!

  • 【转】LoadRunner监控Apache!

    2008-06-22 10:58:10

    一、Apache上的设置
    打开<Apache Installation>\conf\httpd.conf,进行如下修改:

    1、 设置允许查看Apache运行状态的主机

    #

    # Allow server status reports, with the URL of http://servername/server-status

    # Change the ".your-domain.com" to match your domain to enable.

    #

    #取消一下代码前面的注释符号“#”,并且设置Order(顺序)为允许优先

    <Location /server-status>

    SetHandler server-status

    Order allow,deny

    Deny from nothing

    Allow from all

    </Location>

    这样改变以后重新启动Apache在浏览器中输入http://servername/server-status就可以看到Apache运行时的信息,而输入http://servername/server-status?auto就会看到如下信息:

    Total Accesses: 124

    Total kBytes: 444

    CPULoad: 3.32432

    Uptime: 37

    ReqPerSec: 3.35135

    BytesPerSec: 12288

    BytesPerReq: 3666.58

    BusyWorkers: 1

    IdleWorkers: 7

    Scoreboard: ____W___.........................


    看到这样的信息就表示修改成功,这样就可以使用LoadRunner监视Apache了。

    以下两步跟使用LoadRunner监视Apache无关,可以跳过不看。

    2、 改变Apache的设置,打开详细状态开关;

    #

    # ExtendedStatus controls whether Apache will generate "full" status

    # information (ExtendedStatus On) or just basic information (ExtendedStatus

    # Off) when the "server-status" handler is called. The default is Off.

    #

    #取消了下面一行前面的注释符号“#”

    ExtendedStatus On

    3、 有用的设置,查看各模块信息

    #

    # Allow remote server configuration reports, with the URL of

    # http://servername/server-info (requires that mod_info.c be loaded).

    # Change the ".example.com" to match your domain to enable.

    #

    #取消一下代码前面的注释符号“#”,并且设置Order(顺序)为允许优先

    <Location /server-info>

    SetHandler server-info

    Order allow,deny

    Deny from nothing

    Allow from all

    </Location>

    二、LoadRunner上的设置

    经过以上第一项设置以后就可以使用LoadRunner监控Apache的运行情况了,在LoadRunner可用的监视器中双击Web Server Resource Graphs下的Apache节点,然后在右边对应的窗口中添加Apache所在主机的IP地址,并且加入计数器后单击OK,这样就可以在LoadRunner中实时显示Apache的运行状态信息了。

    注意:您可能收到如下消息【Monitor name :Apache. Parsing error, cannot find token: BusyServers. Measurement: BusyServers|192.168.0.186. Hints: 1) Such a measurement does not exist, or the html page may be different from the supported one. 2) Try to replace the Apache.cfg with appropriate Apache_<version>.cfg file in <Installation>\dat\monitors and rerun the application (entry point: CApacheMeasurement::NewData). [MsgId: MMSG-47479]】,这是由于要监视Apache的版本提供的计数器与LoadRunner默认的计数器不一致导致的。此时建议先关闭Controller,打开<Installation>\dat\monitors下的apache.cfg文件(其它文件名类似Apache_<version>.cfg的是Apache监视配置的备份,只有apache.cfg是生效的):

    1、 修改Counter0=IdleServers为Counter0=IdleWorkers,同时修改注释信息Label0=#Idle Servers (Apache)为Label0=#Idle Workers (Apache),描述信息也建议修改;

    2、 修改Counter4=BusyServers为Counter4=BusyWorkers,同时修改注释信息Label4=#Busy Servers (Apache)为Label4=#Busy Workers (Apache) ,描述信息也建议修改。

    然后保存并关闭该文件,重新打开Controller并添加计数器,这样监视就正常了。
  • 录制脚本是的thinktime

    2008-06-22 10:49:58

    考虑时间Thinking Time指的是在性能测试脚本中,事务与事务之间,会有一些短暂的停顿,就好像真实用户在操作时,两次操作之间需要考虑一下。比如用户注册的时候,在打开注册页面到提交注册页面之间,是有一段考虑时间的(用户在填写个人信息)。

    下面就讨论一下在性能测试实战中,为什么要设置考虑时间。

    先说一个概念:吞吐量,这指的是服务器系统(包括软件和硬件)单位时间内处理业务的数量。我们现在做一个小试验,写一个小程序,执行一个简单的业务,并且在程序中进行计时,计算每分钟能执行多少次。然后当我们运行1路这个程序的时候,每分钟能完成约6万次。好,现在问一个问题,如果我们起2路,是不是每一路都能达到 6万/分钟 的吞吐量?

    试验发现,当运行2路的时候,两个程序的数值都降了下来,但是它们的总和仍然是6万次。而且不管我们起多少路,这些程序的性能总和都接近于6万。

    这就好像一个人1分钟最快能吃1个馒头,你让他一个一个吃,他两分钟能吃2个,如果你让他一手拿一个,同时吃,他两分钟吃不了4个,还是只能吃两个。

    我们不是在说“考虑时间”么,哈哈,别急,因为上面的问题必须要先说清楚。

    如果我们需要进行性能测试的业务是一个单纯的业务,就好像上面举的那个例子一样,那么测试脚本中就不需要设置“考虑时间”,因为不管你用什么方法测试,一个服务系统处理单一业务的吞吐量总是一个定值。

    但是在实际环境里面,往往一个系统都是要处理多种业务,并且这些业务之间是有逻辑关系的。举例说明,比如一个论坛系统,每天最常处理的业务有两个:A打开帖子、B回复帖子。那么每天系统处理AB业务的总数是不是一样的呢,答案很明显,看帖子多,回复的少一些。假设A:B=2:1。

    好,如果我们不设置考虑时间,起2路A的脚本,1路B的脚本进行性能测试,我们会得到什么结果呢?我们会得到这两个业务的吞吐量,并且能算出每个小时系统完成A、B业务的总数,吞吐量 × 时间 = 总数。

    这时我们发现,同样时间,AB业务的处理总数却不是2:1的关系,这是为什么呢?原因是这样的,我们在跑AB脚本的时候,这两组脚本都在尽全力争夺服务器的资源,他们的并发路数虽然是2:1,但是给服务器的压力却不一定是2:1,可能会出现偏差,测试结果就是最好的证据。A查看帖子由于响应时间短,因此跑的次数更多,最后的比例可能是4:1。

    那么这样的结果有什么问题呢。总结为一句话:测试环境的业务和真实环境不符,这样测出的数据没有价值。即使测试通过,也不能证明真实环境是ok的;或者即使测试不通过,也不能说明真实环境不ok,呵呵。

    比如上面的例子,如果我们测出的结果是B回复帖子的吞吐量不够,响应时间太长,那可能是因为A业务抢走了过多的,本不属于A的资源,而引起了B的性能降低。

    说到这里,大家应该明白了,我们设置考虑时间,是为了保证测试复合业务的时候,各个业务之间的比例关系符合我们的真实生产环境。
  • LoadRunner如何在大负载下测试

    2008-06-22 10:46:43

       在大负载中使用LoadRunner进行负载测试,需要配置一些环境来满足大负载下各种资源的充足:

    1.为了避免出现“No Buffer Space Available”的错误,需要进行如下配置:

       1)修改注册表:

          * 设置“HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\tcpip\Par
    ameters\TcpTimedWaitDelay”为 30
          * 设置“HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\tcpip\Par
    ameters\MaxUserPort”为 65534
          * 在“HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Session
    Manager\Sub Systems\Windows”设置SharedSection 为 4096

        2)通过在每个脚本的开头添加如下函数来设置“SHUTDOWN”模式为"ABRUPT"

         web_set_sockets_option(“SHUTDOWN_MODE”,”ABRUPT”)

     2.关闭所有的杀毒,反间谍扫描软件等。同时也关闭任务扫描和所有不需要的服务。

     3.脚本运行时设置:

       1)设置日志为“只在错误发生时发送信息”

       2)去掉错误时产生snapshot的选项

       3)在miscellaneous上,去掉 定义每一步为一个事务 的选项

       4)不选择 模拟浏览器缓存,选上“simulate new user on each iteration”和它的子选项

     4.如果下载的页没有资源,在web_url函数中添加“Mode=HTTP”,这样会减少LG上的负载(不用转换成HTML)。默认情况下,web_url的Mode为Mode=HTML

     5.重启LG并且确保他们都能跟Controller连接.

     6.确保LG和控制器上有足够的剩余磁盘空间.

     7.在controller中去掉web page breakdown

     8.限制Vuser在所有LG上同时进行初始化的数目.可以在Controller的Tools > Options > Run-Time Settings中进行修改.每个LG都有这个设置.

     9.限制controller在运行时存储的错误数.通过修改wlrun.ini中的[output]项来实现:

      • FlagLimitOutputMessages=1
      • MaxNumberOfOutputMessages=<errors count> (default is 10,000)

     10.在Controller上修改Monitor的采样率来降低CPU的使用.可以在Controlller的Tools > Options > Monitors 下修改,如下图所示:

     

    11. 如果有很多错误产生,最好不要经常打开Error/Output窗口,因为这样会因为访问数据库而打开另外的数据库连接.

    12.负载测试中不要使用"Show Vuser"选项.

    13.把输出信息重定向到一个文本文件中来代替输出到mdb文件中.可以在wlrun7.ini中修改[output]下的

    ExportMessageToFile=1来实现.

    14.不要在Controller机器上运行虚拟用户.

    15.在场景中设置监视器:

      * 内存使用上 mmdrv进程的private bytes

      * disk使用

      * CPU使用

      * 网络使用

    16.把脚本中所与打印信息的脚本去掉.如下面的代码每次迭代都会调用一次,对大量并发用户的运行产生负面的影响.

    lr_vuser_status_message("pIteration: %s -
    START Action", lr_eval_string("{pIteration}"));
    lr_output_message("pIteration: %s - START Action", lr_eval_string("{pIteration}"));

    Controller处理所有虚拟用户的信息,这样会大大降低Controller的性能. 如下是类似的代码:

    web_reg_find("Text=Time on Server", "SaveCount=cErr", ..);
    web_url( some url …);
    if (atoi(lr_eval_string("{cErr}"))>0) {
    lr_error_message(some message);
    lr_end_transaction("S05_T01_Request_Content_Page", LR_FAIL);
    }

      通常认为在脚本中插入lr_error_message是不好的,除非是调用的客户化的API失败了才有必要插入该语句.如果是LoadRunner的函数调用失败(如上面的web_url调用),它会自动发送一个错误消息.

      在大量用户运行的情况下,控制Controller和LG之间的通信流量是非常重要的.发送多余的信息(错误,输出等信息)会增大通信流量降低负载能力.所以,通常都需要把代码中不必要的信息去掉.

    17.去掉脚本中所有的sleep()的调用,用lr_think_time()来代替.lr_think_time给LR让出控制,即LR能够在Vuser休眠的时候去做其他有用的事情.

    18.不要去掉lr_think_time:使用该函数能更准确的模拟负载,对LG产生相对小的压力

    19.web_reg_save_param和web_reg_find()函数:

       • 在 web_reg_save_param() 中添加“Notfound=empty” 参数.
       • 在 web_reg_find() 添加 "Savecount=some_parameter_name". 如果你想知道它是否成功可以使用atoi(lr_eval_string("{some_paramater_name }"))来衡量.

    20.其他

      可能会出现的问题:

       * 测试产生了太多的错误:

         错误引擎不能处理多于1.5GB的错误

         如果测试过程中每秒产生多与1000个错误,Controller的行为将不可预测

       * 测试产生了大量的在线数据

     上面的两个问题都可以使用如下的方法解决:

       例如: 场景是一个组有1000个虚拟用户

       可以把这个组分成两个组:

       G1 100 Vusers
       G2 900 Vusers

       这两个组可以跟原始的组产生一样的负载,对于G2在组命令行中添加如下参数:

       -disable_data -disable_messages

        _disable_data : 让这个组不发送任信息,不发送任何online信息,不写任何offline信息.

        _disable_message: 让这个组不给Controller发送任何信息(错误,日志)

    注意:使用上面的命令行选项会使该LG不给congtroller发送online和offline信息.这样这个组上的虚拟用户的分析数据就收集不到了.

    21.如果需要远程访问,Mercury仅支持PC anywhere.

    翻译自<LoadRunner Large Load Test Considerations>

      

  • 集合点插入位置不同对结果的影响(转)

    2008-04-16 16:24:08

    LoadRunner中事务和集合点是我们性能测试过程中常用的东东,如果引用位置不慎,结果可能千差万别,下文给出集合点插入位置不同对响应时间结果的影响。

     
    1、集合点插入事务之前
       集合点插入位置在事务之前,则事务的统计时间不包括用户在集合点的等待时间,示例结果如下所示:
    Transactions: Total Passed: 20 Total Failed: 0 Total Stopped: 0        Average Response Time

    Transaction Name Minimum Average Maximum Std. Deviation 90 Percent Pass Fail Stop
    download_Transaction 4.014 43.965 83.73 28.148 83.73 5 0 0
    downloadfile 3.605 3.929 4.084 0.171 4.084 5 0 0
    vuser_end_Transaction 0 0 0.001 0 0.001 5 0 0
    vuser_init_Transaction 0.067 0.092 0.147 0.029 0.147 5 0 0
     
    2、集合点插入事务之后
       集合点插入位置在事务之后,则事务的统计时间包括用户在集合点的等待时间,示例结果如下图所示:
    Transactions: Total Passed: 20 Total Failed: 0 Total Stopped: 0        Average Response time

    Transaction Name Minimum Average Maximum Std. Deviation 90 Percent Pass Fail Stop
    download_Transaction 3.517 43.425 83.193 28.151 83.193 5 0 0
    downloadfile 3.517 43.425 83.193 28.151 83.193 5 0 0
    vuser_end_Transaction 0 0 0 0 0 5 0 0
    vuser_init_Transaction 0.048 0.079 0.11 0.022 0.11 5 0 0
     
     
    结论:集合点一定不能在开始事务之后插入,否则计算响应时间时,把集合点用户的等待时间也计算在内,则
    测试结果不准确。
  • Unitils——简化测试!

    2008-04-08 08:54:38

    Unitils是一个简化测试开发,提高测试维护性的一个开源项目,这里有开发者做的一个presentation.目前提供的功能为:

      General testing utilities :提供一些测试的辅助方法,主要是通过反射进行数据验证。
      Database testing utilities:

      自动维护测试数据库,并且可以自动关闭测试数据库的constranits
      提供类似于dbdeploy和rails migration的数据库版本控制
      简化单元测试数据库connection的获取
      简化dbunit维护测试数据的操作
      Hibernate支持
      单元测试事务的管理,可以选择有Unitils管理,还是spring管理
      Mock object utilities:简化Mock ojbect的creation,injection,和match
      Spring integration:可以方便的在单元测试中获取spring管理的bean。
      以前做数据库测试的时候,最麻烦的就是测试数据集的管理。unitils提供了非常好的数据库测试支持,下面我们看一个简单的例子:

      需要测试的类Java代码
    public class User {  
     private int  id;  
     private String name;  
     //set get略  
    }  
     
    //任何实现都可以,jdbc,hibernate,ibatis等等  
    public class UserDAO {  
      public void save(User user) {    
        ...  
      }       
         
      public User get(int id) {  
        ...  
      }  

    public class User {
     private int  id;
     private String name;
     //set get略
    }

    //任何实现都可以,jdbc,hibernate,ibatis等等
    public class UserDAO {
      public void save(User user) { 
        ...
      }    
      
      public User get(int id) {
        ...
      }
    }
      测试类Java代码
    @SpringApplicationContext({"spring-config.xml"})  
    @DataSet 
    public class UserDAOTest extends UnitilsJUnit4 {  
       @SpringBeanByType 
       UserDao userDao;      
     
      @Test 
      public void testGet() {  
         User user = userDao.get(1);  
         assertEquals("foo", user.getName());     
      }  
     
      @Test 
      public void testSave() {  
          User user = new User();  
          user.setName("bar");   
          userDao.save(user);  
          assertNotNull(user.getId());  
       }  

    @SpringApplicationContext({"spring-config.xml"})
    @DataSet
    public class UserDAOTest extends UnitilsJUnit4 {
       @SpringBeanByType
       UserDao userDao;   

      @Test
      public void testGet() {
         User user = userDao.get(1);
         assertEquals("foo", user.getName());  
      }

      @Test
      public void testSave() {
          User user = new User();
          user.setName("bar");
          userDao.save(user);
          assertNotNull(user.getId());
       }
    }
      测试数据文件。Unitils默认按testClassName.xml到相同目录下找测试文件,然后自动装载,装载前先自动清空测试文件中包含的table,然后再转载这些table的数据。Xml代码
    <?xml version='1.0' encoding='UTF-8'?> 
    <dataset>           
        <user id="1" name="foo" />       
    </dataset> 

    <?xml version='1.0' encoding='UTF-8'?>
    <dataset>        
        <user id="1" name="foo" />    
    </dataset>
      其他的设置主要是unitils在配置文件unitils.properties(http://www.unitils.org/unitils.properties)中修改数据库的配置,并且放到classpath下面。依赖jar可以在unitils网站上看到。
      这样一个UserDAO的测试就做完了。unitils可以为每个类(或者方法,建议是类)指定测试数据文件,在测试开始的时候自动装载相关表格的数据。这是我最感兴趣的功能之一,简化的测试数据的管理。同时unitils的提供的mock支持可以显著减少使用mock时create mock的代码

    Java代码
    @Mock 
    private UserDao mockUserDao; 

    @Mock
    private UserDao mockUserDao; 
    unitils项目还提供了一些其他功能:

    多数据库测试Xml代码
    <?xml version='1.0' encoding='UTF-8'?> 
    <dataset xmlns="SCHEMA_A" xmlns:b="SCHEMA_B"> 
        <user id="1" userName="jack" />      
        <b:role id="1" roleName="admin" /> 
    </dataset> 

    <?xml version='1.0' encoding='UTF-8'?>
    <dataset xmlns="SCHEMA_A" xmlns:b="SCHEMA_B">
        <user id="1" userName="jack" />   
        <b:role id="1" roleName="admin" />
    </dataset>
      简介中提到的类似于dbdeploy和rails migration的DBMaintainer,可以通过sql文件维护测试数据库版本。这个也是比较有用的功能,感兴趣的可以去unitils网站看文档。
      一个Unit Test最佳实践的guide. http://www.unitils.org/guidelines.htm可以很容易扩展unitils,实现自己的modules。

  • Zee之LR脚本练习之七:执行dir命令并把结果写到文件里

    2008-02-14 14:33:00

    Action()
     {
          int count,total=0,i;
          char buffer[1000];
          long file_stream;
         char filename[1024], command[1024],line[100];
         char new_dir[] = "C:\\test";


         if (mkdir(new_dir))
              lr_output_message("Create directory %s failed", new_dir);
         else
              lr_output_message("Created new directory %s", new_dir);

         sprintf (filename, "%s\\%s", new_dir, "newfile.txt");
         sprintf (command, "dir /b c:\\ > %s /w", filename );
         system(command);
         lr_output_message("Created new file %s", filename);

       if((file_stream=fopen(filename,"r"))==NULL)
     {
           lr_error_message("can not open %s",filename);
           return -1;
       }

       for(i=1;i<10;i++)
        {
     if (fgets(line, 100, file_stream) == NULL)
              lr_output_message("fgets error" );
         else
              lr_output_message( "The first line is \"%s\"", line);
        }

        while(!feof(file_stream))
      {
          count=fread(buffer,sizeof(char),1000,file_stream);
           lr_output_message("%3d read",count);
     
        if(ferror(file_stream))
      {
        lr_output_message("error reading file %s",filename);
        break;
            }
       total+=count;
            }
          lr_output_message("Total number of bytes read = %d",total);

         if(fclose(file_stream))
          lr_error_message("Error closing file %s",filename);

         return 0;
    }

  • Zee脚本练习之六:LR中real协议的简易脚本展示

    2008-02-14 14:31:57

    注:蓝色部分为需要添加的URL。

    由于本人较懒,就不再添加注释了。^_^

     

    Action()
    {
       int value;
        unsigned long  current_clip_size;

     lreal_open_player(atoi(lr_eval_string("<data1>")));

     

     lreal_open_url(atoi(lr_eval_string("<data1>")), "URL");

     

     lreal_open_url(atoi(lr_eval_string("<data1>")), "URL");

     

     //   lreal_close_player(2);

     lreal_play(atoi(lr_eval_string("<data1>")), 120000);

    value = lreal_get_property(1,LREAL_LOST_PACKETS);

    lr_output_message("Value is %d",value);
    value = lreal_get_property(1,LREAL_CURRENT_BANDWIDTH);

    lr_output_message("Value is %d",value);

    value = lreal_get_property(1,LREAL_NETWORK_PERFORMANCE);

    lr_output_message("Value is %d",value);

    value = lreal_get_property(1,LREAL_STREAM_QUALITY);

    lr_output_message("Value is %d",value);

    value = lreal_get_property(1,LREAL_BUFFERING_NUM);

    lr_output_message("Value is %d",value);

    value = lreal_get_property(1,LREAL_BUFFERING_TIME);

    lr_output_message("Value is %d",value);

    value = lreal_get_property(1,LREAL_BUFFERING_CONGESTION_NUM);

    lr_output_message("Value is %d",value);

    value = lreal_get_property(1,LREAL_RECOVERED_PACKETS);

    lr_output_message("Value is %d",value);

    current_clip_size = lreal_current_time(atoi(lr_eval_string("<data1>")));

    if (current_clip_size > 0)

           lr_output_message("%d  done", current_clip_size);


     lreal_stop(atoi(lr_eval_string("<data1>")));


     lreal_close_player(atoi(lr_eval_string("<data1>")));

    }

  • LR中HTTP协议两种做文本检查点的方式-Zee

    2008-02-14 14:30:06

    http://www.51testing.com/?17369/action_viewspace_itemid_73305.html


    HTTP中做文本检查点的两种方式:
     
     
    第一种方式:关联取值判断
     
     

    //这种方式多有主动找麻烦的意思 ,但是如果碰到用检查函数做不了,可以考虑用这种方
    //式(我还没有碰到想检查的值检查函数做不了的情况)
    //这种方式不管是要判断的值在什么地方,只要是server response里就可以。
     char buffer[20] = {0};
      int j;
       int i;
       char str1[]="dianping";
     
                    web_reg_save_param("param1",
                                    "LB=www.",
                                    "RB=.com",
                                    "Ord=18",
                                    LAST);
                    web_url("www.hao123.com",
                                    "URL=http://www.hao123.com/",
                                    "Resource=0",
                                    "RecContentType=text/html",
                                    "Referer=",
                                    "Snapshot=t1.inf",
                                    "Mode=HTML",
                                    EXTRARES,
                                    "URL=/line.gif", ENDITEM,
                                    "URL=/images/dropdown.gif", ENDITEM,
                                    "URL=/images/guangg/baike.gif", ENDITEM,
                                    LAST);
    //lr_log_message("%s,", lr_eval_string("{param1}"));
    if (atoi( strcmp(str1,lr_eval_string("{param1}"))) == 0){
    //检查关联函数取到的值,和已定义的值是否相同,如相同打印如下
                     lr_log_message("你给我出来!!");
     }

     
     
    第二种方式:函数判断
     
     
    注意:web_find函数不能检查没有显示在页面上的值;而web_reg_find可以。
                 Web_reg_find不能放在Action的最后。

     web_reg_find("Text=dianping",
                                    LAST);
                    web_url("www.hao123.com",
                                    "URL=http://www.hao123.com/",
                                    "Resource=0",
                                    "RecContentType=text/html",
                                    "Referer=",
                                    "Snapshot=t1.inf",
                                    "Mode=HTML",
                                    EXTRARES,
                                    "URL=/line.gif", ENDITEM,
                                    "URL=/images/dropdown.gif", ENDITEM,
                                    "URL=/images/guangg/baike.gif", ENDITEM,
                                    LAST);
                    web_find("web_find",
                                    "What=音乐MP3",
                                    LAST);

  • QTP 常用函数(转)

    2008-02-14 14:11:47

  • Qtp学习日记002

    2008-02-14 14:04:06

    今天对输出值进行了学习,这一课得本身没有那么难,但是在学习的过程走了很多弯路,所以浪费了很多的时间.所以值得记录的体会还是有的 .

    (1): 在添加录制脚本的时候要注意网页的名字,在网页第二遍出现的时候和第一遍出现得时候名称是不一样的,在运行的时候就不能连贯,程序会报错.

    (2): 在添加输出值的时候,不能够重复的多次的添加.否则会报错

    (3) 在制定表检查点得时候,检查元素一定要是预期的测试观察点,否则表中别的元素出错,测试结果也是失败的,这样会影响对测试结果的判断

    (别的元素不考虑的情况下不应该选上)

     

  • Qtp学习日记001

    2008-02-14 14:01:53

    今天对QTP中页面检查点,文本检查点的设置进行了学习.

    重点学习参数测试,并成功地实现了FOR  NEXT 函数地循环操作.

    操作的过程中遇见的问题:

    1.进行局部循环的时候,只能做一遍,不能实现局部循环.

       原因: 在最后一页与第一页没有衔接点,导致循环无法继续

       解决方法:在最后一页再录制一条操作,这样可降最后一页与第一页衔接

    2.进行局部循环的时候,只能读取参数表中的第一行,无法在每次循环的时候读到参数表中不同值:

       原因:不明.

       解决方式:在计数器的前面添加语句: DataTable.GetSheet (2).SetNextRow

    3.设置了局部循环,程序还是会将所有程序按照参数表中数量再循环一遍:例如设置了三个参数,局部循环设置三次,待测程序将运行9(局部循环内的程序)整个程序运行三遍

        原因: File > settings > run > data table iteration > 选择了 Run on all rows

        解决方法: 改变上述中的选项

    4. 局部循环设置FOR 1 TO 3 局部循环只运行2

        局部循环设置FOR 0TO 2 局部循环运行3

       原因不明

    明天计划: 输出值学习

  • 连接服务器失败

    2007-10-26 16:20:05

    1、我的机子(客户端)
    系统平台 WIN XP ,LR版本7.8。

    2、服务器

    系统平台WIN SERVER 2003 ;IP 192.168.1.77 ;
    服务器没有安装LR7.8;

    如果想要监视服务器的运行情况的话,只需要在运行时连接到你要测试的服务器就行了,在场景里运行脚

    本时不需要连到服务器上。

    我也这么试验过 !
    你在服务器上装一个load generator就行了  
    我试过了 可以解决的

  • LoadRunner录制脚本中的中文乱码问题

    2007-10-26 13:58:15

    在使用LoadRunner 8.1 进行录制脚本,其脚本中中文信息存在乱码,其解决方法如下所示:
    1、新建脚本--->选择协议(Http)-->选项-->高级-->选择“支持字符集”并点选“UTF-8”;
    2、在回放脚本之前:Vuser-->运行时设置-->浏览器-->浏览器仿真-->更改-->使用浏览器-->语言下来选择 “中文(中国)”;
    进行如上设置以后即可。
     
    补充说明:主要看被测试应该程序的编码形式是什么。如果是UTF-8的形式则采用上述形式设置,如果是Gb2312则不进行步骤1、的操作。感觉步骤2、不是很重要,如果哪位有更确切跟合理的解释,请多赐教:)
  • 如何实现并发操作(由并发引出的问题及思考-小结)

    2007-10-26 11:43:48

    在controller中将quantity设置为100,这样相当于将一个用户初始化100次,然后此用户进行100个这样的并发操作。不知理解对否??
    或者是quantity是100,给服务器的压力就是100个这样的用户并发操作,尽管在服务器端看到的在线用户只用一个,但这个用户的操作相当于100个用户同时操作。不知这样的理解是否正确?
    如果上面的理解是正确的,那么我想模拟100个用户同时在线并在服务器端真实可见又当如何实现呢?

    版主的回答:
    两种理解都有问题,这就是LR的机制问题。
    quantity是100,表示利用100个并发用户,这个时候服务器看到的在线用户就是100个。而不是一个用户。LR的机制是:
    LoadRunner  是一种预测系统行为和性能的工业级标准性能测试负载测试工具。通过以模拟上千万用户实施并发负载及实时性能监测的方式来确认和查找问题,LoadRunner 能够对整个企业架构进行测试。通过使用LoadRunner ,企业能最大限度地缩短测试时间,优化性能和加速应用系统的发布周期

    首先,LR并发100个用户时,你在controller 中的Vuser是可以看到100用户的,每个用户不同的ID;
    其次,服务端也可以看到的,不过要视乎你测试的系统,我这边的在应用服务器下LOG可以非常清晰地见到每个不同ID的用户操作,也就是说,客户端每与服务器连接上了,服务器分配唯一一个ID给他;
    再次,LR中的controller虚拟用户时是分线程(Run Vuser as a thread)和进程(Run Vuer as a process)两种的,结果当然有区别的,不知道LZ是否留意???

    我在controller 中的Vuser可以看到100用户的,每个用户都拥有不同的ID,只是这些用户拥有相同的用户名。
    这样的操作就是100个用户的并发操作了,是吗?
    我的设置是Run Vuser as a thread。

    这个问题我稍作描述:
    一般指的并发,是某系统下同时在使用的用户数,而不是用时做某一个操作。

    LR中的设置用户数后,一般都会陆续增加用户,因此是普通意义上的并发。
    如果要测试某一系统下同时做某一操作,例如大家一起点击[申请]提交数据,那么需要在LR中设置集合点。

    集合点的相关资料,你查一下软件帮助,里面说的很清楚。

  • 使用BoundsChecker检测内存泄漏

    2007-10-23 15:11:35

    使用BoundsChecker检测内存泄漏:

       BoundsChecker采用一种被称为 Code Injection的技术,来截获对分配内存和释放内存的函数的调用。简单地说,当你的程序开始运行时,BoundsChecker的DLL被自动载入进程的地址空间(这可以通过system-level的Hook实现),然后它会修改进程中对内存分配和释放的函数调用,让这些调用首先转入它的代码,然后再执行原来的代码。BoundsChecker在做这些动作的时,无须修改被调试程序的源代码或工程配置文件,这使得使用它非常的简便、直接。

       这里我们以malloc函数为例,截获其他的函数方法与此类似。

       需要被截获的函数可能在DLL中,也可能在程序的代码里。比如,如果静态连结C-Runtime Library,那么malloc函数的代码会被连结到程序里。为了截获住对这类函数的调用,BoundsChecker会动态修改这些函数的指令。

       以下两段汇编代码,一段没有BoundsChecker介入,另一段则有BoundsChecker的介入:

       126: _CRTIMP void * __cdecl malloc (

       127: size_t nSize

       128: )

       129: {

       00403C10 push ebp

       00403C11 mov ebp,esp

       130: return _nh_malloc_dbg(nSize, _newmode, _NORMAL_BLOCK, NULL, 0);

       00403C13 push 0

    00403C15 push 0

       00403C17 push 1

       00403C19 mov eax,[__newmode (0042376c)]

       00403C1E push eax

       00403C1F mov ecx,dword ptr [nSize]

       00403C22 push ecx

       00403C23 call _nh_malloc_dbg (00403c80)

       00403C28 add esp,14h

       131: }

       以下这一段代码有BoundsChecker介入:

       126: _CRTIMP void * __cdecl malloc (

       127: size_t nSize

       128: )

       129: {

       00403C10 jmp 01F41EC8

       00403C15 push 0

       00403C17 push 1

       00403C19 mov eax,[__newmode (0042376c)]
      
       00403C1E push eax

    00403C1F mov ecx,dword ptr [nSize]

       00403C22 push ecx

       00403C23 call _nh_malloc_dbg (00403c80)

       00403C28 add esp,14h

       131: }

       当BoundsChecker介入后,函数malloc的前三条汇编指令被替换成一条jmp指令,原来的三条指令被搬到地址01F41EC8处了。当程序进入malloc后先jmp到01F41EC8,执行原来的三条指令,然后就是BoundsChecker的天下了。大致上它会先记录函数的返回地址(函数的返回地址在stack上,所以很容易修改),然后把返回地址指向属于BoundsChecker的代码,接着跳到malloc函数原来的指令,也就是在00403c15的地方。当malloc函数结束的时候,由于返回地址被修改,它会返回到BoundsChecker的代码中,此时BoundsChecker会记录由malloc分配的内存的指针,然后再跳转到到原来的返回地址去。

       如果内存分配/释放函数在DLL中,BoundsChecker则采用另一种方法来截获对这些函数的调用。BoundsChecker通过修改程序的DLL Import Table让table中的函数地址指向自己的地址,以达到截获的目的。关于如何拦截Windows的系统函数,《程序员》杂志2002年8期,《API钩子揭密(下)》,对修改导入地址表做了概要的描述。我就不再赘述。

       截获住这些分配和释放函数,BoundsChecker就能记录被分配的内存或资源的生命周期。接下来的问题是如何与源代码相关,也就是说当BoundsChecker检测到内存泄漏,它如何报告这块内存块是哪段代码分配的。答案是调试信息(Debug Information)。当我们编译一个Debug版的程序时,编译器会把源代码和二进制代码之间的对应关系记录下来,放到一个单独的文件里(.pdb)或者直接连结进目标程序中。有了这些信息,调试器才能完成断点设置,单步执行,查看变量等功能。BoundsChecker支持多种调试信息格式,它通过直接读取调试信息就能得到分配某块内存的源代码在哪个文件,哪一行上。使用Code Injection和Debug Information,使BoundsChecker不但能记录呼叫分配函数的源代码的位置,而且还能记录分配时的Call Stack,以及Call Stack上的函数的源代码位置。这在使用像MFC这样的类库时非常有用,以下我用一个例子来说明:

    void ShowXItemMenu()

       {

       …

       CMenu menu;

       menu.CreatePopupMenu();

       //add menu items.

       menu.TrackPropupMenu();

       …

       }

       void ShowYItemMenu( )

       {

       …

       CMenu menu;

       menu.CreatePopupMenu();

       //add menu items.

       menu.TrackPropupMenu();

       menu.Detach();//this will cause HMENU leak

       …

       }

       BOOL CMenu::CreatePopupMenu()

       {


       hMenu = CreatePopupMenu();

       …

       }

       当调用ShowYItemMenu()时,我们故意造成HMENU的泄漏。但是,对于BoundsChecker来说被泄漏的HMENU是在class CMenu::CreatePopupMenu()中分配的。假设的你的程序有许多地方使用了CMenu的CreatePopupMenu()函数,如果只是告诉你泄漏是由CMenu::CreatePopupMenu()造成的,你依然无法确认问题的根结到底在哪里,在ShowXItemMenu()中还是在ShowYItemMenu()中,或者还有其它的地方也使用了CreatePopupMenu()?有了Call Stack的信息,问题就容易了。BoundsChecker会如下报告泄漏的HMENU的信息:

    Function

    File

    Line

    CMenu::CreatePopupMenu

    E:\8168\vc98\mfc\mfc\include\afxwin1.inl

    1009

    ShowYItemMenu

    E:\testmemleak\mytest.cpp

    100

    这里省略了其他的函数调用


       如此,我们很容易找到发生问题的函数是ShowYItemMenu()。当使用MFC之类的类库编程时,大部分的API调用都被封装在类库的class里,有了Call Stack信息,我们就可以非常容易的追踪到真正发生泄漏的代码。

       记录Call Stack信息会使程序的运行变得非常慢,因此默认情况下BoundsChecker不会记录Call Stack信息。可以按照以下的步骤打开记录Call Stack信息的选项开关:

       1. 打开菜单:BoundsChecker|Setting…

       2. 在Error Detection页中,在Error Detection Scheme的List中选择Custom

       3. 在Category的Combox中选择 Pointer and leak error check

       4. 钩上Report Call Stack复选框

       5. 点击Ok

    基于Code Injection,BoundsChecker还提供了API Parameter的校验功能,memory over run等功能。这些功能对于程序的开发都非常有益。由于这些内容不属于本文的主题,所以不在此详述了。

       尽管BoundsChecker的功能如此强大,但是面对隐式内存泄漏仍然显得苍白无力。所以接下来我们看看如何用Performance Monitor检测内存泄漏。

       使用Performance Monitor检测内存泄漏

       NT的内核在设计过程中已经加入了系统监视功能,比如CPU的使用率,内存的使用情况,I/O操作的频繁度等都作为一个个Counter,应用程序可以通过读取这些Counter了解整个系统的或者某个进程的运行状况。Performance Monitor就是这样一个应用程序。

       为了检测内存泄漏,我们一般可以监视Process对象的Handle Count,Virutal Bytes 和Working Set三个Counter。Handle Count记录了进程当前打开的HANDLE的个数,监视这个Counter有助于我们发现程序是否有Handle泄漏;Virtual Bytes记录了该进程当前在虚地址空间上使用的虚拟内存的大小,NT的内存分配采用了两步走的方法,首先,在虚地址空间上保留一段空间,这时操作系统并没有分配物理内存,只是保留了一段地址。然后,再提交这段空间,这时操作系统才会分配物理内存。所以,Virtual Bytes一般总大于程序的Working Set。监视Virutal Bytes可以帮助我们发现一些系统底层的问题; Working Set记录了操作系统为进程已提交的内存的总量,这个值和程序申请的内存总量存在密切的关系,如果程序存在内存的泄漏这个值会持续增加,但是Virtual Bytes却是跳跃式增加的。

       监视这些Counter可以让我们了解进程使用内存的情况,如果发生了泄漏,即使是隐式内存泄漏,这些Counter的值也会持续增加。但是,我们知道有问题却不知道哪里有问题,所以一般使用Performance Monitor来验证是否有内存泄漏,而使用BoundsChecker来找到和解决问题。

       当Performance Monitor显示有内存泄漏,而BoundsChecker却无法检测到,这时有两种可能:第一种,发生了偶发性内存泄漏。这时你要确保使用Performance Monitor和使用BoundsChecker时,程序的运行环境和操作方法是一致的。第二种,发生了隐式的内存泄漏。这时你要重新审查程序的设计,然后仔细研究Performance Monitor记录的Counter的值的变化图,分析其中的变化和程序运行逻辑的关系,找到一些可能的原因。这是一个痛苦的过程,充满了假设、猜想、验证、失败,但这也是一个积累经验的绝好机会。

       总结

       内存泄漏是个大而复杂的问题,即使是Java和.Net这样有Gabarge Collection机制的环境,也存在着泄漏的可能,比如隐式内存泄漏。由于篇幅和能力的限制,本文只能对这个主题做一个粗浅的研究。其他的问题,比如多模块下的泄漏检测,如何在程序运行时对内存使用情况进行分析等等,都是可以深入研究的题目。如果您有什么想法,建议或发现了某些错误,欢迎和我交流。
  • 什么是內存泄漏

    2007-10-23 15:07:52

    什么是內存泄漏

    所谓的内存泄漏可以理解为内存单元逐渐被无用的数据占用
    在c c++里可以通过内存单元没有释放引起
    java里可以通过 未对作废数据内存单元的引用置null引起
     
    分配了内存而没有释放,逐渐耗尽内存资源,导致系统崩溃。
    内存泄露是指程序中间动态分配了内存,但是在程序结束时没有释放这部分内存,从而造成那一部分内存不可用的情况,重起计算机可以解决,但是也有可能再次发生内存泄露,内存泄露和硬件没有关系,它是由软件设计缺陷引起的。
    例如C语言编程中用到带有指针的运算,因为C指针是直导内存的,当运行完程序而没释放内存的话,那段内存就会泄露;同理会出现在C++中;所以malloc后一定要free,new了之后一定要delete,creatDC之后一定要deleteDC的。
     
    内存泄漏可以分为4类:

    1. 常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。
    2. 偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。
    3. 一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,所以内存泄漏只会发生一次。
    4. 隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。
     
    一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定),使用完后必须显示释放的内存。应用程序一般使用malloc,realloc,new等函数从堆中分配到一块内存,使用完后,程序必须负责相应的调用free或delete释放该内存块,否则,这块内存就不能被再次使用,我们就说这块内存泄漏了。
     
    个人理解:在编程过程中,如果我们在堆中动态审请了一块内存,在使用完这块内存后要及时把这块内存释放掉。如果没有及时释放,操作系统会认为这块内存仍然被使用,这就造成了内存泄露。这是程序开发人员必须要注意的问题。操作系统都有自己的内存管理和分配机制,如果内存管理策略不是很好就会引发内存碎片的存在。若不及时进行碎片整理,也将造成内存泄露。
     
    听说,c/c++,java等语言存在内存泄露的隐患,C#语言机制好像就没有这方面的担忧,是这样嘛?
     
    一个比较简单的判断内存泄漏的方法,看一下你的程序所占用的private bytes是多少,
    如果一直增加,也就说明有内存泄漏。

    performance monitor里面可以检测private bytes的变化情况。
     
     
     
     
741/41234>
Open Toolbar