rails-perftest 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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