app_profiler 0.2.1 → 0.2.2

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: daf174f3e936f7c9e466d16f75cc866f779944ae781e39501dae6d51cd74da42
4
- data.tar.gz: 42871b642bf002450af142941793c41a1bfd76d6b6c6932b45e6cca9c287cb58
3
+ metadata.gz: 15a1a7b3bd1e60dc10753ce4b8e4f2907461b09417aecde43fce3d4565d5f211
4
+ data.tar.gz: 811e16e5383f85f4e2aef6aaf50e832b8cf5714c2cacd730ce4f184c9d1f26c4
5
5
  SHA512:
6
- metadata.gz: cf696efe9cfe2582aafd201e30fe6f82aae17f1b137e93a7d2e2b2c34256f4ace88de0d771fc398844c1010999b5c9424f1b2ab0026395ee88b2ad440a88d54a
7
- data.tar.gz: eb47ff8a3c0131065400a702d447fb8c170c9d2769860921be61d082ad6affe1ca714bbd211fd2cfe2aec78a89a57bbd32fc76d950d5fa1531feb5b43e29dac9
6
+ metadata.gz: 6b6cd68d552a8ac41eebef2cec56ac8e1917c1e321c759297649a62d6c03a3ce7c6ffa06bee820320f62ac4abdde8b8fa26f539508841ea9440d7231160534a8
7
+ data.tar.gz: c320d9e84ceca4834b7c5400dd46cef4ce3f04a7e49530b4381461f1ccb91b91da9cdc2e6eff86202b218efc1ef96754a5f8b40e7bbe433d73f2b3486ea86522
@@ -4,6 +4,7 @@ require "rack"
4
4
  require "app_profiler/middleware/base_action"
5
5
  require "app_profiler/middleware/upload_action"
6
6
  require "app_profiler/middleware/view_action"
7
+ require "app_profiler/sampler/config"
7
8
 
8
9
  module AppProfiler
9
10
  class Middleware
@@ -24,11 +25,11 @@ module AppProfiler
24
25
 
25
26
  def profile(env, params)
26
27
  response = nil
28
+ app_profiler_params = profile_params(params)
27
29
 
28
- return yield unless params.valid?
29
-
30
- params_hash = params.to_h
30
+ return yield unless app_profiler_params
31
31
 
32
+ params_hash = app_profiler_params.to_h
32
33
  return yield unless before_profile(env, params_hash)
33
34
 
34
35
  profile = AppProfiler.run(**params_hash) do
@@ -40,13 +41,21 @@ module AppProfiler
40
41
  action.call(
41
42
  profile,
42
43
  response: response,
43
- autoredirect: params.autoredirect,
44
- async: params.async,
44
+ autoredirect: app_profiler_params.autoredirect,
45
+ async: app_profiler_params.async,
45
46
  )
46
47
 
47
48
  response
48
49
  end
49
50
 
51
+ def profile_params(params)
52
+ return params if params.valid?
53
+
54
+ return unless AppProfiler.profile_sampler_enabled
55
+
56
+ AppProfiler::Sampler.profile_params(params, AppProfiler.profile_sampler_config)
57
+ end
58
+
50
59
  def before_profile(_env, _params)
51
60
  true
52
61
  end
@@ -7,7 +7,7 @@ module AppProfiler
7
7
  DEFAULT_INTERVALS = { "cpu" => 1000, "wall" => 1000, "object" => 2000, "retained" => 0 }.freeze
8
8
  MIN_INTERVALS = { "cpu" => 200, "wall" => 200, "object" => 400, "retained" => 0 }.freeze
9
9
 
10
- attr_reader :autoredirect, :async, :backend
10
+ attr_reader :mode, :autoredirect, :async, :backend
11
11
 
12
12
  def initialize(mode: :wall, interval: nil, ignore_gc: false, autoredirect: false,
13
13
  async: false, backend: nil, metadata: {})
@@ -31,6 +31,7 @@ module AppProfiler
31
31
  ignore_gc: @ignore_gc,
32
32
  metadata: @metadata,
33
33
  backend: @backend,
34
+ async: @async,
34
35
  }
35
36
  end
36
37
  end
@@ -42,6 +42,9 @@ module AppProfiler
42
42
  AppProfiler.after_process_queue = app.config.app_profiler.after_process_queue
43
43
  AppProfiler.backend = app.config.app_profiler.profiler_backend || :stackprof
44
44
  AppProfiler.forward_metadata_on_upload = app.config.app_profiler.forward_metadata_on_upload || false
45
+ AppProfiler.profile_sampler_enabled = app.config.app_profiler.profile_sampler_enabled || false
46
+ AppProfiler.profile_sampler_config = app.config.app_profiler.profile_sampler_config ||
47
+ AppProfiler::Sampler::Config.new
45
48
  end
46
49
 
47
50
  initializer "app_profiler.add_middleware" do |app|
@@ -8,6 +8,10 @@ module AppProfiler
8
8
  @request = request
9
9
  end
10
10
 
11
+ def path
12
+ @request.env["PATH_INFO"]
13
+ end
14
+
11
15
  def autoredirect
12
16
  query_param("autoredirect") || profile_header_param("autoredirect")
13
17
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "app_profiler/sampler/stackprof_config"
4
+ require "app_profiler/sampler/vernier_config"
5
+ module AppProfiler
6
+ module Sampler
7
+ class Config
8
+ attr_reader :sample_rate, :paths, :cpu_interval, :backends_probability
9
+
10
+ SAMPLE_RATE = 0.001 # 0.1%
11
+ PATHS = ["/"]
12
+ BACKEND_PROBABILITES = { stackprof: 1.0, vernier: 0.0 }
13
+ @backends = {}
14
+
15
+ def initialize(sample_rate: SAMPLE_RATE,
16
+ paths: PATHS,
17
+ backends_probability: BACKEND_PROBABILITES,
18
+ backends_config: {
19
+ stackprof: StackprofConfig.new,
20
+ })
21
+
22
+ if sample_rate < 0.0 || sample_rate > 1.0
23
+ raise ArgumentError, "sample_rate must be between 0 and 1"
24
+ end
25
+
26
+ raise ArgumentError, "mode probabilities must sum to 1" unless backends_probability.values.sum == 1.0
27
+
28
+ @sample_rate = sample_rate
29
+ @paths = paths
30
+ @backends_config = backends_config
31
+ @backends_probability = backends_probability
32
+ end
33
+
34
+ def get_backend_config(backend_name)
35
+ @backends_config[backend_name]
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AppProfiler
4
+ module Sampler
5
+ class StackprofConfig
6
+ attr_reader :modes_probability
7
+
8
+ # Default values
9
+ WALL_INTERVAL = 5000
10
+ CPU_INTERVAL = 5000
11
+ OBJECT_INTERVAL = 1000
12
+
13
+ WALL_MODE_PROBABILITY = 0.8
14
+ CPU_MODE_PROBABILITY = 0.1
15
+ OBJECT_MODE_PROBABILITY = 0.1
16
+
17
+ def initialize(
18
+ wall_interval: WALL_INTERVAL,
19
+ cpu_interval: CPU_INTERVAL,
20
+ object_interval: OBJECT_INTERVAL,
21
+ wall_mode_probability: WALL_MODE_PROBABILITY,
22
+ cpu_mode_probability: CPU_MODE_PROBABILITY,
23
+ object_mode_probability: OBJECT_MODE_PROBABILITY
24
+ )
25
+ if wall_mode_probability + cpu_mode_probability + object_mode_probability != 1.0
26
+ raise ArgumentError, "mode probabilities must sum to 1"
27
+ end
28
+
29
+ @modes_probability = {}
30
+ @modes_interval = {}
31
+
32
+ AppProfiler::Backend::StackprofBackend::AVAILABLE_MODES.each do |mode|
33
+ case mode
34
+ when :wall
35
+ @modes_probability[mode] = wall_mode_probability
36
+ @modes_interval[mode] = wall_interval
37
+ when :cpu
38
+ @modes_probability[mode] = cpu_mode_probability
39
+ @modes_interval[mode] = cpu_interval
40
+ when :object
41
+ @modes_probability[mode] = object_mode_probability
42
+ @modes_interval[mode] = object_interval
43
+ end
44
+ end
45
+ end
46
+
47
+ def interval_for(mode)
48
+ @modes_interval[mode]
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AppProfiler
4
+ module Sampler
5
+ class VernierConfig
6
+ attr_reader :modes_probability
7
+
8
+ WALL_INTERVAL = 5000
9
+ RETAINED_INTERVAL = 5000
10
+
11
+ WALL_MODE_PROBABILITY = 1.0
12
+ RETAINED_MODE_PROBABILITY = 0.0
13
+
14
+ def initialize(
15
+ wall_interval: WALL_INTERVAL,
16
+ retained_interval: RETAINED_INTERVAL,
17
+ wall_mode_probability: WALL_MODE_PROBABILITY,
18
+ retained_mode_probability: RETAINED_MODE_PROBABILITY
19
+ )
20
+ if wall_mode_probability + retained_mode_probability != 1.0
21
+ raise ArgumentError, "mode probabilities must sum to 1"
22
+ end
23
+
24
+ @modes_probability = {}
25
+ @modes_interval = {}
26
+
27
+ AppProfiler::Backend::VernierBackend::AVAILABLE_MODES.each do |mode|
28
+ case mode
29
+ when :wall
30
+ @modes_probability[mode] = wall_mode_probability
31
+ @modes_interval[mode] = wall_interval
32
+ when :retained
33
+ @modes_probability[mode] = retained_mode_probability
34
+ @modes_interval[mode] = retained_interval
35
+ end
36
+ end
37
+ end
38
+
39
+ def interval_for(mode)
40
+ @modes_interval[mode]
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "app_profiler/sampler/config"
4
+ module AppProfiler
5
+ module Sampler
6
+ class << self
7
+ def profile_params(request, config)
8
+ random = Kernel.rand
9
+ return unless sample?(random, config, request)
10
+
11
+ get_profile_params(config, random)
12
+ end
13
+
14
+ private
15
+
16
+ def sample?(random, config, request)
17
+ return false if random > config.sample_rate
18
+
19
+ path = request.path
20
+ return false unless config.paths.any? { |p| path.match?(p) }
21
+
22
+ true
23
+ end
24
+
25
+ def get_profile_params(config, random)
26
+ backend_name = select_random(config.backends_probability, random)
27
+ backend_config = config.get_backend_config(backend_name)
28
+
29
+ mode = select_random(backend_config.modes_probability, random)
30
+ interval = backend_config.interval_for(mode)
31
+
32
+ AppProfiler::Parameters.new(
33
+ backend: backend_name,
34
+ mode: mode,
35
+ async: true,
36
+ interval: interval,
37
+ )
38
+ end
39
+
40
+ # Given options with probabilities, select one based on range.
41
+ # For example, given options {a: 0.1, b: 0.2, c: 0.7} and random 0.5,
42
+ # it will return :c
43
+ # Assumes all probabilities sum to 1
44
+
45
+ def select_random(options, random)
46
+ current = 0
47
+ options = options.sort_by do |_, probability|
48
+ probability
49
+ end
50
+
51
+ options.each do |o, probabilty|
52
+ current += probabilty
53
+ if random <= current
54
+ return o
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AppProfiler
4
- VERSION = "0.2.1"
4
+ VERSION = "0.2.2"
5
5
  end
data/lib/app_profiler.rb CHANGED
@@ -38,6 +38,7 @@ module AppProfiler
38
38
  require "app_profiler/profile"
39
39
  require "app_profiler/backend"
40
40
  require "app_profiler/server"
41
+ require "app_profiler/sampler"
41
42
 
42
43
  mattr_accessor :logger, default: Logger.new($stdout)
43
44
  mattr_accessor :root
@@ -62,6 +63,8 @@ module AppProfiler
62
63
  mattr_reader :profile_enqueue_failure, default: nil
63
64
  mattr_reader :after_process_queue, default: nil
64
65
  mattr_accessor :forward_metadata_on_upload, default: false
66
+ mattr_accessor :profile_sampler_enabled, default: false
67
+ mattr_accessor :profile_sampler_config
65
68
 
66
69
  class << self
67
70
  def run(*args, backend: nil, **kwargs, &block)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: app_profiler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gannon McGibbon
@@ -13,7 +13,7 @@ authors:
13
13
  autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
- date: 2024-08-01 00:00:00.000000000 Z
16
+ date: 2024-08-26 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: activesupport
@@ -149,6 +149,10 @@ files:
149
149
  - lib/app_profiler/profile/vernier.rb
150
150
  - lib/app_profiler/railtie.rb
151
151
  - lib/app_profiler/request_parameters.rb
152
+ - lib/app_profiler/sampler.rb
153
+ - lib/app_profiler/sampler/config.rb
154
+ - lib/app_profiler/sampler/stackprof_config.rb
155
+ - lib/app_profiler/sampler/vernier_config.rb
152
156
  - lib/app_profiler/server.rb
153
157
  - lib/app_profiler/storage/base_storage.rb
154
158
  - lib/app_profiler/storage/file_storage.rb
@@ -180,7 +184,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
180
184
  - !ruby/object:Gem::Version
181
185
  version: '0'
182
186
  requirements: []
183
- rubygems_version: 3.5.16
187
+ rubygems_version: 3.5.17
184
188
  signing_key:
185
189
  specification_version: 4
186
190
  summary: Collect performance profiles for your Rails application.