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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/CHANGELOG.md +7 -1
  4. data/README.md +70 -2
  5. data/Rakefile +2 -0
  6. data/bin/derailed +1 -0
  7. data/derailed_benchmarks.gemspec +4 -1
  8. data/gemfiles/rails_5_1.gemfile +2 -0
  9. data/gemfiles/rails_5_2.gemfile +2 -0
  10. data/gemfiles/rails_6_0.gemfile +2 -0
  11. data/gemfiles/rails_git.gemfile +19 -0
  12. data/lib/derailed_benchmarks.rb +20 -0
  13. data/lib/derailed_benchmarks/auth_helper.rb +2 -0
  14. data/lib/derailed_benchmarks/auth_helpers/devise.rb +2 -0
  15. data/lib/derailed_benchmarks/core_ext/kernel_require.rb +7 -2
  16. data/lib/derailed_benchmarks/load_tasks.rb +138 -0
  17. data/lib/derailed_benchmarks/require_tree.rb +3 -1
  18. data/lib/derailed_benchmarks/stats_from_dir.rb +99 -0
  19. data/lib/derailed_benchmarks/stats_in_file.rb +53 -0
  20. data/lib/derailed_benchmarks/tasks.rb +70 -115
  21. data/lib/derailed_benchmarks/version.rb +3 -1
  22. data/test/derailed_benchmarks/core_ext/kernel_require_test.rb +3 -0
  23. data/test/derailed_benchmarks/require_tree_test.rb +2 -0
  24. data/test/derailed_benchmarks/stats_from_dir_test.rb +101 -0
  25. data/test/derailed_test.rb +2 -0
  26. data/test/fixtures/require/child_one.rb +1 -1
  27. data/test/fixtures/require/child_two.rb +1 -1
  28. data/test/fixtures/require/parent_one.rb +2 -1
  29. data/test/fixtures/require/raise_child.rb +2 -0
  30. data/test/fixtures/require/relative_child.rb +2 -0
  31. data/test/fixtures/require/relative_child_two.rb +4 -0
  32. data/test/fixtures/stats/significant/loser.bench.txt +100 -0
  33. data/test/fixtures/stats/significant/winner.bench.txt +100 -0
  34. data/test/integration/tasks_test.rb +9 -0
  35. data/test/rails_app/Rakefile +2 -0
  36. data/test/rails_app/app/controllers/application_controller.rb +2 -0
  37. data/test/rails_app/app/controllers/authenticated_controller.rb +2 -0
  38. data/test/rails_app/app/controllers/pages_controller.rb +2 -0
  39. data/test/rails_app/app/helpers/application_helper.rb +2 -0
  40. data/test/rails_app/app/helpers/authenticated_helper.rb +2 -0
  41. data/test/rails_app/app/models/user.rb +2 -0
  42. data/test/rails_app/config.ru +2 -0
  43. data/test/rails_app/config/application.rb +2 -0
  44. data/test/rails_app/config/boot.rb +3 -1
  45. data/test/rails_app/config/environment.rb +4 -0
  46. data/test/rails_app/config/environments/development.rb +2 -0
  47. data/test/rails_app/config/environments/production.rb +2 -0
  48. data/test/rails_app/config/environments/test.rb +2 -0
  49. data/test/rails_app/config/initializers/backtrace_silencers.rb +2 -0
  50. data/test/rails_app/config/initializers/devise.rb +2 -0
  51. data/test/rails_app/config/initializers/inflections.rb +2 -0
  52. data/test/rails_app/config/initializers/mime_types.rb +2 -0
  53. data/test/rails_app/config/initializers/secret_token.rb +2 -0
  54. data/test/rails_app/config/initializers/session_store.rb +2 -0
  55. data/test/rails_app/config/routes.rb +2 -0
  56. data/test/rails_app/db/migrate/20141210070547_devise_create_users.rb +2 -0
  57. data/test/rails_app/db/schema.rb +2 -0
  58. data/test/rails_app/perf.rake +2 -0
  59. data/test/rails_app/script/rails +2 -0
  60. data/test/support/integration_case.rb +2 -0
  61. data/test/test_helper.rb +6 -2
  62. metadata +30 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cc052693e6702c9fae168ebd5af966409edfd862cb10f493eeeef6f2b56c702a
4
- data.tar.gz: e68aa2d95326f0d462b6f531318277180f2f155098c930fec1f7d9d5055a90c1
3
+ metadata.gz: cefb45902f24e4e4b0d8083b59ad9733005943db3df8a8b97b30552a27ff400b
4
+ data.tar.gz: 9d5921ac19df4db40e78ec2ca6396593f1f5235c3caaae84c332163ed3c5e4ec
5
5
  SHA512:
6
- metadata.gz: da2a2ddb042b1d7d50e2d6ef70dc281fbe09bac107b763e64fe26bb47b30a3a0b46580e618f7c97c29c689b3b0981333a902dbb32f3c8df340a6cc2c54db4cf9
7
- data.tar.gz: 113502784b03d89cafc13ed4856db140352a83ab5e68393e5a09bd9f7942505aa0aec33d8b35858e97d873c2e6435fe1cdb140ae1915dbd60a4d7d2c82129310
6
+ metadata.gz: 3335a72cc6b965b76f6804c3dd71ae5cf61d56f5203447870f0ad31a30c6cef5c47b28cf569311d7c033fe2898f2653ab041d7c7dceefe85e5501c4b79ab06b4
7
+ data.tar.gz: f591582236f863286b65d9a96b33b481e0a2c9cb05a6985ecb4123d121452ebda81a1b5d123f1d0a77c297939e8f6f2de16ca14dad892436109d819ff2b7ee91
@@ -8,6 +8,7 @@ gemfile:
8
8
  - gemfiles/rails_5_1.gemfile
9
9
  - gemfiles/rails_5_2.gemfile
10
10
  - gemfiles/rails_6_0.gemfile
11
+ - gemfiles/rails_git.gemfile
11
12
 
12
13
  before_install:
13
14
  - gem install bundler
@@ -1,5 +1,11 @@
1
1
  # A Log of Changes!
2
2
 
3
+ ## 1.4.0
4
+
5
+ - Allow configuration of `perf:ips` benchmark.
6
+ - Fix bug with `require_relative` [#142](https://github.com/schneems/derailed_benchmarks/pull/142)
7
+ - Introduce `perf:library` to profile patches to libraries (like Rails) [#135](https://github.com/schneems/derailed_benchmarks/pull/135), [#139](https://github.com/schneems/derailed_benchmarks/pull/139), [#140](https://github.com/schneems/derailed_benchmarks/pull/140), [#141](https://github.com/schneems/derailed_benchmarks/pull/141)
8
+
3
9
  ## 1.3.6
4
10
 
5
11
  - `require_relative` is now measured [commit](https://github.com/schneems/derailed_benchmarks/commit/af11bcc46a4fa24f79e4897a51034927a56e077e)
@@ -67,4 +73,4 @@
67
73
 
68
74
  ## [0.0.0] - 2014-08-15
69
75
 
70
- - Initial release
76
+ - Initial release
data/README.md CHANGED
@@ -317,7 +317,7 @@ TOP: 54.1836 MiB
317
317
  action_view/base: 0.4336 MiB
318
318
  ```
319
319
 
320
- You can use `CUT_OFF=0.3` to only show files that have above a certain memory useage, this can be used to help eliminate noise.
320
+ You can use `CUT_OFF=0.3` to only show files that have above a certain memory usage, this can be used to help eliminate noise.
321
321
 
322
322
  If your application code is extremely large at boot consider using `$ derailed exec perf:objects` to debug low level object creation.
323
323
 
@@ -388,12 +388,80 @@ $ bundle exec derailed exec perf:test
388
388
 
389
389
  But I wouldn't, benchmark-ips is a better measure.
390
390
 
391
+ ### Configuring `benchmark-ips`
392
+
393
+ The `benchmark-ips` gem allows for a number of test run customizations, and `derailed_benchmarks` exposes a few of them via environment variables.
394
+
395
+ - `IPS_WARMUP`: number of seconds spent warming up the app, defaullt is `2`
396
+ - `IPS_TIME`: number of seconds to run ips benchmark for after warm up, defaullt is `5`
397
+ - `IPS_SUITE`: custom suite to use to run test
398
+ - `IPS_ITERATIONS`: number of times to run the test, displaying that last result, defaullt is `1`
399
+
400
+ ## I made a patch to to Rails how can I tell if it made my Rails app faster and test for statistical significance
401
+
402
+ When you're trying to submit a performance patch to rails/rails then they'll likely ask you for a benchmark. While you can sometimes provide a microbenchmark, a real world full stack request/response test is the gold standard.
403
+
404
+ That's what this section is about. You'll need a rails app, ideally one you can open source (see [example apps](http://codetriage.com/example_app) if you need inspiration for extracting your private code into something external).
405
+
406
+ Then you'll need to fork rails and make a branch. Then point your rails app to your branch in your gemfile
407
+
408
+ ```
409
+ gem 'rails', github: "<github username>/rails", branch: "<your branch name>"
410
+ ```
411
+
412
+ or point it at your local copy:
413
+
414
+ ```
415
+ gem 'rails', path: "<path/to/your/local/copy/rails>"
416
+ ```
417
+
418
+ To run your test:
419
+
420
+ ```
421
+ $ bundle exec derailed exec perf:library
422
+ ```
423
+
424
+ This will automatically test the two latest commits of Rails (or the library you've specified). If you would like to compare against different SHAs you can manually specify them:
425
+
426
+ ```
427
+ $ SHAS_TO_TEST="7b4d80cb373e,13d6aa3a7b70" bundle exec derailed exec perf:library
428
+ ```
429
+
430
+ Use a comma to seperate your branch names with the `SHAS_TO_TEST` env var, or omit the env var to use the last 2 git commits.
431
+
432
+ If you only include one SHA, then derailed will grab the latest commit and compare it to that SHA.
433
+
434
+ These tests might take a along time to run so the output is stored on disk incase you want to see them in the future, they're at `tmp/library_branches/<timestamp>` and labeled with the same names as your commits.
435
+
436
+ When the test is done it will output which commit "won" and by how much:
437
+
438
+ ```
439
+ ❤️ ❤️ ❤️ (Statistically Significant) ❤️ ❤️ ❤️
440
+
441
+ [7b4d80cb37] "1.8x Faster Partial Caching - Faster Cache Keys" - (10.9711965 seconds)
442
+ FASTER by:
443
+ 1.0870x [older/newer]
444
+ 8.0026% [(older - newer) / older * 100]
445
+ [13d6aa3a7b] "Merge pull request #36284 from kamipo/fix_eager_loading_with_string_joins" - (11.9255485 seconds)
446
+
447
+ P-value: 4.635595463712749e-05
448
+ Is significant? (P-value < 0.05): true
449
+ ```
450
+
451
+ You can provide this to the Rails team along with the example app you used to benchmark (so they can independently verify if needed).
452
+
453
+ Generally performance patches have to be weighted in terms of how much they help versus how large/difficult/gnarly the patch is. If the above example was a really tiny patch and it was in a common component, then half a percent might be a justafiable increase. If it was a huge re-write then it's likely going to be closed. In general I tend to not submit patches unless I'm seeing `>= 1%` performance increases.
454
+
455
+ You can use this to test changes in other libraries that aren't rails, you just have to tell it the path to the library you want to test against with the `DERAILED_PATH_TO_LIBRARY` env var.
456
+
457
+ > To get the best results before running tests you should close all programs on your laptop, turn on a program to prevent your laptop from going to sleep (or increase your sleep timer). Make sure it's plugged into a power outlet and go grab a cup of coffee. If you do anything on your laptop while this test is running you risk the chance of skewing your results.
458
+
459
+ By default derailed will stop once statistical signficance has been detected, you can tune this behavior by setting `DERAILED_STOP_VALID_COUNT` env var. Setting this to a positive number, will increase the number of iterations required that are detected to be statistically significant. For example setting it to 100 might result in 120 runs if it takes 20 runs to detect significance. Generally the more runs you have, the more accurate your averages will be. You can disable this all together by setting `DERAILED_STOP_VALID_COUNT=0` which will force derailed to run all iterations.
391
460
 
392
461
  ## Environment Variables
393
462
 
394
463
  All the tasks accept configuration in the form of environment variables.
395
464
 
396
-
397
465
  ### Increasing or decreasing test count `TEST_COUNT`
398
466
 
399
467
  For tasks that are run a number of times you can set the number using `TEST_COUNT` for example:
data/Rakefile CHANGED
@@ -1,4 +1,6 @@
1
1
  # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
2
4
  require 'rubygems'
3
5
  require 'bundler'
4
6
  require "bundler/gem_tasks"
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  unless File.respond_to? :realpath
4
5
  class File #:nodoc:
@@ -1,4 +1,6 @@
1
1
  # -*- encoding: utf-8 -*-
2
+ # frozen_string_literal: true
3
+
2
4
  lib = File.expand_path('../lib', __FILE__)
3
5
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
6
  require 'derailed_benchmarks/version'
@@ -27,10 +29,11 @@ Gem::Specification.new do |gem|
27
29
  gem.add_dependency "rack", ">= 1"
28
30
  gem.add_dependency "rake", "> 10", "< 13"
29
31
  gem.add_dependency "thor", "~> 0.19"
32
+ gem.add_dependency "ruby-statistics", ">= 2.1"
30
33
 
31
34
  gem.add_development_dependency "capybara", "~> 2"
32
35
  gem.add_development_dependency "m"
33
36
  gem.add_development_dependency "rails", "> 3", "<= 6"
34
- gem.add_development_dependency "devise", "> 3", "< 5"
37
+ gem.add_development_dependency "devise", "> 3", "< 6"
35
38
  gem.add_development_dependency "appraisal", "2.2.0"
36
39
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This file was generated by Appraisal
2
4
 
3
5
  source "https://rubygems.org"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This file was generated by Appraisal
2
4
 
3
5
  source "https://rubygems.org"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This file was generated by Appraisal
2
4
 
3
5
  source "https://rubygems.org"
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ # $ BUNDLE_GEMFILE="$(pwd)/gemfiles/rails_git.gemfile" bundle exec m test/integration/tasks_test.rb:30
4
+
5
+ source "https://rubygems.org"
6
+
7
+ gem "rails", github: "rails/rails", ref: "3054e1d584e7eca110c69a1f8423f2e0866abbf9"
8
+
9
+ gem 'devise', github: "plataformatec/devise"
10
+
11
+ group :development, :test do
12
+ gem "sqlite3", platform: [:ruby, :mswin, :mingw]
13
+ gem "activerecord-jdbcsqlite3-adapter", "~> 1.3.13", platform: :jruby
14
+ gem "test-unit", "~> 3.0"
15
+ end
16
+
17
+ gemspec path: "../"
18
+
19
+ ENV['USING_RAILS_GIT'] = "1"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'time'
2
4
  require 'bundler'
3
5
 
@@ -13,6 +15,21 @@ module DerailedBenchmarks
13
15
  attr_accessor :auth
14
16
  end
15
17
 
18
+ def self.rails_path_on_disk
19
+ require 'rails/version'
20
+ rails_version_file = Rails.method(:version).source_location[0]
21
+ path = Pathname.new(rails_version_file).expand_path.parent.parent
22
+
23
+ while path != Pathname.new("/")
24
+ basename = path.expand_path.basename.to_s
25
+
26
+ break if basename.start_with?("rails") && basename != "railties"
27
+ path = path.parent
28
+ end
29
+ raise "Could not find rails folder on a folder in #{rails_version_file}" if path == Pathname.new("/")
30
+ path.expand_path
31
+ end
32
+
16
33
  def self.add_auth(app)
17
34
  if use_auth = ENV['USE_AUTH']
18
35
  puts "Auth: #{use_auth}"
@@ -26,6 +43,9 @@ end
26
43
  require 'derailed_benchmarks/require_tree'
27
44
  require 'derailed_benchmarks/auth_helper'
28
45
 
46
+ require 'derailed_benchmarks/stats_in_file'
47
+ require 'derailed_benchmarks/stats_from_dir'
48
+
29
49
  if DerailedBenchmarks.gem_is_bundled?("devise")
30
50
  DerailedBenchmarks.auth = DerailedBenchmarks::AuthHelpers::Devise.new
31
51
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'securerandom'
2
4
 
3
5
  module DerailedBenchmarks
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DerailedBenchmarks
2
4
  class AuthHelpers
3
5
  # Devise helper for authenticating requests
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'get_process_mem'
2
4
  require 'derailed_benchmarks/require_tree'
3
5
 
@@ -20,8 +22,11 @@ module Kernel
20
22
  end
21
23
 
22
24
  def require_relative(file)
23
- # Kernel.require_relative(file)
24
- require File.expand_path("../#{file}", caller_locations(1, 1)[0].absolute_path)
25
+ if Pathname.new(file).absolute?
26
+ require file
27
+ else
28
+ require File.expand_path("../#{file}", caller_locations(1, 1)[0].absolute_path)
29
+ end
25
30
  end
26
31
 
27
32
  class << self
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :perf do
4
+ task :rails_load do
5
+ ENV["RAILS_ENV"] ||= "production"
6
+ ENV['RACK_ENV'] = ENV["RAILS_ENV"]
7
+ ENV["DISABLE_SPRING"] = "true"
8
+
9
+ ENV["SECRET_KEY_BASE"] ||= "foofoofoo"
10
+
11
+ ENV['LOG_LEVEL'] ||= "FATAL"
12
+
13
+ require 'rails'
14
+
15
+ puts "Booting: #{Rails.env}"
16
+
17
+ %W{ . lib test config }.each do |file|
18
+ $LOAD_PATH << File.expand_path(file)
19
+ end
20
+
21
+ require 'application'
22
+
23
+ Rails.env = ENV["RAILS_ENV"]
24
+
25
+ DERAILED_APP = Rails.application
26
+
27
+ if DERAILED_APP.respond_to?(:initialized?)
28
+ DERAILED_APP.initialize! unless DERAILED_APP.initialized?
29
+ else
30
+ DERAILED_APP.initialize! unless DERAILED_APP.instance_variable_get(:@initialized)
31
+ end
32
+
33
+ if ENV["DERAILED_SKIP_ACTIVE_RECORD"] && defined? ActiveRecord
34
+ if defined? ActiveRecord::Tasks::DatabaseTasks
35
+ ActiveRecord::Tasks::DatabaseTasks.create_current
36
+ else # Rails 3.2
37
+ raise "No valid database for #{ENV['RAILS_ENV']}, please create one" unless ActiveRecord::Base.connection.active?.inspect
38
+ end
39
+
40
+ ActiveRecord::Migrator.migrations_paths = DERAILED_APP.paths['db/migrate'].to_a
41
+ ActiveRecord::Migration.verbose = true
42
+ ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, nil)
43
+ end
44
+
45
+ DERAILED_APP.config.consider_all_requests_local = true
46
+ end
47
+
48
+ task :rack_load do
49
+ puts "You're not using Rails"
50
+ puts "You need to tell derailed how to boot your app"
51
+ puts "In your perf.rake add:"
52
+ puts
53
+ puts "namespace :perf do"
54
+ puts " task :rack_load do"
55
+ puts " # DERAILED_APP = your code here"
56
+ puts " end"
57
+ puts "end"
58
+ end
59
+
60
+ task :setup do
61
+ if DerailedBenchmarks.gem_is_bundled?("railties")
62
+ Rake::Task["perf:rails_load"].invoke
63
+ else
64
+ Rake::Task["perf:rack_load"].invoke
65
+ end
66
+
67
+ WARM_COUNT = (ENV['WARM_COUNT'] || 0).to_i
68
+ TEST_COUNT = (ENV['TEST_COUNT'] || ENV['CNT'] || 1_000).to_i
69
+ PATH_TO_HIT = ENV["PATH_TO_HIT"] || ENV['ENDPOINT'] || "/"
70
+ puts "Endpoint: #{ PATH_TO_HIT.inspect }"
71
+
72
+ HTTP_HEADER_PREFIX = "HTTP_".freeze
73
+ RACK_HTTP_HEADERS = ENV.select { |key| key.start_with?(HTTP_HEADER_PREFIX) }
74
+
75
+ HTTP_HEADERS = RACK_HTTP_HEADERS.keys.inject({}) do |hash, rack_header_name|
76
+ # e.g. "HTTP_ACCEPT_CHARSET" -> "Accept-Charset"
77
+ header_name = rack_header_name[HTTP_HEADER_PREFIX.size..-1].split("_").map(&:downcase).map(&:capitalize).join("-")
78
+ hash[header_name] = RACK_HTTP_HEADERS[rack_header_name]
79
+ hash
80
+ end
81
+ puts "HTTP headers: #{HTTP_HEADERS}" unless HTTP_HEADERS.empty?
82
+
83
+ CURL_HTTP_HEADER_ARGS = HTTP_HEADERS.map { |http_header_name, value| "-H \"#{http_header_name}: #{value}\"" }.join(" ")
84
+
85
+ require 'rack/test'
86
+ require 'rack/file'
87
+
88
+ DERAILED_APP = DerailedBenchmarks.add_auth(Object.class_eval { remove_const(:DERAILED_APP) })
89
+ if server = ENV["USE_SERVER"]
90
+ @port = (3000..3900).to_a.sample
91
+ puts "Port: #{ @port.inspect }"
92
+ puts "Server: #{ server.inspect }"
93
+ thread = Thread.new do
94
+ Rack::Server.start(app: DERAILED_APP, :Port => @port, environment: "none", server: server)
95
+ end
96
+ sleep 1
97
+
98
+ def call_app(path = File.join("/", PATH_TO_HIT))
99
+ cmd = "curl #{CURL_HTTP_HEADER_ARGS} 'http://localhost:#{@port}#{path}' -s --fail 2>&1"
100
+ response = `#{cmd}`
101
+ unless $?.success?
102
+ STDERR.puts "Couldn't call app."
103
+ STDERR.puts "Bad request to #{cmd.inspect} \n\n***RESPONSE***:\n\n#{ response.inspect }"
104
+
105
+ FileUtils.mkdir_p("tmp")
106
+ File.open("tmp/fail.html", "w+") {|f| f.write response.body }
107
+
108
+ `open #{File.expand_path("tmp/fail.html")}` if ENV["DERAILED_DEBUG"]
109
+
110
+ exit(1)
111
+ end
112
+ end
113
+ else
114
+ @app = Rack::MockRequest.new(DERAILED_APP)
115
+
116
+ def call_app
117
+ response = @app.get(PATH_TO_HIT, RACK_HTTP_HEADERS)
118
+ if response.status != 200
119
+ STDERR.puts "Couldn't call app. Bad request to #{PATH_TO_HIT}! Resulted in #{response.status} status."
120
+ STDERR.puts "\n\n***RESPONSE BODY***\n\n"
121
+ STDERR.puts response.body
122
+
123
+ FileUtils.mkdir_p("tmp")
124
+ File.open("tmp/fail.html", "w+") {|f| f.write response.body }
125
+
126
+ `open #{File.expand_path("tmp/fail.html")}` if ENV["DERAILED_DEBUG"]
127
+
128
+ exit(1)
129
+ end
130
+ response
131
+ end
132
+ end
133
+ if WARM_COUNT > 0
134
+ puts "Warming up app: #{WARM_COUNT} times"
135
+ WARM_COUNT.times { call_app }
136
+ end
137
+ end
138
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Tree structure used to store and sort require memory costs
2
4
  # RequireTree.new('get_process_mem')
3
5
  module DerailedBenchmarks
@@ -38,7 +40,7 @@ module DerailedBenchmarks
38
40
  end
39
41
 
40
42
  def to_string
41
- str = "#{name}: #{cost.round(4)} MiB"
43
+ str = +"#{name}: #{cost.round(4)} MiB"
42
44
  if parent && REQUIRED_BY[self.name.to_s]
43
45
  names = REQUIRED_BY[self.name.to_s].uniq - [parent.name.to_s]
44
46
  if names.any?
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bigdecimal'
4
+ require 'statistics'
5
+
6
+ module DerailedBenchmarks
7
+ # A class used to read several benchmark files
8
+ # it will parse each file, then sort by average
9
+ # time of benchmarks. It can be used to find
10
+ # the fastest and slowest examples and give information
11
+ # about them such as what the percent difference is
12
+ # and if the results are statistically significant
13
+ #
14
+ # Example:
15
+ #
16
+ # branch_info = {}
17
+ # branch_info["loser"] = { desc: "Old commit", time: Time.now, file: dir.join("loser.bench.txt"), name: "loser" }
18
+ # branch_info["winner"] = { desc: "I am the new commit", time: Time.now + 1, file: dir.join("winner.bench.txt"), name: "winner" }
19
+ # stats = DerailedBenchmarks::StatsFromDir.new(branch_info)
20
+ #
21
+ # stats.newest.average # => 10.5
22
+ # stats.oldest.average # => 11.0
23
+ # stats.significant? # => true
24
+ # stats.x_faster # => "1.0476"
25
+ class StatsFromDir
26
+ FORMAT = "%0.4f"
27
+ attr_reader :stats, :oldest, :newest
28
+
29
+ def initialize(hash)
30
+ @files = []
31
+
32
+ hash.each do |branch, info_hash|
33
+ file = info_hash.fetch(:file)
34
+ desc = info_hash.fetch(:desc)
35
+ time = info_hash.fetch(:time)
36
+ @files << StatsForFile.new(file: file, desc: desc, time: time, name: branch)
37
+ end
38
+ @files.sort_by! { |f| f.time }
39
+ @oldest = @files.first
40
+ @newest = @files.last
41
+ end
42
+
43
+ def call
44
+ @files.each(&:call)
45
+ @stats = students_t_test
46
+ self
47
+ end
48
+
49
+ def students_t_test(series_1=oldest.values, series_2=newest.values)
50
+ StatisticalTest::TTest.perform(
51
+ alpha = 0.05,
52
+ :two_tail,
53
+ series_1,
54
+ series_2
55
+ )
56
+ end
57
+
58
+ def significant?
59
+ @stats[:alternative]
60
+ end
61
+
62
+ def p_value
63
+ @stats[:p_value].to_f
64
+ end
65
+
66
+ def x_faster
67
+ FORMAT % (oldest.average/newest.average).to_f
68
+ end
69
+
70
+ def percent_faster
71
+ FORMAT % (((oldest.average - newest.average) / oldest.average).to_f * 100)
72
+ end
73
+
74
+ def change_direction
75
+ newest.average < oldest.average ? "FASTER" : "SLOWER"
76
+ end
77
+
78
+ def banner(io = Kernel)
79
+ io.puts
80
+ if significant?
81
+ io.puts "❤️ ❤️ ❤️ (Statistically Significant) ❤️ ❤️ ❤️"
82
+ else
83
+ io.puts "👎👎👎(NOT Statistically Significant) 👎👎👎"
84
+ end
85
+ io.puts
86
+ io.puts "[#{newest.name}] #{newest.desc.inspect} - (#{newest.average} seconds)"
87
+ io.puts " #{change_direction} by:"
88
+ io.puts " #{x_faster}x [older/newer]"
89
+ io.puts " #{percent_faster}\% [(older - newer) / older * 100]"
90
+ io.puts "[#{oldest.name}] #{oldest.desc.inspect} - (#{oldest.average} seconds)"
91
+ io.puts
92
+ io.puts "Iterations per sample: #{ENV["TEST_COUNT"]}"
93
+ io.puts "Samples: #{newest.values.length}"
94
+ io.puts "P-value: #{p_value}"
95
+ io.puts "Is significant? (P-value < 0.05): #{significant?}"
96
+ io.puts
97
+ end
98
+ end
99
+ end