rails-perftest 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. data/.gitignore +17 -0
  2. data/Gemfile +6 -0
  3. data/LICENSE +22 -0
  4. data/README.md +696 -0
  5. data/Rakefile +37 -0
  6. data/bin/perftest +9 -0
  7. data/lib/generators/rails/app/templates/test/performance/browsing_test.rb +12 -0
  8. data/lib/generators/rails/performance_test/USAGE +10 -0
  9. data/lib/generators/rails/performance_test/performance_test_generator.rb +14 -0
  10. data/lib/generators/rails/performance_test/templates/performance_test.rb +12 -0
  11. data/lib/rails-perftest.rb +2 -0
  12. data/lib/rails/performance_test_help.rb +3 -0
  13. data/lib/rails/perftest/action_controller.rb +3 -0
  14. data/lib/rails/perftest/action_controller/performance_test.rb +3 -0
  15. data/lib/rails/perftest/action_dispatch.rb +3 -0
  16. data/lib/rails/perftest/action_dispatch/performance_test.rb +10 -0
  17. data/lib/rails/perftest/active_support/testing/performance.rb +271 -0
  18. data/lib/rails/perftest/active_support/testing/performance/jruby.rb +115 -0
  19. data/lib/rails/perftest/active_support/testing/performance/rubinius.rb +113 -0
  20. data/lib/rails/perftest/active_support/testing/performance/ruby.rb +173 -0
  21. data/lib/rails/perftest/commands.rb +30 -0
  22. data/lib/rails/perftest/commands/benchmarker.rb +34 -0
  23. data/lib/rails/perftest/commands/profiler.rb +32 -0
  24. data/lib/rails/perftest/railtie.rb +20 -0
  25. data/lib/rails/perftest/railties/testing.tasks +12 -0
  26. data/lib/rails/perftest/version.rb +5 -0
  27. data/rails-perftest.gemspec +26 -0
  28. data/rails-perftest.gemspec.erb +26 -0
  29. data/test/generators/generators_test_helper.rb +16 -0
  30. data/test/generators/performance_test_generator_test.rb +18 -0
  31. data/test/helper.rb +8 -0
  32. data/test/performance_test.rb +58 -0
  33. metadata +171 -0
data/Rakefile ADDED
@@ -0,0 +1,37 @@
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 -- bin/*`.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
data/bin/perftest ADDED
@@ -0,0 +1,9 @@
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'
@@ -0,0 +1,12 @@
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
@@ -0,0 +1,10 @@
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
@@ -0,0 +1,14 @@
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
@@ -0,0 +1,12 @@
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
@@ -0,0 +1,2 @@
1
+ require 'rails/perftest/railtie' if defined? Rails
2
+ require "rails/perftest/version"
@@ -0,0 +1,3 @@
1
+ ActionController::Base.perform_caching = true
2
+ ActiveSupport::Dependencies.mechanism = :require
3
+ Rails.logger.level = ActiveSupport::Logger::INFO
@@ -0,0 +1,3 @@
1
+ module ActionController
2
+ autoload :PerformanceTest, 'rails/perftest/action_controller/performance_test'
3
+ end
@@ -0,0 +1,3 @@
1
+ ActionController::PerformanceTest = ActionDispatch::PerformanceTest
2
+
3
+ ActiveSupport::Deprecation.warn 'ActionController::PerformanceTest is deprecated and will be removed, use ActionDispatch::PerformanceTest instead.'
@@ -0,0 +1,3 @@
1
+ module ActionDispatch
2
+ autoload :PerformanceTest, 'rails/perftest/action_dispatch/performance_test'
3
+ end
@@ -0,0 +1,10 @@
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
+ end
10
+ end
@@ -0,0 +1,271 @@
1
+ require 'fileutils'
2
+ require 'active_support/concern'
3
+ require 'active_support/core_ext/class/delegating_attributes'
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
+ superclass_delegating_accessor :profile_options
15
+ self.profile_options = {}
16
+ end
17
+
18
+ # each implementation should define metrics and freeze the defaults
19
+ DEFAULTS =
20
+ if ARGV.include?('--benchmark') # HAX for rake test
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
+ def run(runner)
39
+ @runner = runner
40
+
41
+ run_warmup
42
+ if full_profile_options && metrics = full_profile_options[:metrics]
43
+ metrics.each do |metric_name|
44
+ if klass = Metrics[metric_name.to_sym]
45
+ run_profile(klass.new)
46
+ end
47
+ end
48
+ end
49
+
50
+ return
51
+ end
52
+
53
+ def run_test(metric, mode)
54
+ result = '.'
55
+ begin
56
+ run_callbacks :setup
57
+ setup
58
+ metric.send(mode) { __send__ method_name }
59
+ rescue Exception => e
60
+ result = @runner.puke(self.class, method_name, e)
61
+ ensure
62
+ begin
63
+ teardown
64
+ run_callbacks :teardown
65
+ rescue Exception => e
66
+ result = @runner.puke(self.class, method_name, e)
67
+ end
68
+ end
69
+ result
70
+ end
71
+
72
+ protected
73
+ # overridden by each implementation.
74
+ def run_gc; end
75
+
76
+ def run_warmup
77
+ run_gc
78
+
79
+ time = Metrics::Time.new
80
+ run_test(time, :benchmark)
81
+ puts "%s (%s warmup)" % [full_test_name, time.format(time.total)]
82
+
83
+ run_gc
84
+ end
85
+
86
+ def run_profile(metric)
87
+ klass = full_profile_options[:benchmark] ? Benchmarker : Profiler
88
+ performer = klass.new(self, metric)
89
+
90
+ performer.run
91
+ puts performer.report
92
+ performer.record
93
+ end
94
+
95
+ class Performer
96
+ delegate :run_test, :full_profile_options, :full_test_name, :to => :@harness
97
+
98
+ def initialize(harness, metric)
99
+ @harness, @metric, @supported = harness, metric, false
100
+ end
101
+
102
+ def report
103
+ if @supported
104
+ rate = @total / full_profile_options[:runs]
105
+ '%20s: %s' % [@metric.name, @metric.format(rate)]
106
+ else
107
+ '%20s: unsupported' % @metric.name
108
+ end
109
+ end
110
+
111
+ protected
112
+ def output_filename
113
+ "#{full_profile_options[:output]}/#{full_test_name}_#{@metric.name}"
114
+ end
115
+ end
116
+
117
+ # overridden by each implementation.
118
+ class Profiler < Performer
119
+ def time_with_block
120
+ before = Time.now
121
+ yield
122
+ Time.now - before
123
+ end
124
+
125
+ def run; end
126
+ def record; end
127
+ end
128
+
129
+ class Benchmarker < Performer
130
+ def initialize(*args)
131
+ super
132
+ @supported = @metric.respond_to?('measure')
133
+ end
134
+
135
+ def run
136
+ return unless @supported
137
+
138
+ full_profile_options[:runs].to_i.times { run_test(@metric, :benchmark) }
139
+ @total = @metric.total
140
+ end
141
+
142
+ def record
143
+ avg = @metric.total / full_profile_options[:runs].to_i
144
+ now = Time.now.utc.xmlschema
145
+ with_output_file do |file|
146
+ file.puts "#{avg},#{now},#{environment}"
147
+ end
148
+ end
149
+
150
+ def environment
151
+ @env ||= [].tap do |env|
152
+ env << "#{$1}.#{$2}" if File.directory?('.git') && `git branch -v` =~ /^\* (\S+)\s+(\S+)/
153
+ env << rails_version if defined?(Rails::VERSION::STRING)
154
+ env << "#{RUBY_ENGINE}-#{RUBY_VERSION}.#{RUBY_PATCHLEVEL}"
155
+ env << RUBY_PLATFORM
156
+ end.join(',')
157
+ end
158
+
159
+ protected
160
+ if defined?(Rails::VERSION::STRING)
161
+ HEADER = 'measurement,created_at,app,rails,ruby,platform'
162
+ else
163
+ HEADER = 'measurement,created_at,app,ruby,platform'
164
+ end
165
+
166
+ def with_output_file
167
+ fname = output_filename
168
+
169
+ if new = !File.exist?(fname)
170
+ FileUtils.mkdir_p(File.dirname(fname))
171
+ end
172
+
173
+ File.open(fname, 'ab') do |file|
174
+ file.puts(HEADER) if new
175
+ yield file
176
+ end
177
+ end
178
+
179
+ def output_filename
180
+ "#{super}.csv"
181
+ end
182
+
183
+ def rails_version
184
+ "rails-#{Rails::VERSION::STRING}#{rails_branch}"
185
+ end
186
+
187
+ def rails_branch
188
+ if File.directory?('vendor/rails/.git')
189
+ Dir.chdir('vendor/rails') do
190
+ ".#{$1}.#{$2}" if `git branch -v` =~ /^\* (\S+)\s+(\S+)/
191
+ end
192
+ end
193
+ end
194
+ end
195
+
196
+ module Metrics
197
+ def self.[](name)
198
+ const_get(name.to_s.camelize)
199
+ rescue NameError
200
+ nil
201
+ end
202
+
203
+ class Base
204
+ include ActiveSupport::NumberHelper
205
+
206
+ attr_reader :total
207
+
208
+ def initialize
209
+ @total = 0
210
+ end
211
+
212
+ def name
213
+ @name ||= self.class.name.demodulize.underscore
214
+ end
215
+
216
+ def benchmark
217
+ with_gc_stats do
218
+ before = measure
219
+ yield
220
+ @total += (measure - before)
221
+ end
222
+ end
223
+
224
+ # overridden by each implementation.
225
+ def profile; end
226
+
227
+ protected
228
+ # overridden by each implementation.
229
+ def with_gc_stats; end
230
+ end
231
+
232
+ class Time < Base
233
+ def measure
234
+ ::Time.now.to_f
235
+ end
236
+
237
+ def format(measurement)
238
+ if measurement < 1
239
+ '%d ms' % (measurement * 1000)
240
+ else
241
+ '%.2f sec' % measurement
242
+ end
243
+ end
244
+ end
245
+
246
+ class Amount < Base
247
+ def format(measurement)
248
+ number_to_delimited(measurement.floor)
249
+ end
250
+ end
251
+
252
+ class DigitalInformationUnit < Base
253
+ def format(measurement)
254
+ number_to_human_size(measurement, :precision => 2)
255
+ end
256
+ end
257
+
258
+ # each implementation provides its own metrics like ProcessTime, Memory or GcRuns
259
+ end
260
+ end
261
+ end
262
+ end
263
+
264
+ case RUBY_ENGINE
265
+ when 'ruby' then require 'rails/perftest/active_support/testing/performance/ruby'
266
+ when 'rbx' then require 'rails/perftest/active_support/testing/performance/rubinius'
267
+ when 'jruby' then require 'rails/perftest/active_support/testing/performance/jruby'
268
+ else
269
+ $stderr.puts 'Your ruby interpreter is not supported for benchmarking.'
270
+ exit
271
+ end
@@ -0,0 +1,115 @@
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 ARGV.include?('--benchmark')
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