Last year I wrote about how to profile your Rails application, which is a lot harder then it seems. Its not so much the profiling itself – its easy enough to create one-off results. Instead, its coming up with a reproducible process that lets you measure performance changes over time.
Some things that don’t work over the long term:
- Insert profiling code into your application code
- Use unit tests for profiling
- Use functional tests for profiling
- Use integration tests for profiling
- Modify standard rails environments (test, development, production for profiling)
So the latest version of ruby-prof introduces a new approach to profiling your Ruby or Rails code that is heavily based on the excellent work Jeremy has done on the request profiler included in newer versions of Rails.
The basic idea is to extend Ruby’s TestUnit library so that individual test cases are profiled by including a new RubyProf::Test module. When you include this module, ruby-prof will run each test once as a warm up and then ten more times to gather profiling data (using another new feature of the 0.7.0 release, the ability to pause and resume a profiling run). Profile data is then output for each test.
Let’s look at an example:
class ExampleTest < Test::Unit::TestCase include RubyProf::Test def test_stuff puts "Test method" end end
The line include RubyProf::Test turns the test case into a profiling test case. The same approach could be used for hooking into other testing frameworks – all patches are of course welcome!
Using a Profile Environment for Rails
Now lets talk about profiling Rails. There are two main issues that make it harder then it seems.
First, to get any useful data you need to profile a Rails app using the production environment settings in conjunction with a test database. Using the development environment doesn’t work because the time it takes Rails to reload classes on each request drowns out any useful information.
Second, how should profile tests be written and where should they go?
The solution I’ve adopted is to use functional like-tests that use a PROFILE environment, and place them in a directory called test/profile.
Let’s look at another example:
require File.dirname(__FILE__) + '/../profile_test_helper' class MyControllerTest < Test::Unit::TestCase include RubyProf::Test fixtures :my_fixture def setup @controller = MyController.new @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new end def test_get get(:index) end end
The only difference between a functional test and a profile test are the inclusion of the RubyProf::Test module and loading profile_test_helper.rb. profile_test_helper is unfortunately needed because the standard test_helper.rb file Rails uses loads the TEST environment. Hopefully future versions of Rails will fix this by allowing greater flexibility in specifying a test environment.
So to get started with profiling your Rails application:
- Copy profile_test_helper.rb from the ruby-prof distribution to your rails test directory
- Modify profile_test_helper.rb as needed to set ruby-prof’s output directory
- Create a profile.rb file in the environments directory
- Update your databases.yml file to include a profile database (just map it to your test database)
- Create a new directory test/profile
- Start writing profiling tests that look similar to the above example
And now you’ll have reproducible profiling tests cases.
So what’s missing? A way of keeping track of how your applications performance changes over time. A quick hack is to use source control to keep profile tests results around. A more sophisticated solution would be to use ruby-prof’s API to dump profile results into a database and then put a nice web front end onto it. Any takers?