derailed_benchmarks 1.3.6 → 1.4.0
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.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/CHANGELOG.md +7 -1
- data/README.md +70 -2
- data/Rakefile +2 -0
- data/bin/derailed +1 -0
- data/derailed_benchmarks.gemspec +4 -1
- data/gemfiles/rails_5_1.gemfile +2 -0
- data/gemfiles/rails_5_2.gemfile +2 -0
- data/gemfiles/rails_6_0.gemfile +2 -0
- data/gemfiles/rails_git.gemfile +19 -0
- data/lib/derailed_benchmarks.rb +20 -0
- data/lib/derailed_benchmarks/auth_helper.rb +2 -0
- data/lib/derailed_benchmarks/auth_helpers/devise.rb +2 -0
- data/lib/derailed_benchmarks/core_ext/kernel_require.rb +7 -2
- data/lib/derailed_benchmarks/load_tasks.rb +138 -0
- data/lib/derailed_benchmarks/require_tree.rb +3 -1
- data/lib/derailed_benchmarks/stats_from_dir.rb +99 -0
- data/lib/derailed_benchmarks/stats_in_file.rb +53 -0
- data/lib/derailed_benchmarks/tasks.rb +70 -115
- data/lib/derailed_benchmarks/version.rb +3 -1
- data/test/derailed_benchmarks/core_ext/kernel_require_test.rb +3 -0
- data/test/derailed_benchmarks/require_tree_test.rb +2 -0
- data/test/derailed_benchmarks/stats_from_dir_test.rb +101 -0
- data/test/derailed_test.rb +2 -0
- data/test/fixtures/require/child_one.rb +1 -1
- data/test/fixtures/require/child_two.rb +1 -1
- data/test/fixtures/require/parent_one.rb +2 -1
- data/test/fixtures/require/raise_child.rb +2 -0
- data/test/fixtures/require/relative_child.rb +2 -0
- data/test/fixtures/require/relative_child_two.rb +4 -0
- data/test/fixtures/stats/significant/loser.bench.txt +100 -0
- data/test/fixtures/stats/significant/winner.bench.txt +100 -0
- data/test/integration/tasks_test.rb +9 -0
- data/test/rails_app/Rakefile +2 -0
- data/test/rails_app/app/controllers/application_controller.rb +2 -0
- data/test/rails_app/app/controllers/authenticated_controller.rb +2 -0
- data/test/rails_app/app/controllers/pages_controller.rb +2 -0
- data/test/rails_app/app/helpers/application_helper.rb +2 -0
- data/test/rails_app/app/helpers/authenticated_helper.rb +2 -0
- data/test/rails_app/app/models/user.rb +2 -0
- data/test/rails_app/config.ru +2 -0
- data/test/rails_app/config/application.rb +2 -0
- data/test/rails_app/config/boot.rb +3 -1
- data/test/rails_app/config/environment.rb +4 -0
- data/test/rails_app/config/environments/development.rb +2 -0
- data/test/rails_app/config/environments/production.rb +2 -0
- data/test/rails_app/config/environments/test.rb +2 -0
- data/test/rails_app/config/initializers/backtrace_silencers.rb +2 -0
- data/test/rails_app/config/initializers/devise.rb +2 -0
- data/test/rails_app/config/initializers/inflections.rb +2 -0
- data/test/rails_app/config/initializers/mime_types.rb +2 -0
- data/test/rails_app/config/initializers/secret_token.rb +2 -0
- data/test/rails_app/config/initializers/session_store.rb +2 -0
- data/test/rails_app/config/routes.rb +2 -0
- data/test/rails_app/db/migrate/20141210070547_devise_create_users.rb +2 -0
- data/test/rails_app/db/schema.rb +2 -0
- data/test/rails_app/perf.rake +2 -0
- data/test/rails_app/script/rails +2 -0
- data/test/support/integration_case.rb +2 -0
- data/test/test_helper.rb +6 -2
- metadata +30 -4
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DerailedBenchmarks
|
4
|
+
# A class for reading in benchmark results
|
5
|
+
# and converting them to numbers for comparison
|
6
|
+
#
|
7
|
+
# Example:
|
8
|
+
#
|
9
|
+
# puts `cat muhfile.bench.txt`
|
10
|
+
#
|
11
|
+
# 9.590142 0.831269 10.457801 ( 10.0)
|
12
|
+
# 9.836019 0.837319 10.728024 ( 11.0)
|
13
|
+
#
|
14
|
+
# x = StatsForFile.new(name: "muhcommit", file: "muhfile.bench.txt", desc: "I made it faster", time: Time.now)
|
15
|
+
# x.values #=> [11.437769, 11.792425]
|
16
|
+
# x.average # => 10.5
|
17
|
+
# x.name # => "muhfile"
|
18
|
+
class StatsForFile
|
19
|
+
attr_reader :name, :values, :desc, :time
|
20
|
+
|
21
|
+
def initialize(file:, name:, desc: "", time: )
|
22
|
+
@file = Pathname.new(file)
|
23
|
+
FileUtils.touch(@file)
|
24
|
+
|
25
|
+
@name = name
|
26
|
+
@desc = desc
|
27
|
+
@time = time
|
28
|
+
end
|
29
|
+
|
30
|
+
def call
|
31
|
+
load_file!
|
32
|
+
|
33
|
+
@average = values.inject(:+) / values.length
|
34
|
+
end
|
35
|
+
|
36
|
+
def average
|
37
|
+
@average.to_f
|
38
|
+
end
|
39
|
+
|
40
|
+
private def load_file!
|
41
|
+
@values = []
|
42
|
+
@file.each_line do |line|
|
43
|
+
line.match(/\( +(\d+\.\d+)\)/)
|
44
|
+
begin
|
45
|
+
values << BigDecimal($1)
|
46
|
+
rescue => e
|
47
|
+
raise e, "Problem with file #{@file.inspect}:\n#{@file.read}\n#{e.message}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
values.freeze
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -1,137 +1,81 @@
|
|
1
|
-
|
2
|
-
task :rails_load do
|
3
|
-
ENV["RAILS_ENV"] ||= "production"
|
4
|
-
ENV['RACK_ENV'] = ENV["RAILS_ENV"]
|
5
|
-
ENV["DISABLE_SPRING"] = "true"
|
6
|
-
|
7
|
-
ENV["SECRET_KEY_BASE"] ||= "foofoofoo"
|
8
|
-
|
9
|
-
ENV['LOG_LEVEL'] ||= "FATAL"
|
10
|
-
|
11
|
-
require 'rails'
|
12
|
-
|
13
|
-
puts "Booting: #{Rails.env}"
|
1
|
+
require_relative 'load_tasks'
|
14
2
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
Rails.env = ENV["RAILS_ENV"]
|
3
|
+
namespace :perf do
|
4
|
+
desc "runs the same test against two different branches for statistical comparison"
|
5
|
+
task :library do
|
6
|
+
DERAILED_SCRIPT_COUNT = (ENV["DERAILED_SCRIPT_COUNT"] ||= "200").to_i
|
7
|
+
ENV["TEST_COUNT"] ||= "200"
|
22
8
|
|
23
|
-
|
9
|
+
raise "test count must be at least 2, is set to #{DERAILED_SCRIPT_COUNT}" if DERAILED_SCRIPT_COUNT < 2
|
10
|
+
script = ENV["DERAILED_SCRIPT"] || "bundle exec derailed exec perf:test"
|
24
11
|
|
25
|
-
if
|
26
|
-
|
12
|
+
if ENV["DERAILED_PATH_TO_LIBRARY"]
|
13
|
+
library_dir = ENV["DERAILED_PATH_TO_LIBRARY"]
|
27
14
|
else
|
28
|
-
|
15
|
+
library_dir = DerailedBenchmarks.rails_path_on_disk
|
29
16
|
end
|
30
17
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
18
|
+
raise "Must be a path with a .git directory '#{library_dir}'" unless File.exist?(File.join(library_dir, ".git"))
|
19
|
+
|
20
|
+
# Use either the explicit SHAs when present or grab last two SHAs from commit history
|
21
|
+
# if only one SHA is given, then use it and the last SHA from commit history
|
22
|
+
branch_names = []
|
23
|
+
branch_names = ENV.fetch("SHAS_TO_TEST").split(",") if ENV["SHAS_TO_TEST"]
|
24
|
+
if branch_names.length < 2
|
25
|
+
Dir.chdir(library_dir) do
|
26
|
+
branches = run!('git log --format="%H" -n 2').chomp.split($INPUT_RECORD_SEPARATOR)
|
27
|
+
if branch_names.empty?
|
28
|
+
branch_names = branches
|
29
|
+
else
|
30
|
+
branch_names << branches.shift
|
31
|
+
end
|
36
32
|
end
|
37
|
-
|
38
|
-
ActiveRecord::Migrator.migrations_paths = DERAILED_APP.paths['db/migrate'].to_a
|
39
|
-
ActiveRecord::Migration.verbose = true
|
40
|
-
ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, nil)
|
41
33
|
end
|
42
34
|
|
43
|
-
|
44
|
-
|
35
|
+
current_library_branch = ""
|
36
|
+
Dir.chdir(library_dir) { current_library_branch = run!('git describe --contains --all HEAD').chomp }
|
45
37
|
|
46
|
-
|
47
|
-
|
48
|
-
puts "You need to tell derailed how to boot your app"
|
49
|
-
puts "In your perf.rake add:"
|
50
|
-
puts
|
51
|
-
puts "namespace :perf do"
|
52
|
-
puts " task :rack_load do"
|
53
|
-
puts " # DERAILED_APP = your code here"
|
54
|
-
puts " end"
|
55
|
-
puts "end"
|
56
|
-
end
|
38
|
+
out_dir = Pathname.new("tmp/library_branches/#{Time.now.strftime('%Y-%m-%d-%H-%M-%s-%N')}")
|
39
|
+
out_dir.mkpath
|
57
40
|
|
58
|
-
|
59
|
-
|
60
|
-
Rake::Task["perf:rails_load"].invoke
|
61
|
-
else
|
62
|
-
Rake::Task["perf:rack_load"].invoke
|
63
|
-
end
|
64
|
-
|
65
|
-
WARM_COUNT = (ENV['WARM_COUNT'] || 0).to_i
|
66
|
-
TEST_COUNT = (ENV['TEST_COUNT'] || ENV['CNT'] || 1_000).to_i
|
67
|
-
PATH_TO_HIT = ENV["PATH_TO_HIT"] || ENV['ENDPOINT'] || "/"
|
68
|
-
puts "Endpoint: #{ PATH_TO_HIT.inspect }"
|
41
|
+
branches_to_test = branch_names.each_with_object({}) {|elem, hash| hash[elem] = out_dir + "#{elem.gsub('/', ':')}.bench.txt" }
|
42
|
+
branch_info = {}
|
69
43
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
hash
|
78
|
-
end
|
79
|
-
puts "HTTP headers: #{HTTP_HEADERS}" unless HTTP_HEADERS.empty?
|
80
|
-
|
81
|
-
CURL_HTTP_HEADER_ARGS = HTTP_HEADERS.map { |http_header_name, value| "-H \"#{http_header_name}: #{value}\"" }.join(" ")
|
82
|
-
|
83
|
-
require 'rack/test'
|
84
|
-
require 'rack/file'
|
85
|
-
|
86
|
-
DERAILED_APP = DerailedBenchmarks.add_auth(Object.class_eval { remove_const(:DERAILED_APP) })
|
87
|
-
if server = ENV["USE_SERVER"]
|
88
|
-
@port = (3000..3900).to_a.sample
|
89
|
-
puts "Port: #{ @port.inspect }"
|
90
|
-
puts "Server: #{ server.inspect }"
|
91
|
-
thread = Thread.new do
|
92
|
-
Rack::Server.start(app: DERAILED_APP, :Port => @port, environment: "none", server: server)
|
44
|
+
branches_to_test.each do |branch, file|
|
45
|
+
Dir.chdir(library_dir) do
|
46
|
+
run!("git checkout '#{branch}'")
|
47
|
+
description = run!("git log --oneline --format=%B -n 1 HEAD | head -n 1").strip
|
48
|
+
time_stamp = run!("git log -n 1 --pretty=format:%ci").strip # https://stackoverflow.com/a/25921837/147390
|
49
|
+
name = run!("git rev-parse --short HEAD").strip
|
50
|
+
branch_info[name] = { desc: description, time: DateTime.parse(time_stamp), file: file }
|
93
51
|
end
|
94
|
-
|
95
|
-
|
96
|
-
def call_app(path = File.join("/", PATH_TO_HIT))
|
97
|
-
cmd = "curl #{CURL_HTTP_HEADER_ARGS} 'http://localhost:#{@port}#{path}' -s --fail 2>&1"
|
98
|
-
response = `#{cmd}`
|
99
|
-
unless $?.success?
|
100
|
-
STDERR.puts "Couldn't call app."
|
101
|
-
STDERR.puts "Bad request to #{cmd.inspect} \n\n***RESPONSE***:\n\n#{ response.inspect }"
|
102
|
-
|
103
|
-
FileUtils.mkdir_p("tmp")
|
104
|
-
File.open("tmp/fail.html", "w+") {|f| f.write response.body }
|
52
|
+
run!("#{script}")
|
53
|
+
end
|
105
54
|
|
106
|
-
|
55
|
+
stats = DerailedBenchmarks::StatsFromDir.new(branch_info)
|
56
|
+
ENV["DERAILED_STOP_VALID_COUNT"] ||= "10"
|
57
|
+
stop_valid_count = Integer(ENV["DERAILED_STOP_VALID_COUNT"])
|
107
58
|
|
108
|
-
|
109
|
-
|
59
|
+
times_significant = 0
|
60
|
+
DERAILED_SCRIPT_COUNT.times do |i|
|
61
|
+
puts "Sample: #{i.next}/#{DERAILED_SCRIPT_COUNT} iterations per sample: #{ENV['TEST_COUNT']}"
|
62
|
+
branches_to_test.each do |branch, file|
|
63
|
+
Dir.chdir(library_dir) { run!("git checkout '#{branch}'") }
|
64
|
+
run!(" #{script} 2>&1 | tail -n 1 >> '#{file}'")
|
110
65
|
end
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
def call_app
|
115
|
-
response = @app.get(PATH_TO_HIT, RACK_HTTP_HEADERS)
|
116
|
-
if response.status != 200
|
117
|
-
STDERR.puts "Couldn't call app. Bad request to #{PATH_TO_HIT}! Resulted in #{response.status} status."
|
118
|
-
STDERR.puts "\n\n***RESPONSE BODY***\n\n"
|
119
|
-
STDERR.puts response.body
|
120
|
-
|
121
|
-
FileUtils.mkdir_p("tmp")
|
122
|
-
File.open("tmp/fail.html", "w+") {|f| f.write response.body }
|
123
|
-
|
124
|
-
`open #{File.expand_path("tmp/fail.html")}` if ENV["DERAILED_DEBUG"]
|
66
|
+
times_significant += 1 if i >= 2 && stats.call.significant?
|
67
|
+
break if stop_valid_count != 0 && times_significant == stop_valid_count
|
68
|
+
end
|
125
69
|
|
126
|
-
|
127
|
-
|
128
|
-
|
70
|
+
ensure
|
71
|
+
if library_dir && current_library_branch
|
72
|
+
puts "Resetting git dir of #{library_dir.inspect} to #{current_library_branch.inspect}"
|
73
|
+
Dir.chdir(library_dir) do
|
74
|
+
run!("git checkout '#{current_library_branch}'")
|
129
75
|
end
|
130
76
|
end
|
131
|
-
|
132
|
-
|
133
|
-
WARM_COUNT.times { call_app }
|
134
|
-
end
|
77
|
+
|
78
|
+
stats.call.banner if stats
|
135
79
|
end
|
136
80
|
|
137
81
|
desc "hits the url TEST_COUNT times"
|
@@ -228,6 +172,11 @@ namespace :perf do
|
|
228
172
|
require 'benchmark/ips'
|
229
173
|
|
230
174
|
Benchmark.ips do |x|
|
175
|
+
x.warmup = Float(ENV["IPS_WARMUP"] || 2)
|
176
|
+
x.time = Float(ENV["IPS_TIME"] || 5)
|
177
|
+
x.suite = ENV["IPS_SUITE"] if ENV["IPS_SUITE"]
|
178
|
+
x.iterations = Integer(ENV["IPS_ITERATIONS"] || 1)
|
179
|
+
|
231
180
|
x.report("ips") { call_app }
|
232
181
|
end
|
233
182
|
end
|
@@ -298,4 +247,10 @@ namespace :perf do
|
|
298
247
|
puts ""
|
299
248
|
puts "Also try uploading #{file_name.inspect} to http://tenderlove.github.io/heap-analyzer/"
|
300
249
|
end
|
250
|
+
|
251
|
+
def run!(cmd)
|
252
|
+
out = `#{cmd}`
|
253
|
+
raise "Error while running #{cmd.inspect}: #{out}" unless $?.success?
|
254
|
+
out
|
255
|
+
end
|
301
256
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'test_helper'
|
2
4
|
|
3
5
|
class KernelRequireTest < ActiveSupport::TestCase
|
@@ -25,6 +27,7 @@ class KernelRequireTest < ActiveSupport::TestCase
|
|
25
27
|
assert_node_in_parent("child_one.rb", parent)
|
26
28
|
child_two = assert_node_in_parent("child_two.rb", parent)
|
27
29
|
assert_node_in_parent("relative_child", parent)
|
30
|
+
assert_node_in_parent("relative_child_two", parent)
|
28
31
|
assert_node_in_parent("raise_child.rb", child_two)
|
29
32
|
end
|
30
33
|
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
class StatsFromDirTest < ActiveSupport::TestCase
|
6
|
+
test "that it works" do
|
7
|
+
dir = fixtures_dir("stats/significant")
|
8
|
+
branch_info = {}
|
9
|
+
branch_info["loser"] = { desc: "Old commit", time: Time.now, file: dir.join("loser.bench.txt"), name: "loser" }
|
10
|
+
branch_info["winner"] = { desc: "I am the new commit", time: Time.now + 1, file: dir.join("winner.bench.txt"), name: "winner" }
|
11
|
+
stats = DerailedBenchmarks::StatsFromDir.new(branch_info).call
|
12
|
+
|
13
|
+
newest = stats.newest
|
14
|
+
oldest = stats.oldest
|
15
|
+
|
16
|
+
assert newest.average < oldest.average
|
17
|
+
|
18
|
+
assert_equal "winner", newest.name
|
19
|
+
assert_equal "loser", oldest.name
|
20
|
+
|
21
|
+
assert 3.6e-05 < stats.p_value
|
22
|
+
assert 3.8e-05 > stats.p_value
|
23
|
+
assert_equal true, stats.significant?
|
24
|
+
|
25
|
+
assert_equal "1.0062", stats.x_faster
|
26
|
+
assert_equal "0.6131", stats.percent_faster
|
27
|
+
end
|
28
|
+
|
29
|
+
test "banner faster" do
|
30
|
+
dir = fixtures_dir("stats/significant")
|
31
|
+
branch_info = {}
|
32
|
+
branch_info["loser"] = { desc: "Old commit", time: Time.now, file: dir.join("loser.bench.txt"), name: "loser" }
|
33
|
+
branch_info["winner"] = { desc: "I am the new commit", time: Time.now + 1, file: dir.join("winner.bench.txt"), name: "winner" }
|
34
|
+
stats = DerailedBenchmarks::StatsFromDir.new(branch_info).call
|
35
|
+
newest = stats.newest
|
36
|
+
oldest = stats.oldest
|
37
|
+
|
38
|
+
# Test fixture for banner
|
39
|
+
def stats.p_value
|
40
|
+
"0.000037"
|
41
|
+
end
|
42
|
+
|
43
|
+
def newest.average
|
44
|
+
10.5
|
45
|
+
end
|
46
|
+
|
47
|
+
def oldest.average
|
48
|
+
11.0
|
49
|
+
end
|
50
|
+
|
51
|
+
expected = <<-EOM
|
52
|
+
[winner] "I am the new commit" - (10.5 seconds)
|
53
|
+
FASTER by:
|
54
|
+
1.0476x [older/newer]
|
55
|
+
4.5455% [(older - newer) / older * 100]
|
56
|
+
[loser] "Old commit" - (11.0 seconds)
|
57
|
+
EOM
|
58
|
+
|
59
|
+
actual = StringIO.new
|
60
|
+
stats.banner(actual)
|
61
|
+
|
62
|
+
assert_match expected, actual.string
|
63
|
+
end
|
64
|
+
|
65
|
+
test "banner slower" do
|
66
|
+
dir = fixtures_dir("stats/significant")
|
67
|
+
branch_info = {}
|
68
|
+
branch_info["loser"] = { desc: "I am the new commit", time: Time.now, file: dir.join("loser.bench.txt"), name: "loser" }
|
69
|
+
branch_info["winner"] = { desc: "Old commit", time: Time.now - 10, file: dir.join("winner.bench.txt"), name: "winner" }
|
70
|
+
stats = DerailedBenchmarks::StatsFromDir.new(branch_info).call
|
71
|
+
newest = stats.newest
|
72
|
+
oldest = stats.oldest
|
73
|
+
|
74
|
+
def oldest.average
|
75
|
+
10.5
|
76
|
+
end
|
77
|
+
|
78
|
+
def newest.average
|
79
|
+
11.0
|
80
|
+
end
|
81
|
+
|
82
|
+
expected = <<-EOM
|
83
|
+
[loser] "I am the new commit" - (11.0 seconds)
|
84
|
+
SLOWER by:
|
85
|
+
0.9545x [older/newer]
|
86
|
+
-4.7619% [(older - newer) / older * 100]
|
87
|
+
[winner] "Old commit" - (10.5 seconds)
|
88
|
+
EOM
|
89
|
+
|
90
|
+
actual = StringIO.new
|
91
|
+
stats.banner(actual)
|
92
|
+
|
93
|
+
assert_match expected, actual.string
|
94
|
+
end
|
95
|
+
|
96
|
+
test "stats from samples with slightly different sizes" do
|
97
|
+
stats = DerailedBenchmarks::StatsFromDir.new({})
|
98
|
+
out = stats.students_t_test([100,101,102], [1,3])
|
99
|
+
assert out[:alternative]
|
100
|
+
end
|
101
|
+
end
|
data/test/derailed_test.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
class ParentOne
|
2
|
-
@retained = ""
|
2
|
+
@retained = +""
|
3
3
|
1_000_000.times.map { @retained << "A" }
|
4
4
|
end
|
5
5
|
require File.expand_path('../child_one.rb', __FILE__)
|
6
6
|
require File.expand_path('../child_two.rb', __FILE__)
|
7
7
|
require_relative 'relative_child'
|
8
|
+
require_relative File.expand_path('relative_child_two', __dir__)
|