app_profiler 0.2.1 → 0.2.2

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: 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.