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