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.
- data/.gitignore +17 -0
- data/Gemfile +6 -0
- data/LICENSE +22 -0
- data/README.md +696 -0
- data/Rakefile +37 -0
- data/bin/perftest +9 -0
- data/lib/generators/rails/app/templates/test/performance/browsing_test.rb +12 -0
- data/lib/generators/rails/performance_test/USAGE +10 -0
- data/lib/generators/rails/performance_test/performance_test_generator.rb +14 -0
- data/lib/generators/rails/performance_test/templates/performance_test.rb +12 -0
- data/lib/rails-perftest.rb +2 -0
- data/lib/rails/performance_test_help.rb +3 -0
- data/lib/rails/perftest/action_controller.rb +3 -0
- data/lib/rails/perftest/action_controller/performance_test.rb +3 -0
- data/lib/rails/perftest/action_dispatch.rb +3 -0
- data/lib/rails/perftest/action_dispatch/performance_test.rb +10 -0
- data/lib/rails/perftest/active_support/testing/performance.rb +271 -0
- data/lib/rails/perftest/active_support/testing/performance/jruby.rb +115 -0
- data/lib/rails/perftest/active_support/testing/performance/rubinius.rb +113 -0
- data/lib/rails/perftest/active_support/testing/performance/ruby.rb +173 -0
- data/lib/rails/perftest/commands.rb +30 -0
- data/lib/rails/perftest/commands/benchmarker.rb +34 -0
- data/lib/rails/perftest/commands/profiler.rb +32 -0
- data/lib/rails/perftest/railtie.rb +20 -0
- data/lib/rails/perftest/railties/testing.tasks +12 -0
- data/lib/rails/perftest/version.rb +5 -0
- data/rails-perftest.gemspec +26 -0
- data/rails-perftest.gemspec.erb +26 -0
- data/test/generators/generators_test_helper.rb +16 -0
- data/test/generators/performance_test_generator_test.rb +18 -0
- data/test/helper.rb +8 -0
- data/test/performance_test.rb +58 -0
- 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,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,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
|