ruby-prof最新的0.5版本开始支持Rails,但是文档并没有相应更新,这是来在Charlie Savage的一篇关于如何在Rails中使用ruby-prof的指南,希望对你有所帮助。
假设你发现你的Rails应用对某个请求的处理花费了太多的时间,并且你已经排除了网络,Web Server,客户端浏览器或者是数据库的嫌疑,那么ruby-prof可以帮你轻松定位出到底是那段代码消耗了太多的时间:
gem install ruby-prof
windows平台用户应该选择针对win32平台的预编译版本,安装完之后,你需要从ruby-prof的安装目录下拷贝plugin到你的Rails应用的vendor/plugins目录:
cd /your/ruby/gems/path/ruby-prof-0.5.0/lib/
cp ruby-prof/rails_plugin/ruby_prof rails_app/vendor/plugins
然后在你的environment.rb中增加:
config.cache_classes = true
如果不设置这一项的话,那么Rails会在处理每个请求时重新将所有的model和controller都加载一遍,这会消耗大量的时间,从而使你的action的花费看起来微不足道。
完成以上操作以后,如果现在你通过浏览器向Rails发送一个请求,那么在请求处理完成后,这个请求的profile已经被写入到了Log里面,它看起来是这个样子:
Completed in 0.71900 (1 reqs/sec)
Rendering: 0.37600 (52%) | DB: 0.18700 (26%)
200 OK [http://localhost/]
Thread ID: 175635930
Total: 0.719
%self total self wait child calls name
26.15 0.19 0.19 0.00 0.00 23 PGconn#exec
26.01 0.19 0.19 0.00 0.00 27 PGresult#each
4.45 0.03 0.03 0.00 0.00 1403 String#match
4.45 0.03 0.03 0.00 0.00 171 IO#write
2.23 0.02 0.02 0.00 0.00 872 String#to_s
2.23 0.02 0.02 0.00 0.00 467 String#gsub
2.23 0.03 0.02 0.00 0.02 76 ManyParser#_parse
2.23 0.05 0.02 0.00 0.03 60 MonitorMixin#synchronize
2.23 0.02 0.02 0.00 0.00 60 Parsers#sequence
2.23 0.02 0.02 0.00 0.00 1640 ParseContext#eof
2.23 0.02 0.02 0.00 0.00 31 ActionView::Partials#add_object_to_local_assigns!
2.23 0.02 0.02 0.00 0.00 11 ActionView::Helpers::UrlHelper#link_to
2.23 0.02 0.02 0.00 0.00 243 Object#add_error
2.23 0.02 0.02 0.00 0.00 175 Kernel#clone
第一行显示了Rails处理这个请求的总花费,接下来就是ruby-prof的plat格式的输出,按照花费总时间由高到低排列,这里只列出了花费大于2%的部分。
从上面的结果可以看出,花费时间最多的是PGconn#exec,这是个连接数据库和执行查询的C扩展,在Rails这段并没有多少优化余地,但是在数据 库段还是存在优化空间的,接下来就是PGresult#each,这是处理数据库查询结果的,它的花费明显太多了,但是它后面的String#match 被执行了1403次,这也不太正常。
如果你觉得总是去Rails log中查看profile结果实在是不方便,那么可以通过Hack ruby-prof来获取图形化的输出,修改vendor/ruby-prof/lib/profiling.rb:
module ActionController #:nodoc:
module Profiling #:nodoc:
def perform_action_with_profiling
if RubyProf.running? or
...
else
...
# Example for Graph html printer
printer = RubyProf::GraphHtmlPrinter.new(result)
File.open('request.html', 'w') do |file|
printer.print(file, {:min_percent => 1,
:print_file => true})
end
end
end
end
end
现在再次执行上面的那个请求,ruby-prof已经创建了一个request.html的文件(windows下位于c:/temp/目录下):
如果你以前没有使用过ruby-prof的graph格式,那么上面的输出可能会让你感到迷惑,不要紧,实际上,我们只需要关心粗体的那一行就够了,在它 上面是调用了它的函数的调用栈,下面则是它调用的函数,至于Array#each, Array#each-1, Array#each-2,它们后面的数字表示存在嵌套调用,数字用于指明嵌套的层次(也就是Array#each调用了Array#each-1, Array#each-1又调用了Array#each-2)。
从上图可以看出,Array#each-4调用了String#match最多次(380),因此我们下一步要做的就是追踪Array#each-4,看看到底谁调用了它。
如果你觉得上面的图对于追踪函数调用关系还是不够直观的话,还有办法,ruby-prof0.5提供了call tree格式的输出:
module ActionController #:nodoc:
module Profiling #:nodoc:
def perform_action_with_profiling
if RubyProf.running? or
...
else
...
# Example for Graph html printer
printer = RubyProf::CallTreePrinter.new(result)
File.open('callgrind.out', 'w') do |file|
printer.print(file, {:min_percent => 1,
:print_file => true})
end
end
end
end
end
现在再次执行同样的请求,你将得到一个callgrind.out的文件,可以使用 KCachegrind (目前只支持Linux,Windows下的版本叫做WinCacheGrind)打开它:
不过,需要注意的是,大多数情况下你并不需要一开始就使用ruby-prof,你可以先从web server log, rails log或者是database log入手,如果确定了问题确实是由于你的rails代码引起的,那么ruby-prof就该登场亮相了。

