scout_apm 5.5.0 → 5.6.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: b3dfd72cdc46892ac46a974e4e3947f81ac2ee6580bee96c0cc6ad845ce50ee7
4
- data.tar.gz: 560fb31452b8c5b526bf77723d121d561096c759640c0266ba894ffe71901d99
3
+ metadata.gz: 46df05a2d67c23f243d1eedfddd2eabdd8a6151ef8974e185ea7c8efbe01d076
4
+ data.tar.gz: 072bcd67b72f477c924dd647f2e3f87e586f3a97ff8dbbd727d5f5991c060f54
5
5
  SHA512:
6
- metadata.gz: 3958265f12804e22dbb4c75d3b03a6d4ad9f87ff989e5382f8d0b890d55c2c2505828cd4022fa7a965092bb7c0948a12baea760b0d15fd05f5ceef6d1a23f962
7
- data.tar.gz: cb45c1255f16f299e7a8cc67c93efd21fd7912682c4d4ed2030498be069022f2cf8811a987bbec0c22ff28150d57ff95222ed0771e0c2758bbba057b1805d4a8
6
+ metadata.gz: 31ab64b2f5a212322989d118edba3f92cb4c4a223618bebd7f4fae318ebb243459dcd730388a080d7e7d6e0ce1147af857bb656f67bdeda902878e167f195f90
7
+ data.tar.gz: 9191e3bf53423a7db1e9c0265a6b8cf03eb7bce0374ed392c137ab1c592dffee312c8efe8e6301e3a0fad17e033429af4c0b98b7608438be44251682236874d6
data/CHANGELOG.markdown CHANGED
@@ -1,5 +1,15 @@
1
1
  # Unreleased
2
2
 
3
+ # 5.6.0
4
+ - New options for sampling and ignore configurationn (#521)
5
+ - `sample_rate` - Set the rate at which requests are sampled globally (1-100, a percentage of requests to keep).
6
+ - `ignore_endpoints` - Ignore endpoints by regex matching prefix (Same as and replaces `ignore`)
7
+ - `sample_endpoints` - Sample endpoints by regex matching prefix (i.e. ['/foo:70']).
8
+ - `endpoint_sample_rate` - Set the rate at which all non-matching web requests are sampled.
9
+ - `ignore_jobs` - Ignore Jobs by explicit name match.
10
+ - `sample_jobs` - Sample Jobs by explicit name match (i.e. ['MyJob:70']).
11
+ - `job_sample_rate` - Set the rate at which all non-matching background jobs are sampled.
12
+
3
13
  # 5.5.0
4
14
  - Fix undeclared logger in grape instruments (#510)
5
15
  - Drop guaranteed support for Rubies <= 2.4
@@ -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
@@ -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',
@@ -184,6 +198,8 @@ module ScoutApm
184
198
  'dev_trace' => BooleanCoercion.new,
185
199
  'enable_background_jobs' => BooleanCoercion.new,
186
200
  'ignore' => JsonCoercion.new,
201
+ 'ignore_endpoints' => JsonCoercion.new,
202
+ 'ignore_jobs' => JsonCoercion.new,
187
203
  'max_traces' => IntegerCoercion.new,
188
204
  'monitor' => BooleanCoercion.new,
189
205
  'collect_remote_ip' => BooleanCoercion.new,
@@ -194,6 +210,11 @@ module ScoutApm
194
210
  'external_service_metric_report_limit' => IntegerCoercion.new,
195
211
  'instrument_http_url_length' => IntegerCoercion.new,
196
212
  'record_queue_time' => BooleanCoercion.new,
213
+ 'sample_rate' => IntegerCoercion.new,
214
+ 'sample_endpoints' => JsonCoercion.new,
215
+ 'sample_jobs' => JsonCoercion.new,
216
+ 'endpoint_sample_rate' => IntegerCoercion.new,
217
+ 'job_sample_rate' => IntegerCoercion.new,
197
218
  'start_resque_server_instrument' => BooleanCoercion.new,
198
219
  'timeline_traces' => BooleanCoercion.new,
199
220
  'auto_instruments' => BooleanCoercion.new,
@@ -290,41 +311,48 @@ module ScoutApm
290
311
 
291
312
  class ConfigDefaults
292
313
  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
314
+ 'compress_payload' => true,
315
+ 'detailed_middleware' => false,
316
+ 'dev_trace' => false,
317
+ 'direct_host' => 'https://apm.scoutapp.com',
318
+ 'disabled_instruments' => [],
319
+ 'enable_background_jobs' => true,
320
+ 'host' => 'https://checkin.scoutapp.com',
321
+ 'ignore' => [],
322
+ 'ignore_endpoints' => [],
323
+ 'ignore_jobs' => [],
324
+ 'log_level' => 'info',
325
+ 'max_traces' => 10,
326
+ 'profile' => true, # for scoutprof
327
+ 'report_format' => 'json',
328
+ 'scm_subdirectory' => '',
329
+ 'uri_reporting' => 'full_path',
330
+ 'remote_agent_host' => '127.0.0.1',
331
+ 'remote_agent_port' => 7721, # picked at random
332
+ 'database_metric_limit' => 5000, # The hard limit on db metrics
333
+ 'database_metric_report_limit' => 1000,
334
+ 'external_service_metric_limit' => 5000, # The hard limit on external service metrics
312
335
  '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',
336
+ 'instrument_http_url_length' => 300,
337
+ 'sample_rate' => 100,
338
+ 'sample_endpoints' => [],
339
+ 'sample_jobs' => [],
340
+ 'endpoint_sample_rate' => 100,
341
+ 'job_sample_rate' => 100,
342
+ 'start_resque_server_instrument' => true, # still only starts if Resque is detected
343
+ 'collect_remote_ip' => true,
344
+ 'record_queue_time' => true,
345
+ 'timeline_traces' => true,
346
+ 'auto_instruments' => false,
347
+ 'auto_instruments_ignore' => [],
348
+ 'use_prepend' => false,
349
+ 'alias_method_instruments' => [],
350
+ 'prepend_instruments' => [],
351
+ 'ssl_cert_file' => File.join( File.dirname(__FILE__), *%w[.. .. data cacert.pem] ),
352
+ 'errors_enabled' => false,
353
+ 'errors_ignored_exceptions' => %w(ActiveRecord::RecordNotFound ActionController::RoutingError),
354
+ 'errors_filtered_params' => %w(password s3-key),
355
+ 'errors_host' => 'https://errors.scoutapm.com',
328
356
  }.freeze
329
357
 
330
358
  def value(key)
@@ -0,0 +1,96 @@
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
+ logger.info("Sampling initialized with config: global_sample_rate: #{@global_sample_rate}, endpoint_sample_rate: #{@endpoint_sample_rate}, sample_endpoints: #{@sample_endpoints}, ignore_endpoints: #{@ignore_endpoints}, job_sample_rate: #@job_sample_rate}, sample_jobs: #{@sample_jobs}, ignore_jobs: #{@ignore_jobs}")
20
+ end
21
+
22
+ def drop_request?(transaction)
23
+ # Individual endpoint/job sampling takes precedence over ignoring.
24
+ # Individual endpoint/job sample rate always takes precedence over general endpoint/job rate.
25
+ # General endpoint/job rate always takes precedence over global sample rate
26
+ if transaction.job?
27
+ job_name = transaction.layer_finder.job.name
28
+ rate = job_sample_rate(job_name)
29
+ return sample?(rate) unless rate.nil?
30
+ return true if ignore_job?(job_name)
31
+ return sample?(@job_sample_rate) unless @job_sample_rate.nil?
32
+ elsif transaction.web?
33
+ uri = transaction.annotations[:uri]
34
+ rate = web_sample_rate(uri)
35
+ return sample?(rate) unless rate.nil?
36
+ return true if ignore_uri?(uri)
37
+ return sample?(@endpoint_sample_rate) unless @endpoint_sample_rate.nil?
38
+ end
39
+
40
+ # global sample check
41
+ if @global_sample_rate
42
+ return sample?(@global_sample_rate)
43
+ end
44
+
45
+ false # don't drop the request
46
+ end
47
+
48
+ def individual_sample_to_hash(sampling_config)
49
+ return nil if sampling_config.blank?
50
+ # config looks like ['/foo:50','/bar:100']. parse it into hash of string: integer
51
+ sample_hash = {}
52
+ sampling_config.each do |sample|
53
+ path, rate = sample.split(':')
54
+ sample_hash[path] = rate.to_i
55
+ end
56
+ sample_hash
57
+ end
58
+
59
+ def ignore_uri?(uri)
60
+ return false if @ignore_endpoints.blank?
61
+ @ignore_endpoints.each do |prefix|
62
+ return true if uri.start_with?(prefix)
63
+ end
64
+ false
65
+ end
66
+
67
+ def web_sample_rate(uri)
68
+ return nil if @sample_endpoints.blank?
69
+ @sample_endpoints.each do |prefix, rate|
70
+ return rate if uri.start_with?(prefix)
71
+ end
72
+ nil
73
+ end
74
+
75
+ def ignore_job?(job_name)
76
+ return false if @ignore_jobs.blank?
77
+ @ignore_jobs.include?(job_name)
78
+ end
79
+
80
+ def job_sample_rate(job_name)
81
+ return nil if @sample_jobs.blank?
82
+ @sample_jobs.fetch(job_name, nil)
83
+ end
84
+
85
+ def sample?(rate)
86
+ rand * 100 > rate
87
+ end
88
+
89
+ private
90
+
91
+ def logger
92
+ ScoutApm::Agent.instance.logger
93
+ end
94
+
95
+ end
96
+ end
@@ -303,7 +303,11 @@ module ScoutApm
303
303
  restore_from_dump! if @agent_context.nil?
304
304
 
305
305
  # Bail out early if the user asked us to ignore this uri
306
- return if @agent_context.ignored_uris.ignore?(annotations[:uri])
306
+ # return if @agent_context.ignored_uris.ignore?(annotations[:uri])
307
+ if @agent_context.sampling.drop_request?(self)
308
+ logger.debug("Dropping request due to sampling")
309
+ return
310
+ end
307
311
 
308
312
  apply_name_override
309
313
 
@@ -1,3 +1,3 @@
1
1
  module ScoutApm
2
- VERSION = "5.5.0"
2
+ VERSION = "5.6.0"
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
@@ -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_equal 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_equal nil, rate
45
+
46
+ rate = sampling.web_sample_rate('/baz/bap')
47
+ assert_equal 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_equal 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,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scout_apm
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.5.0
4
+ version: 5.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Derek Haynes
8
8
  - Andre Lewis
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-01-02 00:00:00.000000000 Z
11
+ date: 2025-01-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -365,6 +365,7 @@ files:
365
365
  - lib/scout_apm/reporting.rb
366
366
  - lib/scout_apm/request_histograms.rb
367
367
  - lib/scout_apm/request_manager.rb
368
+ - lib/scout_apm/sampling.rb
368
369
  - lib/scout_apm/scored_item_set.rb
369
370
  - lib/scout_apm/serializers/app_server_load_serializer.rb
370
371
  - lib/scout_apm/serializers/db_query_serializer_to_json.rb
@@ -471,6 +472,7 @@ files:
471
472
  - test/unit/remote/route_test.rb
472
473
  - test/unit/remote/server_test.rb
473
474
  - test/unit/request_histograms_test.rb
475
+ - test/unit/sampling_test.rb
474
476
  - test/unit/scored_item_set_test.rb
475
477
  - test/unit/serializers/payload_serializer_test.rb
476
478
  - test/unit/slow_request_policy_test.rb