derailed_benchmarks 1.3.6 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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__)
|