autotuner 1.0.1 → 1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 511df4fa38774e70c89c66a4d3692098537189c2281851896327ce7b7df6fc05
4
- data.tar.gz: b97736b7e50e47753c0f487a37e32fd7fc3d3869ab50e6197cea4894978f7208
3
+ metadata.gz: 2110ae589a151476ac745ec4767d13e3146c890df830108745dc077c64f79f50
4
+ data.tar.gz: cea9ce8c687ae32214f41b65466e110abdab8bb00e1aab0596c9d787b510b496
5
5
  SHA512:
6
- metadata.gz: 2bebcb6ee78c76d67b11436a4466629094e8157820a330fbcb93286d82a725b688cc0aad4ee9dd264a929a5669a30f9a8ab90f27a89a4b85885f9437a6b28070
7
- data.tar.gz: 5e69496888210a882723e2d9d54af4daf35546c7ddf14a5c225c07e51aedcf331e8d90d998a6eba267d6d8abcfb9474bd54dbbbee76cd2828dcef6f2a429e35f
6
+ metadata.gz: 75e382f22166a16159065717da0bdff1e428986c6367d3c6598978bb7354ebf25074cb2b4a335ff5a1e252b1f6b377b7cd101f162addc990382e720bd99f8539
7
+ data.tar.gz: 61fabf37f9ebd55b403f023e545c94e8a56bfea52058006f9f7c8a34c07a9f8d12580ca704dab629c99390d741c27d65dfd6e0ca53ecce597c282b846f9d77c1
@@ -0,0 +1,25 @@
1
+ // For format details, see https://aka.ms/devcontainer.json. For config options, see the
2
+ // README at: https://github.com/devcontainers/templates/tree/main/src/ruby
3
+ {
4
+ "name": "autotuner",
5
+ // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
6
+ "image": "ghcr.io/rails/devcontainer/images/ruby:3.4.7",
7
+ "features": {
8
+ "ghcr.io/devcontainers/features/github-cli:1": {}
9
+ }
10
+
11
+ // Features to add to the dev container. More info: https://containers.dev/features.
12
+ // "features": {},
13
+
14
+ // Use 'forwardPorts' to make a list of ports inside the container available locally.
15
+ // "forwardPorts": [],
16
+
17
+ // Use 'postCreateCommand' to run commands after the container is created.
18
+ // "postCreateCommand": "ruby --version",
19
+
20
+ // Configure tool-specific properties.
21
+ // "customizations": {},
22
+
23
+ // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
24
+ // "remoteUser": "root"
25
+ }
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.3.0
1
+ 3.4.7
data/Gemfile CHANGED
@@ -5,8 +5,8 @@ source "https://rubygems.org"
5
5
  # Specify your gem's dependencies in autotuner.gemspec
6
6
  gemspec
7
7
 
8
- gem "rake", "~> 13.0"
8
+ gem "rake", "~> 13.2"
9
9
 
10
- gem "minitest", "~> 5.0"
10
+ gem "minitest", "~> 5.26"
11
11
 
12
- gem "rubocop", "~> 1.21"
12
+ gem "rubocop", "~> 1.81"
data/Gemfile.lock CHANGED
@@ -1,57 +1,62 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- autotuner (1.0.0)
4
+ autotuner (1.1.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
- ast (2.4.2)
10
- json (2.7.1)
11
- language_server-protocol (3.17.0.3)
12
- minitest (5.22.3)
13
- mocha (2.1.0)
9
+ ast (2.4.3)
10
+ json (2.15.2)
11
+ language_server-protocol (3.17.0.5)
12
+ lint_roller (1.1.0)
13
+ minitest (5.26.0)
14
+ mocha (2.7.1)
14
15
  ruby2_keywords (>= 0.0.5)
15
- parallel (1.24.0)
16
- parser (3.3.0.5)
16
+ parallel (1.27.0)
17
+ parser (3.3.10.0)
17
18
  ast (~> 2.4.1)
18
19
  racc
19
- racc (1.7.3)
20
+ prism (1.6.0)
21
+ racc (1.8.1)
20
22
  rainbow (3.1.1)
21
- rake (13.1.0)
22
- regexp_parser (2.9.0)
23
- rexml (3.2.6)
24
- rubocop (1.62.1)
23
+ rake (13.2.1)
24
+ regexp_parser (2.11.3)
25
+ rubocop (1.81.6)
25
26
  json (~> 2.3)
26
- language_server-protocol (>= 3.17.0)
27
+ language_server-protocol (~> 3.17.0.2)
28
+ lint_roller (~> 1.1.0)
27
29
  parallel (~> 1.10)
28
30
  parser (>= 3.3.0.2)
29
31
  rainbow (>= 2.2.2, < 4.0)
30
- regexp_parser (>= 1.8, < 3.0)
31
- rexml (>= 3.2.5, < 4.0)
32
- rubocop-ast (>= 1.31.1, < 2.0)
32
+ regexp_parser (>= 2.9.3, < 3.0)
33
+ rubocop-ast (>= 1.47.1, < 2.0)
33
34
  ruby-progressbar (~> 1.7)
34
- unicode-display_width (>= 2.4.0, < 3.0)
35
- rubocop-ast (1.31.2)
36
- parser (>= 3.3.0.4)
37
- rubocop-minitest (0.35.0)
38
- rubocop (>= 1.61, < 2.0)
39
- rubocop-ast (>= 1.31.1, < 2.0)
40
- rubocop-shopify (2.15.1)
41
- rubocop (~> 1.51)
35
+ unicode-display_width (>= 2.4.0, < 4.0)
36
+ rubocop-ast (1.47.1)
37
+ parser (>= 3.3.7.2)
38
+ prism (~> 1.4)
39
+ rubocop-minitest (0.38.2)
40
+ lint_roller (~> 1.1)
41
+ rubocop (>= 1.75.0, < 2.0)
42
+ rubocop-ast (>= 1.38.0, < 2.0)
43
+ rubocop-shopify (2.17.1)
44
+ rubocop (~> 1.62)
42
45
  ruby-progressbar (1.13.0)
43
46
  ruby2_keywords (0.0.5)
44
- unicode-display_width (2.5.0)
47
+ unicode-display_width (3.2.0)
48
+ unicode-emoji (~> 4.1)
49
+ unicode-emoji (4.1.0)
45
50
 
46
51
  PLATFORMS
47
52
  ruby
48
53
 
49
54
  DEPENDENCIES
50
55
  autotuner!
51
- minitest (~> 5.0)
56
+ minitest (~> 5.26)
52
57
  mocha
53
- rake (~> 13.0)
54
- rubocop (~> 1.21)
58
+ rake (~> 13.2)
59
+ rubocop (~> 1.81)
55
60
  rubocop-minitest
56
61
  rubocop-shopify
57
62
 
data/README.md CHANGED
@@ -64,11 +64,23 @@ While autotuner aims to comprehensively analyze your traffic to give the suggest
64
64
  ## Configuration
65
65
 
66
66
  - `Autotuner.enabled=`: (required, unless `Autotuner.sample_ratio` is set) Sets whether autotuner is enabled or not. When autotuner is disabled, data is not collected and suggestions are not given. Defaults to `false`.
67
- - `Autotuner.sample_ratio=`: (optional) Sets the portion of instances where autotuner is enabled. Pass a value between 0 (enabled on no intances) and 1.0 (enabled on all instances). Note that this does not sample requests, but rather samples the portion of instances that have autotuner enabled (it will be enabled for all requests on those instances). Do not configure `Autotuner.enabled=` when you use this option.
67
+ - `Autotuner.sample_ratio=`: (optional) Sets the portion of instances where autotuner is enabled. Pass a value between 0 (enabled on no instances) and 1.0 (enabled on all instances). Note that this does not sample requests, but rather samples the portion of instances that have autotuner enabled (it will be enabled for all requests on those instances). Do not configure `Autotuner.enabled=` when you use this option.
68
68
  - `Autotuner.reporter=`: (required) Callback called when a heuristic is ready to give a suggestion. The callback will be called with one argument which will be an instance of `Autotuner::Report::Base`. Call `#to_s` on this object to get a string containing instructions and recommendations. You must set this when autotuner is enabled.
69
69
  - `Autotuner.debug_reporter=`: (optional) Callback to periodically emit debug messages of internal state of heuristics. The callback will be called with one argument which will be a hash with the heuristic name as the key and the debug message as the value. Regular users do not need to configure this as this is only useful for debugging purposes.
70
70
  - `Autotuner.metrics_reporter=`: (optional) Callback to emit useful metrics about your service. The callback will be called with a hash containing the metric names (string) as the key and integer values.
71
71
 
72
+ ## Emitted Metrics
73
+
74
+ The following metrics are passed to the `metrics_reporter` callback after each request.
75
+
76
+ | Name | Description |
77
+ | --------------------- | ----------- |
78
+ | `diff.time` | Time spent doing garbage collection during the request. Produced by [GC::stat](https://docs.ruby-lang.org/en/master/GC.html#method-c-stat) |
79
+ | `diff.minor_gc_count` | Number of minor garbage collections that occurred during the request. Produced by [GC::stat](https://docs.ruby-lang.org/en/master/GC.html#method-c-stat) |
80
+ | `diff.major_gc_count` | Number of major garbage collections that occurred during the request. Produced by [GC::stat](https://docs.ruby-lang.org/en/master/GC.html#method-c-stat) |
81
+ | `heap_pages` | Number of heap pages in use after the request. Produced by [GC::stat](https://docs.ruby-lang.org/en/master/GC.html#method-c-stat) |
82
+ | `request_time` | Total duration of the request. |
83
+
72
84
  ## Contributing
73
85
 
74
86
  Bug reports and pull requests are welcome on GitHub at https://github.com/Shopify/autotuner.
data/autotuner.gemspec ADDED
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/autotuner/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "autotuner"
7
+ spec.version = Autotuner::VERSION
8
+ spec.authors = ["Peter Zhu"]
9
+ spec.email = ["peter@peterzhu.ca"]
10
+
11
+ spec.summary = "Get suggestions to tune Ruby's garbage collector"
12
+ spec.homepage = "https://github.com/Shopify/autotuner"
13
+ spec.license = "MIT"
14
+ spec.required_ruby_version = ">= 3.1.0"
15
+
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+ spec.metadata["source_code_uri"] = "https://github.com/Shopify/autotuner"
18
+
19
+ # Specify which files should be added to the gem when it is released.
20
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
+ spec.files = Dir.chdir(__dir__) do
22
+ %x(git ls-files -z).split("\x0").reject do |f|
23
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|circleci)|appveyor)})
24
+ end
25
+ end
26
+ spec.bindir = "exe"
27
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ["lib"]
29
+
30
+ spec.add_development_dependency("mocha")
31
+ spec.add_development_dependency("rubocop-minitest")
32
+ spec.add_development_dependency("rubocop-shopify")
33
+ end
@@ -70,7 +70,7 @@ module Autotuner
70
70
  sum_x_y += x_val * y_val
71
71
  end
72
72
 
73
- ((length * sum_x_y) - (sum_x * sum_y)).to_f / \
73
+ ((length * sum_x_y) - (sum_x * sum_y)).to_f /
74
74
  (Math.sqrt((length * sum_x_2) - (sum_x**2)) * Math.sqrt((length * sum_y_2) - (sum_y**2)))
75
75
  end
76
76
 
@@ -5,15 +5,13 @@ module Autotuner
5
5
  class Base
6
6
  class << self
7
7
  def enabled?
8
- supported? && !@disabled
8
+ !@disabled
9
9
  end
10
10
 
11
11
  def disable!
12
12
  @disabled = true
13
13
  end
14
14
 
15
- private
16
-
17
15
  def supported?
18
16
  raise NotImplementedError
19
17
  end
@@ -4,8 +4,6 @@ module Autotuner
4
4
  module Heuristic
5
5
  class GCCompact < Base
6
6
  class << self
7
- private
8
-
9
7
  def supported?
10
8
  true
11
9
  end
@@ -33,7 +31,7 @@ module Autotuner
33
31
  # Don't give suggestion twice
34
32
  @called_gc_compact = true
35
33
 
36
- Report::String.new(<<~MSG)
34
+ Report::String.new(name, <<~MSG)
37
35
  The following suggestion runs compaction at boot time, which reduces fragmentation inside of the Ruby heap. This can improve performance and reduce memory usage in forking web servers.
38
36
 
39
37
  Before forking your web server, run the following Ruby code:
@@ -41,10 +39,18 @@ module Autotuner
41
39
  3.times { GC.start }
42
40
  GC.compact
43
41
 
44
- For example, in Puma, add the following code into config/puma.rb:
42
+ For example, with Puma, which runs its before fork hook once on boot (before the initial fork), add the following code into config/puma.rb:
43
+
44
+ before_fork do
45
+ 3.times { GC.start }
46
+ GC.compact
47
+ end
48
+
49
+ With Unicorn, which runs its before fork hook before each fork, add the following code into config/unicorn.rb:
45
50
 
46
51
  compacted = false
47
52
  before_fork do
53
+ # avoid invalidating heap pages shared with previously forked children
48
54
  unless compacted
49
55
  3.times { GC.start }
50
56
  GC.compact
@@ -4,8 +4,6 @@ module Autotuner
4
4
  module Heuristic
5
5
  class HeapSizeWarmup < Base
6
6
  class << self
7
- private
8
-
9
7
  def supported?
10
8
  # Ruby 3.2 uses multiple heaps but does not support the
11
9
  # RUBY_GC_HEAP_%d_INIT_SLOTS environment variables, so we cannot
@@ -99,7 +97,13 @@ module Autotuner
99
97
  # Don't generate report if there is nothing to report
100
98
  return if suggested_values.empty?
101
99
 
102
- Report::MultipleEnvironmentVariables.new(REPORT_ASSIST_MESSAGE, env_names, suggested_values, configured_values)
100
+ Report::MultipleEnvironmentVariables.new(
101
+ name,
102
+ REPORT_ASSIST_MESSAGE,
103
+ env_names,
104
+ suggested_values,
105
+ configured_values,
106
+ )
103
107
  end
104
108
 
105
109
  def debug_state
@@ -4,8 +4,6 @@ module Autotuner
4
4
  module Heuristic
5
5
  class Malloc < Base
6
6
  class << self
7
- private
8
-
9
7
  def supported?
10
8
  true
11
9
  end
@@ -67,6 +65,7 @@ module Autotuner
67
65
  @given_suggestion = true
68
66
 
69
67
  Report::MultipleEnvironmentVariables.new(
68
+ name,
70
69
  <<~MSG,
71
70
  The following suggestions reduce the number of minor garbage collection cycles, specifically a cycle called "malloc". Your app runs malloc cycles in approximately #{format("%.2f", malloc_gc_ratio * 100)}% of all minor garbage collection cycles.
72
71
 
@@ -4,8 +4,6 @@ module Autotuner
4
4
  module Heuristic
5
5
  class Oldmalloc < Base
6
6
  class << self
7
- private
8
-
9
7
  def supported?
10
8
  true
11
9
  end
@@ -66,6 +64,7 @@ module Autotuner
66
64
  @given_suggestion = true
67
65
 
68
66
  Report::MultipleEnvironmentVariables.new(
67
+ name,
69
68
  <<~MSG,
70
69
  The following suggestions reduce the number of major garbage collection cycles, specifically a cycle called "oldmalloc". Your app runs oldmalloc cycles in approximately #{format("%.2f", oldmalloc_gc_ratio * 100)}% of all major garbage collection cycles.
71
70
 
@@ -4,8 +4,6 @@ module Autotuner
4
4
  module Heuristic
5
5
  class RememberedWBUnprotectedObjects < Base
6
6
  class << self
7
- private
8
-
9
7
  def supported?
10
8
  # Ruby 3.3.0 and later have support RUBY_GC_HEAP_REMEMBERED_WB_UNPROTECTED_OBJECTS_LIMIT_RATIO
11
9
  RUBY_VERSION >= "3.3.0"
@@ -64,6 +62,7 @@ module Autotuner
64
62
  @given_suggestion = true
65
63
 
66
64
  Report::SingleEnvironmentVariable.new(
65
+ name,
67
66
  <<~MSG,
68
67
  The following suggestions reduce the number of major garbage collection cycles, specifically a cycle called "remembered write barrier unprotected" (also know as "shady" due to historical reasons). Your app runs remembered write barrier unprotected cycles in approximately #{format("%.2f", wb_unprotected_gc_ratio * 100)}% of all major garbage collection cycles.
69
68
 
@@ -3,10 +3,10 @@
3
3
  module Autotuner
4
4
  module Heuristics
5
5
  HEURISTICS = Heuristic::Base.subclasses.freeze
6
- ENABLED_HEURISTICS = HEURISTICS.dup.keep_if(&:enabled?).freeze
6
+ SUPPORTED_HEURISTICS = HEURISTICS.dup.keep_if(&:supported?).freeze
7
7
 
8
- def enabled_heuristics
9
- ENABLED_HEURISTICS
8
+ def supported_heuristics
9
+ SUPPORTED_HEURISTICS
10
10
  end
11
11
  end
12
12
  end
@@ -7,9 +7,10 @@ module Autotuner
7
7
  It is always recommended to experiment with these suggestions as some suggestions may not always yield positive performance improvements. The recommended method is to perform A/B testing where a portion of traffic does not have the these suggested values and a portion of traffic with these suggested values.
8
8
  MSG
9
9
 
10
- attr_reader :assist_message
10
+ attr_reader :heuristic_name, :assist_message
11
11
 
12
- def initialize(assist_message)
12
+ def initialize(heuristic_name, assist_message)
13
+ @heuristic_name = heuristic_name
13
14
  @assist_message = assist_message
14
15
  end
15
16
 
@@ -7,8 +7,8 @@ module Autotuner
7
7
  attr_reader :suggested_value
8
8
  attr_reader :configured_value
9
9
 
10
- def initialize(assist_message, env_name, suggested_value, configured_value)
11
- super(assist_message)
10
+ def initialize(heuristic_name, assist_message, env_name, suggested_value, configured_value)
11
+ super(heuristic_name, assist_message)
12
12
  @env_name = env_name
13
13
  @suggested_value = suggested_value
14
14
  @configured_value = configured_value
@@ -25,7 +25,7 @@ module Autotuner
25
25
  end
26
26
 
27
27
  def suggested_tuning_str(env, suggested, configured)
28
- str = +" #{env}=#{suggested}"
28
+ str = " #{env}=#{suggested}"
29
29
  str << " (configured value: #{configured})" if configured
30
30
  str << "\n"
31
31
  str
@@ -7,8 +7,8 @@ module Autotuner
7
7
  attr_reader :suggested_value
8
8
  attr_reader :configured_value
9
9
 
10
- def initialize(assist_message, env_name, suggested_value, configured_value)
11
- super(assist_message)
10
+ def initialize(heuristic_name, assist_message, env_name, suggested_value, configured_value)
11
+ super(heuristic_name, assist_message)
12
12
 
13
13
  @env_name = env_name
14
14
  @suggested_value = suggested_value
@@ -5,8 +5,6 @@ module Autotuner
5
5
  HEURISTICS_POLLING_FREQUENCY = 100
6
6
  DEBUG_EMIT_FREQUENCY = 1000
7
7
 
8
- attr_reader :heuristics
9
-
10
8
  def initialize
11
9
  @request_count = 0
12
10
 
@@ -14,7 +12,7 @@ module Autotuner
14
12
 
15
13
  @system_context = SystemContext.new
16
14
 
17
- @heuristics = Autotuner.enabled_heuristics.map { |h| h.new(@system_context) }
15
+ @heuristics = Autotuner.supported_heuristics.map { |h| h.new(@system_context) }
18
16
  end
19
17
 
20
18
  def request
@@ -27,6 +25,16 @@ module Autotuner
27
25
 
28
26
  private
29
27
 
28
+ def enabled_heuristics
29
+ Enumerator.new do |y|
30
+ @heuristics.each do |heuristic|
31
+ next unless heuristic.class.enabled?
32
+
33
+ y << heuristic
34
+ end
35
+ end
36
+ end
37
+
30
38
  def before_request
31
39
  @request_context.before_request
32
40
 
@@ -38,7 +46,7 @@ module Autotuner
38
46
 
39
47
  @system_context.update(@request_context)
40
48
 
41
- heuristics.each do |heuristic|
49
+ enabled_heuristics.each do |heuristic|
42
50
  heuristic.call(@request_context)
43
51
  end
44
52
 
@@ -48,7 +56,7 @@ module Autotuner
48
56
  end
49
57
 
50
58
  def emit_heuristic_reports
51
- heuristics.each do |heuristic|
59
+ enabled_heuristics.each do |heuristic|
52
60
  report = heuristic.tuning_report
53
61
 
54
62
  next unless report
@@ -68,7 +76,7 @@ module Autotuner
68
76
  system_context: @system_context.debug_state,
69
77
  }
70
78
 
71
- heuristics.each do |h|
79
+ enabled_heuristics.each do |h|
72
80
  debug_states[h.name] = h.debug_state
73
81
  end
74
82
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Autotuner
4
- VERSION = "1.0.1"
4
+ VERSION = "1.1.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: autotuner
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Zhu
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-05-06 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: mocha
@@ -52,13 +51,13 @@ dependencies:
52
51
  - - ">="
53
52
  - !ruby/object:Gem::Version
54
53
  version: '0'
55
- description:
56
54
  email:
57
55
  - peter@peterzhu.ca
58
56
  executables: []
59
57
  extensions: []
60
58
  extra_rdoc_files: []
61
59
  files:
60
+ - ".devcontainer/devcontainer.json"
62
61
  - ".rubocop.yml"
63
62
  - ".ruby-version"
64
63
  - Gemfile
@@ -66,6 +65,7 @@ files:
66
65
  - LICENSE.txt
67
66
  - README.md
68
67
  - Rakefile
68
+ - autotuner.gemspec
69
69
  - lib/autotuner.rb
70
70
  - lib/autotuner/configuration.rb
71
71
  - lib/autotuner/data_structure/data_points.rb
@@ -92,7 +92,6 @@ licenses:
92
92
  metadata:
93
93
  homepage_uri: https://github.com/Shopify/autotuner
94
94
  source_code_uri: https://github.com/Shopify/autotuner
95
- post_install_message:
96
95
  rdoc_options: []
97
96
  require_paths:
98
97
  - lib
@@ -107,8 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
107
106
  - !ruby/object:Gem::Version
108
107
  version: '0'
109
108
  requirements: []
110
- rubygems_version: 3.5.3
111
- signing_key:
109
+ rubygems_version: 3.6.9
112
110
  specification_version: 4
113
111
  summary: Get suggestions to tune Ruby's garbage collector
114
112
  test_files: []