derailed_benchmarks 1.7.0 → 2.1.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/.circleci/config.yml +75 -0
- data/.github/workflows/check_changelog.yml +11 -8
- data/CHANGELOG.md +24 -1
- data/README.md +73 -11
- data/derailed_benchmarks.gemspec +6 -5
- data/gemfiles/rails_5_1.gemfile +3 -1
- data/gemfiles/rails_5_2.gemfile +3 -3
- data/gemfiles/rails_6_1.gemfile +13 -0
- data/gemfiles/rails_git.gemfile +2 -2
- data/lib/derailed_benchmarks.rb +4 -2
- data/lib/derailed_benchmarks/core_ext/kernel_require.rb +20 -9
- data/lib/derailed_benchmarks/git/commit.rb +36 -0
- data/lib/derailed_benchmarks/git/in_path.rb +59 -0
- data/lib/derailed_benchmarks/git/switch_project.rb +128 -0
- data/lib/derailed_benchmarks/git_switch_project.rb +1 -0
- data/lib/derailed_benchmarks/load_tasks.rb +1 -1
- data/lib/derailed_benchmarks/require_tree.rb +11 -1
- data/lib/derailed_benchmarks/{stats_in_file.rb → stats_for_file.rb} +8 -2
- data/lib/derailed_benchmarks/stats_from_dir.rb +40 -21
- data/lib/derailed_benchmarks/tasks.rb +63 -66
- data/lib/derailed_benchmarks/version.rb +1 -1
- data/test/derailed_benchmarks/core_ext/kernel_require_test.rb +70 -11
- data/test/derailed_benchmarks/git_switch_project_test.rb +83 -0
- data/test/derailed_benchmarks/require_tree_test.rb +1 -1
- data/test/derailed_benchmarks/stats_from_dir_test.rb +29 -12
- data/test/derailed_test.rb +15 -0
- data/test/fixtures/require/autoload_child.rb +5 -0
- data/test/fixtures/require/autoload_parent.rb +8 -0
- data/test/fixtures/require/child_one.rb +1 -1
- data/test/fixtures/require/child_two.rb +1 -1
- data/test/fixtures/require/load_child.rb +3 -0
- data/test/fixtures/require/load_parent.rb +5 -0
- data/test/fixtures/require/parent_one.rb +1 -1
- data/test/integration/tasks_test.rb +36 -6
- data/test/rails_app/config/storage.yml +0 -0
- data/test/test_helper.rb +6 -1
- metadata +67 -37
- data/.travis.yml +0 -18
- data/Appraisals +0 -26
@@ -0,0 +1,59 @@
|
|
1
|
+
module DerailedBenchmarks
|
2
|
+
# A class for running commands in a git directory
|
3
|
+
#
|
4
|
+
# It's faster to check if we're already in that directory instead
|
5
|
+
# of having to `cd` into each time. https://twitter.com/schneems/status/1305196730170961920
|
6
|
+
#
|
7
|
+
# Example:
|
8
|
+
#
|
9
|
+
# in_git_path = InGitPath.new(`bundle info heapy --path`.strip)
|
10
|
+
# in_git_path.checkout!("f0f92b06156f2274021aa42f15326da041ee9009")
|
11
|
+
# in_git_path.short_sha # => "f0f92b0"
|
12
|
+
class Git::InPath
|
13
|
+
attr_reader :path
|
14
|
+
|
15
|
+
def initialize(path)
|
16
|
+
@path = path
|
17
|
+
end
|
18
|
+
|
19
|
+
def description
|
20
|
+
run!("git log --oneline --format=%B -n 1 HEAD | head -n 1")
|
21
|
+
end
|
22
|
+
|
23
|
+
def short_sha
|
24
|
+
run!("git rev-parse --short HEAD")
|
25
|
+
end
|
26
|
+
|
27
|
+
def time_stamp_string
|
28
|
+
run!("git log -n 1 --pretty=format:%ci") # https://stackoverflow.com/a/25921837/147390
|
29
|
+
end
|
30
|
+
|
31
|
+
def branch
|
32
|
+
branch = run!("git rev-parse --abbrev-ref HEAD")
|
33
|
+
branch == "HEAD" ? nil : branch
|
34
|
+
end
|
35
|
+
|
36
|
+
def checkout!(ref)
|
37
|
+
run!("git checkout '#{ref}' 2>&1")
|
38
|
+
end
|
39
|
+
|
40
|
+
def time
|
41
|
+
DateTime.parse(time_stamp_string)
|
42
|
+
end
|
43
|
+
|
44
|
+
def run(cmd)
|
45
|
+
if Dir.pwd == path
|
46
|
+
out = `#{cmd}`.strip
|
47
|
+
else
|
48
|
+
out = `cd #{path} && #{cmd}`.strip
|
49
|
+
end
|
50
|
+
out
|
51
|
+
end
|
52
|
+
|
53
|
+
def run!(cmd)
|
54
|
+
out = run(cmd)
|
55
|
+
raise "Error while running #{cmd.inspect}: #{out}" unless $?.success?
|
56
|
+
out
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module DerailedBenchmarks
|
2
|
+
class Git
|
3
|
+
end
|
4
|
+
end
|
5
|
+
require_relative "in_path.rb"
|
6
|
+
require_relative "commit.rb"
|
7
|
+
|
8
|
+
module DerailedBenchmarks
|
9
|
+
# Wraps two or more git commits in a specific location
|
10
|
+
#
|
11
|
+
# Returns an array of GitCommit objects that can be used to manipulate
|
12
|
+
# and checkout the repo
|
13
|
+
#
|
14
|
+
# Example:
|
15
|
+
#
|
16
|
+
# `git clone https://sharpstone/default_ruby tmp/default_ruby`
|
17
|
+
#
|
18
|
+
# project = GitSwitchProject.new(path: "tmp/default_ruby")
|
19
|
+
#
|
20
|
+
# By default it will represent the last two commits:
|
21
|
+
#
|
22
|
+
# project.commits.length # => 2
|
23
|
+
#
|
24
|
+
# You can pass in explicit REFs in an array:
|
25
|
+
#
|
26
|
+
# ref_array = ["da748a59340be8b950e7bbbfb32077eb67d70c3c", "9b19275a592f148e2a53b87ead4ccd8c747539c9"]
|
27
|
+
# project = GitSwitchProject.new(path: "tmp/default_ruby", ref_array: ref_array)
|
28
|
+
#
|
29
|
+
# puts project.commits.map(&:ref) == ref_array # => true
|
30
|
+
#
|
31
|
+
#
|
32
|
+
# It knows the current branch or sha:
|
33
|
+
#
|
34
|
+
# `cd tmp/ruby && git checkout -b mybranch`
|
35
|
+
# project.current_branch_or_sha #=> "mybranch"
|
36
|
+
#
|
37
|
+
# It can be used for safely wrapping checkouts to ensure the project returns to it's original branch:
|
38
|
+
#
|
39
|
+
# project.restore_branch_on_return do
|
40
|
+
# project.commits.first.checkout!
|
41
|
+
# project.current_branch_or_sha # => "da748a593"
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# project.current_branch_or_sha # => "mybranch"
|
45
|
+
class Git::SwitchProject
|
46
|
+
attr_reader :commits
|
47
|
+
|
48
|
+
def initialize(path: , ref_array: [], io: STDOUT, log_dir: "/dev/null")
|
49
|
+
@path = Pathname.new(path)
|
50
|
+
|
51
|
+
@in_git_path = Git::InPath.new(@path.expand_path)
|
52
|
+
|
53
|
+
raise "Must be a path with a .git directory '#{@path}'" if !@path.join(".git").exist?
|
54
|
+
@io = io
|
55
|
+
@commits = []
|
56
|
+
log_dir = Pathname(log_dir)
|
57
|
+
|
58
|
+
expand_refs(ref_array).each do |ref|
|
59
|
+
restore_branch_on_return(quiet: true) do
|
60
|
+
@commits << Git::Commit.new(path: @path, ref: ref, log_dir: log_dir)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
if (duplicate = @commits.group_by(&:short_sha).detect {|(k, v)| v.length > 1})
|
65
|
+
raise "Duplicate SHA resolved #{duplicate[0].inspect}: #{duplicate[1].map {|c| "'#{c.ref}' => '#{c.short_sha}'"}.join(", ") } at #{@path}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def current_branch_or_sha
|
70
|
+
branch_or_sha = @in_git_path.branch
|
71
|
+
branch_or_sha ||= @in_git_path.short_sha
|
72
|
+
branch_or_sha
|
73
|
+
end
|
74
|
+
|
75
|
+
def dirty?
|
76
|
+
!clean?
|
77
|
+
end
|
78
|
+
|
79
|
+
# https://stackoverflow.com/a/3879077/147390
|
80
|
+
def clean?
|
81
|
+
@in_git_path.run("git diff-index --quiet HEAD --") && $?.success?
|
82
|
+
end
|
83
|
+
|
84
|
+
private def status(pattern: "*.gemspec")
|
85
|
+
@in_git_path.run("git status #{pattern}")
|
86
|
+
end
|
87
|
+
|
88
|
+
def restore_branch_on_return(quiet: false)
|
89
|
+
if dirty? && status.include?("gemspec")
|
90
|
+
dirty_gemspec = true
|
91
|
+
unless quiet
|
92
|
+
@io.puts "Working tree at #{@path} is dirty, stashing. This will be popped on return"
|
93
|
+
@io.puts "Bundler modifies gemspec files on git install, this is normal"
|
94
|
+
@io.puts "Original status:\n#{status}"
|
95
|
+
end
|
96
|
+
@in_git_path.run!("git stash")
|
97
|
+
end
|
98
|
+
branch_or_sha = self.current_branch_or_sha
|
99
|
+
yield
|
100
|
+
ensure
|
101
|
+
return unless branch_or_sha
|
102
|
+
@io.puts "Resetting git dir of '#{@path.to_s}' to #{branch_or_sha.inspect}" unless quiet
|
103
|
+
|
104
|
+
@in_git_path.checkout!(branch_or_sha)
|
105
|
+
if dirty_gemspec
|
106
|
+
out = @in_git_path.run!("git stash apply 2>&1")
|
107
|
+
@io.puts "Applying stash of '#{@path.to_s}':\n#{out}" unless quiet
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# case ref_array.length
|
112
|
+
# when >= 2
|
113
|
+
# returns original array
|
114
|
+
# when 1
|
115
|
+
# returns the given ref plus the one before it
|
116
|
+
# when 0
|
117
|
+
# returns the most recent 2 refs
|
118
|
+
private def expand_refs(ref_array)
|
119
|
+
return ref_array if ref_array.length >= 2
|
120
|
+
|
121
|
+
@in_git_path.checkout!(ref_array.first) if ref_array.first
|
122
|
+
|
123
|
+
branches_string = @in_git_path.run!("git log --format='%H' -n 2")
|
124
|
+
ref_array = branches_string.split($/)
|
125
|
+
return ref_array
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
|
@@ -110,7 +110,7 @@ namespace :perf do
|
|
110
110
|
STDERR.puts "Bad request to #{cmd.inspect} \n\n***RESPONSE***:\n\n#{ response.inspect }"
|
111
111
|
|
112
112
|
FileUtils.mkdir_p("tmp")
|
113
|
-
File.open("tmp/fail.html", "w+") {|f| f.write response
|
113
|
+
File.open("tmp/fail.html", "w+") {|f| f.write response }
|
114
114
|
|
115
115
|
`open #{File.expand_path("tmp/fail.html")}` if ENV["DERAILED_DEBUG"]
|
116
116
|
|
@@ -13,6 +13,16 @@ module DerailedBenchmarks
|
|
13
13
|
def initialize(name)
|
14
14
|
@name = name
|
15
15
|
@children = {}
|
16
|
+
@cost = 0
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.reset!
|
20
|
+
REQUIRED_BY.clear
|
21
|
+
if defined?(Kernel::REQUIRE_STACK)
|
22
|
+
Kernel::REQUIRE_STACK.clear
|
23
|
+
|
24
|
+
Kernel::REQUIRE_STACK.push(TOP_REQUIRE)
|
25
|
+
end
|
16
26
|
end
|
17
27
|
|
18
28
|
def <<(tree)
|
@@ -40,7 +50,7 @@ module DerailedBenchmarks
|
|
40
50
|
end
|
41
51
|
|
42
52
|
def to_string
|
43
|
-
str =
|
53
|
+
str = String.new("#{name}: #{cost.round(4)} MiB")
|
44
54
|
if parent && REQUIRED_BY[self.name.to_s]
|
45
55
|
names = REQUIRED_BY[self.name.to_s].uniq - [parent.name.to_s]
|
46
56
|
if names.any?
|
@@ -16,24 +16,30 @@ module DerailedBenchmarks
|
|
16
16
|
# x.average # => 10.5
|
17
17
|
# x.name # => "muhfile"
|
18
18
|
class StatsForFile
|
19
|
-
attr_reader :name, :values, :desc, :time
|
19
|
+
attr_reader :name, :values, :desc, :time, :short_sha
|
20
20
|
|
21
|
-
def initialize(file:, name:, desc: "", time: )
|
21
|
+
def initialize(file:, name:, desc: "", time: , short_sha: nil)
|
22
22
|
@file = Pathname.new(file)
|
23
23
|
FileUtils.touch(@file)
|
24
24
|
|
25
25
|
@name = name
|
26
26
|
@desc = desc
|
27
27
|
@time = time
|
28
|
+
@short_sha = short_sha
|
28
29
|
end
|
29
30
|
|
30
31
|
def call
|
31
32
|
load_file!
|
33
|
+
return if values.empty?
|
32
34
|
|
33
35
|
@median = (values[(values.length - 1) / 2] + values[values.length/ 2]) / 2.0
|
34
36
|
@average = values.inject(:+) / values.length
|
35
37
|
end
|
36
38
|
|
39
|
+
def empty?
|
40
|
+
values.empty?
|
41
|
+
end
|
42
|
+
|
37
43
|
def median
|
38
44
|
@median.to_f
|
39
45
|
end
|
@@ -2,9 +2,9 @@
|
|
2
2
|
|
3
3
|
require 'bigdecimal'
|
4
4
|
require 'statistics'
|
5
|
-
require 'unicode_plot'
|
6
5
|
require 'stringio'
|
7
6
|
require 'mini_histogram'
|
7
|
+
require 'mini_histogram/plot'
|
8
8
|
|
9
9
|
module DerailedBenchmarks
|
10
10
|
# A class used to read several benchmark files
|
@@ -29,14 +29,28 @@ module DerailedBenchmarks
|
|
29
29
|
FORMAT = "%0.4f"
|
30
30
|
attr_reader :stats, :oldest, :newest
|
31
31
|
|
32
|
-
def initialize(
|
32
|
+
def initialize(input)
|
33
33
|
@files = []
|
34
34
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
35
|
+
if input.is_a?(Hash)
|
36
|
+
hash = input
|
37
|
+
hash.each do |branch, info_hash|
|
38
|
+
file = info_hash.fetch(:file)
|
39
|
+
desc = info_hash.fetch(:desc)
|
40
|
+
time = info_hash.fetch(:time)
|
41
|
+
short_sha = info_hash[:short_sha]
|
42
|
+
@files << StatsForFile.new(file: file, desc: desc, time: time, name: branch, short_sha: short_sha)
|
43
|
+
end
|
44
|
+
else
|
45
|
+
input.each do |commit|
|
46
|
+
@files << StatsForFile.new(
|
47
|
+
file: commit.file,
|
48
|
+
desc: commit.desc,
|
49
|
+
time: commit.time,
|
50
|
+
name: commit.ref,
|
51
|
+
short_sha: commit.short_sha
|
52
|
+
)
|
53
|
+
end
|
40
54
|
end
|
41
55
|
@files.sort_by! { |f| f.time }
|
42
56
|
@oldest = @files.first
|
@@ -46,6 +60,8 @@ module DerailedBenchmarks
|
|
46
60
|
def call
|
47
61
|
@files.each(&:call)
|
48
62
|
|
63
|
+
return self if @files.detect(&:empty?)
|
64
|
+
|
49
65
|
stats_95 = statistical_test(confidence: 95)
|
50
66
|
|
51
67
|
# If default check is good, see if we also pass a more rigorous test
|
@@ -104,25 +120,28 @@ module DerailedBenchmarks
|
|
104
120
|
end
|
105
121
|
|
106
122
|
def histogram(io = $stdout)
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
123
|
+
dual_histogram = MiniHistogram.dual_plot do |a, b|
|
124
|
+
a.values = newest.values
|
125
|
+
a.options = {
|
126
|
+
title: "\n [#{newest.short_sha || newest.name}] description:\n #{newest.desc.inspect}",
|
127
|
+
xlabel: "# of runs in range"
|
128
|
+
}
|
129
|
+
b.values = oldest.values
|
130
|
+
b.options = {
|
131
|
+
title: "\n [#{oldest.short_sha || oldest.name}] description:\n #{oldest.desc.inspect}",
|
116
132
|
xlabel: "# of runs in range"
|
117
|
-
|
118
|
-
plot.render(io)
|
119
|
-
io.puts
|
133
|
+
}
|
120
134
|
end
|
121
135
|
|
122
136
|
io.puts
|
137
|
+
io.puts "Histograms (time ranges are in seconds):"
|
138
|
+
io.puts(dual_histogram)
|
139
|
+
io.puts
|
123
140
|
end
|
124
141
|
|
125
142
|
def banner(io = $stdout)
|
143
|
+
return if @files.detect(&:empty?)
|
144
|
+
|
126
145
|
io.puts
|
127
146
|
if significant?
|
128
147
|
io.puts "❤️ ❤️ ❤️ (Statistically Significant) ❤️ ❤️ ❤️"
|
@@ -130,11 +149,11 @@ module DerailedBenchmarks
|
|
130
149
|
io.puts "👎👎👎(NOT Statistically Significant) 👎👎👎"
|
131
150
|
end
|
132
151
|
io.puts
|
133
|
-
io.puts "[#{newest.name}] #{newest.desc.inspect}
|
152
|
+
io.puts "[#{newest.short_sha || newest.name}] (#{FORMAT % newest.median} seconds) #{newest.desc.inspect} ref: #{newest.name.inspect}"
|
134
153
|
io.puts " #{change_direction} by:"
|
135
154
|
io.puts " #{align}#{FORMAT % x_faster}x [older/newer]"
|
136
155
|
io.puts " #{FORMAT % percent_faster}\% [(older - newer) / older * 100]"
|
137
|
-
io.puts "[#{oldest.name}] #{oldest.desc.inspect}
|
156
|
+
io.puts "[#{oldest.short_sha || oldest.name}] (#{FORMAT % oldest.median} seconds) #{oldest.desc.inspect} ref: #{oldest.name.inspect}"
|
138
157
|
io.puts
|
139
158
|
io.puts "Iterations per sample: #{ENV["TEST_COUNT"]}"
|
140
159
|
io.puts "Samples: #{newest.values.length}"
|
@@ -17,96 +17,57 @@ namespace :perf do
|
|
17
17
|
script = ENV["DERAILED_SCRIPT"] || "bundle exec derailed exec perf:test"
|
18
18
|
|
19
19
|
if ENV["DERAILED_PATH_TO_LIBRARY"]
|
20
|
-
library_dir = ENV["DERAILED_PATH_TO_LIBRARY"]
|
20
|
+
library_dir = ENV["DERAILED_PATH_TO_LIBRARY"].chomp
|
21
21
|
else
|
22
22
|
library_dir = DerailedBenchmarks.rails_path_on_disk
|
23
23
|
end
|
24
|
-
|
25
|
-
raise "Must be a path with a .git directory '#{library_dir}'" unless File.exist?(File.join(library_dir, ".git"))
|
26
|
-
|
27
|
-
# Use either the explicit SHAs when present or grab last two SHAs from commit history
|
28
|
-
# if only one SHA is given, then use it and the last SHA from commit history
|
29
|
-
branch_names = []
|
30
|
-
branch_names = ENV.fetch("SHAS_TO_TEST").split(",") if ENV["SHAS_TO_TEST"]
|
31
|
-
if branch_names.length < 2
|
32
|
-
Dir.chdir(library_dir) do
|
33
|
-
run!("git checkout '#{branch_names.first}'") unless branch_names.empty?
|
34
|
-
|
35
|
-
branches = run!('git log --format="%H" -n 2').chomp.split($/)
|
36
|
-
if branch_names.empty?
|
37
|
-
branch_names = branches
|
38
|
-
else
|
39
|
-
branches.shift
|
40
|
-
branch_names << branches.shift
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
current_library_branch = ""
|
46
|
-
Dir.chdir(library_dir) { current_library_branch = run!('git describe --contains --all HEAD').chomp }
|
24
|
+
library_dir = Pathname.new(library_dir)
|
47
25
|
|
48
26
|
out_dir = Pathname.new("tmp/compare_branches/#{Time.now.strftime('%Y-%m-%d-%H-%M-%s-%N')}")
|
49
27
|
out_dir.mkpath
|
50
28
|
|
51
|
-
|
52
|
-
branch_info = {}
|
53
|
-
branch_to_sha = {}
|
29
|
+
ref_string = ENV["SHAS_TO_TEST"] || ENV["REFS_TO_TEST"] || ""
|
54
30
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
short_sha = run!("git rev-parse --short HEAD").strip
|
61
|
-
branch_to_sha[branch] = short_sha
|
31
|
+
project = DerailedBenchmarks::Git::SwitchProject.new(
|
32
|
+
path: library_dir,
|
33
|
+
ref_array: ref_string.split(","),
|
34
|
+
log_dir: out_dir
|
35
|
+
)
|
62
36
|
|
63
|
-
|
64
|
-
end
|
65
|
-
run!("#{script}")
|
66
|
-
end
|
37
|
+
stats = DerailedBenchmarks::StatsFromDir.new(project.commits)
|
67
38
|
|
39
|
+
# Advertise branch names early to make sure people know what they're testing
|
68
40
|
puts
|
69
41
|
puts
|
70
|
-
|
71
|
-
short_sha
|
72
|
-
desc = branch_info[short_sha][:desc]
|
73
|
-
puts "Testing #{i + 1}: #{short_sha}: #{desc}"
|
42
|
+
project.commits.each_with_index do |commit, i|
|
43
|
+
puts "Testing #{i + 1}: #{commit.short_sha}: #{commit.description}"
|
74
44
|
end
|
75
45
|
puts
|
76
46
|
puts
|
77
47
|
|
78
|
-
|
79
|
-
|
80
|
-
|
48
|
+
project.restore_branch_on_return do
|
49
|
+
DERAILED_SCRIPT_COUNT.times do |i|
|
50
|
+
puts "Sample: #{i.next}/#{DERAILED_SCRIPT_COUNT} iterations per sample: #{ENV['TEST_COUNT']}"
|
51
|
+
project.commits.each do |commit|
|
52
|
+
commit.checkout!
|
81
53
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
Dir.chdir(library_dir) { run!("git checkout '#{branch}'") }
|
86
|
-
run!(" #{script} 2>&1 | tail -n 1 >> '#{file}'")
|
87
|
-
end
|
54
|
+
output = run!("#{script} 2>&1")
|
55
|
+
commit.log.open("a") {|f| f.puts output.lines.last }
|
56
|
+
end
|
88
57
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
58
|
+
if (i % 50).zero?
|
59
|
+
puts "Intermediate result"
|
60
|
+
stats.call.banner
|
61
|
+
puts "Continuing execution"
|
62
|
+
end
|
94
63
|
end
|
95
64
|
end
|
96
65
|
|
97
66
|
ensure
|
98
|
-
if library_dir && current_library_branch
|
99
|
-
puts "Resetting git dir of '#{library_dir.to_s}' to #{current_library_branch.inspect}"
|
100
|
-
Dir.chdir(library_dir) do
|
101
|
-
run!("git checkout '#{current_library_branch}'")
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
67
|
if stats
|
106
|
-
stats.call
|
107
|
-
stats.banner
|
68
|
+
stats.call.banner
|
108
69
|
|
109
|
-
result_file = out_dir
|
70
|
+
result_file = out_dir.join("results.txt")
|
110
71
|
File.open(result_file, "w") do |f|
|
111
72
|
stats.banner(f)
|
112
73
|
end
|
@@ -286,6 +247,42 @@ namespace :perf do
|
|
286
247
|
puts "Also try uploading #{file_name.inspect} to http://tenderlove.github.io/heap-analyzer/"
|
287
248
|
end
|
288
249
|
|
250
|
+
desc "three heaps generation for comparison."
|
251
|
+
task :heap_diff => [:setup] do
|
252
|
+
require 'objspace'
|
253
|
+
|
254
|
+
launch_time = Time.now.iso8601
|
255
|
+
FileUtils.mkdir_p("tmp")
|
256
|
+
ObjectSpace.trace_object_allocations_start
|
257
|
+
3.times do |i|
|
258
|
+
file_name = "tmp/#{launch_time}-heap-#{i}.ndjson"
|
259
|
+
puts "Running #{ TEST_COUNT } times"
|
260
|
+
TEST_COUNT.times {
|
261
|
+
call_app
|
262
|
+
}
|
263
|
+
GC.start
|
264
|
+
|
265
|
+
puts "Heap file generated: #{ file_name.inspect }"
|
266
|
+
ObjectSpace.dump_all(output: File.open(file_name, 'w'))
|
267
|
+
end
|
268
|
+
|
269
|
+
require 'heapy'
|
270
|
+
|
271
|
+
puts ""
|
272
|
+
puts "Diff"
|
273
|
+
puts "===="
|
274
|
+
Heapy::Diff.new(
|
275
|
+
before: "tmp/#{launch_time}-heap-0.ndjson",
|
276
|
+
after: "tmp/#{launch_time}-heap-1.ndjson",
|
277
|
+
retained: "tmp/#{launch_time}-heap-2.ndjson"
|
278
|
+
).call
|
279
|
+
|
280
|
+
puts ""
|
281
|
+
puts "Run `$ heapy --help` for more options"
|
282
|
+
puts ""
|
283
|
+
puts "Also read https://speakerdeck.com/samsaffron/why-ruby-2-dot-1-excites-me?slide=27 to understand better what you are reading."
|
284
|
+
end
|
285
|
+
|
289
286
|
def run!(cmd)
|
290
287
|
out = `#{cmd}`
|
291
288
|
raise "Error while running #{cmd.inspect}: #{out}" unless $?.success?
|