scout_apm 5.4.0 → 5.6.4

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: 6e125a347fd3bbfa0b52f6dde597daf1dc95d97ba95cb6c44553bc36fae71b82
4
- data.tar.gz: 9f0c708e386be527f255582195a791938c1663b418999816b79f25e4f7caaf6f
3
+ metadata.gz: 9c929de92cd0a3b2690bac6ef665c8c4d7187dd3ecc838cc881033d8cf5c413c
4
+ data.tar.gz: 3148b06815beb3e506e271bb560fb700b5882d503c1a94f646011117ee424d9b
5
5
  SHA512:
6
- metadata.gz: d7e079bde40861cf673834ec606f8a09fc84bd4416eddccd22abd29d5cd64dbf071347d832549f840dd3b07026c8a3d4e6788f458587128482a1b44682479ec4
7
- data.tar.gz: e20c7d5fadbfe008f1febb62e3e31e5aa23cc4410f02634329493b10159603fb467e4ae66d504709f247ff457b0bf5d386f62baa0835010e7e5a0954160f7b97
6
+ metadata.gz: 28afece432426708eb8ce9bdb527dddd2820261574ca299887ba1294ecc98b34663c04dc15c9cf0ffb78f5211abb9b8d78ba6e202c7a6d28867edda65727b902
7
+ data.tar.gz: 3e81497fa32092e4f037d272ee1a76f555bd9a557c9855a304e96acc93cdedaf24e0a4b21d86e895dddb1db569ec35954cbff30340f8e9803b7d799b28866522
@@ -19,13 +19,6 @@ jobs:
19
19
  fail-fast: false
20
20
  matrix:
21
21
  include:
22
- - ruby: 2.1
23
- gemfile: gems/rails3.gemfile
24
- - ruby: 2.2
25
- - ruby: 2.3
26
- gemfile: gems/sqlite3-1.3.gemfile
27
- - ruby: 2.4
28
- gemfile: gems/sqlite3-1.3.gemfile
29
22
  - ruby: 2.5
30
23
  - ruby: 2.6
31
24
  - ruby: 2.6
@@ -49,15 +42,23 @@ jobs:
49
42
  - ruby: "3.0"
50
43
  gemfile: gems/sidekiq.gemfile
51
44
  test_features: "sidekiq_install"
45
+ - ruby: "3.0"
46
+ gemfile: gems/sidekiq7.gemfile
47
+ test_features: "sidekiq_install"
48
+ - ruby: "3.3"
49
+ gemfile: gems/sidekiq8.gemfile
50
+ test_features: "sidekiq_install"
52
51
  - ruby: 3.1
53
52
  - ruby: 3.2
53
+ gemfile: gems/sqlite3-v2.gemfile
54
+ - ruby: 3.3
55
+ gemfile: gems/sqlite3-v2.gemfile
54
56
  env:
55
57
  BUNDLE_GEMFILE: ${{ matrix.gemfile }}
56
58
  SCOUT_TEST_FEATURES: ${{ matrix.test_features }}
57
59
  SCOUT_USE_PREPEND: ${{ matrix.prepend }}
58
60
 
59
- # https://github.com/ruby/setup-ruby/issues/496
60
- runs-on: ${{ matrix.ruby == '2.2' && 'ubuntu-20.04' || 'ubuntu-latest' }}
61
+ runs-on: ubuntu-latest
61
62
 
62
63
  steps:
63
64
  - uses: actions/checkout@v4
data/CHANGELOG.markdown CHANGED
@@ -1,5 +1,31 @@
1
1
  # Unreleased
2
2
 
3
+ # 5.6.4
4
+ - Use new ActiveJob class name and timestamp units for Sidekiq 8+ (#544)
5
+
6
+ # 5.6.2
7
+ - Fix deprecation warning for Sidekiq 7.1.5+ (#535)
8
+ - Add support for Mongoid 8 and 9. Remove support for old versions. (#538)
9
+ - Detect deployed SHA from Kamal (#528)
10
+
11
+ # 5.6.1
12
+ - Fix `job_sample_rate` and `endpoint_sample_rate` default configuration values (#529)
13
+
14
+ # 5.6.0
15
+ - New options for sampling and ignore configuration (#521)
16
+ - `sample_rate` - Set the rate at which requests are sampled globally (1-100, a percentage of requests to keep).
17
+ - `ignore_endpoints` - Ignore endpoints by regex matching prefix (Same as and replaces `ignore`)
18
+ - `sample_endpoints` - Sample endpoints by regex matching prefix (i.e. ['/foo:70']).
19
+ - `endpoint_sample_rate` - Set the rate at which all non-matching web requests are sampled.
20
+ - `ignore_jobs` - Ignore Jobs by explicit name match.
21
+ - `sample_jobs` - Sample Jobs by explicit name match (i.e. ['MyJob:70']).
22
+ - `job_sample_rate` - Set the rate at which all non-matching background jobs are sampled.
23
+
24
+ # 5.5.0
25
+ - Fix undeclared logger in grape instruments (#510)
26
+ - Drop guaranteed support for Rubies <= 2.4
27
+ - Instrument Resque without relying on forking per job (#514)
28
+
3
29
  # 5.4.0
4
30
  * Add support for GoodJob (#506)
5
31
  * Add support for Solid Queue (#508)
@@ -668,7 +694,7 @@ reusing that version number to avoid confusion.
668
694
 
669
695
  # 1.2.12
670
696
 
671
- * Add uri_reporting option to report bare path (as opposed to fullpath). Default is 'fullpath'; set to 'path' to avoid exposing URL parameters.
697
+ * Add uri_reporting option to report bare path (as opposed to fullpath). Default is 'fullpath'; set to 'path' to avoid exposing URL parameters.
672
698
 
673
699
  # 1.2.11
674
700
 
@@ -0,0 +1,4 @@
1
+ eval_gemfile("../Gemfile")
2
+
3
+ gem "rails", "~> 7.0"
4
+ gem "sqlite3", "~> 1.4"
@@ -1,3 +1,3 @@
1
1
  eval_gemfile("../Gemfile")
2
2
 
3
- gem "sqlite3", "~> 1.3.5"
3
+ gem 'sidekiq', '~> 7.0'
@@ -0,0 +1,4 @@
1
+ eval_gemfile("../Gemfile")
2
+
3
+ gem 'sqlite3', '~> 2.0'
4
+ gem 'sidekiq', '~> 8.0'
@@ -0,0 +1,3 @@
1
+ eval_gemfile("../Gemfile")
2
+
3
+ gem "sqlite3", ">= 2.1"
@@ -208,7 +208,7 @@ module ScoutApm
208
208
 
209
209
  @error_service_background_worker = ScoutApm::BackgroundWorker.new(context, ERROR_SEND_FREQUENCY)
210
210
  @error_service_background_worker_thread = Thread.new do
211
- @error_service_background_worker.start {
211
+ @error_service_background_worker.start {
212
212
  periodic_work.run
213
213
  }
214
214
  end
@@ -91,6 +91,10 @@ module ScoutApm
91
91
  @logger ||= LoggerFactory.build(config, environment)
92
92
  end
93
93
 
94
+ def sampling
95
+ @sampling ||= ScoutApm::Sampling.new(config)
96
+ end
97
+
94
98
  def ignored_uris
95
99
  @ignored_uris ||= ScoutApm::IgnoredUris.new(config.value('ignore'))
96
100
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module ScoutApm
3
4
  def self.AutoInstrument(name, backtrace)
@@ -7,8 +7,7 @@ module ScoutApm
7
7
 
8
8
  def present?
9
9
  defined?(::Resque) &&
10
- ::Resque.respond_to?(:before_first_fork) &&
11
- ::Resque.respond_to?(:after_fork)
10
+ ::Resque.respond_to?(:before_first_fork)
12
11
  end
13
12
 
14
13
  # Lies. This forks really aggressively, but we have to do handling
@@ -19,13 +18,14 @@ module ScoutApm
19
18
  end
20
19
 
21
20
  def install
22
- install_before_fork
23
- install_after_fork
21
+ install_before_first_fork
22
+ install_instruments
24
23
  end
25
24
 
26
- def install_before_fork
25
+ def install_before_first_fork
27
26
  ::Resque.before_first_fork do
28
27
  begin
28
+ # Don't check fork_per_job here in case some workers fork_per_job and some don't.
29
29
  if ScoutApm::Agent.instance.context.config.value('start_resque_server_instrument')
30
30
  ScoutApm::Agent.instance.start
31
31
  ScoutApm::Agent.instance.context.start_remote_server!(bind, port)
@@ -33,31 +33,14 @@ module ScoutApm
33
33
  logger.info("Not starting remote server due to 'start_resque_server_instrument' setting")
34
34
  end
35
35
  rescue Errno::EADDRINUSE
36
- ScoutApm::Agent.instance.context.logger.warn "Error while Installing Resque Instruments, Port #{port} already in use. Set via the `remote_agent_port` configuration option"
36
+ logger.warn "Error while Installing Resque Instruments, Port #{port} already in use. Set via the `remote_agent_port` configuration option"
37
37
  rescue => e
38
- ScoutApm::Agent.instance.context.logger.warn "Error while Installing Resque before_first_fork: #{e.inspect}"
38
+ logger.warn "Error while Installing Resque before_first_fork: #{e.inspect}"
39
39
  end
40
40
  end
41
41
  end
42
42
 
43
- def install_after_fork
44
- ::Resque.after_fork do
45
- begin
46
- ScoutApm::Agent.instance.context.become_remote_client!(bind, port)
47
- inject_job_instrument
48
- rescue => e
49
- ScoutApm::Agent.instance.context.logger.warn "Error while Installing Resque after_fork: #{e.inspect}"
50
- end
51
- end
52
- end
53
-
54
- # Insert ourselves into the point when resque turns a string "TestJob"
55
- # into the class constant TestJob, and insert our instrumentation plugin
56
- # into that constantized class
57
- #
58
- # This automates away any need for the user to insert our instrumentation into
59
- # each of their jobs
60
- def inject_job_instrument
43
+ def install_instruments
61
44
  ::Resque::Job.class_eval do
62
45
  def payload_class_with_scout_instruments
63
46
  klass = payload_class_without_scout_instruments
@@ -80,9 +63,12 @@ module ScoutApm
80
63
  end
81
64
 
82
65
  def config
83
- @config || ScoutApm::Agent.instance.context.config
66
+ @config ||= ScoutApm::Agent.instance.context.config
67
+ end
68
+
69
+ def logger
70
+ @logger ||= ScoutApm::Agent.instance.context.logger
84
71
  end
85
72
  end
86
73
  end
87
74
  end
88
-
@@ -69,8 +69,21 @@ module ScoutApm
69
69
  end
70
70
  end
71
71
 
72
+ def self.sidekiq_version_8?
73
+ if defined?(::Sidekiq::VERSION)
74
+ ::Sidekiq::VERSION.to_i >= 8
75
+ else
76
+ false
77
+ end
78
+ end
79
+
72
80
  UNKNOWN_CLASS_PLACEHOLDER = 'UnknownJob'.freeze
73
- ACTIVE_JOB_KLASS = 'ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper'.freeze
81
+ # This name was changed in Sidekiq 8
82
+ ACTIVE_JOB_KLASS = if sidekiq_version_8?
83
+ 'Sidekiq::ActiveJob::Wrapper'.freeze
84
+ else
85
+ 'ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper'.freeze
86
+ end
74
87
  DELAYED_WRAPPER_KLASS = 'Sidekiq::Extensions::DelayedClass'.freeze
75
88
 
76
89
 
@@ -119,7 +132,14 @@ module ScoutApm
119
132
  def latency(msg, time = Time.now.to_f)
120
133
  created_at = msg['enqueued_at'] || msg['created_at']
121
134
  if created_at
122
- (time - created_at)
135
+ # Sidekiq 8+ uses milliseconds, older versions use seconds.
136
+ # Do it this way because downstream expects seconds.
137
+ if self.class.sidekiq_version_8?
138
+ # Convert milliseconds to seconds for consistency.
139
+ (time - (created_at.to_f / 1000.0))
140
+ else
141
+ (time - created_at)
142
+ end
123
143
  else
124
144
  0
125
145
  end
@@ -42,6 +42,13 @@ require 'scout_apm/environment'
42
42
  # any instruments listed in this array. Default: []
43
43
  # prepend_instruments - If `use_prepend` is false, force using Module#prepend for any
44
44
  # instruments listed in this array. Default: []
45
+ # ignore_endpoints - An array of endpoints to ignore. These are matched as regular expressions. (supercedes 'ignore')
46
+ # ignore_jobs - An array of job names to ignore.
47
+ # sample_rate - Rate to sample entire application. An integer between 0 and 100. 0 means no traces are sent, 100 means all traces are sent.
48
+ # sample_endpoints - An array of endpoints to sample. These are matched as regular expressions with individual sample rate of 0 to 100.
49
+ # sample_jobs - An array of job names with individual sample rate of 0 to 100.
50
+ # endpoint_sample_rate - Rate to sample all endpoints. An integer between 0 and 100. 0 means no traces are sent, 100 means all traces are sent. (supercedes 'sample_rate')
51
+ # job_sample_rate - Rate to sample all jobs. An integer between 0 and 100. 0 means no traces are sent, 100 means all traces are sent. (supercedes 'sample_rate')
45
52
  #
46
53
  # Any of these config settings can be set with an environment variable prefixed
47
54
  # by SCOUT_ and uppercasing the key: SCOUT_LOG_LEVEL for instance.
@@ -67,6 +74,8 @@ module ScoutApm
67
74
  'host',
68
75
  'hostname',
69
76
  'ignore',
77
+ 'ignore_endpoints',
78
+ 'ignore_jobs',
70
79
  'key',
71
80
  'log_class',
72
81
  'log_file_path',
@@ -83,6 +92,11 @@ module ScoutApm
83
92
  'remote_agent_port',
84
93
  'report_format',
85
94
  'revision_sha',
95
+ 'sample_rate',
96
+ 'sample_endpoints',
97
+ 'sample_jobs',
98
+ 'endpoint_sample_rate',
99
+ 'job_sample_rate',
86
100
  'scm_subdirectory',
87
101
  'start_resque_server_instrument',
88
102
  'ssl_cert_file',
@@ -170,6 +184,13 @@ module ScoutApm
170
184
  end
171
185
  end
172
186
 
187
+ class NullableIntegerCoercion
188
+ def coerce(val)
189
+ return val if val.nil?
190
+ val.to_i
191
+ end
192
+ end
193
+
173
194
  # Simply returns the passed in value, without change
174
195
  class NullCoercion
175
196
  def coerce(val)
@@ -184,6 +205,8 @@ module ScoutApm
184
205
  'dev_trace' => BooleanCoercion.new,
185
206
  'enable_background_jobs' => BooleanCoercion.new,
186
207
  'ignore' => JsonCoercion.new,
208
+ 'ignore_endpoints' => JsonCoercion.new,
209
+ 'ignore_jobs' => JsonCoercion.new,
187
210
  'max_traces' => IntegerCoercion.new,
188
211
  'monitor' => BooleanCoercion.new,
189
212
  'collect_remote_ip' => BooleanCoercion.new,
@@ -194,6 +217,11 @@ module ScoutApm
194
217
  'external_service_metric_report_limit' => IntegerCoercion.new,
195
218
  'instrument_http_url_length' => IntegerCoercion.new,
196
219
  'record_queue_time' => BooleanCoercion.new,
220
+ 'sample_rate' => IntegerCoercion.new,
221
+ 'sample_endpoints' => JsonCoercion.new,
222
+ 'sample_jobs' => JsonCoercion.new,
223
+ 'endpoint_sample_rate' => NullableIntegerCoercion.new,
224
+ 'job_sample_rate' => NullableIntegerCoercion.new,
197
225
  'start_resque_server_instrument' => BooleanCoercion.new,
198
226
  'timeline_traces' => BooleanCoercion.new,
199
227
  'auto_instruments' => BooleanCoercion.new,
@@ -290,41 +318,48 @@ module ScoutApm
290
318
 
291
319
  class ConfigDefaults
292
320
  DEFAULTS = {
293
- 'compress_payload' => true,
294
- 'detailed_middleware' => false,
295
- 'dev_trace' => false,
296
- 'direct_host' => 'https://apm.scoutapp.com',
297
- 'disabled_instruments' => [],
298
- 'enable_background_jobs' => true,
299
- 'host' => 'https://checkin.scoutapp.com',
300
- 'ignore' => [],
301
- 'log_level' => 'info',
302
- 'max_traces' => 10,
303
- 'profile' => true, # for scoutprof
304
- 'report_format' => 'json',
305
- 'scm_subdirectory' => '',
306
- 'uri_reporting' => 'full_path',
307
- 'remote_agent_host' => '127.0.0.1',
308
- 'remote_agent_port' => 7721, # picked at random
309
- 'database_metric_limit' => 5000, # The hard limit on db metrics
310
- 'database_metric_report_limit' => 1000,
311
- 'external_service_metric_limit' => 5000, # The hard limit on external service metrics
321
+ 'compress_payload' => true,
322
+ 'detailed_middleware' => false,
323
+ 'dev_trace' => false,
324
+ 'direct_host' => 'https://apm.scoutapp.com',
325
+ 'disabled_instruments' => [],
326
+ 'enable_background_jobs' => true,
327
+ 'host' => 'https://checkin.scoutapp.com',
328
+ 'ignore' => [],
329
+ 'ignore_endpoints' => [],
330
+ 'ignore_jobs' => [],
331
+ 'log_level' => 'info',
332
+ 'max_traces' => 10,
333
+ 'profile' => true, # for scoutprof
334
+ 'report_format' => 'json',
335
+ 'scm_subdirectory' => '',
336
+ 'uri_reporting' => 'full_path',
337
+ 'remote_agent_host' => '127.0.0.1',
338
+ 'remote_agent_port' => 7721, # picked at random
339
+ 'database_metric_limit' => 5000, # The hard limit on db metrics
340
+ 'database_metric_report_limit' => 1000,
341
+ 'external_service_metric_limit' => 5000, # The hard limit on external service metrics
312
342
  'external_service_metric_report_limit' => 1000,
313
- 'instrument_http_url_length' => 300,
314
- 'start_resque_server_instrument' => true, # still only starts if Resque is detected
315
- 'collect_remote_ip' => true,
316
- 'record_queue_time' => true,
317
- 'timeline_traces' => true,
318
- 'auto_instruments' => false,
319
- 'auto_instruments_ignore' => [],
320
- 'use_prepend' => false,
321
- 'alias_method_instruments' => [],
322
- 'prepend_instruments' => [],
323
- 'ssl_cert_file' => File.join( File.dirname(__FILE__), *%w[.. .. data cacert.pem] ),
324
- 'errors_enabled' => false,
325
- 'errors_ignored_exceptions' => %w(ActiveRecord::RecordNotFound ActionController::RoutingError),
326
- 'errors_filtered_params' => %w(password s3-key),
327
- 'errors_host' => 'https://errors.scoutapm.com',
343
+ 'instrument_http_url_length' => 300,
344
+ 'sample_rate' => 100,
345
+ 'sample_endpoints' => [],
346
+ 'sample_jobs' => [],
347
+ 'endpoint_sample_rate' => nil,
348
+ 'job_sample_rate' => nil,
349
+ 'start_resque_server_instrument' => true, # still only starts if Resque is detected
350
+ 'collect_remote_ip' => true,
351
+ 'record_queue_time' => true,
352
+ 'timeline_traces' => true,
353
+ 'auto_instruments' => false,
354
+ 'auto_instruments_ignore' => [],
355
+ 'use_prepend' => false,
356
+ 'alias_method_instruments' => [],
357
+ 'prepend_instruments' => [],
358
+ 'ssl_cert_file' => File.join( File.dirname(__FILE__), *%w[.. .. data cacert.pem] ),
359
+ 'errors_enabled' => false,
360
+ 'errors_ignored_exceptions' => %w(ActiveRecord::RecordNotFound ActionController::RoutingError),
361
+ 'errors_filtered_params' => %w(password s3-key),
362
+ 'errors_host' => 'https://errors.scoutapm.com',
328
363
  }.freeze
329
364
 
330
365
  def value(key)
@@ -28,7 +28,7 @@ module ScoutApm
28
28
 
29
29
  def install_sidekiq_with_error_handler
30
30
  ::Sidekiq.configure_server do |config|
31
- config.error_handlers << proc { |exception, job_info|
31
+ config.error_handlers << proc { |exception, job_info, sidekiq_config|
32
32
  context = ScoutApm::Agent.instance.context
33
33
 
34
34
  # Bail out early, and reraise if the error is not interesting.
@@ -20,6 +20,7 @@ module ScoutApm
20
20
  detect_from_config ||
21
21
  detect_from_heroku ||
22
22
  detect_from_capistrano ||
23
+ detect_from_kamal ||
23
24
  detect_from_mina ||
24
25
  detect_from_git
25
26
  end
@@ -44,8 +45,13 @@ module ScoutApm
44
45
  nil
45
46
  end
46
47
 
48
+ # https://github.com/basecamp/kamal
49
+ def detect_from_kamal
50
+ ENV['KAMAL_VERSION']
51
+ end
52
+
47
53
  # https://github.com/mina-deploy/mina
48
- def detect_from_mina
54
+ def detect_from_mina
49
55
  File.read(File.join(app_root, '.mina_git_revision')).strip
50
56
  rescue
51
57
  logger.debug "Unable to detect Git Revision from Mina: #{$!.message}"
@@ -53,7 +53,7 @@ module ScoutApm
53
53
  self.options[:path].first,
54
54
  ].compact.map{ |n| n.to_s }.join("/")
55
55
  rescue => e
56
- logger.info("Error getting Grape Endpoint Name. Error: #{e.message}. Options: #{self.options.inspect}")
56
+ ScoutApm::Agent.instance.context.logger.info("Error getting Grape Endpoint Name. Error: #{e.message}. Options: #{self.options.inspect}")
57
57
  name = "Grape/Unknown"
58
58
  end
59
59
 
@@ -20,25 +20,12 @@ module ScoutApm
20
20
  @installed = true
21
21
 
22
22
  # Mongoid versions that use Moped should instrument Moped.
23
+ ### See moped instrument for Moped driven deploys
23
24
  if defined?(::Mongoid) and !defined?(::Moped)
24
- logger.info "Instrumenting Mongoid 2.x"
25
25
  @installed = true
26
26
 
27
- ### OLD (2.x) mongoids
28
- if defined?(::Mongoid::Collection)
29
- ::Mongoid::Collection.class_eval do
30
- include ScoutApm::Tracer
31
- (::Mongoid::Collections::Operations::ALL - [:<<, :[]]).each do |method|
32
- instrument_method method, :type => "MongoDB", :name => '#{@klass}/' + method.to_s
33
- end
34
- end
35
- end
36
-
37
- ### See moped instrument for Moped driven deploys
38
-
39
- ### 5.x Mongoid
40
- if (mongoid_v5? || mongoid_v6? || mongoid_v7?) && defined?(::Mongoid::Contextual::Mongo)
41
- logger.info "Instrumenting Mongoid 5.x/6.x/7.x"
27
+ if (mongoid_at_least_5?) && defined?(::Mongoid::Contextual::Mongo)
28
+ logger.info "Instrumenting Mongoid"
42
29
  # All the public methods from Mongoid::Contextual::Mongo.
43
30
  # TODO: Geo and MapReduce support (?). They are in other Contextual::* classes
44
31
  methods = [
@@ -88,31 +75,17 @@ module ScoutApm
88
75
  ]
89
76
 
90
77
  ::Mongoid::Contextual::Mongo.class_eval(with_scout_instruments)
78
+ else
79
+ logger.warn "Expected method #{method} not defined in Mongoid::Contextual::Mongo."
91
80
  end
92
81
  end
93
82
  end
94
83
  end
95
84
  end
96
85
 
97
- def mongoid_v5?
98
- if defined?(::Mongoid::VERSION)
99
- ::Mongoid::VERSION =~ /\A5/
100
- else
101
- false
102
- end
103
- end
104
-
105
- def mongoid_v6?
86
+ def mongoid_at_least_5?
106
87
  if defined?(::Mongoid::VERSION)
107
- ::Mongoid::VERSION =~ /\A6/
108
- else
109
- false
110
- end
111
- end
112
-
113
- def mongoid_v7?
114
- if defined?(::Mongoid::VERSION)
115
- ::Mongoid::VERSION =~ /\A7/
88
+ ::Mongoid::VERSION =~ /\A[56789]/
116
89
  else
117
90
  false
118
91
  end
@@ -134,4 +107,3 @@ module ScoutApm
134
107
  end
135
108
  end
136
109
  end
137
-
@@ -1,6 +1,15 @@
1
1
  module ScoutApm
2
2
  module Instruments
3
3
  module Resque
4
+ def before_perform_become_client(*args)
5
+ # Don't become remote client if explicitly disabled or if forking is disabled to force synchronous recording.
6
+ if config.value('start_resque_server_instrument') && forking?
7
+ ScoutApm::Agent.instance.context.become_remote_client!(bind, port)
8
+ else
9
+ logger.debug("Not becoming remote client due to 'start_resque_server_instrument' setting or 'fork_per_job' setting")
10
+ end
11
+ end
12
+
4
13
  def around_perform_with_scout_instruments(*args)
5
14
  job_name = self.to_s
6
15
  queue = find_queue
@@ -17,7 +26,6 @@ module ScoutApm
17
26
  started_queue = true
18
27
  req.start_layer(ScoutApm::Layer.new('Job', job_name))
19
28
  started_job = true
20
-
21
29
  yield
22
30
  rescue => e
23
31
  req.error!
@@ -33,7 +41,28 @@ module ScoutApm
33
41
  return queue if self.respond_to?(:queue)
34
42
  return "unknown"
35
43
  end
44
+
45
+ private
46
+
47
+ def bind
48
+ config.value("remote_agent_host")
49
+ end
50
+
51
+ def port
52
+ config.value("remote_agent_port")
53
+ end
54
+
55
+ def config
56
+ @config ||= ScoutApm::Agent.instance.context.config
57
+ end
58
+
59
+ def logger
60
+ @logger ||= ScoutApm::Agent.instance.context.logger
61
+ end
62
+
63
+ def forking?
64
+ @forking ||= ENV["FORK_PER_JOB"] != "false"
65
+ end
36
66
  end
37
67
  end
38
68
  end
39
-
@@ -0,0 +1,104 @@
1
+ module ScoutApm
2
+ class Sampling
3
+ attr_reader :global_sample_rate, :sample_endpoints, :sample_uri_regex, :sample_jobs, :ignore_uri_regex, :ignore_jobs
4
+
5
+ def initialize(config)
6
+ @global_sample_rate = config.value('sample_rate')
7
+ # web endpoints matched prefix by regex
8
+ # jobs matched explicitly by name
9
+
10
+ # for now still support old config key ('ignore') for backwards compatibility
11
+ @ignore_endpoints = config.value('ignore').present? ? config.value('ignore') : config.value('ignore_endpoints')
12
+ @sample_endpoints = individual_sample_to_hash(config.value('sample_endpoints'))
13
+ @endpoint_sample_rate = config.value('endpoint_sample_rate')
14
+
15
+ @ignore_jobs = config.value('ignore_jobs')
16
+ @sample_jobs = individual_sample_to_hash(config.value('sample_jobs'))
17
+ @job_sample_rate = config.value('job_sample_rate')
18
+
19
+ log_string = "Sampling initialized with config: "
20
+ log_string += "global_sample_rate: #{@global_sample_rate.inspect}, "
21
+ log_string += "endpoint_sample_rate: #{@endpoint_sample_rate.inspect}, "
22
+ log_string += "sample_endpoints: #{@sample_endpoints.inspect}, "
23
+ log_string += "ignore_endpoints: #{@ignore_endpoints.inspect}, "
24
+ log_string += "job_sample_rate: #{@job_sample_rate.inspect}, "
25
+ log_string += "sample_jobs: #{@sample_jobs.inspect}, "
26
+ log_string += "ignore_jobs: #{@ignore_jobs.inspect}"
27
+ logger.info(log_string)
28
+ end
29
+
30
+ def drop_request?(transaction)
31
+ # Individual endpoint/job sampling takes precedence over ignoring.
32
+ # Individual endpoint/job sample rate always takes precedence over general endpoint/job rate.
33
+ # General endpoint/job rate always takes precedence over global sample rate
34
+ if transaction.job?
35
+ job_name = transaction.layer_finder.job.name
36
+ rate = job_sample_rate(job_name)
37
+ return sample?(rate) unless rate.nil?
38
+ return true if ignore_job?(job_name)
39
+ return sample?(@job_sample_rate) unless @job_sample_rate.nil?
40
+ elsif transaction.web?
41
+ uri = transaction.annotations[:uri]
42
+ rate = web_sample_rate(uri)
43
+ return sample?(rate) unless rate.nil?
44
+ return true if ignore_uri?(uri)
45
+ return sample?(@endpoint_sample_rate) unless @endpoint_sample_rate.nil?
46
+ end
47
+
48
+ # global sample check
49
+ if @global_sample_rate
50
+ return sample?(@global_sample_rate)
51
+ end
52
+
53
+ false # don't drop the request
54
+ end
55
+
56
+ def individual_sample_to_hash(sampling_config)
57
+ return nil if sampling_config.blank?
58
+ # config looks like ['/foo:50','/bar:100']. parse it into hash of string: integer
59
+ sample_hash = {}
60
+ sampling_config.each do |sample|
61
+ path, rate = sample.split(':')
62
+ sample_hash[path] = rate.to_i
63
+ end
64
+ sample_hash
65
+ end
66
+
67
+ def ignore_uri?(uri)
68
+ return false if @ignore_endpoints.blank?
69
+ @ignore_endpoints.each do |prefix|
70
+ return true if uri.start_with?(prefix)
71
+ end
72
+ false
73
+ end
74
+
75
+ def web_sample_rate(uri)
76
+ return nil if @sample_endpoints.blank?
77
+ @sample_endpoints.each do |prefix, rate|
78
+ return rate if uri.start_with?(prefix)
79
+ end
80
+ nil
81
+ end
82
+
83
+ def ignore_job?(job_name)
84
+ return false if @ignore_jobs.blank?
85
+ @ignore_jobs.include?(job_name)
86
+ end
87
+
88
+ def job_sample_rate(job_name)
89
+ return nil if @sample_jobs.blank?
90
+ @sample_jobs.fetch(job_name, nil)
91
+ end
92
+
93
+ def sample?(rate)
94
+ rand * 100 > rate
95
+ end
96
+
97
+ private
98
+
99
+ def logger
100
+ ScoutApm::Agent.instance.logger
101
+ end
102
+
103
+ end
104
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # A TrackedRequest is a stack of layers, where completed layers (go into, then
2
4
  # come out of a layer) are forgotten as they finish. Layers are attached to
3
5
  # their children as the process goes, building a tree structure within the
@@ -303,7 +305,11 @@ module ScoutApm
303
305
  restore_from_dump! if @agent_context.nil?
304
306
 
305
307
  # Bail out early if the user asked us to ignore this uri
306
- return if @agent_context.ignored_uris.ignore?(annotations[:uri])
308
+ # return if @agent_context.ignored_uris.ignore?(annotations[:uri])
309
+ if @agent_context.sampling.drop_request?(self)
310
+ logger.debug("Dropping request due to sampling")
311
+ return
312
+ end
307
313
 
308
314
  apply_name_override
309
315
 
@@ -12,7 +12,8 @@ module ScoutApm
12
12
  end
13
13
 
14
14
  def run
15
- Bundler.rubygems.all_specs.map {|spec| [spec.name, spec.version.to_s] }
15
+ specs = Bundler.rubygems.public_send(Bundler.rubygems.respond_to?(:installed_specs) ? :installed_specs : :all_specs)
16
+ specs.map { |spec| [spec.name, spec.version.to_s] }
16
17
  rescue => e
17
18
  logger.warn("Couldn't fetch Gem information: #{e.message}")
18
19
  []
@@ -1,3 +1,3 @@
1
1
  module ScoutApm
2
- VERSION = "5.4.0"
2
+ VERSION = "5.6.4"
3
3
  end
data/lib/scout_apm.rb CHANGED
@@ -111,6 +111,7 @@ require 'scout_apm/instruments/samplers'
111
111
  require 'scout_apm/app_server_load'
112
112
 
113
113
  require 'scout_apm/ignored_uris.rb'
114
+ require 'scout_apm/sampling.rb'
114
115
  require 'scout_apm/utils/active_record_metric_name'
115
116
  require 'scout_apm/utils/backtrace_parser'
116
117
  require 'scout_apm/utils/installed_gems'
data/test/test_helper.rb CHANGED
@@ -38,6 +38,10 @@ class FakeConfigOverlay
38
38
  @values[key]
39
39
  end
40
40
 
41
+ def values
42
+ @values
43
+ end
44
+
41
45
  def has_key?(key)
42
46
  @values.has_key?(key)
43
47
  end
@@ -169,3 +173,28 @@ end
169
173
  class Minitest::Test
170
174
  include CustomAsserts
171
175
  end
176
+
177
+ class FakeTrackedRequest
178
+ def self.new_web_request(uri)
179
+ context = ScoutApm::Agent.instance.context
180
+ fake_store = ScoutApm::FakeStore.new
181
+ req = ScoutApm::TrackedRequest.new(context, fake_store)
182
+
183
+ first_layer = ScoutApm::Layer.new("Controller", "index")
184
+ req.start_layer(first_layer)
185
+ req.annotate_request(:uri => uri)
186
+
187
+ req
188
+ end
189
+
190
+ def self.new_job_request(job_name)
191
+ context = ScoutApm::Agent.instance.context
192
+ fake_store = ScoutApm::FakeStore.new
193
+ req = ScoutApm::TrackedRequest.new(context, fake_store)
194
+
195
+ first_layer = ScoutApm::Layer.new("Job", job_name)
196
+ req.start_layer(first_layer)
197
+
198
+ req
199
+ end
200
+ end
@@ -18,7 +18,9 @@ class SidekiqTest < Minitest::Test
18
18
  def test_starts_on_startup
19
19
  ::ScoutApm::Agent.any_instance.expects(:start)
20
20
  SidekiqIntegration.new.install
21
- Sidekiq.options[:lifecycle_events][:startup].map(&:call)
21
+ ::Sidekiq.configure_server do |config|
22
+ config[:lifecycle_events][:startup].map(&:call)
23
+ end
22
24
  end
23
25
  end
24
26
 
@@ -100,13 +102,21 @@ class SidekiqTest < Minitest::Test
100
102
  ########################################
101
103
  def test_latency_from_created_at
102
104
  # Created at time 80, but now it is 200. Latency was 120
103
- msg = { 'created_at' => 80 }
105
+ msg = if SidekiqMiddleware.sidekiq_version_8?
106
+ { 'created_at' => 80000 } # milliseconds for Sidekiq 8+
107
+ else
108
+ { 'created_at' => 80 }
109
+ end
104
110
  assert_equal 120, SidekiqMiddleware.new.latency(msg, 200)
105
111
  end
106
112
 
107
113
  def test_latency_from_enqueued_at
108
114
  # Created at time 80, but now it is 200. Latency was 120
109
- msg = { 'enqueued_at' => 80 }
115
+ msg = if SidekiqMiddleware.sidekiq_version_8?
116
+ { 'enqueued_at' => 80000 } # milliseconds for Sidekiq 8+
117
+ else
118
+ { 'enqueued_at' => 80 }
119
+ end
110
120
  assert_equal 120, SidekiqMiddleware.new.latency(msg, 200)
111
121
  end
112
122
 
@@ -72,6 +72,26 @@ class ConfigTest < Minitest::Test
72
72
  assert_equal ["a"], coercion.coerce(["a"])
73
73
  end
74
74
 
75
+ def test_integer_coercion
76
+ coercion = ScoutApm::Config::IntegerCoercion.new
77
+ assert_equal 1, coercion.coerce("1")
78
+ assert_equal 1, coercion.coerce(1)
79
+ assert_equal 0, coercion.coerce("0")
80
+ assert_equal 0, coercion.coerce(0)
81
+ assert_equal 0, coercion.coerce("")
82
+ assert_equal 0, coercion.coerce(nil)
83
+ end
84
+
85
+ def test_nullable_integer_coercion
86
+ coercion = ScoutApm::Config::NullableIntegerCoercion.new
87
+ assert_equal 1, coercion.coerce("1")
88
+ assert_equal 1, coercion.coerce(1)
89
+ assert_equal 0, coercion.coerce("0")
90
+ assert_equal 0, coercion.coerce(0)
91
+ assert_equal 0, coercion.coerce("")
92
+ assert_nil coercion.coerce(nil)
93
+ end
94
+
75
95
  def test_any_keys_found
76
96
  ENV.stubs(:has_key?).returns(nil)
77
97
 
@@ -3,13 +3,75 @@ require 'test_helper'
3
3
  require 'scout_apm/git_revision'
4
4
 
5
5
  class GitRevisionTest < Minitest::Test
6
- # TODO - other tests that would be nice:
7
- # * ensure we only detect once, on initialize.
8
- # * tests for reading cap files
6
+ def setup
7
+ @env = ENV.to_h
8
+ end
9
+
10
+ def teardown
11
+ ENV.replace(@env)
12
+ end
13
+
14
+ def test_sha_detected_once
15
+ ENV['HEROKU_SLUG_COMMIT'] = 'initial_slug'
16
+ revision = ScoutApm::GitRevision.new(ScoutApm::AgentContext.new)
17
+ assert_equal 'initial_slug', revision.sha
18
+
19
+ ENV['HEROKU_SLUG_COMMIT'] = 'new_slug'
20
+ assert_equal 'initial_slug', revision.sha
21
+ end
22
+
23
+ def test_sha_from_config
24
+ config = make_fake_config('revision_sha' => 'config_sha')
25
+ context = ScoutApm::AgentContext.new().tap { |c| c.config = config }
26
+ revision = ScoutApm::GitRevision.new(context)
27
+
28
+ assert_equal 'config_sha', revision.sha
29
+ end
9
30
 
10
31
  def test_sha_from_heroku
11
32
  ENV['HEROKU_SLUG_COMMIT'] = 'heroku_slug'
12
33
  revision = ScoutApm::GitRevision.new(ScoutApm::AgentContext.new)
13
34
  assert_equal 'heroku_slug', revision.sha
14
35
  end
36
+
37
+ def test_sha_from_capistrano
38
+ Dir.mktmpdir do |dir|
39
+ context = context_with_file_in_root(File.join(dir, 'REVISION'), 'capistrano_sha')
40
+ revision = ScoutApm::GitRevision.new(context)
41
+ assert_equal 'capistrano_sha', revision.sha
42
+ end
43
+ end
44
+
45
+ def test_sha_from_kamal
46
+ ENV['KAMAL_VERSION'] = 'kamal_sha'
47
+ revision = ScoutApm::GitRevision.new(ScoutApm::AgentContext.new)
48
+ assert_equal 'kamal_sha', revision.sha
49
+ end
50
+
51
+
52
+ def test_sha_from_mina
53
+ Dir.mktmpdir do |dir|
54
+ context = context_with_file_in_root(File.join(dir, '.mina_git_revision'), 'mina_sha')
55
+ revision = ScoutApm::GitRevision.new(context)
56
+ assert_equal 'mina_sha', revision.sha
57
+ end
58
+ end
59
+
60
+ def test_sha_from_git
61
+ short_sha = `git rev-parse --short HEAD`.strip
62
+ skip 'git not installed or not in a git repository' if short_sha.empty?
63
+
64
+ revision = ScoutApm::GitRevision.new(ScoutApm::AgentContext.new)
65
+ assert_equal short_sha, revision.sha
66
+ end
67
+
68
+ private
69
+
70
+ def context_with_file_in_root(file_name, contents)
71
+ config = make_fake_config({})
72
+ env = make_fake_environment(root: File.dirname(file_name))
73
+ File.write(file_name, contents)
74
+
75
+ ScoutApm::AgentContext.new().tap { |c| c.config = config; c.environment = env }
76
+ end
15
77
  end
@@ -0,0 +1,215 @@
1
+ require 'test_helper'
2
+
3
+ require 'scout_apm/sampling'
4
+
5
+ class SamplingTest < Minitest::Test
6
+
7
+ def setup
8
+ @global_sample_config = FakeConfigOverlay.new(
9
+ {
10
+ 'sample_rate' => 80,
11
+ }
12
+ )
13
+
14
+ @individual_config = FakeConfigOverlay.new(
15
+ {
16
+ 'sample_endpoints' => ['/foo/bar:100', '/foo:50', '/bar/zap:80'],
17
+ 'ignore_endpoints' => ['/baz'],
18
+ 'sample_jobs' => ['joba:50'],
19
+ 'ignore_jobs' => 'jobb,jobc',
20
+ }
21
+ )
22
+ end
23
+
24
+ def test_individual_sample_to_hash
25
+ sampling = ScoutApm::Sampling.new(@individual_config)
26
+ assert_equal({'/foo/bar' => 100, '/foo' => 50, '/bar/zap' => 80}, sampling.individual_sample_to_hash(@individual_config.value('sample_endpoints')))
27
+
28
+ sampling = ScoutApm::Sampling.new(@global_sample_config)
29
+ assert_nil sampling.individual_sample_to_hash(@global_sample_config.value('sample_endpoints'))
30
+ end
31
+
32
+ def test_uri_ignore
33
+ sampling = ScoutApm::Sampling.new(@individual_config)
34
+ assert_equal true, sampling.ignore_uri?('/baz/bap')
35
+ assert_equal false, sampling.ignore_uri?('/foo/far')
36
+ end
37
+
38
+ def test_uri_sample
39
+ sampling = ScoutApm::Sampling.new(@individual_config)
40
+ rate = sampling.web_sample_rate('/foo/far')
41
+ assert_equal 50, rate
42
+
43
+ rate = sampling.web_sample_rate('/bar')
44
+ assert_nil rate
45
+
46
+ rate = sampling.web_sample_rate('/baz/bap')
47
+ assert_nil rate
48
+
49
+ rate = sampling.web_sample_rate('/foo/bar/baz')
50
+ assert_equal 100, rate
51
+ end
52
+
53
+ def test_job_ignore
54
+ sampling = ScoutApm::Sampling.new(@individual_config)
55
+ assert_equal true, sampling.ignore_job?('jobb')
56
+ assert_equal false, sampling.ignore_job?('joba')
57
+ end
58
+
59
+ def test_job_sample
60
+ sampling = ScoutApm::Sampling.new(@individual_config)
61
+ assert_equal 50, sampling.job_sample_rate('joba')
62
+ assert_nil sampling.job_sample_rate('jobb')
63
+ end
64
+
65
+ def test_sample
66
+ sampling = ScoutApm::Sampling.new(@individual_config)
67
+ sampling.stub(:rand, 0.01) do
68
+ assert_equal(false, sampling.sample?(50))
69
+ end
70
+ sampling.stub(:rand, 0.99) do
71
+ assert_equal(true, sampling.sample?(50))
72
+ end
73
+ end
74
+
75
+ def test_old_ignore
76
+ config = FakeConfigOverlay.new({'ignore' => ['/foo', '/bar']})
77
+ sampling = ScoutApm::Sampling.new(config)
78
+ assert_equal true, sampling.ignore_uri?('/foo')
79
+ assert_equal true, sampling.ignore_uri?('/bar/bap')
80
+ assert_equal false, sampling.ignore_uri?('/baz')
81
+ end
82
+
83
+ def test_web_request_individual_sampling
84
+ sampling = ScoutApm::Sampling.new(@individual_config)
85
+
86
+ # should be ignored
87
+ transaction = FakeTrackedRequest.new_web_request('/baz/bap')
88
+ assert_equal true, sampling.drop_request?(transaction)
89
+
90
+ # should be kept
91
+ transaction = FakeTrackedRequest.new_web_request('/faz/bap')
92
+ assert_equal false, sampling.drop_request?(transaction)
93
+
94
+ transaction = FakeTrackedRequest.new_web_request('/foo/far')
95
+ sampling.stub(:rand, 0.01) do
96
+ assert_equal false, sampling.drop_request?(transaction)
97
+ end
98
+
99
+ # passes individual sample but caught by global rate
100
+ sampling.stub(:rand, 0.99) do
101
+ assert_equal true, sampling.drop_request?(transaction)
102
+ end
103
+ end
104
+
105
+ def test_web_reqeust_general_sampling
106
+ config = FakeConfigOverlay.new(@individual_config.values.merge({'endpoint_sample_rate' => 80}))
107
+ sampling = ScoutApm::Sampling.new(config)
108
+
109
+ transaction = FakeTrackedRequest.new_web_request('/foo/far')
110
+ transaction2 = FakeTrackedRequest.new_web_request('/ooo/oar')
111
+ # /foo/far sampled at 50 specifically, /ooo/oar caught by general endpoint rate of 80
112
+ sampling.stub(:rand, 0.01) do
113
+ assert_equal false, sampling.drop_request?(transaction)
114
+ assert_equal false, sampling.drop_request?(transaction2)
115
+ end
116
+
117
+ sampling.stub(:rand, 0.70) do
118
+ assert_equal true, sampling.drop_request?(transaction)
119
+ assert_equal false, sampling.drop_request?(transaction2)
120
+ end
121
+
122
+ sampling.stub(:rand, 0.99) do
123
+ assert_equal true, sampling.drop_request?(transaction)
124
+ assert_equal true, sampling.drop_request?(transaction2)
125
+ end
126
+ end
127
+
128
+ def test_web_request_with_global_sampling
129
+ config = FakeConfigOverlay.new(@individual_config.values.merge({'sample_rate' => 20}))
130
+ sampling = ScoutApm::Sampling.new(config)
131
+
132
+ # caught by individual rate
133
+ transaction = FakeTrackedRequest.new_web_request('/foo/far')
134
+ sampling.stub(:rand, 0.01) do
135
+ assert_equal false, sampling.drop_request?(transaction)
136
+ end
137
+
138
+ # passes individual rate (50) but caught by global rate (20)
139
+ sampling.stub(:rand, 0.30) do
140
+ assert_equal false, sampling.drop_request?(transaction)
141
+ end
142
+
143
+ # passes individual rate
144
+ sampling.stub(:rand, 0.99) do
145
+ assert_equal true, sampling.drop_request?(transaction)
146
+ end
147
+
148
+ end
149
+
150
+ def test_job_request_individual_sampling
151
+ sampling = ScoutApm::Sampling.new(@individual_config)
152
+
153
+ # should be ignored
154
+ transaction = FakeTrackedRequest.new_job_request('jobb')
155
+ assert_equal true, sampling.drop_request?(transaction)
156
+
157
+ # should be kept
158
+ transaction = FakeTrackedRequest.new_job_request('jobz')
159
+ assert_equal false, sampling.drop_request?(transaction)
160
+
161
+ # should be sampled if rand > 50
162
+ transaction = FakeTrackedRequest.new_job_request('joba')
163
+ sampling.stub(:rand, 0.01) do
164
+ assert_equal false, sampling.drop_request?(transaction)
165
+ end
166
+
167
+ sampling.stub(:rand, 0.99) do
168
+ assert_equal true, sampling.drop_request?(transaction)
169
+ end
170
+ end
171
+
172
+ def test_job_general_sampling
173
+ config = FakeConfigOverlay.new(@individual_config.values.merge({'job_sample_rate' => 80}))
174
+ sampling = ScoutApm::Sampling.new(config)
175
+
176
+ transaction = FakeTrackedRequest.new_job_request('joba')
177
+ transaction2 = FakeTrackedRequest.new_job_request('jobz')
178
+ # joba sampled at 50 specifically, jobz caught by general job rate of 80
179
+ sampling.stub(:rand, 0.01) do
180
+ assert_equal false, sampling.drop_request?(transaction)
181
+ assert_equal false, sampling.drop_request?(transaction2)
182
+ end
183
+
184
+ sampling.stub(:rand, 0.70) do
185
+ assert_equal true, sampling.drop_request?(transaction)
186
+ assert_equal false, sampling.drop_request?(transaction2)
187
+ end
188
+
189
+ sampling.stub(:rand, 0.99) do
190
+ assert_equal true, sampling.drop_request?(transaction)
191
+ assert_equal true, sampling.drop_request?(transaction2)
192
+ end
193
+ end
194
+
195
+ def test_job_request_global_sampling
196
+ config = FakeConfigOverlay.new(@individual_config.values.merge({'sample_rate' => 20}))
197
+ sampling = ScoutApm::Sampling.new(config)
198
+
199
+ # caught by individual rate
200
+ transaction = FakeTrackedRequest.new_job_request('joba')
201
+ sampling.stub(:rand, 0.01) do
202
+ assert_equal false, sampling.drop_request?(transaction)
203
+ end
204
+
205
+ # passes individual rate (50) but caught by global rate (20)
206
+ sampling.stub(:rand, 0.30) do
207
+ assert_equal false, sampling.drop_request?(transaction)
208
+ end
209
+
210
+ # passes individual rate
211
+ sampling.stub(:rand, 0.99) do
212
+ assert_equal true, sampling.drop_request?(transaction)
213
+ end
214
+ end
215
+ end
metadata CHANGED
@@ -1,15 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scout_apm
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.4.0
4
+ version: 5.6.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Derek Haynes
8
8
  - Andre Lewis
9
- autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2024-09-10 00:00:00.000000000 Z
11
+ date: 2025-04-11 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: minitest
@@ -239,8 +238,11 @@ files:
239
238
  - gems/rails4.gemfile
240
239
  - gems/rails5.gemfile
241
240
  - gems/rails6.gemfile
241
+ - gems/rails7.gemfile
242
242
  - gems/sidekiq.gemfile
243
- - gems/sqlite3-1.3.gemfile
243
+ - gems/sidekiq7.gemfile
244
+ - gems/sidekiq8.gemfile
245
+ - gems/sqlite3-v2.gemfile
244
246
  - gems/typhoeus.gemfile
245
247
  - lib/scout_apm.rb
246
248
  - lib/scout_apm/agent.rb
@@ -366,6 +368,7 @@ files:
366
368
  - lib/scout_apm/reporting.rb
367
369
  - lib/scout_apm/request_histograms.rb
368
370
  - lib/scout_apm/request_manager.rb
371
+ - lib/scout_apm/sampling.rb
369
372
  - lib/scout_apm/scored_item_set.rb
370
373
  - lib/scout_apm/serializers/app_server_load_serializer.rb
371
374
  - lib/scout_apm/serializers/db_query_serializer_to_json.rb
@@ -472,6 +475,7 @@ files:
472
475
  - test/unit/remote/route_test.rb
473
476
  - test/unit/remote/server_test.rb
474
477
  - test/unit/request_histograms_test.rb
478
+ - test/unit/sampling_test.rb
475
479
  - test/unit/scored_item_set_test.rb
476
480
  - test/unit/serializers/payload_serializer_test.rb
477
481
  - test/unit/slow_request_policy_test.rb
@@ -489,7 +493,6 @@ homepage: https://github.com/scoutapp/scout_apm_ruby
489
493
  licenses:
490
494
  - MIT
491
495
  metadata: {}
492
- post_install_message:
493
496
  rdoc_options: []
494
497
  require_paths:
495
498
  - lib
@@ -505,8 +508,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
505
508
  - !ruby/object:Gem::Version
506
509
  version: '0'
507
510
  requirements: []
508
- rubygems_version: 3.5.16
509
- signing_key:
511
+ rubygems_version: 3.6.2
510
512
  specification_version: 4
511
513
  summary: Ruby application performance monitoring
512
514
  test_files: []