当前位置:Linux教程 - Linux文化 - 应用程序跟踪对性能改变进行量化分析

应用程序跟踪对性能改变进行量化分析


使用 Apache Web 服务器作为示例,以了解如何分析公共配置的性能含义。使用应用程序跟踪来观察应用程序运行过程中进行的系统调用。通过统计调用的次数和发生的时间,您可以轻松地了解性能改变的影响。

您可以对应用程序进行跟踪,以找出它们暂停或不运行的原因。并且可以使用同样的方法,了解更多关于应用程序的信息并理解某些配置的性能含义。因为 Apache 非常流行并且大多数读者对它都比较熟悉,所以本文使用 Apache 作为示例。Apache 所进行的每次系统调用都会对 Web 页面的提供带来延迟,通过跟踪不同配置下的 Web 服务器,您可以确定具体配置的影响。

应用程序跟踪概述

在应用程序的执行过程中,当需要打开文件、发送数据包或者使用系统资源时,它会对基础操作系统进行相应的系统调用。跟踪应用程序意味着可以在调用发生时观察到这些系统调用,这将使得您能够深入地了解该应用程序的行为。在 Solaris 和 IBM AIX® 操作系统 (AIX) 中,使用 truss 命令完成这项任务,而在 Linux® 中则使用 strace。清单 1 显示了对 pwd 命令进行跟踪的示例。

清单 1. 跟踪 pwd 命令

-bash-3.00$ truss pwd ... getcwd("/export/home/sean", 1025) = 0 /export/home/sean write(1, " / e x p o r t / h o m e".., 18) = 18 _exit(0)

在删除开始处与加载该应用程序相关的输出内容后,您可以看到所进行的三次系统调用:

getcwd 返回当前工作目录。输出内容中显示了字符串“/export/home/sean”返回到缓冲区。

write 可以显示给定的字符串。因为在其执行之后才显示这个系统调用,所以先输出了它的执行结果。还可以注意到,write 系统调用的结果是写入字符的个数数目,在这个示例中为 17 加上一个回车。

_exit 使用错误代码 0 退出该程序,这个错误代码通常表示成功结束。

尽管这是一个很简单的示例,但它演示了通过应用程序跟踪可以观察程序内部机制的程度。有关跟踪方面更深入的信息,请参见参考资料部分。

Apache 配置的简要介绍

可以通过一个名为 httpd.conf 的文件对 Apache Web 服务器进行配置。清单 2 显示了一个简单配置中的部分内容。

清单 2. 示例 httpd.conf

DocumentRoot "/var/apache/htdocs" Options FollowSymLinks AllowOverride None Options Indexes FollowSymLinks MultiViews

第一行定义了在何处可以找到 HTML 文件。将所有的请求都映射到这个目录。如果请求 /project/charter.html,将使用 /var/apache/htdocs/project/charter.html 提供该页面。httpd.conf 中剩余的部分由两个 Directory 节组成。 和 之间的任何内容都将应用于指定目录及其所有的子目录。在本例中,第一节将两项设置应用于根目录,而第二节则指向 /var/apache/htdocs。

如果多个节应用于单个请求,那么将会对这些命令进行组合,并且最适合的目录具有高优先级。例如,将使用 /var/apache/htdocs/project/charter.html 提供 /project/charter.html 请求的页面。/var/apache/htdocs 是 / 的子目录,所以 Options Indexes FollowSymLinks MultiViews 来自第二节,AllowOverride None 来自第一节。

可以对许多内容进行配置,并且每项内容都具有其性能含义。本文余下的内容重点关注于如何对这些改变的影响进行量化分析。

建立基准

在您进行任何调整之前,务必要了解系统当前的运行方式。使用 -X 参数启动 Apache,这个参数将强制 Apache 进入单进程调试模式。这样做可以确保将请求发送到正在进行跟踪的进程,并且消除常规进程间通信所带来的开销。

在守护进程启动之后,通过运行 ps -ef 找到相应的进程 ID,并查找 httpd 守护进程。在找到该进程之后,使用 truss -c -p PID 附加到该进程。-c 选项表示对系统调用进行计数,而不是逐个显示它们,而 -p 则表示将跟踪器附加于一个正在运行的进程。

使用 Web 浏览器请求文档。在页面加载之后,回到 truss 应用程序,然后按 Ctrl-C 以结束计数。对于静态 HTML 页面,您应该看到如清单 3 所示的内容(为使这些数字变得更有趣,本示例进行了 100 次相同的请求)。

清单 3. 系统调用基准

sunbox# truss -c -p 15026 (make the Web request 100 times) ^C syscall seconds calls errors read .009 200 write .020 200 close .020 200 time .004 300 alarm .018 1100 fcntl .009 300 sigaction .007 400 munmap .007 100 llseek .001 100 pollsys .005 100 mmap64 .008 100 stat64 .007 100 open64 .006 100 accept .019 100 getsockname .002 100 setsockopt .002 100 -------- ------ ---- sys totals: .149 3600 0 usr time: .120 elapsed: 8.960

truss 返回系统调用的列表、执行调用耗费的总时间、调用的次数和任何发现的问题。在这个报表的结尾处,返回了这些系统调用耗费的总时间,以及执行应用程序所耗费的时间。对于这些目的来说,所耗时间是没有意义的,因为它指的是从启动 truss 开始到其结束的时间,而与 Web 请求没有任何关系。

清单 3 显示了最简单的情况。在来自 Web 浏览器的连接请求到达后,accept 系统调用完成该连接。Web 服务器使用 read 调用获得请求的内容,将请求的内容映射到磁盘上的文件。Web 服务器首先使用 stat64 验证该文件是否存在,使用 open64 打开该文件以便进行读取,然后使用 mmap64 将该文件的内容映射到内存中。然后使用 write 将这个文件发送回客户端,使用另一个 write 生成日志文件,并且服务器执行来自浏览器的最后一个 read。该列表中其余的调用都是系统开销,并且当配置发生改变时,不会有显著的变化。

解释这些数值

100 次请求总共耗费 0.269 秒 (0.149 + 0.120),这样的性能相当不错,并且该服务器每秒钟应该可以提供大约 370 个页面 (100/0.269)。但是不能完全相信这些数值,因为它们仅表示进程耗费在 CPU 上的时间,而不是其真正的执行时间(也称为时钟时间)。还有更多的因素需要考虑,如磁盘和网络的速度、计算机上正运行的其他内容、该守护进程运行于调试模式的事实。您还需要考虑系统调用跟踪本身的开销。

本文中使用的方法重点关注这些操作的相对计时和使用应用程序跟踪消除浪费掉的操作。如果您需要了解 Web 服务器每秒可以提供的页面数目,参考资料部分中有相应的软件链接,它可以帮助您确定该数值。

跟踪 AllowOverride 范围

Apache 允许管理员通过 .htaccess 机制将配置权委托给个别的用户。.htaccess 是一个包含附加配置指令的文件,如果在 httpd.conf 中通过 AllowOverride 配置了请求的目录,那么 Web 服务器将搜索这个文件。清单 4 显示了前面的配置了 AllowOverride Limit 的配置信息,它允许用户获取访问 Web 页面的用户名和密码。

清单 4. 配置了 AllowOverride 的示例 httpd.conf

DocumentRoot "/var/apache/htdocs" Options FollowSymLinks AllowOverride Limit Options Indexes FollowSymLinks MultiViews

重新启动 httpd 守护进程并再次运行这些测试,其结果如清单 5 所示。

清单 5. 开启了 AllowOverride Limit 的 100 次请求的结果

sunbox# truss -c -t write,read,open64,stat64,mmap64 -p 21136 ^C syscall seconds calls errors read .012 200 write .021 200 mmap64 .007 100 stat64 .007 100 open64 .022 500 400 -------- ------ ---- sys totals: .072 1100 400 usr time: .141 elapsed: 16.660

初看起来,系统调用耗费的时间下降了,但这是因为使用了 -t 选项将跟踪任务限制于一些有意义的系统调用。大多数系统调用并没有发生变化,但现在有 500 次 open64 调用,其中有 400 次返回了错误。执行 open64 的时间增加了(从 0.006 秒增加到 0.22 秒),同时用户空间部分的时间也增加了(从 0.12 秒增加到 0.141 秒)。

时间增加是因为 Apache 现在必须完成附加的工作以处理该请求,即使没有配置重写。单独的 AllowOverride Limit 配置明显地增加了开销。问题依然存在,即什么导致了这些错误? 要回答这个问题,可以跟踪单个 Web 请求,如清单 6 所示。

清单 6. 确定 open64 调用失败的原因

sunbox# truss -t open64 -p 21136 open64("/.htaccess", O_RDONLY) Err#2 ENOENT open64("/var/.htaccess", O_RDONLY) Err#2 ENOENT open64("/var/apache/.htaccess", O_RDONLY) Err#2 ENOENT open64("/var/apache/htdocs/.htaccess", O_RDONLY) Err#2 ENOENT open64("/var/apache/htdocs/test.html", O_RDONLY) = 5

清单 6 显示了当请求进入时,Apache 对每个指向 /var/apache/htdocs 的目录进行检查并尝试打开 .htaccess 文件,但是因为 AllowOverride 配置为根目录,所以这个文件并不存在。Apache 必须在每个子目录中查找 .htaccess 文件的重写信息,并对它们进行处理。这样一来,由于额外的系统调用、更多的用户空间开销和额外的磁盘活动,从而进一步增加了延迟。对于 100 次请求来说,增加零点几秒的时间看起来并不是很明显,但是对于一台繁忙的服务器,就会增加更长的延迟。

既然您了解了重写的范围,那么理想的解决方案是不允许重写,并且强制在 httpd.conf 中对所有的内容进行配置。如果失败,可以将配置的范围限制于需要它的目录。在这个研究示例中,把 AllowOverride Limit 放到第二个 Directory 节中,这将仅添加一个额外的 open64 调用,以便在 /var/apache/htdocs 中查找 .htaccess。对所有的父目录进行搜索是浪费时间,因为在这个配置中,不会使用其中的任何文件来提供页面。

研究主机名查找

当 Web 服务器接收到一个请求时,它所知道的关于客户端的信息只有其 IP 地址,如 129.42.42.212 对应于 IBM.com。然而,Web 服务器并不知道这个地址来自于 IBM.com,因此,它必须进行反向 DNS 查找。这样做需要耗费一定的时间,如果在发送请求之前需要这个名称,那么将会延迟对客户端的响应。过去,Apache 在缺省情况下会执行这些反向查找,但现在这种行为已经有了改变。

还有另一种情况,其中必须进行反向 DNS 搜索。当基于主机名(而不是 IP 地址)配置访问限制时,Apache 必须首先将 IP 地址反向解析为相应的主机名,然后再次将主机名解析为 IP 地址,以确保它们正确匹配。因为反向域名搜索可以由地址块所有者确定,所以要防止 IP 欺骗的发生,必须进行第二次查找。可以通过应用程序跟踪来确定 DNS 解析的影响吗?

要对其进行测试,可以从前面的示例中删除 AllowOverride Limit,然后添加 Allow from ibm.com 代替缺省的 Allow from all。然后,对 DNS 服务器进行更改以便向您的工作站返回 something.ibm.com,确保初始反向检查能够成功并且随后的正向查找必须通过 Internet 完成。在示例运行过程中,对保护的 Web 服务器的请求耗费了 15 秒的时间。相反,使用 IP 地址代替 ibm.com 所耗费的时间小于半秒钟。清单 7 显示了在使用 DNS 确保安全时,对一些系统调用进行统计。

清单 7. 通过主机名进行限制的 Web 请求的 truss 输出

bash-3.00# truss -c -p 26089 ^C syscall seconds calls errors read .000 5 write .000 3 open .000 2 close .001 10 time .000 3 stat .000 5 alarm .000 8 fcntl .000 7 2 sigaction .000 3 sysconfig .000 5 pollsys .001 5 door_info .000 2 stat64 .000 1 open64 .000 2 so_socket .001 5 accept .000 1 connect .002 5 recvfrom .000 2 send .001 5 getsockname .000 1 setsockopt .000 1 -------- ------ ---- sys totals: .011 81 2 usr time: .004

truss 报告了该进程所耗费的时间远远小于客户端感觉到的时间(0.015 秒与 15 秒)。这是因为大多数套接字操作都是异步地 进行的,其中套接字进行轮询以检查数据是否出现,而不是使得应用程序处于阻塞(等待)状态以等待响应。如此一来,应用程序在等待结果的时候不会消耗 CPU 时间。这就解释了 truss 报告的时间和客户端感觉到的时间之间出现差异的原因。

truss 并没有忘记所做的更改,0.015 秒比本文中研究的第一个简单示例要高一个数量级。从系统调用计数中可以看出,有一些以前没有出现过的调用,包括 read、write、close 和 stat。以及还有 send、connect、so_socket 和 pollsys,这些系统调用用于进行名称请求。因为名称解析可以来自于不同的来源,包括本地文件系统和名称缓存守护进程,所以必须对这几个位置进行检查。在随后的调用中,请求时间小于 1 秒,这是因为对 DNS 信息进行了缓存。

最后,大部分的延迟来自于远程名称服务器和正向解析。这个事实进一步强调了,如果您希望依赖于名称查找,那么就需要使用 DNS 缓存和快速 DNS 服务器。然而,最佳的解决方案是使用一种可选的方法来处理这个问题。一种比较简单的解决方案是指定 IP 地址块(如 Allow from 10.0.0.0/8),这种方法比 DNS 查找要快得多。因为反向和正向查找必须匹配,所以对名称进行的操作,很可能可以用于网段。另外,Apache 可以集成各种身份验证系统,所以基于用户的身份验证是另一种可选方法。

系统调用与库调用

如果熟悉套接字编程,那么您可能会寻找 gethostbyname 和其他类似的调用,这些调用都可以用来执行主机查找功能。有一些库调用,由 /usr/lib 中的系统库 libc 和 libsocket 提供。库调用封装了一个或多个系统调用,以及额外的逻辑。您可以将它们作为程序员友好的接口来进行系统调用。例如,gethostbyname 库调用将执行许多步骤,以便根据服务器的配置查找相应的名称,如检查 /etc/hosts 或搜索网络信息系统 (NIS) 或 DNS。这些步骤包含了内核所提供的更小的构件,即系统调用。在大多数这样的这种情况下,两者之间的差别通常很小。truss 还可以跟踪库调用,但是无法提供像系统调用那样详细的信息。

结束语

监视和统计系统调用的功能不仅有助于进行故障排除,还有助于理解应用程序配置如何对性能产生影响。与系统的其他部分之间的每个交互操作都会调用一个或多个系统调用,并且每次调用都会增加系统开销。这并不是说系统调用非常糟糕,如果程序员不与其他的部分进行交互,那将是很乏味的。相反,在改变应用程序时,您可以监视系统调用的使用,以便更好地了解这些改变对总体性能的影响。