├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── bin └── rake ├── exe └── perftest ├── gemfiles ├── Gemfile-rails-5.2 ├── Gemfile-rails-6.0 ├── Gemfile-rails-6.1 └── Gemfile-rails-7.0 ├── lib ├── generators │ └── rails │ │ ├── app │ │ └── templates │ │ │ └── test │ │ │ └── performance │ │ │ └── browsing_test.rb │ │ └── performance_test │ │ ├── USAGE │ │ ├── performance_test_generator.rb │ │ └── templates │ │ └── performance_test.rb ├── rails-perftest.rb └── rails │ ├── performance_test_help.rb │ └── perftest │ ├── action_controller.rb │ ├── action_controller │ └── performance_test.rb │ ├── action_dispatch.rb │ ├── action_dispatch │ └── performance_test.rb │ ├── active_support │ └── testing │ │ ├── performance.rb │ │ └── performance │ │ ├── jruby.rb │ │ ├── rubinius.rb │ │ └── ruby.rb │ ├── commands.rb │ ├── commands │ ├── benchmarker.rb │ └── profiler.rb │ ├── railtie.rb │ ├── railties │ └── testing.tasks │ └── version.rb ├── rails-perftest.gemspec ├── rails-perftest.gemspec.erb └── test ├── generators ├── generators_test_helper.rb └── performance_test_generator_test.rb ├── helper.rb └── performance_test.rb /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | jobs: 4 | tests: 5 | runs-on: ubuntu-latest 6 | strategy: 7 | fail-fast: false 8 | matrix: 9 | include: 10 | - ruby: 2.5 11 | os: ubuntu-latest 12 | gemfile: "gemfiles/Gemfile-rails-5.2" 13 | - ruby: 2.5 14 | os: ubuntu-latest 15 | gemfile: "gemfiles/Gemfile-rails-6.0" 16 | - ruby: 2.5 17 | os: ubuntu-latest 18 | gemfile: "gemfiles/Gemfile-rails-6.1" 19 | - ruby: 2.6 20 | os: ubuntu-latest 21 | gemfile: "gemfiles/Gemfile-rails-5.2" 22 | - ruby: 2.6 23 | os: ubuntu-latest 24 | gemfile: "gemfiles/Gemfile-rails-6.0" 25 | - ruby: 2.6 26 | os: ubuntu-latest 27 | gemfile: "gemfiles/Gemfile-rails-6.1" 28 | - ruby: 2.7 29 | os: ubuntu-latest 30 | gemfile: "gemfiles/Gemfile-rails-6.0" 31 | - ruby: 2.7 32 | os: ubuntu-latest 33 | gemfile: "gemfiles/Gemfile-rails-6.1" 34 | - ruby: 2.7 35 | os: ubuntu-latest 36 | gemfile: "gemfiles/Gemfile-rails-7.0" 37 | - ruby: '3.0' 38 | os: ubuntu-latest 39 | gemfile: "gemfiles/Gemfile-rails-6.1" 40 | - ruby: '3.0' 41 | os: ubuntu-latest 42 | gemfile: "gemfiles/Gemfile-rails-7.0" 43 | - ruby: '3.1' 44 | os: ubuntu-latest 45 | gemfile: "gemfiles/Gemfile-rails-7.0" 46 | steps: 47 | - uses: actions/checkout@v2 48 | - uses: actions/cache@v2 49 | with: 50 | path: /home/runner/bundle 51 | key: bundle-use-ruby-${{ matrix.ruby }}-${{ matrix.gemfile }}-gems-${{ hashFiles(matrix.gemfile) }}-${{ hashFiles('**/*.gemspec') }} 52 | restore-keys: | 53 | bundle-use-ruby-${{ matrix.ruby }}-${{ matrix.gemfile }}-gems- 54 | - name: Set up Ruby 55 | uses: ruby/setup-ruby@v1 56 | with: 57 | ruby-version: ${{ matrix.ruby }} 58 | - name: Bundle install 59 | run: | 60 | gem install bundler -v 2.2.24 61 | bundle config path /home/runner/bundle 62 | bundle config --global gemfile ${{ matrix.gemfile }} 63 | bundle install --jobs 4 --retry 3 64 | - name: Run tests 65 | run: bundle exec rake 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .ruby-version 6 | .rvmrc 7 | .yardoc 8 | InstalledFiles 9 | _yardoc 10 | coverage 11 | doc/ 12 | gemfiles/Gemfile*.lock 13 | lib/bundler/man 14 | pkg 15 | rdoc 16 | spec/reports 17 | test/tmp 18 | tmp 19 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in rails-perftest.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | rails-perftest (0.0.7) 5 | 6 | GEM 7 | remote: https://rubygems.org/ 8 | specs: 9 | actionmailer (7.0.2.3) 10 | actionpack (= 7.0.2.3) 11 | actionview (= 7.0.2.3) 12 | activejob (= 7.0.2.3) 13 | activesupport (= 7.0.2.3) 14 | mail (~> 2.5, >= 2.5.4) 15 | net-imap 16 | net-pop 17 | net-smtp 18 | rails-dom-testing (~> 2.0) 19 | actionpack (7.0.2.3) 20 | actionview (= 7.0.2.3) 21 | activesupport (= 7.0.2.3) 22 | rack (~> 2.0, >= 2.2.0) 23 | rack-test (>= 0.6.3) 24 | rails-dom-testing (~> 2.0) 25 | rails-html-sanitizer (~> 1.0, >= 1.2.0) 26 | actionview (7.0.2.3) 27 | activesupport (= 7.0.2.3) 28 | builder (~> 3.1) 29 | erubi (~> 1.4) 30 | rails-dom-testing (~> 2.0) 31 | rails-html-sanitizer (~> 1.1, >= 1.2.0) 32 | activejob (7.0.2.3) 33 | activesupport (= 7.0.2.3) 34 | globalid (>= 0.3.6) 35 | activemodel (7.0.2.3) 36 | activesupport (= 7.0.2.3) 37 | activerecord (7.0.2.3) 38 | activemodel (= 7.0.2.3) 39 | activesupport (= 7.0.2.3) 40 | activesupport (7.0.2.3) 41 | concurrent-ruby (~> 1.0, >= 1.0.2) 42 | i18n (>= 1.6, < 2) 43 | minitest (>= 5.1) 44 | tzinfo (~> 2.0) 45 | builder (3.2.4) 46 | concurrent-ruby (1.1.9) 47 | crass (1.0.6) 48 | digest (3.1.0) 49 | erubi (1.10.0) 50 | globalid (1.0.0) 51 | activesupport (>= 5.0) 52 | i18n (1.10.0) 53 | concurrent-ruby (~> 1.0) 54 | io-wait (0.2.1) 55 | loofah (2.15.0) 56 | crass (~> 1.0.2) 57 | nokogiri (>= 1.5.9) 58 | mail (2.7.1) 59 | mini_mime (>= 0.1.1) 60 | method_source (1.0.0) 61 | mini_mime (1.1.2) 62 | mini_portile2 (2.8.0) 63 | minitest (5.15.0) 64 | net-imap (0.2.3) 65 | digest 66 | net-protocol 67 | strscan 68 | net-pop (0.1.1) 69 | digest 70 | net-protocol 71 | timeout 72 | net-protocol (0.1.2) 73 | io-wait 74 | timeout 75 | net-smtp (0.3.1) 76 | digest 77 | net-protocol 78 | timeout 79 | nokogiri (1.13.3) 80 | mini_portile2 (~> 2.8.0) 81 | racc (~> 1.4) 82 | racc (1.6.0) 83 | rack (2.2.3) 84 | rack-test (1.1.0) 85 | rack (>= 1.0, < 3) 86 | rails-dom-testing (2.0.3) 87 | activesupport (>= 4.2.0) 88 | nokogiri (>= 1.6) 89 | rails-html-sanitizer (1.4.2) 90 | loofah (~> 2.3) 91 | railties (7.0.2.3) 92 | actionpack (= 7.0.2.3) 93 | activesupport (= 7.0.2.3) 94 | method_source 95 | rake (>= 12.2) 96 | thor (~> 1.0) 97 | zeitwerk (~> 2.5) 98 | rake (13.0.6) 99 | ruby-prof (1.4.3) 100 | sqlite3 (1.3.10) 101 | strscan (3.0.1) 102 | thor (1.2.1) 103 | timeout (0.2.0) 104 | tzinfo (2.0.4) 105 | concurrent-ruby (~> 1.0) 106 | zeitwerk (2.5.4) 107 | 108 | PLATFORMS 109 | ruby 110 | 111 | DEPENDENCIES 112 | actionmailer (>= 5.2) 113 | actionpack (>= 5.2) 114 | activemodel (>= 5.2) 115 | activerecord (>= 5.2) 116 | minitest (>= 3) 117 | rails-perftest! 118 | railties (>= 5.2) 119 | ruby-prof (>= 1.0.0) 120 | sqlite3 (>= 1.3) 121 | 122 | BUNDLED WITH 123 | 2.2.24 124 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Yves Senn 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Performance Testing Rails Applications 2 | ====================================== 3 | 4 | This guide covers the various ways of performance testing a Ruby on Rails 5 | application. 6 | 7 | After reading this guide, you will know: 8 | 9 | * The various types of benchmarking and profiling metrics. 10 | * How to generate performance and benchmarking tests. 11 | * How to install and use a GC-patched Ruby binary to measure memory usage and object 12 | allocation. 13 | * The benchmarking information provided by Rails inside the log files. 14 | * Various tools facilitating benchmarking and profiling. 15 | 16 | Performance testing is an integral part of the development cycle. It is very 17 | important that you don't make your end users wait for too long before the page 18 | is completely loaded. Ensuring a pleasant browsing experience for end users and 19 | cutting the cost of unnecessary hardware is important for any non-trivial web 20 | application. 21 | 22 | -------------------------------------------------------------------------------- 23 | 24 | Installation 25 | ---------------------- 26 | 27 | As of rails 4 performance tests are no longer part of the default 28 | stack. If you want to use performance tests simply follow these instructions. 29 | 30 | Add this line to your application's Gemfile: 31 | 32 | gem 'rails-perftest' 33 | 34 | 35 | If you want to benchmark/profile under MRI or REE, add this line as well: 36 | 37 | gem 'ruby-prof' 38 | 39 | Now run `bundle install` and you're ready to go. 40 | 41 | Performance Test Cases 42 | ---------------------- 43 | 44 | Rails performance tests are a special type of integration tests, designed for 45 | benchmarking and profiling the test code. With performance tests, you can 46 | determine where your application's memory or speed problems are coming from, 47 | and get a more in-depth picture of those problems. 48 | 49 | ### Generating Performance Tests 50 | 51 | Rails provides a generator called `performance_test` for creating new 52 | performance tests: 53 | 54 | ```bash 55 | $ rails generate performance_test homepage 56 | ``` 57 | 58 | This generates `homepage_test.rb` in the `test/performance` directory: 59 | 60 | ```ruby 61 | require 'test_helper' 62 | require 'rails/performance_test_help' 63 | 64 | class HomepageTest < ActionDispatch::PerformanceTest 65 | # Refer to the documentation for all available options 66 | # self.profile_options = { runs: 5, metrics: [:wall_time, :memory], 67 | # output: 'tmp/performance', formats: [:flat] } 68 | 69 | test "homepage" do 70 | get '/' 71 | end 72 | end 73 | ``` 74 | 75 | ### Examples 76 | 77 | Let's assume your application has the following controller and model: 78 | 79 | ```ruby 80 | # routes.rb 81 | root to: 'home#dashboard' 82 | resources :posts 83 | 84 | # home_controller.rb 85 | class HomeController < ApplicationController 86 | def dashboard 87 | @users = User.last_ten.includes(:avatars) 88 | @posts = Post.all_today 89 | end 90 | end 91 | 92 | # posts_controller.rb 93 | class PostsController < ApplicationController 94 | def create 95 | @post = Post.create(params[:post]) 96 | redirect_to(@post) 97 | end 98 | end 99 | 100 | # post.rb 101 | class Post < ActiveRecord::Base 102 | before_save :recalculate_costly_stats 103 | 104 | def slow_method 105 | # I fire gallzilion queries sleeping all around 106 | end 107 | 108 | private 109 | 110 | def recalculate_costly_stats 111 | # CPU heavy calculations 112 | end 113 | end 114 | ``` 115 | 116 | #### Controller Example 117 | 118 | Because performance tests are a special kind of integration test, you can use 119 | the `get` and `post` methods in them. 120 | 121 | Here's the performance test for `HomeController#dashboard` and 122 | `PostsController#create`: 123 | 124 | ```ruby 125 | require 'test_helper' 126 | require 'rails/performance_test_help' 127 | 128 | class PostPerformanceTest < ActionDispatch::PerformanceTest 129 | def setup 130 | # Application requires logged-in user 131 | login_as(:lifo) 132 | end 133 | 134 | test "homepage" do 135 | get '/dashboard' 136 | end 137 | 138 | test "creating new post" do 139 | post '/posts', post: { body: 'lifo is fooling you' } 140 | end 141 | end 142 | ``` 143 | 144 | You can find more details about the `get` and `post` methods in the 145 | [Testing Rails Applications](http://guides.rubyonrails.org/testing.html) guide. 146 | 147 | #### Model Example 148 | 149 | Even though the performance tests are integration tests and hence closer to 150 | the request/response cycle by nature, you can still performance test pure model 151 | code. 152 | 153 | Performance test for `Post` model: 154 | 155 | ```ruby 156 | require 'test_helper' 157 | require 'rails/performance_test_help' 158 | 159 | class PostModelTest < ActionDispatch::PerformanceTest 160 | test "creation" do 161 | Post.create body: 'still fooling you', cost: '100' 162 | end 163 | 164 | test "slow method" do 165 | # Using posts(:awesome) fixture 166 | posts(:awesome).slow_method 167 | end 168 | end 169 | ``` 170 | 171 | ### Modes 172 | 173 | Performance tests can be run in two modes: Benchmarking and Profiling. 174 | 175 | #### Benchmarking 176 | 177 | Benchmarking makes it easy to quickly gather a few metrics about each test run. 178 | By default, each test case is run **4 times** in benchmarking mode. 179 | 180 | To run performance tests in benchmarking mode: 181 | 182 | ```bash 183 | $ rake test:benchmark 184 | ``` 185 | 186 | To run a single test pass it as TEST: 187 | ```bash 188 | $ bin/rake test:benchmark TEST=test/performance/your_test.rb 189 | ``` 190 | 191 | #### Profiling 192 | 193 | Profiling allows you to make an in-depth analysis of each of your tests by using 194 | an external profiler. Depending on your Ruby interpreter, this profiler can be 195 | native (Rubinius, JRuby) or not (MRI, which uses RubyProf). By default, each 196 | test case is run **once** in profiling mode. 197 | 198 | To run performance tests in profiling mode: 199 | 200 | ```bash 201 | $ rake test:profile 202 | ``` 203 | 204 | ### Metrics 205 | 206 | Benchmarking and profiling run performance tests and give you multiple metrics. 207 | The availability of each metric is determined by the interpreter being used—none 208 | of them support all metrics—and by the mode in use. A brief description of each 209 | metric and their availability across interpreters/modes is given below. 210 | 211 | #### Wall Time 212 | 213 | Wall time measures the real world time elapsed during the test run. It is 214 | affected by any other processes concurrently running on the system. 215 | 216 | #### Process Time 217 | 218 | Process time measures the time taken by the process. It is unaffected by any 219 | other processes running concurrently on the same system. Hence, process time 220 | is likely to be constant for any given performance test, irrespective of the 221 | machine load. 222 | 223 | #### CPU Time 224 | 225 | Similar to process time, but leverages the more accurate CPU clock counter 226 | available on the Pentium and PowerPC platforms. 227 | 228 | #### User Time 229 | 230 | User time measures the amount of time the CPU spent in user-mode, i.e. within 231 | the process. This is not affected by other processes and by the time it possibly 232 | spends blocked. 233 | 234 | #### Memory 235 | 236 | Memory measures the amount of memory used for the performance test case. 237 | 238 | #### Objects 239 | 240 | Objects measures the number of objects allocated for the performance test case. 241 | 242 | #### GC Runs 243 | 244 | GC Runs measures the number of times GC was invoked for the performance test case. 245 | 246 | #### GC Time 247 | 248 | GC Time measures the amount of time spent in GC for the performance test case. 249 | 250 | #### Metric Availability 251 | 252 | ##### Benchmarking 253 | 254 | | Interpreter | Wall Time | Process Time | CPU Time | User Time | Memory | Objects | GC Runs | GC Time | 255 | | ------------ | --------- | ------------ | -------- | --------- | ------ | ------- | ------- | ------- | 256 | | **MRI** | yes | yes | yes | no | yes | yes | yes | yes | 257 | | **REE** | yes | yes | yes | no | yes | yes | yes | yes | 258 | | **Rubinius** | yes | no | no | no | yes | yes | yes | yes | 259 | | **JRuby** | yes | no | no | yes | yes | yes | yes | yes | 260 | 261 | ##### Profiling 262 | 263 | | Interpreter | Wall Time | Process Time | CPU Time | User Time | Memory | Objects | GC Runs | GC Time | 264 | | ------------ | --------- | ------------ | -------- | --------- | ------ | ------- | ------- | ------- | 265 | | **MRI** | yes | yes | no | no | yes | yes | yes | yes | 266 | | **REE** | yes | yes | no | no | yes | yes | yes | yes | 267 | | **Rubinius** | yes | no | no | no | no | no | no | no | 268 | | **JRuby** | yes | no | no | no | no | no | no | no | 269 | 270 | NOTE: To profile under JRuby you'll need to run `export JRUBY_OPTS="-Xlaunch.inproc=false --profile.api"` 271 | **before** the performance tests. 272 | 273 | ### Understanding the Output 274 | 275 | Performance tests generate different outputs inside `tmp/performance` directory 276 | depending on their mode and metric. 277 | 278 | #### Benchmarking 279 | 280 | In benchmarking mode, performance tests generate two types of outputs. 281 | 282 | ##### Command Line 283 | 284 | This is the primary form of output in benchmarking mode. Example: 285 | 286 | ```bash 287 | BrowsingTest#test_homepage (31 ms warmup) 288 | wall_time: 6 ms 289 | memory: 437.27 KB 290 | objects: 5,514 291 | gc_runs: 0 292 | gc_time: 19 ms 293 | ``` 294 | 295 | ##### CSV Files 296 | 297 | Performance test results are also appended to `.csv` files inside `tmp/performance`. 298 | For example, running the default `BrowsingTest#test_homepage` will generate 299 | following five files: 300 | 301 | * BrowsingTest#test_homepage_gc_runs.csv 302 | * BrowsingTest#test_homepage_gc_time.csv 303 | * BrowsingTest#test_homepage_memory.csv 304 | * BrowsingTest#test_homepage_objects.csv 305 | * BrowsingTest#test_homepage_wall_time.csv 306 | 307 | As the results are appended to these files each time the performance tests are 308 | run in benchmarking mode, you can collect data over a period of time. This can 309 | be very helpful in analyzing the effects of code changes. 310 | 311 | Sample output of `BrowsingTest#test_homepage_wall_time.csv`: 312 | 313 | ```bash 314 | measurement,created_at,app,rails,ruby,platform 315 | 0.00738224999999992,2009-01-08T03:40:29Z,,3.0.0,ruby-1.8.7.249,x86_64-linux 316 | 0.00755874999999984,2009-01-08T03:46:18Z,,3.0.0,ruby-1.8.7.249,x86_64-linux 317 | 0.00762099999999993,2009-01-08T03:49:25Z,,3.0.0,ruby-1.8.7.249,x86_64-linux 318 | 0.00603075000000008,2009-01-08T04:03:29Z,,3.0.0,ruby-1.8.7.249,x86_64-linux 319 | 0.00619899999999995,2009-01-08T04:03:53Z,,3.0.0,ruby-1.8.7.249,x86_64-linux 320 | 0.00755449999999991,2009-01-08T04:04:55Z,,3.0.0,ruby-1.8.7.249,x86_64-linux 321 | 0.00595999999999997,2009-01-08T04:05:06Z,,3.0.0,ruby-1.8.7.249,x86_64-linux 322 | 0.00740450000000004,2009-01-09T03:54:47Z,,3.0.0,ruby-1.8.7.249,x86_64-linux 323 | 0.00603150000000008,2009-01-09T03:54:57Z,,3.0.0,ruby-1.8.7.249,x86_64-linux 324 | 0.00771250000000012,2009-01-09T15:46:03Z,,3.0.0,ruby-1.8.7.249,x86_64-linux 325 | ``` 326 | 327 | #### Profiling 328 | 329 | In profiling mode, performance tests can generate multiple types of outputs. 330 | The command line output is always presented but support for the others is 331 | dependent on the interpreter in use. A brief description of each type and 332 | their availability across interpreters is given below. 333 | 334 | ##### Command Line 335 | 336 | This is a very basic form of output in profiling mode: 337 | 338 | ```bash 339 | BrowsingTest#test_homepage (58 ms warmup) 340 | process_time: 63 ms 341 | memory: 832.13 KB 342 | objects: 7,882 343 | ``` 344 | 345 | ##### Flat 346 | 347 | Flat output shows the metric—time, memory, etc—measure in each method. 348 | [Check Ruby-Prof documentation for a better explanation](http://ruby-prof.rubyforge.org/files/examples/flat_txt.html). 349 | 350 | ##### Graph 351 | 352 | Graph output shows the metric measure in each method, which methods call it and 353 | which methods it calls. [Check Ruby-Prof documentation for a better explanation](http://ruby-prof.rubyforge.org/files/examples/graph_txt.html). 354 | 355 | ##### Tree 356 | 357 | Tree output is profiling information in calltree format for use by [kcachegrind](http://kcachegrind.sourceforge.net/html/Home.html) 358 | and similar tools. 359 | 360 | ##### Output Availability 361 | 362 | | | Flat | Graph | Tree | 363 | | ------------ | ---- | ----- | ---- | 364 | | **MRI** | yes | yes | yes | 365 | | **REE** | yes | yes | yes | 366 | | **Rubinius** | yes | yes | no | 367 | | **JRuby** | yes | yes | no | 368 | 369 | ### Tuning Test Runs 370 | 371 | Test runs can be tuned by setting the `profile_options` class variable on your 372 | test class. 373 | 374 | ```ruby 375 | require 'test_helper' 376 | require 'rails/performance_test_help' 377 | 378 | class BrowsingTest < ActionDispatch::PerformanceTest 379 | self.profile_options = { runs: 5, metrics: [:wall_time, :memory] } 380 | 381 | test "homepage" 382 | get '/' 383 | end 384 | end 385 | ``` 386 | 387 | In this example, the test would run 5 times and measure wall time and memory. 388 | There are a few configurable options: 389 | 390 | | Option | Description | Default | Mode | 391 | | ---------- | ------------------------------------------ | ----------------------------- | --------- | 392 | | `:runs` | Number of runs. | Benchmarking: 4, Profiling: 1 | Both | 393 | | `:output` | Directory to use when writing the results. | `tmp/performance` | Both | 394 | | `:metrics` | Metrics to use. | See below. | Both | 395 | | `:formats` | Formats to output to. | See below. | Profiling | 396 | 397 | Metrics and formats have different defaults depending on the interpreter in use. 398 | 399 | | Interpreter | Mode | Default metrics | Default formats | 400 | | -------------- | ------------ | ------------------------------------------------------- | ----------------------------------------------- | 401 | | **MRI/REE** | Benchmarking | `[:wall_time, :memory, :objects, :gc_runs, :gc_time]` | N/A | 402 | | | Profiling | `[:process_time, :memory, :objects]` | `[:flat, :graph_html, :call_tree, :call_stack]` | 403 | | **Rubinius** | Benchmarking | `[:wall_time, :memory, :objects, :gc_runs, :gc_time]` | N/A | 404 | | | Profiling | `[:wall_time]` | `[:flat, :graph]` | 405 | | **JRuby** | Benchmarking | `[:wall_time, :user_time, :memory, :gc_runs, :gc_time]` | N/A | 406 | | | Profiling | `[:wall_time]` | `[:flat, :graph]` | 407 | 408 | As you've probably noticed by now, metrics and formats are specified using a 409 | symbol array with each name [underscored.](http://api.rubyonrails.org/classes/String.html#method-i-underscore) 410 | 411 | ### Performance Test Environment 412 | 413 | Performance tests are run in the `test` environment. But running performance 414 | tests will set the following configuration parameters: 415 | 416 | ```bash 417 | ActionController::Base.perform_caching = true 418 | ActiveSupport::Dependencies.mechanism = :require if ActiveSupport::Dependencies.respond_to?(:mechanism=) 419 | Rails.logger.level = ActiveSupport::Logger::INFO 420 | ``` 421 | 422 | As `ActionController::Base.perform_caching` is set to `true`, performance tests 423 | will behave much as they do in the `production` environment. 424 | 425 | ### Installing GC-Patched MRI 1.x.x 426 | 427 | Since Ruby 2 is now mainstream and handles garbage collection issues these docs have been cut. [View older readme explaining how to install optimized Ruby 1 builds](https://github.com/rails/rails-perftest/blob/2d4bbd6206f86a3bbe38ed825dddbf555ffdce41/README.md#installing-gc-patched-mri). 428 | 429 | Command Line Tools 430 | ------------------ 431 | 432 | Writing performance test cases could be an overkill when you are looking for one 433 | time tests. Rails ships with two command line tools that enable quick and dirty 434 | performance testing: 435 | 436 | ### `benchmarker` 437 | 438 | Usage: 439 | 440 | ```bash 441 | Usage: perftest benchmarker 'Ruby.code' 'Ruby.more_code' ... [OPTS] 442 | -r, --runs N Number of runs. 443 | Default: 4 444 | -o, --output PATH Directory to use when writing the results. 445 | Default: tmp/performance 446 | -m, --metrics a,b,c Metrics to use. 447 | Default: wall_time,memory,objects,gc_runs,gc_time 448 | ``` 449 | 450 | Example: 451 | 452 | ```bash 453 | $ perftest benchmarker 'Item.all' 'CouchItem.all' --runs 3 --metrics wall_time,memory 454 | ``` 455 | 456 | ### `profiler` 457 | 458 | Usage: 459 | 460 | ```bash 461 | Usage: perftest profiler 'Ruby.code' 'Ruby.more_code' ... [OPTS] 462 | -r, --runs N Number of runs. 463 | Default: 1 464 | -o, --output PATH Directory to use when writing the results. 465 | Default: tmp/performance 466 | -m, --metrics a,b,c Metrics to use. 467 | Default: process_time,memory,objects 468 | -f, --formats x,y,z Formats to output to. 469 | Default: flat,graph_html,call_tree 470 | ``` 471 | 472 | Example: 473 | 474 | ```bash 475 | $ perftest profiler 'Item.all' 'CouchItem.all' --runs 2 --metrics process_time --formats flat 476 | ``` 477 | 478 | NOTE: Metrics and formats vary from interpreter to interpreter. Pass `--help` to 479 | each tool to see the defaults for your interpreter. 480 | 481 | Helper Methods 482 | -------------- 483 | 484 | Rails provides various helper methods inside Active Record, Action Controller 485 | and Action View to measure the time taken by a given piece of code. The method 486 | is called `benchmark()` in all the three components. 487 | 488 | ### Model 489 | 490 | ```ruby 491 | Project.benchmark("Creating project") do 492 | project = Project.create("name" => "stuff") 493 | project.create_manager("name" => "David") 494 | project.milestones << Milestone.all 495 | end 496 | ``` 497 | 498 | This benchmarks the code enclosed in the `Project.benchmark("Creating project") do...end` 499 | block and prints the result to the log file: 500 | 501 | ```ruby 502 | Creating project (185.3ms) 503 | ``` 504 | 505 | Please refer to the [API docs](http://api.rubyonrails.org/classes/ActiveSupport/Benchmarkable.html#method-i-benchmark) 506 | for additional options to `benchmark()`. 507 | 508 | ### Controller 509 | 510 | Similarly, you could use this helper method inside [controllers.](http://api.rubyonrails.org/classes/ActiveSupport/Benchmarkable.html) 511 | 512 | ```ruby 513 | def process_projects 514 | benchmark("Processing projects") do 515 | Project.process(params[:project_ids]) 516 | Project.update_cached_projects 517 | end 518 | end 519 | ``` 520 | 521 | NOTE: `benchmark` is a class method inside controllers. 522 | 523 | ### View 524 | 525 | And in [views](http://api.rubyonrails.org/classes/ActiveSupport/Benchmarkable.html) 526 | 527 | ```erb 528 | <% benchmark("Showing projects partial") do %> 529 | <%= render @projects %> 530 | <% end %> 531 | ``` 532 | 533 | Request Logging 534 | --------------- 535 | 536 | Rails log files contain very useful information about the time taken to serve 537 | each request. Here's a typical log file entry: 538 | 539 | ```bash 540 | Processing ItemsController#index (for 127.0.0.1 at 2009-01-08 03:06:39) [GET] 541 | Rendering template within layouts/items 542 | Rendering items/index 543 | Completed in 5ms (View: 2, DB: 0) | 200 OK [http://0.0.0.0/items] 544 | ``` 545 | 546 | For this section, we're only interested in the last line: 547 | 548 | ```bash 549 | Completed in 5ms (View: 2, DB: 0) | 200 OK [http://0.0.0.0/items] 550 | ``` 551 | 552 | This data is fairly straightforward to understand. Rails uses millisecond(ms) as 553 | the metric to measure the time taken. The complete request spent 5 ms inside 554 | Rails, out of which 2 ms were spent rendering views and none was spent 555 | communication with the database. It's safe to assume that the remaining 3 ms 556 | were spent inside the controller. 557 | 558 | Useful Links 559 | ------------ 560 | 561 | ### Rails Plugins and Gems 562 | 563 | * [Rails Analyzer](http://rails-analyzer.rubyforge.org) 564 | * [Rails Footnotes](https://github.com/josevalim/rails-footnotes/tree/master) 565 | * [Query Reviewer](https://github.com/nesquena/query_reviewer) 566 | * [MiniProfiler](http://www.miniprofiler.com) 567 | 568 | ### Generic Tools 569 | 570 | * [httperf](http://www.hpl.hp.com/research/linux/httperf/) 571 | * [ab](http://httpd.apache.org/docs/2.2/programs/ab.html) 572 | * [JMeter](http://jakarta.apache.org/jmeter/) 573 | * [kcachegrind](http://kcachegrind.sourceforge.net/html/Home.html) 574 | 575 | ### Tutorials and Documentation 576 | 577 | * [ruby-prof API Documentation](http://ruby-prof.rubyforge.org) 578 | * [Request Profiling Railscast](http://railscasts.com/episodes/98-request-profiling) - Outdated, but useful for understanding call graphs. 579 | 580 | Commercial Products 581 | ------------------- 582 | 583 | Rails has been lucky to have a few companies dedicated to Rails-specific 584 | performance tools: 585 | 586 | * [New Relic](https://www.newrelic.com) 587 | * [Scout](https://scoutapp.com) 588 | * [Skylight](https://www.skylight.io) 589 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | require "bundler/gem_tasks" 3 | 4 | require 'rake/testtask' 5 | 6 | namespace :test do 7 | Rake::TestTask.new("regular") do |t| 8 | t.libs = ["test"] 9 | t.pattern = "test/*_test.rb" 10 | t.ruby_opts = ['-w'] 11 | end 12 | 13 | Rake::TestTask.new("generators") do |t| 14 | t.libs = ["test"] 15 | t.pattern = "test/generators/*_test.rb" 16 | t.ruby_opts = ['-w'] 17 | end 18 | end 19 | 20 | task :test => ['test:regular', 'test:generators'] 21 | task :default => :test 22 | 23 | specname = "rails-perftest.gemspec" 24 | deps = `git ls-files`.split("\n") - [specname] 25 | 26 | file specname => deps do 27 | files = `git ls-files`.split("\n") 28 | test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 29 | executables = `git ls-files -- exe/*`.split("\n").map{ |f| File.basename(f) } 30 | 31 | require 'erb' 32 | 33 | File.open specname, 'w:utf-8' do |f| 34 | f.write ERB.new(File.read("#{specname}.erb")).result(binding) 35 | end 36 | end 37 | task :build => specname 38 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'rake' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('rake', 'rake') 17 | -------------------------------------------------------------------------------- /exe/perftest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | current_dir = Dir.pwd 4 | rails_root = current_dir 5 | 6 | APP_PATH = File.expand_path('config/application', rails_root) 7 | require File.expand_path('config/boot', rails_root) 8 | 9 | require 'rails/perftest/commands' 10 | -------------------------------------------------------------------------------- /gemfiles/Gemfile-rails-5.2: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gemspec path: '..' 3 | 4 | gem 'rails', '~> 5.2.0' 5 | -------------------------------------------------------------------------------- /gemfiles/Gemfile-rails-6.0: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gemspec path: '..' 3 | 4 | gem 'rails', '~> 6.0.0' 5 | -------------------------------------------------------------------------------- /gemfiles/Gemfile-rails-6.1: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gemspec path: '..' 3 | 4 | gem 'rails', '~> 6.1.0' 5 | -------------------------------------------------------------------------------- /gemfiles/Gemfile-rails-7.0: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gemspec path: '..' 3 | 4 | gem 'rails', '~> 7.0.0' 5 | -------------------------------------------------------------------------------- /lib/generators/rails/app/templates/test/performance/browsing_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'rails/performance_test_help' 3 | 4 | class BrowsingTest < ActionDispatch::PerformanceTest 5 | # Refer to the documentation for all available options 6 | # self.profile_options = { runs: 5, metrics: [:wall_time, :memory], 7 | # output: 'tmp/performance', formats: [:flat] } 8 | 9 | test "homepage" do 10 | get '/' 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/generators/rails/performance_test/USAGE: -------------------------------------------------------------------------------- 1 | Description: 2 | Stubs out a new performance test. Pass the name of the test, either 3 | CamelCased or under_scored, as an argument. 4 | 5 | This generator invokes the current performance tool, which defaults to 6 | TestUnit. 7 | 8 | Example: 9 | `rails generate performance_test GeneralStories` creates a GeneralStories 10 | performance test in test/performance/general_stories_test.rb 11 | -------------------------------------------------------------------------------- /lib/generators/rails/performance_test/performance_test_generator.rb: -------------------------------------------------------------------------------- 1 | module Rails 2 | module Generators 3 | class PerformanceTestGenerator < NamedBase # :nodoc: 4 | check_class_collision suffix: "Test" 5 | 6 | source_root File.expand_path("../templates", __FILE__) 7 | 8 | def create_performance_test_file 9 | template 'performance_test.rb', File.join('test/performance', class_path, "#{file_name}_test.rb") 10 | end 11 | 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/generators/rails/performance_test/templates/performance_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'rails/performance_test_help' 3 | 4 | class <%= class_name %>Test < ActionDispatch::PerformanceTest 5 | # Refer to the documentation for all available options 6 | # self.profile_options = { runs: 5, metrics: [:wall_time, :memory], 7 | # output: 'tmp/performance', formats: [:flat] } 8 | 9 | test "homepage" do 10 | get '/' 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/rails-perftest.rb: -------------------------------------------------------------------------------- 1 | require 'rails/perftest/railtie' if defined? Rails 2 | require "rails/perftest/version" 3 | -------------------------------------------------------------------------------- /lib/rails/performance_test_help.rb: -------------------------------------------------------------------------------- 1 | ActionController::Base.perform_caching = true 2 | ActiveSupport::Dependencies.mechanism = :require if ActiveSupport::Dependencies.respond_to?(:mechanism=) 3 | Rails.logger.level = ActiveSupport::Logger::INFO 4 | -------------------------------------------------------------------------------- /lib/rails/perftest/action_controller.rb: -------------------------------------------------------------------------------- 1 | module ActionController 2 | autoload :PerformanceTest, 'rails/perftest/action_controller/performance_test' 3 | end 4 | -------------------------------------------------------------------------------- /lib/rails/perftest/action_controller/performance_test.rb: -------------------------------------------------------------------------------- 1 | ActionController::PerformanceTest = ActionDispatch::PerformanceTest 2 | 3 | ActiveSupport::Deprecation.warn 'ActionController::PerformanceTest is deprecated and will be removed, use ActionDispatch::PerformanceTest instead.' 4 | -------------------------------------------------------------------------------- /lib/rails/perftest/action_dispatch.rb: -------------------------------------------------------------------------------- 1 | module ActionDispatch 2 | autoload :PerformanceTest, 'rails/perftest/action_dispatch/performance_test' 3 | end 4 | -------------------------------------------------------------------------------- /lib/rails/perftest/action_dispatch/performance_test.rb: -------------------------------------------------------------------------------- 1 | require 'rails/perftest/active_support/testing/performance' 2 | 3 | module ActionDispatch 4 | # An integration test that runs a code profiler on your test methods. 5 | # Profiling output for combinations of each test method, measurement, and 6 | # output format are written to your tmp/performance directory. 7 | class PerformanceTest < ActionDispatch::IntegrationTest 8 | include ActiveSupport::Testing::Performance 9 | 10 | minitest_version = Gem.loaded_specs["minitest"].version 11 | 12 | if minitest_version < Gem::Version.create('5.0.0') 13 | include Minitest4AndLower 14 | elsif minitest_version < Gem::Version.create('5.11.0') 15 | include Minitest5 16 | else 17 | include Minitest511AndGreater 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/rails/perftest/active_support/testing/performance.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | require 'active_support/concern' 3 | require 'active_support/core_ext/class/attribute' 4 | require 'active_support/core_ext/string/inflections' 5 | require 'active_support/core_ext/module/delegation' 6 | require 'active_support/number_helper' 7 | 8 | module ActiveSupport 9 | module Testing 10 | module Performance 11 | extend ActiveSupport::Concern 12 | 13 | included do 14 | class_attribute :profile_options 15 | self.profile_options = {} 16 | end 17 | 18 | # each implementation should define metrics and freeze the defaults 19 | DEFAULTS = 20 | if ENV["BENCHMARK_TESTS"] 21 | { :runs => 4, 22 | :output => 'tmp/performance', 23 | :benchmark => true } 24 | else 25 | { :runs => 1, 26 | :output => 'tmp/performance', 27 | :benchmark => false } 28 | end 29 | 30 | def full_profile_options 31 | DEFAULTS.merge(profile_options) 32 | end 33 | 34 | def full_test_name 35 | "#{self.class.name}##{method_name}" 36 | end 37 | 38 | module Minitest4AndLower 39 | def run(runner) 40 | @runner = runner 41 | _performance_run 42 | return 43 | end 44 | 45 | def performance_failure(e) 46 | @runner.puke(self.class, method_name, e) 47 | end 48 | end 49 | 50 | module Minitest5 51 | def run 52 | _performance_run 53 | self 54 | end 55 | 56 | def performance_failure(e) 57 | _performance_failure(e) 58 | end 59 | end 60 | 61 | module Minitest511AndGreater 62 | def run 63 | _performance_run 64 | ::Minitest::Result.from self 65 | end 66 | 67 | def performance_failure(e) 68 | _performance_failure(e) 69 | end 70 | end 71 | 72 | def _performance_failure(e) 73 | case e 74 | when Minitest::Assertion 75 | self.failures << e 76 | else 77 | self.failures << Minitest::UnexpectedError.new(e) 78 | end 79 | end 80 | 81 | def _performance_run 82 | run_warmup 83 | if full_profile_options && metrics = full_profile_options[:metrics] 84 | metrics.each do |metric_name| 85 | if klass = Metrics[metric_name.to_sym] 86 | run_profile(klass.new) 87 | end 88 | end 89 | end 90 | end 91 | 92 | def run_test(metric, mode) 93 | result = '.' 94 | begin 95 | before_setup 96 | setup 97 | after_setup 98 | metric.send(mode) { __send__ method_name } 99 | rescue Exception => e 100 | result = performance_failure(e) 101 | ensure 102 | begin 103 | before_teardown 104 | teardown 105 | after_teardown 106 | rescue Exception => e 107 | result = performance_failure(e) 108 | end 109 | end 110 | result 111 | end 112 | 113 | protected 114 | # overridden by each implementation. 115 | def run_gc; end 116 | 117 | def run_warmup 118 | run_gc 119 | 120 | time = Metrics::Time.new 121 | run_test(time, :benchmark) 122 | puts "%s (%s warmup)" % [full_test_name, time.format(time.total)] 123 | 124 | run_gc 125 | end 126 | 127 | def run_profile(metric) 128 | klass = full_profile_options[:benchmark] ? Benchmarker : Profiler 129 | performer = klass.new(self, metric) 130 | 131 | performer.run 132 | puts performer.report 133 | performer.record 134 | end 135 | 136 | class Performer 137 | delegate :run_test, :full_profile_options, :full_test_name, :to => :@harness 138 | 139 | def initialize(harness, metric) 140 | @harness, @metric, @supported = harness, metric, false 141 | end 142 | 143 | def report 144 | if @supported 145 | rate = @total / full_profile_options[:runs] 146 | '%20s: %s' % [@metric.name, @metric.format(rate)] 147 | else 148 | '%20s: unsupported' % @metric.name 149 | end 150 | end 151 | 152 | protected 153 | def output_filename 154 | "#{full_profile_options[:output]}/#{full_test_name}_#{@metric.name}" 155 | end 156 | end 157 | 158 | # overridden by each implementation. 159 | class Profiler < Performer 160 | def time_with_block 161 | before = Time.now 162 | yield 163 | Time.now - before 164 | end 165 | 166 | def run; end 167 | def record; end 168 | end 169 | 170 | class Benchmarker < Performer 171 | def initialize(*args) 172 | super 173 | @supported = @metric.respond_to?('measure') 174 | end 175 | 176 | def run 177 | return unless @supported 178 | 179 | full_profile_options[:runs].to_i.times { run_test(@metric, :benchmark) } 180 | @total = @metric.total 181 | end 182 | 183 | def record 184 | avg = @metric.total / full_profile_options[:runs].to_i 185 | now = Time.now.utc.xmlschema 186 | with_output_file do |file| 187 | file.puts "#{avg},#{now},#{environment}" 188 | end 189 | end 190 | 191 | def environment 192 | @env ||= [].tap do |env| 193 | env << "#{$1}.#{$2}" if File.directory?('.git') && `git branch -v` =~ /^\* (\S+)\s+(\S+)/ 194 | env << rails_version if defined?(Rails::VERSION::STRING) 195 | env << "#{RUBY_ENGINE}-#{RUBY_VERSION}.#{RUBY_PATCHLEVEL}" 196 | env << RUBY_PLATFORM 197 | end.join(',') 198 | end 199 | 200 | protected 201 | if defined?(Rails::VERSION::STRING) 202 | HEADER = 'measurement,created_at,app,rails,ruby,platform' 203 | else 204 | HEADER = 'measurement,created_at,app,ruby,platform' 205 | end 206 | 207 | def with_output_file 208 | fname = output_filename 209 | 210 | if new = !File.exist?(fname) 211 | FileUtils.mkdir_p(File.dirname(fname)) 212 | end 213 | 214 | File.open(fname, 'ab') do |file| 215 | file.puts(HEADER) if new 216 | yield file 217 | end 218 | end 219 | 220 | def output_filename 221 | "#{super}.csv" 222 | end 223 | 224 | def rails_version 225 | "rails-#{Rails::VERSION::STRING}#{rails_branch}" 226 | end 227 | 228 | def rails_branch 229 | if File.directory?('vendor/rails/.git') 230 | Dir.chdir('vendor/rails') do 231 | ".#{$1}.#{$2}" if `git branch -v` =~ /^\* (\S+)\s+(\S+)/ 232 | end 233 | end 234 | end 235 | end 236 | 237 | module Metrics 238 | def self.[](name) 239 | const_get(name.to_s.camelize) 240 | rescue NameError 241 | nil 242 | end 243 | 244 | class Base 245 | include ActiveSupport::NumberHelper 246 | 247 | attr_reader :total 248 | 249 | def initialize 250 | @total = 0 251 | end 252 | 253 | def name 254 | @name ||= self.class.name.demodulize.underscore 255 | end 256 | 257 | def benchmark 258 | with_gc_stats do 259 | before = measure 260 | yield 261 | @total += (measure - before) 262 | end 263 | end 264 | 265 | # overridden by each implementation. 266 | def profile; end 267 | 268 | protected 269 | # overridden by each implementation. 270 | def with_gc_stats; end 271 | end 272 | 273 | class Time < Base 274 | def measure 275 | ::Time.now.to_f 276 | end 277 | 278 | def format(measurement) 279 | if measurement < 1 280 | '%d ms' % (measurement * 1000) 281 | else 282 | '%.2f sec' % measurement 283 | end 284 | end 285 | end 286 | 287 | class Amount < Base 288 | def format(measurement) 289 | number_to_delimited(measurement.floor) 290 | end 291 | end 292 | 293 | class DigitalInformationUnit < Base 294 | def format(measurement) 295 | number_to_human_size(measurement, :precision => 2) 296 | end 297 | end 298 | 299 | # each implementation provides its own metrics like ProcessTime, Memory or GcRuns 300 | end 301 | end 302 | end 303 | end 304 | 305 | case RUBY_ENGINE 306 | when 'ruby' then require 'rails/perftest/active_support/testing/performance/ruby' 307 | when 'rbx' then require 'rails/perftest/active_support/testing/performance/rubinius' 308 | when 'jruby' then require 'rails/perftest/active_support/testing/performance/jruby' 309 | else 310 | $stderr.puts 'Your ruby interpreter is not supported for benchmarking.' 311 | exit 312 | end 313 | -------------------------------------------------------------------------------- /lib/rails/perftest/active_support/testing/performance/jruby.rb: -------------------------------------------------------------------------------- 1 | require 'jruby/profiler' 2 | require 'java' 3 | java_import java.lang.management.ManagementFactory 4 | 5 | module ActiveSupport 6 | module Testing 7 | module Performance 8 | DEFAULTS.merge!( 9 | if ENV["BENCHMARK_TESTS"] 10 | {:metrics => [:wall_time, :user_time, :memory, :gc_runs, :gc_time]} 11 | else 12 | { :metrics => [:wall_time], 13 | :formats => [:flat, :graph] } 14 | end).freeze 15 | 16 | protected 17 | def run_gc 18 | ManagementFactory.memory_mx_bean.gc 19 | end 20 | 21 | class Profiler < Performer 22 | def initialize(*args) 23 | super 24 | @supported = @metric.is_a?(Metrics::WallTime) 25 | end 26 | 27 | def run 28 | return unless @supported 29 | 30 | @total = time_with_block do 31 | @data = JRuby::Profiler.profile do 32 | full_profile_options[:runs].to_i.times { run_test(@metric, :profile) } 33 | end 34 | end 35 | end 36 | 37 | def record 38 | return unless @supported 39 | 40 | klasses = full_profile_options[:formats].map { |f| JRuby::Profiler.const_get("#{f.to_s.camelize}ProfilePrinter") }.compact 41 | 42 | klasses.each do |klass| 43 | fname = output_filename(klass) 44 | FileUtils.mkdir_p(File.dirname(fname)) 45 | File.open(fname, 'wb') do |file| 46 | klass.new(@data).printProfile(file) 47 | end 48 | end 49 | end 50 | 51 | protected 52 | def output_filename(printer_class) 53 | suffix = 54 | case printer_class.name.demodulize 55 | when 'FlatProfilePrinter'; 'flat.txt' 56 | when 'GraphProfilePrinter'; 'graph.txt' 57 | else printer_class.name.sub(/ProfilePrinter$/, '').underscore 58 | end 59 | 60 | "#{super()}_#{suffix}" 61 | end 62 | end 63 | 64 | module Metrics 65 | class Base 66 | def profile 67 | yield 68 | end 69 | 70 | protected 71 | def with_gc_stats 72 | ManagementFactory.memory_mx_bean.gc 73 | yield 74 | end 75 | end 76 | 77 | class WallTime < Time 78 | def measure 79 | super 80 | end 81 | end 82 | 83 | class CpuTime < Time 84 | def measure 85 | ManagementFactory.thread_mx_bean.get_current_thread_cpu_time / 1000 / 1000 / 1000.0 # seconds 86 | end 87 | end 88 | 89 | class UserTime < Time 90 | def measure 91 | ManagementFactory.thread_mx_bean.get_current_thread_user_time / 1000 / 1000 / 1000.0 # seconds 92 | end 93 | end 94 | 95 | class Memory < DigitalInformationUnit 96 | def measure 97 | ManagementFactory.memory_mx_bean.non_heap_memory_usage.used + ManagementFactory.memory_mx_bean.heap_memory_usage.used 98 | end 99 | end 100 | 101 | class GcRuns < Amount 102 | def measure 103 | ManagementFactory.garbage_collector_mx_beans.inject(0) { |total_runs, current_gc| total_runs += current_gc.collection_count } 104 | end 105 | end 106 | 107 | class GcTime < Time 108 | def measure 109 | ManagementFactory.garbage_collector_mx_beans.inject(0) { |total_time, current_gc| total_time += current_gc.collection_time } / 1000.0 # seconds 110 | end 111 | end 112 | end 113 | end 114 | end 115 | end 116 | -------------------------------------------------------------------------------- /lib/rails/perftest/active_support/testing/performance/rubinius.rb: -------------------------------------------------------------------------------- 1 | require 'rubinius/agent' 2 | 3 | module ActiveSupport 4 | module Testing 5 | module Performance 6 | DEFAULTS.merge!( 7 | if ENV["BENCHMARK_TESTS"] 8 | {:metrics => [:wall_time, :memory, :objects, :gc_runs, :gc_time]} 9 | else 10 | { :metrics => [:wall_time], 11 | :formats => [:flat, :graph] } 12 | end).freeze 13 | 14 | protected 15 | def run_gc 16 | GC.run(true) 17 | end 18 | 19 | class Performer; end 20 | 21 | class Profiler < Performer 22 | def initialize(*args) 23 | super 24 | @supported = @metric.is_a?(Metrics::WallTime) 25 | end 26 | 27 | def run 28 | return unless @supported 29 | 30 | @profiler = Rubinius::Profiler::Instrumenter.new 31 | 32 | @total = time_with_block do 33 | @profiler.profile(false) do 34 | full_profile_options[:runs].to_i.times { run_test(@metric, :profile) } 35 | end 36 | end 37 | end 38 | 39 | def record 40 | return unless @supported 41 | 42 | if(full_profile_options[:formats].include?(:flat)) 43 | create_path_and_open_file(:flat) do |file| 44 | @profiler.show(file) 45 | end 46 | end 47 | 48 | if(full_profile_options[:formats].include?(:graph)) 49 | create_path_and_open_file(:graph) do |file| 50 | @profiler.show(file) 51 | end 52 | end 53 | end 54 | 55 | protected 56 | def create_path_and_open_file(printer_name) 57 | fname = "#{output_filename}_#{printer_name}.txt" 58 | FileUtils.mkdir_p(File.dirname(fname)) 59 | File.open(fname, 'wb') do |file| 60 | yield(file) 61 | end 62 | end 63 | end 64 | 65 | module Metrics 66 | class Base 67 | attr_reader :loopback 68 | 69 | def profile 70 | yield 71 | end 72 | 73 | protected 74 | def with_gc_stats 75 | @loopback = Rubinius::Agent.loopback 76 | GC.run(true) 77 | yield 78 | end 79 | end 80 | 81 | class WallTime < Time 82 | def measure 83 | super 84 | end 85 | end 86 | 87 | class Memory < DigitalInformationUnit 88 | def measure 89 | loopback.get("system.memory.counter.bytes").last 90 | end 91 | end 92 | 93 | class Objects < Amount 94 | def measure 95 | loopback.get("system.memory.counter.objects").last 96 | end 97 | end 98 | 99 | class GcRuns < Amount 100 | def measure 101 | loopback.get("system.gc.full.count").last + loopback.get("system.gc.young.count").last 102 | end 103 | end 104 | 105 | class GcTime < Time 106 | def measure 107 | (loopback.get("system.gc.full.wallclock").last + loopback.get("system.gc.young.wallclock").last) / 1000.0 108 | end 109 | end 110 | end 111 | end 112 | end 113 | end 114 | -------------------------------------------------------------------------------- /lib/rails/perftest/active_support/testing/performance/ruby.rb: -------------------------------------------------------------------------------- 1 | begin 2 | require 'ruby-prof' 3 | rescue LoadError 4 | $stderr.puts 'Specify ruby-prof as application\'s dependency in Gemfile to run benchmarks.' 5 | raise 6 | end 7 | 8 | module ActiveSupport 9 | module Testing 10 | module Performance 11 | DEFAULTS.merge!( 12 | if ENV["BENCHMARK_TESTS"] 13 | { :metrics => [:wall_time, :memory, :objects, :gc_runs, :gc_time] } 14 | else 15 | { :min_percent => 0.01, 16 | :metrics => [:process_time, :memory, :objects], 17 | :formats => [:flat, :graph_html, :call_tree, :call_stack] } 18 | end).freeze 19 | 20 | protected 21 | remove_method :run_gc 22 | def run_gc 23 | GC.start 24 | end 25 | 26 | class Profiler < Performer 27 | def initialize(*args) 28 | super 29 | @supported = @metric.measure_mode rescue false 30 | end 31 | 32 | remove_method :run 33 | def run 34 | return unless @supported 35 | 36 | RubyProf.measure_mode = @metric.measure_mode 37 | RubyProf.start 38 | RubyProf.pause 39 | full_profile_options[:runs].to_i.times { run_test(@metric, :profile) } 40 | @data = RubyProf.stop 41 | @total = @data.threads.sum(0) { |thread| thread.methods.max.total_time } 42 | end 43 | 44 | remove_method :record 45 | def record 46 | return unless @supported 47 | 48 | klasses = full_profile_options[:formats].map { |f| RubyProf.const_get("#{f.to_s.camelize}Printer") }.compact 49 | 50 | klasses.each do |klass| 51 | fname = output_filename(klass) 52 | printer = klass.new(@data) 53 | options = full_profile_options.slice(:min_percent) 54 | path = File.dirname(fname) 55 | FileUtils.mkdir_p(path) 56 | if printer.is_a?(RubyProf::CallTreePrinter) && RubyProf::VERSION >= "0.16" 57 | printer.print(options.merge(:path => path)) 58 | call_grind_file = Dir.glob("#{path}/*callgrind.out*").first 59 | File.rename(call_grind_file, fname) 60 | else 61 | File.open(fname, 'wb') do |file| 62 | printer.print(file, options) 63 | end 64 | end 65 | end 66 | end 67 | 68 | protected 69 | def output_filename(printer_class) 70 | suffix = 71 | case printer_class.name.demodulize 72 | when 'FlatPrinter'; 'flat.txt' 73 | when 'FlatPrinterWithLineNumbers'; 'flat_line_numbers.txt' 74 | when 'GraphPrinter'; 'graph.txt' 75 | when 'GraphHtmlPrinter'; 'graph.html' 76 | when 'GraphYamlPrinter'; 'graph.yml' 77 | when 'CallTreePrinter'; 'tree.txt' 78 | when 'CallStackPrinter'; 'stack.html' 79 | when 'DotPrinter'; 'graph.dot' 80 | else printer_class.name.sub(/Printer$/, '').underscore 81 | end 82 | 83 | "#{super()}_#{suffix}" 84 | end 85 | end 86 | 87 | module Metrics 88 | class Base 89 | def measure_mode 90 | self.class::Mode 91 | end 92 | 93 | remove_method :profile 94 | def profile 95 | RubyProf.resume 96 | yield 97 | ensure 98 | RubyProf.pause 99 | end 100 | 101 | protected 102 | remove_method :with_gc_stats 103 | def with_gc_stats 104 | GC::Profiler.enable 105 | GC.start 106 | yield 107 | ensure 108 | GC::Profiler.disable 109 | end 110 | end 111 | 112 | class ProcessTime < Time 113 | Mode = RubyProf::PROCESS_TIME if RubyProf.const_defined?(:PROCESS_TIME) 114 | 115 | def measure 116 | RubyProf.measure_mode = Mode 117 | super 118 | end 119 | end 120 | 121 | class WallTime < Time 122 | Mode = RubyProf::WALL_TIME if RubyProf.const_defined?(:WALL_TIME) 123 | 124 | def measure 125 | RubyProf.measure_mode = Mode 126 | super 127 | end 128 | end 129 | 130 | class CpuTime < Time 131 | # from ruby-prof changelog: 'Remove the CPU_TIME measurement mode since it duplicates the PROCESS_TIME mode and required inline assembly code' 132 | Mode = RubyProf::PROCESS_TIME if RubyProf.const_defined?(:PROCESS_TIME) 133 | 134 | def measure 135 | RubyProf.measure_mode = Mode 136 | super 137 | end 138 | end 139 | 140 | class Memory < DigitalInformationUnit 141 | Mode = RubyProf::MEMORY if RubyProf.const_defined?(:MEMORY) 142 | 143 | # Ruby 1.9 + GCdata patch 144 | if GC.respond_to?(:malloc_allocated_size) 145 | def measure 146 | RubyProf.measure_mode = Mode 147 | GC.malloc_allocated_size 148 | end 149 | end 150 | end 151 | 152 | class Objects < Amount 153 | Mode = RubyProf::ALLOCATIONS if RubyProf.const_defined?(:ALLOCATIONS) 154 | 155 | # Ruby 1.9 + GCdata patch 156 | if GC.respond_to?(:malloc_allocations) 157 | def measure 158 | GC.malloc_allocations 159 | end 160 | end 161 | end 162 | 163 | class GcRuns < Amount 164 | Mode = RubyProf::GC_RUNS if RubyProf.const_defined?(:GC_RUNS) 165 | 166 | def measure 167 | GC.count 168 | end 169 | end 170 | 171 | class GcTime < Time 172 | Mode = RubyProf::GC_TIME if RubyProf.const_defined?(:GC_TIME) 173 | 174 | def measure 175 | GC::Profiler.total_time 176 | end 177 | end 178 | end 179 | end 180 | end 181 | end 182 | -------------------------------------------------------------------------------- /lib/rails/perftest/commands.rb: -------------------------------------------------------------------------------- 1 | ARGV << '--help' if ARGV.empty? 2 | 3 | help_message = <<-EOT 4 | Usage: rperf COMMAND [ARGS] 5 | 6 | benchmarker See how fast a piece of code runs 7 | profiler Get profile information from a piece of code 8 | 9 | All commands can be run with -h (or --help) for more information. 10 | EOT 11 | 12 | command = ARGV.shift 13 | 14 | case command 15 | 16 | when 'benchmarker', 'profiler' 17 | require APP_PATH 18 | Rails.application.require_environment! 19 | require "rails/perftest/commands/#{command}" 20 | 21 | when '-h', '--help' 22 | puts help_message 23 | else 24 | puts "Error: Command '#{command}' not recognized" 25 | if %x{rake #{command} --dry-run 2>&1 } && $?.success? 26 | puts "Did you mean: `$ rake #{command}` ?\n\n" 27 | end 28 | puts help_message 29 | exit(1) 30 | end 31 | -------------------------------------------------------------------------------- /lib/rails/perftest/commands/benchmarker.rb: -------------------------------------------------------------------------------- 1 | require 'optparse' 2 | require 'rails/test_help' 3 | require 'rails/performance_test_help' 4 | 5 | ENV["BENCHMARK_TESTS"] = '1' 6 | 7 | require 'rails/perftest/active_support/testing/performance' 8 | 9 | def options 10 | options = {} 11 | defaults = ActiveSupport::Testing::Performance::DEFAULTS 12 | 13 | OptionParser.new do |opt| 14 | opt.banner = "Usage: rails benchmarker 'Ruby.code' 'Ruby.more_code' ... [OPTS]" 15 | opt.on('-r', '--runs N', Numeric, 'Number of runs.', "Default: #{defaults[:runs]}") { |r| options[:runs] = r } 16 | opt.on('-o', '--output PATH', String, 'Directory to use when writing the results.', "Default: #{defaults[:output]}") { |o| options[:output] = o } 17 | opt.on('-m', '--metrics a,b,c', Array, 'Metrics to use.', "Default: #{defaults[:metrics].join(",")}") { |m| options[:metrics] = m.map(&:to_sym) } 18 | opt.parse!(ARGV) 19 | end 20 | 21 | options 22 | end 23 | 24 | class BenchmarkerTest < ActionDispatch::PerformanceTest #:nodoc: 25 | self.profile_options = options 26 | 27 | ARGV.each do |expression| 28 | eval <<-RUBY 29 | def test_#{expression.parameterize('_')} 30 | #{expression} 31 | end 32 | RUBY 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/rails/perftest/commands/profiler.rb: -------------------------------------------------------------------------------- 1 | require 'optparse' 2 | require 'rails/test_help' 3 | require 'rails/performance_test_help' 4 | require 'rails/perftest/active_support/testing/performance' 5 | 6 | def options 7 | options = {} 8 | defaults = ActiveSupport::Testing::Performance::DEFAULTS 9 | 10 | OptionParser.new do |opt| 11 | opt.banner = "Usage: rails profiler 'Ruby.code' 'Ruby.more_code' ... [OPTS]" 12 | opt.on('-r', '--runs N', Numeric, 'Number of runs.', "Default: #{defaults[:runs]}") { |r| options[:runs] = r } 13 | opt.on('-o', '--output PATH', String, 'Directory to use when writing the results.', "Default: #{defaults[:output]}") { |o| options[:output] = o } 14 | opt.on('-m', '--metrics a,b,c', Array, 'Metrics to use.', "Default: #{defaults[:metrics].join(",")}") { |m| options[:metrics] = m.map(&:to_sym) } 15 | opt.on('-f', '--formats x,y,z', Array, 'Formats to output to.', "Default: #{defaults[:formats].join(",")}") { |m| options[:formats] = m.map(&:to_sym) } 16 | opt.parse!(ARGV) 17 | end 18 | 19 | options 20 | end 21 | 22 | class ProfilerTest < ActionDispatch::PerformanceTest #:nodoc: 23 | self.profile_options = options 24 | 25 | ARGV.each do |expression| 26 | eval <<-RUBY 27 | def test_#{expression.parameterize('_')} 28 | #{expression} 29 | end 30 | RUBY 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/rails/perftest/railtie.rb: -------------------------------------------------------------------------------- 1 | require 'rails/railtie' 2 | 3 | module Rails 4 | module PerformanceTest 5 | class Railtie < ::Rails::Railtie 6 | 7 | rake_tasks do 8 | load 'rails/perftest/railties/testing.tasks' 9 | end 10 | 11 | initializer "action_dispatch.performance_test" do 12 | ActiveSupport.on_load(:action_controller) do 13 | require "rails/perftest/action_dispatch" 14 | require "rails/perftest/action_controller" 15 | end 16 | end 17 | 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/rails/perftest/railties/testing.tasks: -------------------------------------------------------------------------------- 1 | require 'rake/testtask' 2 | 3 | namespace :test do 4 | task 'perftest:benchmark_mode' do 5 | ENV["BENCHMARK_TESTS"] = '1' 6 | end 7 | 8 | Rake::TestTask.new(benchmark: ['test:prepare', 'test:perftest:benchmark_mode']) do |t| 9 | t.libs << 'test' 10 | t.pattern = 'test/performance/**/*_test.rb' 11 | end 12 | 13 | Rake::TestTask.new(profile: 'test:prepare') do |t| 14 | t.libs << 'test' 15 | t.pattern = 'test/performance/**/*_test.rb' 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/rails/perftest/version.rb: -------------------------------------------------------------------------------- 1 | module Rails 2 | module Perftest 3 | VERSION = "0.0.7" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /rails-perftest.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.expand_path('../lib/rails/perftest/version', __FILE__) 3 | 4 | Gem::Specification.new do |gem| 5 | gem.authors = ["Yves Senn"] 6 | gem.email = ["yves.senn@gmail.com"] 7 | gem.description = %q{Rails performance tests (removed from core in Rails 4.0)} 8 | gem.summary = %q{ActionDispatch::PerformanceTest, ActiveSupport::Testing::Performance extracted from Rails.} 9 | gem.homepage = "https://github.com/rails/rails-perftest" 10 | gem.license = "MIT" 11 | 12 | gem.files = [".gitignore",".travis.yml","Gemfile","Gemfile.lock","LICENSE","README.md","Rakefile","bin/rake","exe/perftest","gemfiles/Gemfile-rails-4.0","gemfiles/Gemfile-rails-4.1","lib/generators/rails/app/templates/test/performance/browsing_test.rb","lib/generators/rails/performance_test/USAGE","lib/generators/rails/performance_test/performance_test_generator.rb","lib/generators/rails/performance_test/templates/performance_test.rb","lib/rails-perftest.rb","lib/rails/performance_test_help.rb","lib/rails/perftest/action_controller.rb","lib/rails/perftest/action_controller/performance_test.rb","lib/rails/perftest/action_dispatch.rb","lib/rails/perftest/action_dispatch/performance_test.rb","lib/rails/perftest/active_support/testing/performance.rb","lib/rails/perftest/active_support/testing/performance/jruby.rb","lib/rails/perftest/active_support/testing/performance/rubinius.rb","lib/rails/perftest/active_support/testing/performance/ruby.rb","lib/rails/perftest/commands.rb","lib/rails/perftest/commands/benchmarker.rb","lib/rails/perftest/commands/profiler.rb","lib/rails/perftest/railtie.rb","lib/rails/perftest/railties/testing.tasks","lib/rails/perftest/version.rb","rails-perftest.gemspec","rails-perftest.gemspec.erb","test/generators/generators_test_helper.rb","test/generators/performance_test_generator_test.rb","test/helper.rb","test/performance_test.rb"] 13 | gem.bindir = "exe" 14 | gem.executables = ["perftest"] 15 | gem.test_files = ["test/generators/generators_test_helper.rb","test/generators/performance_test_generator_test.rb","test/helper.rb","test/performance_test.rb"] 16 | gem.name = "rails-perftest" 17 | gem.require_paths = ["lib"] 18 | gem.version = Rails::Perftest::VERSION 19 | 20 | gem.add_development_dependency 'actionmailer', '>= 5.2' 21 | gem.add_development_dependency 'actionpack', '>= 5.2' 22 | gem.add_development_dependency 'activemodel', '>= 5.2' 23 | gem.add_development_dependency 'activerecord', '>= 5.2' 24 | gem.add_development_dependency 'minitest', '>= 3' 25 | gem.add_development_dependency 'railties', '>= 5.2' 26 | gem.add_development_dependency 'ruby-prof', '>= 1.0.0' 27 | gem.add_development_dependency 'sqlite3', '>= 1.3' 28 | end 29 | -------------------------------------------------------------------------------- /rails-perftest.gemspec.erb: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.expand_path('../lib/rails/perftest/version', __FILE__) 3 | 4 | Gem::Specification.new do |gem| 5 | gem.authors = ["Yves Senn"] 6 | gem.email = ["yves.senn@gmail.com"] 7 | gem.description = %q{Rails performance tests (removed from core in Rails 4.0)} 8 | gem.summary = %q{ActionDispatch::PerformanceTest, ActiveSupport::Testing::Performance extracted from Rails.} 9 | gem.homepage = "https://github.com/rails/rails-perftest" 10 | gem.license = "MIT" 11 | 12 | gem.files = [<%= files.map(&:inspect).join ',' %>] 13 | gem.bindir = "exe" 14 | gem.executables = [<%= executables.map(&:inspect).join ',' %>] 15 | gem.test_files = [<%= test_files.map(&:inspect).join ',' %>] 16 | gem.name = "rails-perftest" 17 | gem.require_paths = ["lib"] 18 | gem.version = Rails::Perftest::VERSION 19 | 20 | gem.add_development_dependency 'ruby-prof', '>= 1.0.0' 21 | gem.add_development_dependency 'minitest', '>= 3' 22 | gem.add_development_dependency 'railties', '~> 4.0' 23 | gem.add_development_dependency 'activerecord', '~> 4.0' 24 | gem.add_development_dependency 'activemodel', '~> 4.0' 25 | gem.add_development_dependency 'actionmailer', '~> 4.0' 26 | gem.add_development_dependency 'actionpack', '~> 4.0' 27 | gem.add_development_dependency 'sqlite3', '~> 1.3' 28 | end 29 | -------------------------------------------------------------------------------- /test/generators/generators_test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | 3 | require 'fileutils' 4 | 5 | require 'rails/all' 6 | require 'rails/generators' 7 | require 'rails/generators/test_case' 8 | 9 | module TestApp 10 | class Application < Rails::Application 11 | end 12 | end 13 | 14 | # Call configure to load the settings from 15 | # Rails.application.config.generators to Rails::Generators 16 | Rails.application.load_generators 17 | ActiveSupport::TestCase.test_order = :random if ActiveSupport::TestCase.respond_to?(:test_order=) 18 | -------------------------------------------------------------------------------- /test/generators/performance_test_generator_test.rb: -------------------------------------------------------------------------------- 1 | require 'generators/generators_test_helper' 2 | require 'generators/rails/performance_test/performance_test_generator' 3 | 4 | class PerformanceTestGeneratorTest < Rails::Generators::TestCase 5 | tests Rails::Generators::PerformanceTestGenerator 6 | arguments %w(dashboard) 7 | destination File.expand_path("../../tmp", __FILE__) 8 | 9 | def setup 10 | super 11 | prepare_destination 12 | end 13 | 14 | def test_performance_test_skeleton_is_created 15 | run_generator 16 | assert_file "test/performance/dashboard_test.rb", /class DashboardTest < ActionDispatch::PerformanceTest/ 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /test/helper.rb: -------------------------------------------------------------------------------- 1 | ORIG_ARGV = ARGV.dup 2 | 3 | require 'minitest/autorun' 4 | 5 | require 'active_support' 6 | 7 | # Show backtraces for deprecated behavior for quicker cleanup. 8 | ActiveSupport::Deprecation.debug = true 9 | ActiveSupport::TestCase.test_order = :random if ActiveSupport::TestCase.respond_to?(:test_order=) 10 | -------------------------------------------------------------------------------- /test/performance_test.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | require 'rails/perftest/active_support/testing/performance' 3 | 4 | module ActiveSupport 5 | module Testing 6 | class PerformanceTest < ActiveSupport::TestCase 7 | 8 | def test_amount_format 9 | amount_metric = ActiveSupport::Testing::Performance::Metrics[:amount].new 10 | assert_equal "0", amount_metric.format(0) 11 | assert_equal "1", amount_metric.format(1.23) 12 | assert_equal "40,000,000", amount_metric.format(40000000) 13 | end 14 | 15 | def test_time_format 16 | time_metric = ActiveSupport::Testing::Performance::Metrics[:time].new 17 | assert_equal "0 ms", time_metric.format(0) 18 | assert_equal "40 ms", time_metric.format(0.04) 19 | assert_equal "41 ms", time_metric.format(0.0415) 20 | assert_equal "1.23 sec", time_metric.format(1.23) 21 | assert_equal "40000.00 sec", time_metric.format(40000) 22 | assert_equal "-5000 ms", time_metric.format(-5) 23 | end 24 | 25 | def test_space_format 26 | space_metric = ActiveSupport::Testing::Performance::Metrics[:digital_information_unit].new 27 | assert_equal "0 Bytes", space_metric.format(0) 28 | assert_equal "0 Bytes", space_metric.format(0.4) 29 | assert_equal "1 Byte", space_metric.format(1.23) 30 | assert_equal "123 Bytes", space_metric.format(123) 31 | assert_equal "123 Bytes", space_metric.format(123.45) 32 | assert_equal "12 KB", space_metric.format(12345) 33 | assert_equal "1.2 MB", space_metric.format(1234567) 34 | assert_equal "9.3 GB", space_metric.format(10**10) 35 | assert_equal "91 TB", space_metric.format(10**14) 36 | assert_equal "890 PB", space_metric.format(10**18) 37 | end 38 | 39 | def test_environment_format_without_rails 40 | metric = ActiveSupport::Testing::Performance::Metrics[:time].new 41 | benchmarker = ActiveSupport::Testing::Performance::Benchmarker.new(self, metric) 42 | assert_match "#{RUBY_ENGINE}-#{RUBY_VERSION}.#{RUBY_PATCHLEVEL},#{RUBY_PLATFORM}", benchmarker.environment 43 | end 44 | 45 | def test_environment_format_with_rails 46 | version = Module.new 47 | version.const_set :STRING, "4.0.0" 48 | Rails.const_set :VERSION, version 49 | 50 | metric = ActiveSupport::Testing::Performance::Metrics[:time].new 51 | benchmarker = ActiveSupport::Testing::Performance::Benchmarker.new(self, metric) 52 | assert_match "rails-4.0.0,#{RUBY_ENGINE}-#{RUBY_VERSION}.#{RUBY_PATCHLEVEL},#{RUBY_PLATFORM}", benchmarker.environment 53 | ensure 54 | Object.send :remove_const, :Rails 55 | end 56 | end 57 | end 58 | end 59 | --------------------------------------------------------------------------------