scout_apm 1.6.8 → 2.0.0.pre
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/.gitignore +8 -1
- data/CHANGELOG.markdown +7 -57
- data/ext/allocations/allocations.c +84 -0
- data/ext/allocations/extconf.rb +3 -0
- data/lib/scout_apm/agent/reporting.rb +9 -32
- data/lib/scout_apm/agent.rb +45 -31
- data/lib/scout_apm/app_server_load.rb +1 -2
- data/lib/scout_apm/attribute_arranger.rb +0 -4
- data/lib/scout_apm/background_worker.rb +6 -9
- data/lib/scout_apm/bucket_name_splitter.rb +3 -3
- data/lib/scout_apm/call_set.rb +1 -0
- data/lib/scout_apm/config.rb +110 -66
- data/lib/scout_apm/environment.rb +16 -10
- data/lib/scout_apm/framework_integrations/rails_2.rb +12 -14
- data/lib/scout_apm/framework_integrations/rails_3_or_4.rb +5 -17
- data/lib/scout_apm/framework_integrations/ruby.rb +0 -4
- data/lib/scout_apm/framework_integrations/sinatra.rb +0 -4
- data/lib/scout_apm/histogram.rb +0 -20
- data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +1 -4
- data/lib/scout_apm/instruments/active_record.rb +149 -8
- data/lib/scout_apm/instruments/mongoid.rb +5 -78
- data/lib/scout_apm/instruments/process/process_cpu.rb +0 -12
- data/lib/scout_apm/instruments/process/process_memory.rb +14 -43
- data/lib/scout_apm/layaway.rb +34 -134
- data/lib/scout_apm/layaway_file.rb +50 -27
- data/lib/scout_apm/layer.rb +45 -1
- data/lib/scout_apm/layer_converters/allocation_metric_converter.rb +17 -0
- data/lib/scout_apm/layer_converters/converter_base.rb +4 -6
- data/lib/scout_apm/layer_converters/job_converter.rb +1 -0
- data/lib/scout_apm/layer_converters/metric_converter.rb +2 -1
- data/lib/scout_apm/layer_converters/slow_job_converter.rb +42 -21
- data/lib/scout_apm/layer_converters/slow_request_converter.rb +58 -37
- data/lib/scout_apm/metric_meta.rb +1 -5
- data/lib/scout_apm/metric_set.rb +6 -15
- data/lib/scout_apm/reporter.rb +4 -6
- data/lib/scout_apm/serializers/metrics_to_json_serializer.rb +5 -1
- data/lib/scout_apm/serializers/payload_serializer_to_json.rb +1 -3
- data/lib/scout_apm/serializers/slow_jobs_serializer_to_json.rb +5 -3
- data/lib/scout_apm/slow_job_policy.rb +19 -89
- data/lib/scout_apm/slow_job_record.rb +12 -20
- data/lib/scout_apm/slow_request_policy.rb +12 -80
- data/lib/scout_apm/slow_transaction.rb +16 -20
- data/lib/scout_apm/stackprof_tree_collapser.rb +103 -0
- data/lib/scout_apm/store.rb +16 -78
- data/lib/scout_apm/tracked_request.rb +53 -36
- data/lib/scout_apm/utils/active_record_metric_name.rb +2 -0
- data/lib/scout_apm/utils/fake_stack_prof.rb +40 -0
- data/lib/scout_apm/utils/klass_helper.rb +26 -0
- data/lib/scout_apm/utils/sql_sanitizer.rb +1 -1
- data/lib/scout_apm/utils/sql_sanitizer_regex.rb +2 -2
- data/lib/scout_apm/utils/sql_sanitizer_regex_1_8_7.rb +2 -2
- data/lib/scout_apm/version.rb +1 -1
- data/lib/scout_apm.rb +13 -7
- data/scout_apm.gemspec +3 -1
- data/test/test_helper.rb +3 -4
- data/test/unit/layaway_test.rb +8 -5
- data/test/unit/serializers/payload_serializer_test.rb +2 -2
- data/test/unit/slow_item_set_test.rb +1 -2
- data/test/unit/sql_sanitizer_test.rb +0 -6
- metadata +28 -20
- data/LICENSE.md +0 -27
- data/lib/scout_apm/instruments/grape.rb +0 -69
- data/lib/scout_apm/instruments/percentile_sampler.rb +0 -37
- data/lib/scout_apm/request_histograms.rb +0 -46
- data/lib/scout_apm/scored_item_set.rb +0 -79
- data/test/unit/metric_set_test.rb +0 -101
- data/test/unit/scored_item_set_test.rb +0 -65
- data/test/unit/slow_request_policy_test.rb +0 -42
data/lib/scout_apm/config.rb
CHANGED
@@ -7,103 +7,147 @@ require 'scout_apm/environment'
|
|
7
7
|
#
|
8
8
|
# application_root - override the detected directory of the application
|
9
9
|
# data_file - override the default temporary storage location. Must be a location in a writable directory
|
10
|
-
#
|
11
|
-
# direct_host - override the default "direct" host. The direct_host bypasses the ingestion pipeline and goes directly to the webserver, and is primarily used for features under development.
|
10
|
+
# hostname - override the default hostname detection. Default varies by environment - either system hostname, or PAAS hostname
|
12
11
|
# key - the account key with Scout APM. Found in Settings in the Web UI
|
13
12
|
# log_file_path - either a directory or "STDOUT".
|
14
13
|
# log_level - DEBUG / INFO / WARN as usual
|
15
14
|
# monitor - true or false. False prevents any instrumentation from starting
|
16
15
|
# name - override the name reported to APM. This is the name that shows in the Web UI
|
17
16
|
# uri_reporting - 'path' or 'full_path' default is 'full_path', which reports URL params as well as the path.
|
18
|
-
# report_format - 'json' or 'marshal'.
|
17
|
+
# report_format - 'json' or 'marshal'. Json is default, Marshal is deprecated
|
19
18
|
#
|
20
19
|
# Any of these config settings can be set with an environment variable prefixed
|
21
20
|
# by SCOUT_ and uppercasing the key: SCOUT_LOG_LEVEL for instance.
|
22
21
|
|
22
|
+
|
23
|
+
# Config - Made up of config overlay
|
24
|
+
# Default -> File -> Environment Var
|
25
|
+
# QUESTION: How to embed arrays or hashes into ENV?
|
26
|
+
|
23
27
|
module ScoutApm
|
24
28
|
class Config
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
'ignore_traces' => [],
|
34
|
-
}.freeze
|
35
|
-
|
36
|
-
def initialize(config_path = nil)
|
37
|
-
@config_path = config_path
|
29
|
+
# Load up a config instance without attempting to load a file.
|
30
|
+
# Useful for bootstrapping.
|
31
|
+
def self.without_file
|
32
|
+
overlays = [
|
33
|
+
ConfigEnvironment.new,
|
34
|
+
ConfigDefaults.new,
|
35
|
+
]
|
36
|
+
new(overlays)
|
38
37
|
end
|
39
38
|
|
40
|
-
|
41
|
-
|
39
|
+
# Load up a config instance, attempting to load a yaml file. Allows a
|
40
|
+
# definite location if requested, or will attempt to load the default
|
41
|
+
# configuration file: APP_ROOT/config/scout_apm.yml
|
42
|
+
def self.with_file(file_path=nil)
|
43
|
+
overlays = [
|
44
|
+
ConfigEnvironment.new,
|
45
|
+
ConfigFile.new(file_path),
|
46
|
+
ConfigDefaults.new,
|
47
|
+
]
|
48
|
+
new(overlays)
|
42
49
|
end
|
43
50
|
|
44
|
-
|
45
|
-
|
46
|
-
# then from the settings file.
|
47
|
-
#
|
48
|
-
# If you set env_only, then it will not attempt to read the config file at
|
49
|
-
# all, and only read off the ENV var this is useful to break a loop during
|
50
|
-
# boot, where we needed an option to set the application root.
|
51
|
-
def value(key, env_only=false)
|
52
|
-
value = if env_only
|
53
|
-
ENV['SCOUT_' + key.upcase]
|
54
|
-
else
|
55
|
-
ENV['SCOUT_' + key.upcase] || setting(key)
|
56
|
-
end
|
57
|
-
|
58
|
-
value.to_s.strip.length.zero? ? nil : value
|
51
|
+
def initialize(overlays)
|
52
|
+
@overlays = overlays
|
59
53
|
end
|
60
54
|
|
61
|
-
|
55
|
+
def value(key)
|
56
|
+
@overlays.each do |overlay|
|
57
|
+
if result = overlay.value(key)
|
58
|
+
return result
|
59
|
+
end
|
60
|
+
end
|
62
61
|
|
63
|
-
|
64
|
-
@config_path || File.join(ScoutApm::Environment.instance.root, "config", "scout_apm.yml")
|
62
|
+
nil
|
65
63
|
end
|
66
64
|
|
67
|
-
|
68
|
-
|
69
|
-
|
65
|
+
class ConfigDefaults
|
66
|
+
DEFAULTS = {
|
67
|
+
'host' => 'https://checkin.scoutapp.com',
|
68
|
+
'log_level' => 'info',
|
69
|
+
'stackprof_interval' => 20000, # microseconds, 1000 = 1 millisecond, so 20k == 20 milliseconds
|
70
|
+
'uri_reporting' => 'full_path',
|
71
|
+
'report_format' => 'json',
|
72
|
+
'disabled_instruments' => [],
|
73
|
+
'enable_background_jobs' => true,
|
74
|
+
}.freeze
|
70
75
|
|
71
|
-
|
72
|
-
|
76
|
+
def value(key)
|
77
|
+
DEFAULTS[key]
|
78
|
+
end
|
73
79
|
end
|
74
80
|
|
75
|
-
|
76
|
-
(
|
81
|
+
class ConfigEnvironment
|
82
|
+
def value(key)
|
83
|
+
val = ENV['SCOUT_' + key.upcase]
|
84
|
+
val.to_s.strip.length.zero? ? nil : val
|
85
|
+
end
|
77
86
|
end
|
78
87
|
|
79
|
-
|
80
|
-
|
81
|
-
|
88
|
+
# Attempts to load a configuration file, and parse it as YAML. If the file
|
89
|
+
# is not found, inaccessbile, or unparsable, log a message to that effect,
|
90
|
+
# and move on.
|
91
|
+
class ConfigFile
|
92
|
+
def initialize(file_path=nil)
|
93
|
+
@resolved_file_path = file_path || determine_file_path
|
94
|
+
load_file(@resolved_file_path)
|
95
|
+
end
|
82
96
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
settings_hash = YAML.load(ERB.new(File.read(config_file)).result(binding))[config_environment] || {}
|
97
|
+
def value(key)
|
98
|
+
if @file_loaded
|
99
|
+
val = @settings[key]
|
100
|
+
val.to_s.strip.length.zero? ? nil : val
|
88
101
|
else
|
89
|
-
|
102
|
+
nil
|
90
103
|
end
|
91
|
-
rescue Exception => e
|
92
|
-
logger.warn "Unable to load the config file."
|
93
|
-
logger.warn e.message
|
94
|
-
logger.warn e.backtrace
|
95
104
|
end
|
96
|
-
DEFAULTS.merge(settings_hash)
|
97
|
-
end
|
98
105
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
106
|
+
private
|
107
|
+
|
108
|
+
def load_file(file)
|
109
|
+
if !File.exist?(@resolved_file_path)
|
110
|
+
logger.info("Configuration file #{file} does not exist, skipping.")
|
111
|
+
@file_loaded = false
|
112
|
+
return
|
113
|
+
end
|
114
|
+
|
115
|
+
if !app_environment
|
116
|
+
logger.info("Could not determine application environment, aborting configuration file load")
|
117
|
+
@file_loaded = false
|
118
|
+
return
|
119
|
+
end
|
120
|
+
|
121
|
+
begin
|
122
|
+
raw_file = File.read(@resolved_file_path)
|
123
|
+
erb_file = ERB.new(raw_file).result(binding)
|
124
|
+
parsed_yaml = YAML.load(erb_file)
|
125
|
+
@settings = parsed_yaml[app_environment]
|
126
|
+
|
127
|
+
if !@settings.is_a? Hash
|
128
|
+
raise ("Missing environment key for: #{app_environment}. This can happen if the key is missing, or with a malformed configuration file," +
|
129
|
+
" check that there is a top level #{app_environment} key.")
|
130
|
+
end
|
131
|
+
|
132
|
+
logger.info("Loaded Configuration: #{@resolved_file_path}. Using environment: #{app_environment}")
|
133
|
+
@file_loaded = true
|
134
|
+
rescue Exception => e # Explicit `Exception` handling to catch SyntaxError and anything else that ERB or YAML may throw
|
135
|
+
logger.info("Failed loading configuration file: #{e.message}. ScoutAPM will continue starting with configuration from ENV and defaults")
|
136
|
+
@file_loaded = false
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def determine_file_path
|
141
|
+
File.join(ScoutApm::Environment.instance.root, "config", "scout_apm.yml")
|
142
|
+
end
|
143
|
+
|
144
|
+
def app_environment
|
145
|
+
ScoutApm::Environment.instance.env
|
146
|
+
end
|
147
|
+
|
148
|
+
# TODO: Make this better
|
149
|
+
def logger
|
150
|
+
ScoutApm::Agent.instance.logger || Logger.new(STDOUT)
|
107
151
|
end
|
108
152
|
end
|
109
153
|
end
|
@@ -70,10 +70,6 @@ module ScoutApm
|
|
70
70
|
framework_integration.database_engine
|
71
71
|
end
|
72
72
|
|
73
|
-
def raw_database_adapter
|
74
|
-
framework_integration.raw_database_adapter
|
75
|
-
end
|
76
|
-
|
77
73
|
def processors
|
78
74
|
@processors ||= begin
|
79
75
|
proc_file = '/proc/cpuinfo'
|
@@ -88,12 +84,11 @@ module ScoutApm
|
|
88
84
|
end
|
89
85
|
|
90
86
|
def root
|
91
|
-
|
92
|
-
framework_root
|
87
|
+
@root ||= deploy_integration? ? deploy_integration.root : framework_root
|
93
88
|
end
|
94
89
|
|
95
90
|
def framework_root
|
96
|
-
if override_root = Agent.instance.config.value("application_root"
|
91
|
+
if override_root = Agent.instance.config.value("application_root")
|
97
92
|
return override_root
|
98
93
|
end
|
99
94
|
if framework == :rails
|
@@ -108,8 +103,7 @@ module ScoutApm
|
|
108
103
|
end
|
109
104
|
|
110
105
|
def hostname
|
111
|
-
|
112
|
-
@hostname ||= config_hostname || platform_integration.hostname
|
106
|
+
@hostname ||= Agent.instance.config.value("hostname") || platform_integration.hostname
|
113
107
|
end
|
114
108
|
|
115
109
|
# Returns the whole integration object
|
@@ -134,7 +128,7 @@ module ScoutApm
|
|
134
128
|
end
|
135
129
|
|
136
130
|
def background_job_integration
|
137
|
-
if Agent.instance.config.value("enable_background_jobs"
|
131
|
+
if Agent.instance.config.value("enable_background_jobs")
|
138
132
|
@background_job_integration ||= BACKGROUND_JOB_INTEGRATIONS.detect {|integration| integration.present?}
|
139
133
|
else
|
140
134
|
nil
|
@@ -175,6 +169,18 @@ module ScoutApm
|
|
175
169
|
@ruby_2 ||= defined?(RUBY_VERSION) && RUBY_VERSION.match(/^2/)
|
176
170
|
end
|
177
171
|
|
172
|
+
# Returns a string representation of the OS (ex: darwin, linux)
|
173
|
+
def os
|
174
|
+
return @os if @os
|
175
|
+
raw_os = RbConfig::CONFIG['target_os']
|
176
|
+
match = raw_os.match(/([a-z]+)/)
|
177
|
+
if match
|
178
|
+
@os = match[1]
|
179
|
+
else
|
180
|
+
@os = raw_os
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
178
184
|
### framework checks
|
179
185
|
|
180
186
|
def sinatra?
|
@@ -37,14 +37,18 @@ module ScoutApm
|
|
37
37
|
default = :mysql
|
38
38
|
|
39
39
|
if defined?(ActiveRecord::Base)
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
40
|
+
config = ActiveRecord::Base.configurations[env]
|
41
|
+
if config && config["adapter"]
|
42
|
+
case config["adapter"].to_s
|
43
|
+
when "postgres" then :postgres
|
44
|
+
when "postgresql" then :postgres
|
45
|
+
when "postgis" then :postgres
|
46
|
+
when "sqlite3" then :sqlite
|
47
|
+
when "mysql" then :mysql
|
48
|
+
else default
|
49
|
+
end
|
50
|
+
else
|
51
|
+
default
|
48
52
|
end
|
49
53
|
else
|
50
54
|
default
|
@@ -52,12 +56,6 @@ module ScoutApm
|
|
52
56
|
rescue
|
53
57
|
default
|
54
58
|
end
|
55
|
-
|
56
|
-
def raw_database_adapter
|
57
|
-
ActiveRecord::Base.configurations[env]["adapter"]
|
58
|
-
rescue
|
59
|
-
nil
|
60
|
-
end
|
61
59
|
end
|
62
60
|
end
|
63
61
|
end
|
@@ -34,19 +34,17 @@ module ScoutApm
|
|
34
34
|
|
35
35
|
def database_engine
|
36
36
|
return @database_engine if @database_engine
|
37
|
-
default = :
|
37
|
+
default = :mysql
|
38
38
|
|
39
39
|
@database_engine = if defined?(ActiveRecord::Base)
|
40
|
-
adapter =
|
40
|
+
adapter = get_database_adapter # can be nil
|
41
41
|
|
42
42
|
case adapter.to_s
|
43
43
|
when "postgres" then :postgres
|
44
44
|
when "postgresql" then :postgres
|
45
45
|
when "postgis" then :postgres
|
46
46
|
when "sqlite3" then :sqlite
|
47
|
-
when "sqlite" then :sqlite
|
48
47
|
when "mysql" then :mysql
|
49
|
-
when "mysql2" then :mysql
|
50
48
|
else default
|
51
49
|
end
|
52
50
|
else
|
@@ -55,21 +53,11 @@ module ScoutApm
|
|
55
53
|
end
|
56
54
|
end
|
57
55
|
|
58
|
-
def
|
59
|
-
|
60
|
-
ActiveRecord::Base.connection_config[:adapter].to_s
|
61
|
-
else
|
62
|
-
nil
|
63
|
-
end
|
64
|
-
|
65
|
-
if adapter.nil?
|
66
|
-
adapter = ActiveRecord::Base.configurations[env]["adapter"]
|
67
|
-
end
|
68
|
-
|
69
|
-
return adapter
|
56
|
+
def get_database_adapter
|
57
|
+
ActiveRecord::Base.configurations[env]["adapter"]
|
70
58
|
rescue # this would throw an exception if ActiveRecord::Base is defined but no configuration exists.
|
71
59
|
nil
|
72
60
|
end
|
73
61
|
end
|
74
62
|
end
|
75
|
-
end
|
63
|
+
end
|
data/lib/scout_apm/histogram.rb
CHANGED
@@ -56,26 +56,6 @@ module ScoutApm
|
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
|
-
# Given a value, where in this histogram does it fall?
|
60
|
-
# Returns a float between 0 and 1
|
61
|
-
def approximate_quantile_of_value(v)
|
62
|
-
mutex.synchronize do
|
63
|
-
return 100 if total == 0
|
64
|
-
|
65
|
-
count_examined = 0
|
66
|
-
|
67
|
-
bins.each_with_index do |bin, index|
|
68
|
-
if v <= bin.value
|
69
|
-
break
|
70
|
-
end
|
71
|
-
|
72
|
-
count_examined += bin.count
|
73
|
-
end
|
74
|
-
|
75
|
-
count_examined / total.to_f
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
59
|
def mean
|
80
60
|
mutex.synchronize do
|
81
61
|
if total == 0
|
@@ -61,10 +61,7 @@ module ScoutApm
|
|
61
61
|
req = ScoutApm::RequestManager.lookup
|
62
62
|
path = ScoutApm::Agent.instance.config.value("uri_reporting") == 'path' ? request.path : request.fullpath
|
63
63
|
req.annotate_request(:uri => path)
|
64
|
-
|
65
|
-
# IP Spoofing Protection can throw an exception, just move on w/o remote ip
|
66
|
-
req.context.add_user(:ip => request.remote_ip) rescue nil
|
67
|
-
|
64
|
+
req.context.add_user(:ip => request.remote_ip)
|
68
65
|
req.set_headers(request.headers)
|
69
66
|
req.web!
|
70
67
|
|
@@ -27,14 +27,44 @@ module ScoutApm
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def add_instruments
|
30
|
-
|
30
|
+
# Setup Tracer on AR::Base
|
31
|
+
if Utils::KlassHelper.defined?("ActiveRecord::Base")
|
32
|
+
::ActiveRecord::Base.class_eval do
|
33
|
+
include ::ScoutApm::Tracer
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Install #log tracing
|
38
|
+
if Utils::KlassHelper.defined?("ActiveRecord::ConnectionAdapters::AbstractAdapter")
|
31
39
|
::ActiveRecord::ConnectionAdapters::AbstractAdapter.module_eval do
|
32
40
|
include ::ScoutApm::Instruments::ActiveRecordInstruments
|
33
41
|
include ::ScoutApm::Tracer
|
34
42
|
end
|
43
|
+
end
|
44
|
+
|
45
|
+
if Utils::KlassHelper.defined?("ActiveRecord::Querying")
|
46
|
+
::ActiveRecord::Querying.module_eval do
|
47
|
+
include ::ScoutApm::Tracer
|
48
|
+
include ::ScoutApm::Instruments::ActiveRecordQueryingInstruments
|
49
|
+
end
|
50
|
+
end
|
35
51
|
|
36
|
-
|
52
|
+
if Utils::KlassHelper.defined?("ActiveRecord::FinderMethods")
|
53
|
+
::ActiveRecord::FinderMethods.module_eval do
|
37
54
|
include ::ScoutApm::Tracer
|
55
|
+
include ::ScoutApm::Instruments::ActiveRecordFinderMethodsInstruments
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
if Utils::KlassHelper.defined?("ActiveSupport::Notifications")
|
60
|
+
ActiveSupport::Notifications.subscribe("instantiation.active_record") do |event_name, start, stop, uuid, payload|
|
61
|
+
req = ScoutApm::RequestManager.lookup
|
62
|
+
layer = req.current_layer
|
63
|
+
if layer && layer.type == "ActiveRecord"
|
64
|
+
layer.annotate_layer(payload)
|
65
|
+
else
|
66
|
+
ScoutApm::Agent.instance.logger.debug("Expected layer type: ActiveRecord, got #{layer && layer.type}")
|
67
|
+
end
|
38
68
|
end
|
39
69
|
end
|
40
70
|
rescue
|
@@ -44,6 +74,13 @@ module ScoutApm
|
|
44
74
|
|
45
75
|
# Contains ActiveRecord instrument, aliasing +ActiveRecord::ConnectionAdapters::AbstractAdapter#log+ calls
|
46
76
|
# to trace calls to the database.
|
77
|
+
################################################################################
|
78
|
+
# #log instrument.
|
79
|
+
#
|
80
|
+
# #log is very close to where AR calls out to the database itself. We have access
|
81
|
+
# to the real SQL, and an AR generated "name" for the Query
|
82
|
+
#
|
83
|
+
################################################################################
|
47
84
|
module ActiveRecordInstruments
|
48
85
|
def self.included(instrumented_class)
|
49
86
|
ScoutApm::Agent.instance.logger.info "Instrumenting #{instrumented_class.inspect}"
|
@@ -51,19 +88,123 @@ module ScoutApm
|
|
51
88
|
unless instrumented_class.method_defined?(:log_without_scout_instruments)
|
52
89
|
alias_method :log_without_scout_instruments, :log
|
53
90
|
alias_method :log, :log_with_scout_instruments
|
54
|
-
protected :log
|
55
91
|
end
|
56
92
|
end
|
57
|
-
end
|
93
|
+
end
|
58
94
|
|
59
95
|
def log_with_scout_instruments(*args, &block)
|
96
|
+
# Extract data from the arguments
|
60
97
|
sql, name = args
|
61
|
-
|
62
|
-
|
63
|
-
|
98
|
+
metric_name = Utils::ActiveRecordMetricName.new(sql, name)
|
99
|
+
desc = Utils::SqlSanitizer.new(sql)
|
100
|
+
|
101
|
+
# Get current ScoutApm context
|
102
|
+
req = ScoutApm::RequestManager.lookup
|
103
|
+
current_layer = req.current_layer
|
104
|
+
|
105
|
+
|
106
|
+
# If we call #log, we have a real query to run, and we've already
|
107
|
+
# gotten through the cache gatekeeper. Since we want to only trace real
|
108
|
+
# queries, and not repeated identical queries that just hit cache, we
|
109
|
+
# mark layer as ignorable initially in #find_by_sql, then only when we
|
110
|
+
# know it's a real database call do we mark it back as usable.
|
111
|
+
#
|
112
|
+
# This flag is later used in SlowRequestConverter to skip adding ignorable layers
|
113
|
+
current_layer.annotate_layer(:ignorable => false) if current_layer
|
114
|
+
|
115
|
+
# Either: update the current layer and yield, don't start a new one.
|
116
|
+
if current_layer && current_layer.type == "ActiveRecord"
|
117
|
+
# TODO: Get rid of call .to_s, need to find this without forcing a previous run of the name logic
|
118
|
+
if current_layer.name.to_s == Utils::ActiveRecordMetricName::DEFAULT_METRIC
|
119
|
+
current_layer.name = metric_name
|
120
|
+
current_layer.desc = desc
|
121
|
+
end
|
122
|
+
|
64
123
|
log_without_scout_instruments(sql, name, &block)
|
124
|
+
|
125
|
+
# OR: Start a new layer, we didn't pick up instrumentation earlier in the stack.
|
126
|
+
else
|
127
|
+
layer = ScoutApm::Layer.new("ActiveRecord", metric_name)
|
128
|
+
layer.desc = desc
|
129
|
+
req.start_layer(layer)
|
130
|
+
begin
|
131
|
+
log_without_scout_instruments(sql, name, &block)
|
132
|
+
ensure
|
133
|
+
req.stop_layer
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
################################################################################
|
140
|
+
# Entry-point of instruments.
|
141
|
+
#
|
142
|
+
# We instrument both ActiveRecord::Querying#find_by_sql and
|
143
|
+
# ActiveRecord::FinderMethods#find_with_associations. These are early in
|
144
|
+
# the chain of calls when you're using ActiveRecord.
|
145
|
+
#
|
146
|
+
# Later on, they will call into #log, which we also instrument, at which
|
147
|
+
# point, we can fill in additional data gathered at that point (name, sql)
|
148
|
+
#
|
149
|
+
# Caveats:
|
150
|
+
# * We don't have a name for the query yet.
|
151
|
+
# * The query hasn't hit the cache yet. In the case of a cache hit, we
|
152
|
+
# won't hit #log, so won't get a name, leaving the misleading default.
|
153
|
+
# * One call here can result in several calls to #log, especially in the
|
154
|
+
# case where Rails needs to load the schema details for the table being
|
155
|
+
# queried.
|
156
|
+
################################################################################
|
157
|
+
|
158
|
+
module ActiveRecordQueryingInstruments
|
159
|
+
def self.included(instrumented_class)
|
160
|
+
ScoutApm::Agent.instance.logger.info "Instrumenting ActiveRecord::Querying - #{instrumented_class.inspect}"
|
161
|
+
instrumented_class.class_eval do
|
162
|
+
unless instrumented_class.method_defined?(:find_by_sql_without_scout_instruments)
|
163
|
+
alias_method :find_by_sql_without_scout_instruments, :find_by_sql
|
164
|
+
alias_method :find_by_sql, :find_by_sql_with_scout_instruments
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def find_by_sql_with_scout_instruments(*args, &block)
|
170
|
+
req = ScoutApm::RequestManager.lookup
|
171
|
+
layer = ScoutApm::Layer.new("ActiveRecord", Utils::ActiveRecordMetricName::DEFAULT_METRIC)
|
172
|
+
layer.annotate_layer(:ignorable => true)
|
173
|
+
req.start_layer(layer)
|
174
|
+
req.ignore_children!
|
175
|
+
begin
|
176
|
+
find_by_sql_without_scout_instruments(*args)
|
177
|
+
ensure
|
178
|
+
req.acknowledge_children!
|
179
|
+
req.stop_layer
|
65
180
|
end
|
66
181
|
end
|
67
|
-
end
|
182
|
+
end
|
183
|
+
|
184
|
+
module ActiveRecordFinderMethodsInstruments
|
185
|
+
def self.included(instrumented_class)
|
186
|
+
ScoutApm::Agent.instance.logger.info "Instrumenting ActiveRecord::FinderMethods - #{instrumented_class.inspect}"
|
187
|
+
instrumented_class.class_eval do
|
188
|
+
unless instrumented_class.method_defined?(:find_with_associations_without_scout_instruments)
|
189
|
+
alias_method :find_with_associations_without_scout_instruments, :find_with_associations
|
190
|
+
alias_method :find_with_associations, :find_with_associations_with_scout_instruments
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def find_with_associations_with_scout_instruments(*args, &block)
|
196
|
+
req = ScoutApm::RequestManager.lookup
|
197
|
+
layer = ScoutApm::Layer.new("ActiveRecord", Utils::ActiveRecordMetricName::DEFAULT_METRIC)
|
198
|
+
layer.annotate_layer(:ignorable => true)
|
199
|
+
req.start_layer(layer)
|
200
|
+
req.ignore_children!
|
201
|
+
begin
|
202
|
+
find_with_associations_without_scout_instruments(*args)
|
203
|
+
ensure
|
204
|
+
req.acknowledge_children!
|
205
|
+
req.stop_layer
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
68
209
|
end
|
69
210
|
end
|