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 +4 -4
- data/.github/workflows/test.yml +10 -9
- data/CHANGELOG.markdown +27 -1
- data/gems/rails7.gemfile +4 -0
- data/gems/{sqlite3-1.3.gemfile → sidekiq7.gemfile} +1 -1
- data/gems/sidekiq8.gemfile +4 -0
- data/gems/sqlite3-v2.gemfile +3 -0
- data/lib/scout_apm/agent.rb +1 -1
- data/lib/scout_apm/agent_context.rb +4 -0
- data/lib/scout_apm/auto_instrument/layer.rb +1 -0
- data/lib/scout_apm/background_job_integrations/resque.rb +13 -27
- data/lib/scout_apm/background_job_integrations/sidekiq.rb +22 -2
- data/lib/scout_apm/config.rb +69 -34
- data/lib/scout_apm/error_service/sidekiq.rb +1 -1
- data/lib/scout_apm/git_revision.rb +7 -1
- data/lib/scout_apm/instruments/grape.rb +1 -1
- data/lib/scout_apm/instruments/mongoid.rb +7 -35
- data/lib/scout_apm/instruments/resque.rb +31 -2
- data/lib/scout_apm/sampling.rb +104 -0
- data/lib/scout_apm/tracked_request.rb +7 -1
- data/lib/scout_apm/utils/installed_gems.rb +2 -1
- data/lib/scout_apm/version.rb +1 -1
- data/lib/scout_apm.rb +1 -0
- data/test/test_helper.rb +29 -0
- data/test/unit/background_job_integrations/sidekiq_test.rb +13 -3
- data/test/unit/config_test.rb +20 -0
- data/test/unit/git_revision_test.rb +65 -3
- data/test/unit/sampling_test.rb +215 -0
- metadata +9 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9c929de92cd0a3b2690bac6ef665c8c4d7187dd3ecc838cc881033d8cf5c413c
|
4
|
+
data.tar.gz: 3148b06815beb3e506e271bb560fb700b5882d503c1a94f646011117ee424d9b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 28afece432426708eb8ce9bdb527dddd2820261574ca299887ba1294ecc98b34663c04dc15c9cf0ffb78f5211abb9b8d78ba6e202c7a6d28867edda65727b902
|
7
|
+
data.tar.gz: 3e81497fa32092e4f037d272ee1a76f555bd9a557c9855a304e96acc93cdedaf24e0a4b21d86e895dddb1db569ec35954cbff30340f8e9803b7d799b28866522
|
data/.github/workflows/test.yml
CHANGED
@@ -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
|
-
|
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
|
|
data/gems/rails7.gemfile
ADDED
data/lib/scout_apm/agent.rb
CHANGED
@@ -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
|
@@ -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
|
-
|
23
|
-
|
21
|
+
install_before_first_fork
|
22
|
+
install_instruments
|
24
23
|
end
|
25
24
|
|
26
|
-
def
|
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
|
-
|
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
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
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
|
data/lib/scout_apm/config.rb
CHANGED
@@ -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'
|
294
|
-
'detailed_middleware'
|
295
|
-
'dev_trace'
|
296
|
-
'direct_host'
|
297
|
-
'disabled_instruments'
|
298
|
-
'enable_background_jobs'
|
299
|
-
'host'
|
300
|
-
'ignore'
|
301
|
-
'
|
302
|
-
'
|
303
|
-
'
|
304
|
-
'
|
305
|
-
'
|
306
|
-
'
|
307
|
-
'
|
308
|
-
'
|
309
|
-
'
|
310
|
-
'
|
311
|
-
'
|
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'
|
314
|
-
'
|
315
|
-
'
|
316
|
-
'
|
317
|
-
'
|
318
|
-
'
|
319
|
-
'
|
320
|
-
'
|
321
|
-
'
|
322
|
-
'
|
323
|
-
'
|
324
|
-
'
|
325
|
-
'
|
326
|
-
'
|
327
|
-
'
|
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
|
-
|
28
|
-
|
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
|
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 =~ /\
|
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.
|
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
|
[]
|
data/lib/scout_apm/version.rb
CHANGED
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.
|
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 =
|
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 =
|
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
|
|
data/test/unit/config_test.rb
CHANGED
@@ -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
|
-
|
7
|
-
|
8
|
-
|
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
|
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:
|
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/
|
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.
|
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: []
|