derailed_benchmarks 1.8.0 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 24d0d07556d2d6676ea8b2c06d58583071fd9302c056d36db05d252480889308
4
- data.tar.gz: 0c783db16c1612173975c12760b101fc30cbd5765d296b2fd66506992305ac69
3
+ metadata.gz: 1004e69d7a94d86e514586324fc21ce1871ca9f8690c989eafcf5283fc69e293
4
+ data.tar.gz: 0bfbb09013d068279ee58e1361ef44f122c944662cc590941299b12d80151a80
5
5
  SHA512:
6
- metadata.gz: e37aa667a5fe031a384a343de999a34ef9f1e0ba98f8190d6f86d326ee30f968e3555344fbaf8841f99da85354c2b4383fe1b75ab166eb0df9134d20ee48fd95
7
- data.tar.gz: 3f581637e835d808b8264786ffd70159fe534c4147e72e65f5649fe7ecb0b92b9edc215c9edd43e2ca51a510793ae475da3e0318262103bb741823746d9f3421
6
+ metadata.gz: 48dee96d301b69c53f17467bd19918c9ea788568b5d630fe3888e2c0659345dfdef66be0b911ed2624af1cc11c9507a3700760b40f261d846f092b5e49743b79
7
+ data.tar.gz: d69df370bb2cd4aebc48d1b077eb0d81b9da5dc659209da091d15b49df76af196f32c3b4daa0d402db2fa1aaf968067fc8eb9e3c690a9fce1c2c4458087256aa
@@ -0,0 +1,75 @@
1
+ version: 2.1
2
+ orbs:
3
+ ruby: circleci/ruby@1.1.2
4
+ references:
5
+ run_tests: &run_tests
6
+ run:
7
+ name: Run test suite
8
+ command: bundle exec rake test
9
+ # Needed because tests execute raw git commands
10
+ set_git_config: &set_git_config
11
+ run:
12
+ name: Set Git config
13
+ command: git config --global user.email "you@example.com"; git config --global user.name "Your Name"
14
+ restore: &restore
15
+ restore_cache:
16
+ keys:
17
+ - v1_bundler_deps-{{ .Environment.CIRCLE_JOB }}
18
+ save: &save
19
+ save_cache:
20
+ paths:
21
+ - ./vendor/bundle
22
+ key: v1_bundler_deps-{{ .Environment.CIRCLE_JOB }} # CIRCLE_JOB e.g. "ruby-2.5"
23
+ bundle: &bundle
24
+ run:
25
+ name: install dependencies
26
+ command: |
27
+ echo "export BUNDLE_JOBS=4" >> $BASH_ENV
28
+ echo "export BUNDLE_RETRY=3" >> $BASH_ENV
29
+ echo "export BUNDLE_PATH=$(pwd)/vendor/bundle" >> $BASH_ENV
30
+ echo "export BUNDLE_GEMFILE=$(pwd)/gemfiles/$GEMFILE_NAME" >> $BASH_ENV
31
+ source $BASH_ENV
32
+
33
+ bundle install
34
+ bundle update
35
+ bundle clean
36
+ mysteps: &mysteps
37
+ steps:
38
+ - checkout
39
+ - <<: *set_git_config
40
+ - <<: *restore
41
+ - <<: *bundle
42
+ - <<: *run_tests
43
+ - <<: *save
44
+
45
+ jobs:
46
+ test:
47
+ parameters:
48
+ ruby_version:
49
+ type: string
50
+ gemfile:
51
+ type: string
52
+ docker:
53
+ - image: "ruby:<< parameters.ruby_version >>"
54
+ environment:
55
+ GEMFILE_NAME: <<parameters.gemfile>>
56
+ steps:
57
+ - checkout
58
+ - <<: *set_git_config
59
+ - <<: *restore
60
+ - <<: *bundle
61
+ - <<: *run_tests
62
+ - <<: *save
63
+
64
+ workflows:
65
+ all-tests:
66
+ jobs:
67
+ - test:
68
+ matrix:
69
+ parameters:
70
+ ruby_version: ["2.5.8", "2.7.2", "3.0.0"]
71
+ gemfile: ["rails_5_2.gemfile", "rails_6_1.gemfile", "rails_git.gemfile"]
72
+ exclude:
73
+ - ruby_version: "3.0.0"
74
+ gemfile: rails_5_2.gemfile
75
+ name: test-ruby-<<matrix.ruby_version>>-<<matrix.gemfile>>
data/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  ## HEAD
2
2
 
3
+ ## 2.1.1
4
+
5
+ - Fix Thor's deprecation warning by implementing `exit_on_failure?` (https://github.com/schneems/derailed_benchmarks/pull/195)
6
+
7
+ ## 2.1.0
8
+
9
+ - Add `perf:heap_diff` tool (https://github.com/schneems/derailed_benchmarks/pull/193)
10
+
11
+ ## 2.0.1
12
+
13
+ - `rack-test` dependency added (https://github.com/schneems/derailed_benchmarks/pull/187)
14
+
15
+ ## 2.0.0
16
+
17
+ - Syntax errors easier to debug with `dead_end` gem (https://github.com/schneems/derailed_benchmarks/pull/182)
18
+ - Minimum ruby version is now 2.5 (https://github.com/schneems/derailed_benchmarks/pull/183)
19
+ - Histograms are now printed side-by-side (https://github.com/schneems/derailed_benchmarks/pull/179)
20
+
21
+ ## 1.8.1
22
+
23
+ - Derailed now tracks memory use from `load` in addition to `require` (https://github.com/schneems/derailed_benchmarks/pull/178)
24
+ - Correct logging of unsuccessful curl requests to file (https://github.com/schneems/derailed_benchmarks/pull/172)
25
+
3
26
  ## 1.8.0
4
27
 
5
28
  - Ruby 2.2 is now officialy supported and tested (https://github.com/schneems/derailed_benchmarks/pull/177)
data/README.md CHANGED
@@ -9,16 +9,13 @@ A series of things you can use to benchmark a Rails or Ruby app.
9
9
 
10
10
  ## Compatibility/Requirements
11
11
 
12
- This gem has been tested and is known to work with Rails 3.2+ using Ruby
13
- 2.2+. Some commands __may__ work on older versions of Ruby, but not all commands are supported.
14
-
15
12
  For some benchmarks, not all, you'll need to verify you have a working version of curl on your OS:
16
13
 
17
14
  ```
18
15
  $ which curl
19
16
  /usr/bin/curl
20
17
  $ curl -V
21
- curl 7.37.1 #...
18
+ curl 7.64.1 #...
22
19
  ```
23
20
 
24
21
  ## Install
@@ -201,12 +198,17 @@ You can run commands against your app by running `$ derailed exec`. There are se
201
198
  ```
202
199
  $ bundle exec derailed exec --help
203
200
  $ derailed exec perf:allocated_objects # outputs allocated object diff after app is called TEST_COUNT times
201
+ $ derailed exec perf:app # runs the performance test against two most recent commits of the current app
204
202
  $ derailed exec perf:gc # outputs GC::Profiler.report data while app is called TEST_COUNT times
203
+ $ derailed exec perf:heap # heap analyzer
205
204
  $ derailed exec perf:ips # iterations per second
205
+ $ derailed exec perf:library # runs the same test against two different branches for statistical comparison
206
206
  $ derailed exec perf:mem # show memory usage caused by invoking require per gem
207
- $ derailed exec perf:objects # profiles ruby allocation
208
207
  $ derailed exec perf:mem_over_time # outputs memory usage over time
208
+ $ derailed exec perf:objects # profiles ruby allocation
209
+ $ derailed exec perf:stackprof # stackprof
209
210
  $ derailed exec perf:test # hits the url TEST_COUNT times
211
+ $ derailed exec perf:heap_diff # three heaps generation for comparison
210
212
  ```
211
213
 
212
214
  Instead of going over each command we'll look at common problems and which commands are best used to diagnose them. Later on we'll cover all of the environment variables you can use to configure derailed benchmarks in it's own section.
@@ -274,7 +276,7 @@ This is similar to `$ bundle exec derailed bundle:objects` however it includes o
274
276
 
275
277
  ## I want a Heap Dump
276
278
 
277
- If you're still struggling with runtime memory you can generate a heap dump that can later be analyzed using [heap_inspect](https://github.com/schneems/heapy).
279
+ If you're still struggling with runtime memory you can generate a heap dump that can later be analyzed using [heapy](https://github.com/schneems/heapy).
278
280
 
279
281
  ```
280
282
  $ bundle exec derailed exec perf:heap
@@ -298,6 +300,40 @@ For more help on getting data from a heap dump see
298
300
  $ heapy --help
299
301
  ```
300
302
 
303
+ ### I want more heap dumps
304
+
305
+ When searching for a leak, you can use heap dumps for comparison to see what is
306
+ retained. See [Analyzing memory heaps](https://medium.com/klaxit-techblog/tracking-a-ruby-memory-leak-in-2021-9eb56575f731#875b)
307
+ (inspired from [SamSaffron's original idea](https://speakerdeck.com/samsaffron/why-ruby-2-dot-1-excites-me?slide=27))
308
+ for a clear example. You can generate 3 dumps (one every `TEST_COUNT` calls) using the
309
+ next command:
310
+
311
+ ```
312
+ $ bundle exec derailed exec perf:heap_diff
313
+ Endpoint: "/"
314
+ Running 1000 times
315
+ Heap file generated: "tmp/2021-05-06T15:19:26+02:00-heap-0.ndjson"
316
+ Running 1000 times
317
+ Heap file generated: "tmp/2021-05-06T15:19:26+02:00-heap-1.ndjson"
318
+ Running 1000 times
319
+ Heap file generated: "tmp/2021-05-06T15:19:26+02:00-heap-2.ndjson"
320
+
321
+ Diff
322
+ ====
323
+ Retained STRING 90 objects of size 4790/91280 (in bytes) at: /Users/ulysse/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/rack-2.2.3/lib/rack/utils.rb:461
324
+ Retained ICLASS 20 objects of size 800/91280 (in bytes) at: /Users/ulysse/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sinatra-contrib-2.0.8.1/lib/sinatra/namespace.rb:198
325
+ Retained DATA 20 objects of size 1360/91280 (in bytes) at: /Users/ulysse/.rbenv/versions/2.7.2/lib/ruby/2.7.0/monitor.rb:238
326
+ Retained STRING 20 objects of size 800/91280 (in bytes) at: /Users/ulysse/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/rack-protection-2.0.8.1/lib/rack/protection/xss_header.rb:20
327
+ Retained STRING 10 objects of size 880/91280 (in bytes) at: /Users/ulysse/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/newrelic_rpm-5.4.0.347/lib/new_relic/agent/transaction.rb:890
328
+ Retained CLASS 10 objects of size 4640/91280 (in bytes) at: /Users/ulysse/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sinatra-contrib-2.0.8.1/lib/sinatra/namespace.rb:198
329
+ Retained IMEMO 10 objects of size 480/91280 (in bytes) at: /Users/ulysse/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sinatra-2.0.8.1/lib/sinatra/base.rb:1017
330
+ ...
331
+
332
+ Run `$ heapy --help` for more options
333
+
334
+ Also read https://medium.com/klaxit-techblog/tracking-a-ruby-memory-leak-in-2021-9eb56575f731#875b to understand better what you are reading.
335
+ ```
336
+
301
337
  ### Memory Is large at boot.
302
338
 
303
339
  Ruby memory typically goes in one direction, up. If your memory is large when you boot the application it will likely only increase. In addition to debugging memory retained from dependencies obtained while running `$ derailed bundle:mem` you'll likely want to see how your own files contribute to memory use.
@@ -448,11 +484,37 @@ When the test is done it will output which commit "won" and by how much:
448
484
  ```
449
485
  ❤️ ❤️ ❤️ (Statistically Significant) ❤️ ❤️ ❤️
450
486
 
451
- [7b4d80cb37] "1.8x Faster Partial Caching - Faster Cache Keys" - (10.9711965 seconds)
452
- FASTER by:
453
- 1.0870x [older/newer]
454
- 8.0026% [(older - newer) / older * 100]
455
- [13d6aa3a7b] "Merge pull request #36284 from kamipo/fix_eager_loading_with_string_joins" - (11.9255485 seconds)
487
+ [f1ab117] (11.3844 seconds) "I am the new commit" ref: "winner"
488
+ FASTER 🚀🚀🚀 by:
489
+ 1.0062x [older/newer]
490
+ 0.6147% [(older - newer) / older * 100]
491
+ [5594a2d] (11.4548 seconds) "Old commit" ref: "loser"
492
+
493
+ Iterations per sample:
494
+ Samples: 100
495
+
496
+ Test type: Kolmogorov Smirnov
497
+ Confidence level: 99.0 %
498
+ Is significant? (max > critical): true
499
+ D critical: 0.2145966026289347
500
+ D max: 0.26
501
+
502
+ Histograms (time ranges are in seconds):
503
+
504
+ [f1ab117] description: [5594a2d] description:
505
+ "I am the new commit" "Old commit"
506
+ ┌ ┐ ┌ ┐
507
+ [11.2 , 11.28) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 12 [11.2 , 11.28) ┤▇▇▇▇ 3
508
+ [11.28, 11.36) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 22 [11.28, 11.36) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 19
509
+ [11.35, 11.43) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 30 [11.35, 11.43) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 17
510
+ [11.43, 11.51) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 17 [11.43, 11.51) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 25
511
+ [11.5 , 11.58) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 13 [11.5 , 11.58) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 15
512
+ [11.58, 11.66) ┤▇▇▇▇▇▇▇ 6 [11.58, 11.66) ┤▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 13
513
+ [11.65, 11.73) ┤ 0 [11.65, 11.73) ┤▇▇▇▇ 3
514
+ [11.73, 11.81) ┤ 0 [11.73, 11.81) ┤▇▇▇▇ 3
515
+ [11.8 , 11.88) ┤ 0 [11.8 , 11.88) ┤▇▇▇ 2
516
+ └ ┘ └ ┘
517
+ # of runs in range # of runs in range
456
518
  ```
457
519
 
458
520
  You can provide this to the Rails team along with the example app you used to benchmark (so they can independently verify if needed).
data/bin/derailed CHANGED
@@ -20,6 +20,9 @@ Bundler.setup
20
20
  require 'thor'
21
21
 
22
22
  class DerailedBenchmarkCLI < Thor
23
+ def self.exit_on_failure?
24
+ true
25
+ end
23
26
 
24
27
  desc "exec", "executes given derailed benchmark"
25
28
  def exec(task = nil)
@@ -20,21 +20,23 @@ Gem::Specification.new do |gem|
20
20
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
21
21
  gem.require_paths = ["lib"]
22
22
 
23
- gem.required_ruby_version = ">= 2.2.0"
23
+ gem.required_ruby_version = ">= 2.5.0"
24
24
 
25
25
  gem.add_dependency "heapy", "~> 0"
26
- gem.add_dependency "memory_profiler", "~> 0"
26
+ gem.add_dependency "memory_profiler", ">= 0", "< 2"
27
27
  gem.add_dependency "get_process_mem", "~> 0"
28
28
  gem.add_dependency "benchmark-ips", "~> 2"
29
29
  gem.add_dependency "rack", ">= 1"
30
30
  gem.add_dependency "rake", "> 10", "< 14"
31
31
  gem.add_dependency "thor", ">= 0.19", "< 2"
32
32
  gem.add_dependency "ruby-statistics", ">= 2.1"
33
- gem.add_dependency "mini_histogram", ">= 0.2.1"
33
+ gem.add_dependency "mini_histogram", ">= 0.3.0"
34
+ gem.add_dependency "dead_end", ">= 0"
35
+ gem.add_dependency "rack-test", ">= 0"
34
36
 
37
+ gem.add_development_dependency "webrick", ">= 0"
35
38
  gem.add_development_dependency "capybara", "~> 2"
36
39
  gem.add_development_dependency "m"
37
40
  gem.add_development_dependency "rails", "> 3", "<= 7"
38
41
  gem.add_development_dependency "devise", "> 3", "< 6"
39
- gem.add_development_dependency "appraisal", "2.2.0"
40
42
  end
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # BUNDLE_GEMFILE="gemfiles/rails_5_1.gemfile" bundle exec m test/integration/tasks_test.rb:30
4
+ #
3
5
  # This file was generated by Appraisal
4
6
 
5
7
  source "https://rubygems.org"
6
8
 
7
- gem "rails", "~> 5.1.0"
9
+ gem "rails", "~> 5.1.7"
8
10
 
9
11
  group :development, :test do
10
12
  gem "sqlite3", platform: [:ruby, :mswin, :mingw]
@@ -1,10 +1,10 @@
1
- # frozen_string_literal: true
2
-
3
1
  # This file was generated by Appraisal
2
+ #
3
+ # BUNDLE_GEMFILE="gemfiles/rails_5_2.gemfile" bundle exec m test/integration/tasks_test.rb:30
4
4
 
5
5
  source "https://rubygems.org"
6
6
 
7
- gem "rails", "~> 5.2.0"
7
+ gem "rails", "~> 5.2.4.4"
8
8
 
9
9
  group :development, :test do
10
10
  gem "sqlite3", platform: [:ruby, :mswin, :mingw]
@@ -0,0 +1,13 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 6.1.1"
6
+
7
+ group :development, :test do
8
+ gem "sqlite3", platform: [:ruby, :mswin, :mingw]
9
+ gem "activerecord-jdbcsqlite3-adapter", "~> 1.3.13", platform: :jruby
10
+ gem "test-unit", "~> 3.0"
11
+ end
12
+
13
+ gemspec path: "../"
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # $ BUNDLE_GEMFILE="$(pwd)/gemfiles/rails_git.gemfile" bundle exec m test/integration/tasks_test.rb:30
3
+ # $ BUNDLE_GEMFILE="$(pwd)/gemfiles/rails_git.gemfile" bundle exec m test/integration/tasks_test.rb:50
4
4
 
5
5
  source "https://rubygems.org"
6
6
 
7
- gem "rails", github: "rails/rails", ref: "3054e1d584e7eca110c69a1f8423f2e0866abbf9"
7
+ gem "rails", github: "rails/rails", ref: "12bb9d32f56883914abcd98fd72e3c68c444808d"
8
8
 
9
9
  gem 'devise', github: "plataformatec/devise"
10
10
 
@@ -2,9 +2,10 @@
2
2
 
3
3
  require 'time'
4
4
  require 'bundler'
5
-
6
5
  require 'get_process_mem'
7
6
 
7
+ require 'dead_end'
8
+
8
9
  module DerailedBenchmarks
9
10
  def self.gem_is_bundled?(name)
10
11
  specs = ::Bundler.locked_gems.specs.each_with_object({}) {|spec, hash| hash[spec.name] = spec }
@@ -17,12 +17,16 @@ module Kernel
17
17
 
18
18
  alias_method :original_require, :require
19
19
  alias_method :original_require_relative, :require_relative
20
+ alias_method(:original_load, :load)
21
+
22
+ def load(file, wrap = false)
23
+ measure_memory_impact(file) do |file|
24
+ original_load(file)
25
+ end
26
+ end
20
27
 
21
28
  def require(file)
22
29
  measure_memory_impact(file) do |file|
23
- # "source_annotation_extractor" is deprecated in Rails 6
24
- # # if we don't skip the library it leads to a crash
25
- # next if file == "rails/source_annotation_extractor" && Rails.version >= '6.0'
26
30
  original_require(file)
27
31
  end
28
32
  end
@@ -67,22 +71,29 @@ module Kernel
67
71
  end
68
72
  end
69
73
 
70
- # Top level node that will store all require information for the entire app
71
- TOP_REQUIRE = DerailedBenchmarks::RequireTree.new("TOP")
72
- REQUIRE_STACK.push(TOP_REQUIRE)
73
74
 
75
+ # I honestly have no idea why this Object delegation is needed
76
+ # I keep staring at bootsnap and it doesn't have to do this
77
+ # is there a bug in their implementation they haven't caught or
78
+ # am I doing something different?
74
79
  class Object
75
80
  private
81
+ def load(path, wrap = false)
82
+ Kernel.load(path, wrap)
83
+ end
76
84
 
77
85
  def require(path)
78
86
  Kernel.require(path)
79
87
  end
80
88
  end
81
89
 
82
- # Don't forget to assign a cost to the top level
83
- cost_before_requiring_anything = GetProcessMem.new.mb
84
- TOP_REQUIRE.cost = cost_before_requiring_anything
90
+ # Top level node that will store all require information for the entire app
91
+ TOP_REQUIRE = DerailedBenchmarks::RequireTree.new("TOP")
92
+ REQUIRE_STACK.push(TOP_REQUIRE)
93
+ TOP_REQUIRE.cost = GetProcessMem.new.mb
94
+
85
95
  def TOP_REQUIRE.print_sorted_children(*args)
86
96
  self.cost = GetProcessMem.new.mb - self.cost
87
97
  super
88
98
  end
99
+
@@ -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.body }
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)
@@ -38,7 +38,7 @@ module DerailedBenchmarks
38
38
  file = info_hash.fetch(:file)
39
39
  desc = info_hash.fetch(:desc)
40
40
  time = info_hash.fetch(:time)
41
- short_sha = info_hash["short_sha"]
41
+ short_sha = info_hash[:short_sha]
42
42
  @files << StatsForFile.new(file: file, desc: desc, time: time, name: branch, short_sha: short_sha)
43
43
  end
44
44
  else
@@ -120,22 +120,23 @@ module DerailedBenchmarks
120
120
  end
121
121
 
122
122
  def histogram(io = $stdout)
123
- newest_histogram = MiniHistogram.new(newest.values)
124
- oldest_histogram = MiniHistogram.new(oldest.values)
125
- MiniHistogram.set_average_edges!(newest_histogram, oldest_histogram)
126
-
127
- {newest => newest_histogram, oldest => oldest_histogram}.each do |report, histogram|
128
- plot = histogram.plot(
129
- title: "\n#{' ' * 18 }Histogram - [#{report.short_sha || report.name}] #{report.desc.inspect}",
130
- ylabel: "Time (s)",
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}",
131
127
  xlabel: "# of runs in range"
132
- )
133
-
134
- plot.render(io)
135
- io.puts
128
+ }
129
+ b.values = oldest.values
130
+ b.options = {
131
+ title: "\n [#{oldest.short_sha || oldest.name}] description:\n #{oldest.desc.inspect}",
132
+ xlabel: "# of runs in range"
133
+ }
136
134
  end
137
135
 
138
136
  io.puts
137
+ io.puts "Histograms (time ranges are in seconds):"
138
+ io.puts(dual_histogram)
139
+ io.puts
139
140
  end
140
141
 
141
142
  def banner(io = $stdout)
@@ -247,6 +247,42 @@ namespace :perf do
247
247
  puts "Also try uploading #{file_name.inspect} to http://tenderlove.github.io/heap-analyzer/"
248
248
  end
249
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://medium.com/klaxit-techblog/tracking-a-ruby-memory-leak-in-2021-9eb56575f731#875b to understand better what you are reading."
284
+ end
285
+
250
286
  def run!(cmd)
251
287
  out = `#{cmd}`
252
288
  raise "Error while running #{cmd.inspect}: #{out}" unless $?.success?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DerailedBenchmarks
4
- VERSION = "1.8.0"
4
+ VERSION = "2.1.1"
5
5
  end
@@ -3,7 +3,6 @@
3
3
  require 'test_helper'
4
4
 
5
5
  class KernelRequireTest < ActiveSupport::TestCase
6
-
7
6
  setup do
8
7
  require 'derailed_benchmarks/core_ext/kernel_require'
9
8
  GC.disable
@@ -11,23 +10,83 @@ class KernelRequireTest < ActiveSupport::TestCase
11
10
 
12
11
  teardown do
13
12
  GC.enable
13
+ DerailedBenchmarks::RequireTree.reset!
14
+ end
15
+
16
+ test "profiles load" do
17
+ in_fork do
18
+ require fixtures_dir("require/load_parent.rb")
19
+
20
+ parent = assert_node_in_parent("load_parent.rb", TOP_REQUIRE)
21
+
22
+ assert_node_in_parent("load_child.rb", parent)
23
+ end
24
+ end
25
+
26
+ test "profiles autoload" do
27
+ skip if RUBY_VERSION.start_with?("2.2") # Fails on CI, I can't install Ruby 2.2 locally to debug https://stackoverflow.com/questions/63926460/install-ruby-2-2-on-mac-osx-catalina-with-ruby-install, https://github.com/postmodern/ruby-install/issues/375
28
+
29
+ in_fork do
30
+ require fixtures_dir("require/autoload_parent.rb")
31
+ parent = assert_node_in_parent("autoload_parent.rb", TOP_REQUIRE)
32
+
33
+ assert_node_in_parent("autoload_child.rb", parent)
34
+ end
14
35
  end
15
36
 
37
+ test "core extension profiles useage" do
38
+ in_fork do
39
+ require fixtures_dir("require/parent_one.rb")
40
+ parent = assert_node_in_parent("parent_one.rb", TOP_REQUIRE)
41
+ assert_node_in_parent("child_one.rb", parent)
42
+ child_two = assert_node_in_parent("child_two.rb", parent)
43
+ assert_node_in_parent("relative_child", parent)
44
+ assert_node_in_parent("relative_child_two", parent)
45
+ assert_node_in_parent("raise_child.rb", child_two)
46
+ end
47
+ end
48
+
49
+ # Checks to see that the given file name is present in the
50
+ # parent tree node and that the memory of that file
51
+ # is less than the parent (since the parent should include itself
52
+ # plus its children)
53
+ #
54
+ # Returns the child node
16
55
  def assert_node_in_parent(file_name, parent)
17
56
  file = fixtures_dir(File.join("require", file_name))
18
57
  node = parent[file]
19
- assert node, "Expected:\n#{parent.children}\nto include:\n#{file.inspect}"
20
- assert node.cost < parent.cost, "Expected:\n#{node.inspect}\nto cost less than:\n#{parent.inspect}" unless parent == TOP_REQUIRE
58
+ assert node, "Expected: #{parent.name} to include: #{file.to_s} but it did not.\nChildren: #{parent.children.map(&:name).map(&:to_s)}"
59
+ unless parent == TOP_REQUIRE
60
+ assert node.cost < parent.cost, "Expected: #{node.name.inspect} (#{node.cost}) to cost less than: #{parent.name.inspect} (#{parent.cost})"
61
+ end
21
62
  node
22
63
  end
23
64
 
24
- test "core extension profiles useage" do
25
- require fixtures_dir("require/parent_one.rb")
26
- parent = assert_node_in_parent("parent_one.rb", TOP_REQUIRE)
27
- assert_node_in_parent("child_one.rb", parent)
28
- child_two = assert_node_in_parent("child_two.rb", parent)
29
- assert_node_in_parent("relative_child", parent)
30
- assert_node_in_parent("relative_child_two", parent)
31
- assert_node_in_parent("raise_child.rb", child_two)
65
+ # Used to get semi-clean process memory
66
+ # It would be better to run the requires in a totally different process
67
+ # but...that would take engineering
68
+ #
69
+ # If I was going to do that, I would find a way to serialize RequireTree
70
+ # into a json structure with file names and costs, run the script
71
+ # dump the json to a file, then in this process read the file and
72
+ # run assertions
73
+ def in_fork
74
+ Tempfile.create("stdout") do |tmp_file|
75
+ pid = fork do
76
+ $stdout.reopen(tmp_file, "w")
77
+ $stderr.reopen(tmp_file, "w")
78
+ $stdout.sync = true
79
+ $stderr.sync = true
80
+ yield
81
+ Kernel.exit!(0) # needed for https://github.com/seattlerb/minitest/pull/683
82
+ end
83
+ Process.waitpid(pid)
84
+
85
+ if $?.success?
86
+ print File.read(tmp_file)
87
+ else
88
+ raise File.read(tmp_file)
89
+ end
90
+ end
32
91
  end
33
92
  end
@@ -9,7 +9,7 @@ class RequireTree < ActiveSupport::TestCase
9
9
  end
10
10
 
11
11
  def teardown
12
- DerailedBenchmarks::RequireTree.const_set("REQUIRED_BY", {})
12
+ DerailedBenchmarks::RequireTree.reset!
13
13
  end
14
14
 
15
15
  test "default_cost" do
@@ -45,13 +45,15 @@ class StatsFromDirTest < ActiveSupport::TestCase
45
45
  test "histogram output" do
46
46
  dir = fixtures_dir("stats/significant")
47
47
  branch_info = {}
48
- branch_info["loser"] = { desc: "Old commit", time: Time.now, file: dir.join("loser.bench.txt"), name: "loser" }
49
- branch_info["winner"] = { desc: "I am the new commit", time: Time.now + 1, file: dir.join("winner.bench.txt"), name: "winner" }
48
+ branch_info["loser"] = { desc: "Old commit", time: Time.now, file: dir.join("loser.bench.txt"), short_sha: "5594a2d" }
49
+ branch_info["winner"] = { desc: "I am the new commit", time: Time.now + 1, file: dir.join("winner.bench.txt"), short_sha: "f1ab117" }
50
50
  stats = DerailedBenchmarks::StatsFromDir.new(branch_info).call
51
51
 
52
52
  io = StringIO.new
53
53
  stats.call.banner(io)
54
54
 
55
+ puts io.string
56
+
55
57
  assert_match(/11\.2 , 11\.28/, io.string)
56
58
  assert_match(/11\.8 , 11\.88/, io.string)
57
59
  end
@@ -11,4 +11,19 @@ class DerailedBenchmarksTest < ActiveSupport::TestCase
11
11
  assert DerailedBenchmarks.gem_is_bundled?("rack")
12
12
  refute DerailedBenchmarks.gem_is_bundled?("wicked")
13
13
  end
14
+
15
+ test "readme contains correct output" do
16
+ readme_path = File.join(__dir__, "..", "README.md")
17
+ lines = File.foreach(readme_path)
18
+ lineno = 1
19
+ expected = lines.lazy.drop_while { |line|
20
+ lineno += 1
21
+ line != "$ bundle exec derailed exec --help\n"
22
+ }.drop(1).take_while { |line| line != "```\n" }.force.join
23
+ assert_equal(
24
+ expected,
25
+ `bundle exec derailed exec --help`,
26
+ "Please update README.md:#{lineno}"
27
+ )
28
+ end
14
29
  end
@@ -0,0 +1,5 @@
1
+ @retained = String.new("")
2
+ 1_000_000.times.map { @retained << "A" }
3
+
4
+ module AutoLoadChild
5
+ end
@@ -0,0 +1,8 @@
1
+ @retained = String.new("")
2
+ 1_000_000.times.map { @retained << "A" }
3
+
4
+ autoload :AutoLoadChild, File.join(__dir__, 'autoload_child.rb')
5
+
6
+ if AutoLoadChild
7
+ # yay
8
+ end
@@ -0,0 +1,3 @@
1
+ @retained = String.new("")
2
+ 1_000_000.times.map { @retained << "A" }
3
+
@@ -0,0 +1,5 @@
1
+ @retained = String.new("")
2
+ 1_000_000.times.map { @retained << "A" }
3
+
4
+ load File.join(__dir__, "load_child.rb")
5
+
@@ -48,7 +48,8 @@ class TasksTest < ActiveSupport::TestCase
48
48
 
49
49
  skip unless ENV['USING_RAILS_GIT']
50
50
 
51
- env = { "TEST_COUNT" => 2, "DERAILED_SCRIPT_COUNT" => 2, "SHAS_TO_TEST" => "3054e1d584e7eca110c69a1f8423f2e0866abbf9,80f989aecece1a2b1830e9c953e5887421b52d3c"}
51
+ env = { "TEST_COUNT" => 2, "DERAILED_SCRIPT_COUNT" => 2,
52
+ "SHAS_TO_TEST" => "acb6631cd99cdfe7db356773ef74cad7cbb570ed,12bb9d32f56883914abcd98fd72e3c68c444808d"}
52
53
  puts rake "perf:library", { env: env }
53
54
  end
54
55
 
@@ -58,7 +59,8 @@ class TasksTest < ActiveSupport::TestCase
58
59
  skip unless ENV['USING_RAILS_GIT']
59
60
 
60
61
  error = assert_raises {
61
- env = { "DERAILED_SCRIPT" => "nopenopenop", "TEST_COUNT" => 2, "DERAILED_SCRIPT_COUNT" => 2, "SHAS_TO_TEST" => "3054e1d584e7eca110c69a1f8423f2e0866abbf9,80f989aecece1a2b1830e9c953e5887421b52d3c"}
62
+ env = { "DERAILED_SCRIPT" => "nopenopenop", "TEST_COUNT" => 2, "DERAILED_SCRIPT_COUNT" => 2,
63
+ "SHAS_TO_TEST" => "acb6631cd99cdfe7db356773ef74cad7cbb570ed,12bb9d32f56883914abcd98fd72e3c68c444808d"}
62
64
  puts rake "perf:library", { env: env }
63
65
  }
64
66
 
@@ -151,6 +153,10 @@ class TasksTest < ActiveSupport::TestCase
151
153
  end
152
154
 
153
155
  test 'ips' do
154
- rake "perf:mem_over_time"
156
+ rake "perf:ips"
157
+ end
158
+
159
+ test 'heap_diff' do
160
+ rake "perf:heap_diff", env: { "TEST_COUNT" => 5 }
155
161
  end
156
162
  end
File without changes
data/test/test_helper.rb CHANGED
@@ -51,7 +51,6 @@ class ActiveSupport::IntegrationCase
51
51
  end
52
52
  end
53
53
 
54
-
55
54
  def fixtures_dir(name = "")
56
55
  root_path("test/fixtures").join(name)
57
56
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: derailed_benchmarks
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.0
4
+ version: 2.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Schneeman
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-09-16 00:00:00.000000000 Z
11
+ date: 2021-07-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: heapy
@@ -28,16 +28,22 @@ dependencies:
28
28
  name: memory_profiler
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
+ - - "<"
35
+ - !ruby/object:Gem::Version
36
+ version: '2'
34
37
  type: :runtime
35
38
  prerelease: false
36
39
  version_requirements: !ruby/object:Gem::Requirement
37
40
  requirements:
38
- - - "~>"
41
+ - - ">="
39
42
  - !ruby/object:Gem::Version
40
43
  version: '0'
44
+ - - "<"
45
+ - !ruby/object:Gem::Version
46
+ version: '2'
41
47
  - !ruby/object:Gem::Dependency
42
48
  name: get_process_mem
43
49
  requirement: !ruby/object:Gem::Requirement
@@ -140,14 +146,56 @@ dependencies:
140
146
  requirements:
141
147
  - - ">="
142
148
  - !ruby/object:Gem::Version
143
- version: 0.2.1
149
+ version: 0.3.0
150
+ type: :runtime
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ version: 0.3.0
157
+ - !ruby/object:Gem::Dependency
158
+ name: dead_end
159
+ requirement: !ruby/object:Gem::Requirement
160
+ requirements:
161
+ - - ">="
162
+ - !ruby/object:Gem::Version
163
+ version: '0'
164
+ type: :runtime
165
+ prerelease: false
166
+ version_requirements: !ruby/object:Gem::Requirement
167
+ requirements:
168
+ - - ">="
169
+ - !ruby/object:Gem::Version
170
+ version: '0'
171
+ - !ruby/object:Gem::Dependency
172
+ name: rack-test
173
+ requirement: !ruby/object:Gem::Requirement
174
+ requirements:
175
+ - - ">="
176
+ - !ruby/object:Gem::Version
177
+ version: '0'
144
178
  type: :runtime
145
179
  prerelease: false
146
180
  version_requirements: !ruby/object:Gem::Requirement
147
181
  requirements:
148
182
  - - ">="
149
183
  - !ruby/object:Gem::Version
150
- version: 0.2.1
184
+ version: '0'
185
+ - !ruby/object:Gem::Dependency
186
+ name: webrick
187
+ requirement: !ruby/object:Gem::Requirement
188
+ requirements:
189
+ - - ">="
190
+ - !ruby/object:Gem::Version
191
+ version: '0'
192
+ type: :development
193
+ prerelease: false
194
+ version_requirements: !ruby/object:Gem::Requirement
195
+ requirements:
196
+ - - ">="
197
+ - !ruby/object:Gem::Version
198
+ version: '0'
151
199
  - !ruby/object:Gem::Dependency
152
200
  name: capybara
153
201
  requirement: !ruby/object:Gem::Requirement
@@ -216,20 +264,6 @@ dependencies:
216
264
  - - "<"
217
265
  - !ruby/object:Gem::Version
218
266
  version: '6'
219
- - !ruby/object:Gem::Dependency
220
- name: appraisal
221
- requirement: !ruby/object:Gem::Requirement
222
- requirements:
223
- - - '='
224
- - !ruby/object:Gem::Version
225
- version: 2.2.0
226
- type: :development
227
- prerelease: false
228
- version_requirements: !ruby/object:Gem::Requirement
229
- requirements:
230
- - - '='
231
- - !ruby/object:Gem::Version
232
- version: 2.2.0
233
267
  description: " Go faster, off the Rails "
234
268
  email:
235
269
  - richard.schneeman+rubygems@gmail.com
@@ -238,10 +272,9 @@ executables:
238
272
  extensions: []
239
273
  extra_rdoc_files: []
240
274
  files:
275
+ - ".circleci/config.yml"
241
276
  - ".github/workflows/check_changelog.yml"
242
277
  - ".gitignore"
243
- - ".travis.yml"
244
- - Appraisals
245
278
  - CHANGELOG.md
246
279
  - Gemfile
247
280
  - README.md
@@ -252,6 +285,7 @@ files:
252
285
  - gemfiles/rails_5_1.gemfile
253
286
  - gemfiles/rails_5_2.gemfile
254
287
  - gemfiles/rails_6_0.gemfile
288
+ - gemfiles/rails_6_1.gemfile
255
289
  - gemfiles/rails_git.gemfile
256
290
  - lib/derailed_benchmarks.rb
257
291
  - lib/derailed_benchmarks/auth_helper.rb
@@ -272,8 +306,12 @@ files:
272
306
  - test/derailed_benchmarks/require_tree_test.rb
273
307
  - test/derailed_benchmarks/stats_from_dir_test.rb
274
308
  - test/derailed_test.rb
309
+ - test/fixtures/require/autoload_child.rb
310
+ - test/fixtures/require/autoload_parent.rb
275
311
  - test/fixtures/require/child_one.rb
276
312
  - test/fixtures/require/child_two.rb
313
+ - test/fixtures/require/load_child.rb
314
+ - test/fixtures/require/load_parent.rb
277
315
  - test/fixtures/require/parent_one.rb
278
316
  - test/fixtures/require/raise_child.rb
279
317
  - test/fixtures/require/relative_child.rb
@@ -312,6 +350,7 @@ files:
312
350
  - test/rails_app/config/locales/en.yml
313
351
  - test/rails_app/config/locales/es.yml
314
352
  - test/rails_app/config/routes.rb
353
+ - test/rails_app/config/storage.yml
315
354
  - test/rails_app/db/migrate/20141210070547_devise_create_users.rb
316
355
  - test/rails_app/db/schema.rb
317
356
  - test/rails_app/perf.rake
@@ -341,14 +380,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
341
380
  requirements:
342
381
  - - ">="
343
382
  - !ruby/object:Gem::Version
344
- version: 2.2.0
383
+ version: 2.5.0
345
384
  required_rubygems_version: !ruby/object:Gem::Requirement
346
385
  requirements:
347
386
  - - ">="
348
387
  - !ruby/object:Gem::Version
349
388
  version: '0'
350
389
  requirements: []
351
- rubygems_version: 3.1.2
390
+ rubygems_version: 3.2.22
352
391
  signing_key:
353
392
  specification_version: 4
354
393
  summary: Benchmarks designed to performance test your ENTIRE site
@@ -358,8 +397,12 @@ test_files:
358
397
  - test/derailed_benchmarks/require_tree_test.rb
359
398
  - test/derailed_benchmarks/stats_from_dir_test.rb
360
399
  - test/derailed_test.rb
400
+ - test/fixtures/require/autoload_child.rb
401
+ - test/fixtures/require/autoload_parent.rb
361
402
  - test/fixtures/require/child_one.rb
362
403
  - test/fixtures/require/child_two.rb
404
+ - test/fixtures/require/load_child.rb
405
+ - test/fixtures/require/load_parent.rb
363
406
  - test/fixtures/require/parent_one.rb
364
407
  - test/fixtures/require/raise_child.rb
365
408
  - test/fixtures/require/relative_child.rb
@@ -398,6 +441,7 @@ test_files:
398
441
  - test/rails_app/config/locales/en.yml
399
442
  - test/rails_app/config/locales/es.yml
400
443
  - test/rails_app/config/routes.rb
444
+ - test/rails_app/config/storage.yml
401
445
  - test/rails_app/db/migrate/20141210070547_devise_create_users.rb
402
446
  - test/rails_app/db/schema.rb
403
447
  - test/rails_app/perf.rake
data/.travis.yml DELETED
@@ -1,20 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.2.10
4
- - 2.5.8
5
- - 2.7.1
6
-
7
- gemfile:
8
- - gemfiles/rails_5_1.gemfile
9
- - gemfiles/rails_6_0.gemfile
10
- - gemfiles/rails_git.gemfile
11
-
12
- before_install:
13
- - gem install bundler -v 1.17.3
14
-
15
- jobs:
16
- allow_failures:
17
- - rvm: 2.2.10
18
- gemfile: gemfiles/rails_6_0.gemfile
19
- - rvm: 2.2.10
20
- gemfile: gemfiles/rails_git.gemfile
data/Appraisals DELETED
@@ -1,26 +0,0 @@
1
- # -*- mode: ruby -*-
2
- # vi: set ft=ruby :
3
-
4
- appraise "rails-3-2" do
5
- gem "rails", "~> 3.2.0"
6
- end
7
-
8
- appraise "rails-4-0" do
9
- gem "rails", "~> 4.0.0"
10
- end
11
-
12
- appraise "rails-4-1" do
13
- gem "rails", "~> 4.1.0"
14
- end
15
-
16
- appraise "rails-4-2" do
17
- gem "rails", "~> 4.2.0"
18
- end
19
-
20
- appraise "rails-5-0" do
21
- gem "rails", "~> 5.0.0"
22
- end
23
-
24
- appraise "rails-5-1" do
25
- gem "rails", "~> 5.1.0"
26
- end