scout_apm 5.8.0 → 6.0.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 +4 -4
- data/.github/workflows/test.yml +2 -0
- data/CHANGELOG.markdown +14 -2
- data/README.markdown +20 -8
- data/gems/instruments.gemfile +1 -0
- data/lib/scout_apm/auto_instrument/instruction_sequence.rb +2 -1
- data/lib/scout_apm/auto_instrument/parser.rb +150 -2
- data/lib/scout_apm/auto_instrument/prism.rb +357 -0
- data/lib/scout_apm/auto_instrument/rails.rb +9 -155
- data/lib/scout_apm/auto_instrument/requirements.rb +11 -0
- data/lib/scout_apm/background_job_integrations/delayed_job.rb +15 -1
- data/lib/scout_apm/background_job_integrations/sidekiq.rb +89 -1
- data/lib/scout_apm/config.rb +32 -7
- data/lib/scout_apm/context.rb +3 -1
- data/lib/scout_apm/error_service/error_record.rb +1 -1
- data/lib/scout_apm/instrument_manager.rb +2 -0
- data/lib/scout_apm/instruments/http_client.rb +10 -0
- data/lib/scout_apm/instruments/httpx.rb +119 -0
- data/lib/scout_apm/instruments/opensearch.rb +131 -0
- data/lib/scout_apm/sampling.rb +25 -13
- data/lib/scout_apm/server_integrations/puma.rb +21 -4
- data/lib/scout_apm/version.rb +1 -1
- data/lib/scout_apm.rb +9 -3
- data/test/unit/auto_instrument/controller-ast.prism.txt +1015 -0
- data/test/unit/auto_instrument/controller-instrumented.rb +36 -11
- data/test/unit/auto_instrument/controller.rb +25 -0
- data/test/unit/auto_instrument/hash_shorthand_controller-instrumented.rb +28 -10
- data/test/unit/auto_instrument/hash_shorthand_controller.rb +19 -1
- data/test/unit/auto_instrument_test.rb +7 -1
- data/test/unit/background_job_integrations/sidekiq_test.rb +38 -0
- data/test/unit/config_test.rb +14 -0
- data/test/unit/error_service/error_buffer_test.rb +31 -0
- data/test/unit/error_test.rb +1 -1
- data/test/unit/ignored_uris_test.rb +7 -0
- data/test/unit/instruments/http_client_test.rb +0 -2
- data/test/unit/instruments/httpx_test.rb +78 -0
- data/test/unit/sampling_test.rb +10 -10
- metadata +8 -2
- /data/test/unit/auto_instrument/{controller-ast.txt → controller-ast.parser.txt} +0 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen_string_literal: false
|
|
2
|
+
|
|
3
|
+
module ScoutApm
|
|
4
|
+
module Instruments
|
|
5
|
+
class OpenSearch
|
|
6
|
+
attr_reader :context
|
|
7
|
+
|
|
8
|
+
def initialize(context)
|
|
9
|
+
@context = context
|
|
10
|
+
@installed = false
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def logger
|
|
14
|
+
context.logger
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def installed?
|
|
18
|
+
@installed
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def install(prepend:)
|
|
22
|
+
if defined?(::OpenSearch) &&
|
|
23
|
+
defined?(::OpenSearch::Transport) &&
|
|
24
|
+
defined?(::OpenSearch::Transport::Client)
|
|
25
|
+
|
|
26
|
+
@installed = true
|
|
27
|
+
|
|
28
|
+
logger.info "Instrumenting OpenSearch. Prepend: #{prepend}"
|
|
29
|
+
|
|
30
|
+
if prepend
|
|
31
|
+
::OpenSearch::Transport::Client.send(:include, ScoutApm::Tracer)
|
|
32
|
+
::OpenSearch::Transport::Client.send(:prepend, OpenSearchTransportClientInstrumentationPrepend)
|
|
33
|
+
else
|
|
34
|
+
::OpenSearch::Transport::Client.class_eval do
|
|
35
|
+
include ScoutApm::Tracer
|
|
36
|
+
|
|
37
|
+
def perform_request_with_scout_instruments(*args, &block)
|
|
38
|
+
name = _sanitize_name(args[1])
|
|
39
|
+
|
|
40
|
+
self.class.instrument("OpenSearch", name, :ignore_children => true) do
|
|
41
|
+
perform_request_without_scout_instruments(*args, &block)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
alias_method :perform_request_without_scout_instruments, :perform_request
|
|
46
|
+
alias_method :perform_request, :perform_request_with_scout_instruments
|
|
47
|
+
|
|
48
|
+
def _sanitize_name(path)
|
|
49
|
+
name = path.split("/").last.gsub(/^_/, '')
|
|
50
|
+
allowed_names = ["bench",
|
|
51
|
+
"bulk",
|
|
52
|
+
"count",
|
|
53
|
+
"exists",
|
|
54
|
+
"explain",
|
|
55
|
+
"field_stats",
|
|
56
|
+
"health",
|
|
57
|
+
"mget",
|
|
58
|
+
"mlt",
|
|
59
|
+
"mpercolate",
|
|
60
|
+
"msearch",
|
|
61
|
+
"mtermvectors",
|
|
62
|
+
"percolate",
|
|
63
|
+
"query",
|
|
64
|
+
"scroll",
|
|
65
|
+
"search_shards",
|
|
66
|
+
"source",
|
|
67
|
+
"suggest",
|
|
68
|
+
"template",
|
|
69
|
+
"termvectors",
|
|
70
|
+
"update",
|
|
71
|
+
"search", ]
|
|
72
|
+
|
|
73
|
+
if allowed_names.include?(name)
|
|
74
|
+
name
|
|
75
|
+
else
|
|
76
|
+
"Unknown"
|
|
77
|
+
end
|
|
78
|
+
rescue
|
|
79
|
+
"Unknown"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
module OpenSearchTransportClientInstrumentationPrepend
|
|
88
|
+
def perform_request(*args, &block)
|
|
89
|
+
name = _sanitize_name(args[1])
|
|
90
|
+
|
|
91
|
+
self.class.instrument("OpenSearch", name, :ignore_children => true) do
|
|
92
|
+
super(*args, &block)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def _sanitize_name(path)
|
|
97
|
+
name = path.split("/").last.gsub(/^_/, '')
|
|
98
|
+
allowed_names = ["bench",
|
|
99
|
+
"bulk",
|
|
100
|
+
"count",
|
|
101
|
+
"exists",
|
|
102
|
+
"explain",
|
|
103
|
+
"field_stats",
|
|
104
|
+
"health",
|
|
105
|
+
"mget",
|
|
106
|
+
"mlt",
|
|
107
|
+
"mpercolate",
|
|
108
|
+
"msearch",
|
|
109
|
+
"mtermvectors",
|
|
110
|
+
"percolate",
|
|
111
|
+
"query",
|
|
112
|
+
"scroll",
|
|
113
|
+
"search_shards",
|
|
114
|
+
"source",
|
|
115
|
+
"suggest",
|
|
116
|
+
"template",
|
|
117
|
+
"termvectors",
|
|
118
|
+
"update",
|
|
119
|
+
"search", ]
|
|
120
|
+
|
|
121
|
+
if allowed_names.include?(name)
|
|
122
|
+
name
|
|
123
|
+
else
|
|
124
|
+
"Unknown"
|
|
125
|
+
end
|
|
126
|
+
rescue
|
|
127
|
+
"Unknown"
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
data/lib/scout_apm/sampling.rb
CHANGED
|
@@ -8,7 +8,8 @@ module ScoutApm
|
|
|
8
8
|
# jobs matched explicitly by name
|
|
9
9
|
|
|
10
10
|
# for now still support old config key ('ignore') for backwards compatibility
|
|
11
|
-
|
|
11
|
+
raw_ignore = config.value_present?('ignore') ? config.value('ignore') : config.value('ignore_endpoints')
|
|
12
|
+
@ignore_endpoints = IgnoredUris.new(raw_ignore) unless raw_ignore.blank?
|
|
12
13
|
@sample_endpoints = individual_sample_to_hash(config.value('sample_endpoints'))
|
|
13
14
|
@endpoint_sample_rate = config.value('endpoint_sample_rate')
|
|
14
15
|
|
|
@@ -34,20 +35,20 @@ module ScoutApm
|
|
|
34
35
|
if transaction.job?
|
|
35
36
|
job_name = transaction.layer_finder.job.name
|
|
36
37
|
rate = job_sample_rate(job_name)
|
|
37
|
-
return
|
|
38
|
+
return downsample?(rate) unless rate.nil?
|
|
38
39
|
return true if ignore_job?(job_name)
|
|
39
|
-
return
|
|
40
|
+
return downsample?(@job_sample_rate) unless @job_sample_rate.nil?
|
|
40
41
|
elsif transaction.web?
|
|
41
42
|
uri = transaction.annotations[:uri]
|
|
42
43
|
rate = web_sample_rate(uri)
|
|
43
|
-
return
|
|
44
|
+
return downsample?(rate) unless rate.nil?
|
|
44
45
|
return true if ignore_uri?(uri)
|
|
45
|
-
return
|
|
46
|
+
return downsample?(@endpoint_sample_rate) unless @endpoint_sample_rate.nil?
|
|
46
47
|
end
|
|
47
48
|
|
|
48
49
|
# global sample check
|
|
49
50
|
if @global_sample_rate
|
|
50
|
-
return
|
|
51
|
+
return downsample?(@global_sample_rate)
|
|
51
52
|
end
|
|
52
53
|
|
|
53
54
|
false # don't drop the request
|
|
@@ -59,17 +60,14 @@ module ScoutApm
|
|
|
59
60
|
sample_hash = {}
|
|
60
61
|
sampling_config.each do |sample|
|
|
61
62
|
path, _, rate = sample.rpartition(':')
|
|
62
|
-
sample_hash[path] = rate
|
|
63
|
+
sample_hash[path] = coerce_to_rate(rate)
|
|
63
64
|
end
|
|
64
65
|
sample_hash
|
|
65
66
|
end
|
|
66
67
|
|
|
67
68
|
def ignore_uri?(uri)
|
|
68
69
|
return false if @ignore_endpoints.blank?
|
|
69
|
-
@ignore_endpoints.
|
|
70
|
-
return true if uri.start_with?(prefix)
|
|
71
|
-
end
|
|
72
|
-
false
|
|
70
|
+
@ignore_endpoints.ignore?(uri)
|
|
73
71
|
end
|
|
74
72
|
|
|
75
73
|
def web_sample_rate(uri)
|
|
@@ -90,8 +88,9 @@ module ScoutApm
|
|
|
90
88
|
@sample_jobs.fetch(job_name, nil)
|
|
91
89
|
end
|
|
92
90
|
|
|
93
|
-
def
|
|
94
|
-
|
|
91
|
+
def downsample?(rate)
|
|
92
|
+
# Should we drop this request based on the sample rate?
|
|
93
|
+
rand > rate
|
|
95
94
|
end
|
|
96
95
|
|
|
97
96
|
private
|
|
@@ -100,5 +99,18 @@ module ScoutApm
|
|
|
100
99
|
ScoutApm::Agent.instance.logger
|
|
101
100
|
end
|
|
102
101
|
|
|
102
|
+
def coerce_to_rate(val)
|
|
103
|
+
# Analogous to Config::SampleRateCoercion
|
|
104
|
+
v = val.to_f
|
|
105
|
+
# Anything above 1 is assumed a percentage for backwards compat, so convert to a decimal
|
|
106
|
+
if v > 1
|
|
107
|
+
v = v / 100
|
|
108
|
+
end
|
|
109
|
+
if v < 0 || v > 1
|
|
110
|
+
logger.warn("Sample rates must be between 0 and 1. You passed in #{val.inspect}, which we interpreted as #{v}. Clamping.")
|
|
111
|
+
v = v.clamp(0, 1)
|
|
112
|
+
end
|
|
113
|
+
v
|
|
114
|
+
end
|
|
103
115
|
end
|
|
104
116
|
end
|
|
@@ -53,11 +53,28 @@ module ScoutApm
|
|
|
53
53
|
#
|
|
54
54
|
def install
|
|
55
55
|
old = ::Puma.cli_config.options.user_options[:before_worker_boot] || []
|
|
56
|
-
new = Array(old) + [Proc.new do
|
|
57
|
-
logger.info "Installing Puma worker loop."
|
|
58
|
-
ScoutApm::Agent.instance.start_background_worker
|
|
59
|
-
end]
|
|
60
56
|
|
|
57
|
+
hook =
|
|
58
|
+
if Gem::Version.new(::Puma::Const::PUMA_VERSION) < Gem::Version.new("7.0.0")
|
|
59
|
+
# Puma < 7 uses a raw block
|
|
60
|
+
Proc.new do
|
|
61
|
+
logger.info "Installing Puma worker loop."
|
|
62
|
+
ScoutApm::Agent.instance.start_background_worker
|
|
63
|
+
end
|
|
64
|
+
else
|
|
65
|
+
# Puma >= 7 uses the structured hook format:
|
|
66
|
+
# https://github.com/puma/puma/commit/b16790f7a3c1bfc1847225a58897c9fbd19981f8
|
|
67
|
+
{
|
|
68
|
+
block: Proc.new {
|
|
69
|
+
logger.info "Installing Puma worker loop."
|
|
70
|
+
ScoutApm::Agent.instance.start_background_worker
|
|
71
|
+
},
|
|
72
|
+
id: nil,
|
|
73
|
+
cluster_only: false
|
|
74
|
+
}
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
new = Array.new(old) + [hook]
|
|
61
78
|
::Puma.cli_config.options[:before_worker_boot] = new
|
|
62
79
|
rescue
|
|
63
80
|
logger.warn "Unable to install Puma worker loop: #{$!.message}"
|
data/lib/scout_apm/version.rb
CHANGED
data/lib/scout_apm.rb
CHANGED
|
@@ -84,6 +84,7 @@ require 'scout_apm/histogram'
|
|
|
84
84
|
require 'scout_apm/instruments/net_http'
|
|
85
85
|
require 'scout_apm/instruments/http_client'
|
|
86
86
|
require 'scout_apm/instruments/http'
|
|
87
|
+
require 'scout_apm/instruments/httpx'
|
|
87
88
|
require 'scout_apm/instruments/typhoeus'
|
|
88
89
|
require 'scout_apm/instruments/moped'
|
|
89
90
|
require 'scout_apm/instruments/mongoid'
|
|
@@ -92,6 +93,7 @@ require 'scout_apm/instruments/redis'
|
|
|
92
93
|
require 'scout_apm/instruments/redis5'
|
|
93
94
|
require 'scout_apm/instruments/influxdb'
|
|
94
95
|
require 'scout_apm/instruments/elasticsearch'
|
|
96
|
+
require 'scout_apm/instruments/opensearch'
|
|
95
97
|
require 'scout_apm/instruments/active_record'
|
|
96
98
|
require 'scout_apm/instruments/action_controller_rails_2'
|
|
97
99
|
require 'scout_apm/instruments/action_controller_rails_3_rails4'
|
|
@@ -225,11 +227,15 @@ if defined?(Rails) && defined?(Rails::VERSION) && defined?(Rails::VERSION::MAJOR
|
|
|
225
227
|
# Attempt to start right away, this will work best for preloading apps, Unicorn & Puma & similar
|
|
226
228
|
ScoutApm::Agent.instance.install
|
|
227
229
|
|
|
228
|
-
if ScoutApm::Agent.instance.context.config.value("auto_instruments")
|
|
229
|
-
|
|
230
|
+
if ScoutApm::Agent.instance.context.config.value("auto_instruments") &&
|
|
231
|
+
ScoutApm::Agent.instance.should_load_instruments?
|
|
232
|
+
require 'scout_apm/auto_instrument/requirements'
|
|
233
|
+
if defined?(Prism) || defined?(Parser::TreeRewriter)
|
|
230
234
|
ScoutApm::Agent.instance.context.logger.debug("AutoInstruments is enabled.")
|
|
231
235
|
require 'scout_apm/auto_instrument'
|
|
232
|
-
else
|
|
236
|
+
else
|
|
237
|
+
# AutoInstruments is turned on, but we don't have the prerequisites to use it
|
|
238
|
+
# Prism should be available for Ruby >= 3.3.0
|
|
233
239
|
ScoutApm::Agent.instance.context.logger.debug("AutoInstruments is enabled, but Parser::TreeRewriter is missing. Update 'parser' gem to >= 2.5.0.")
|
|
234
240
|
end
|
|
235
241
|
else
|