scout_apm 3.0.0.pre28 → 4.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/test.yml +49 -0
- data/.gitignore +1 -1
- data/.rubocop.yml +5 -5
- data/.travis.yml +19 -14
- data/CHANGELOG.markdown +145 -4
- data/Gemfile +1 -7
- data/README.markdown +13 -4
- data/Rakefile +1 -1
- data/ext/allocations/allocations.c +2 -0
- data/gems/README.md +28 -0
- data/gems/octoshark.gemfile +4 -0
- data/gems/rails3.gemfile +5 -0
- data/gems/rails4.gemfile +4 -0
- data/gems/rails5.gemfile +4 -0
- data/gems/rails6.gemfile +4 -0
- data/lib/scout_apm.rb +39 -9
- data/lib/scout_apm/agent.rb +29 -10
- data/lib/scout_apm/agent/exit_handler.rb +0 -1
- data/lib/scout_apm/agent_context.rb +22 -3
- data/lib/scout_apm/app_server_load.rb +7 -2
- data/lib/scout_apm/attribute_arranger.rb +0 -2
- data/lib/scout_apm/auto_instrument.rb +5 -0
- data/lib/scout_apm/auto_instrument/instruction_sequence.rb +31 -0
- data/lib/scout_apm/auto_instrument/layer.rb +23 -0
- data/lib/scout_apm/auto_instrument/parser.rb +27 -0
- data/lib/scout_apm/auto_instrument/rails.rb +175 -0
- data/lib/scout_apm/background_job_integrations/delayed_job.rb +1 -1
- data/lib/scout_apm/background_job_integrations/faktory.rb +103 -0
- data/lib/scout_apm/background_job_integrations/legacy_sneakers.rb +55 -0
- data/lib/scout_apm/background_job_integrations/que.rb +134 -0
- data/lib/scout_apm/background_job_integrations/resque.rb +6 -2
- data/lib/scout_apm/background_job_integrations/shoryuken.rb +124 -0
- data/lib/scout_apm/background_job_integrations/sidekiq.rb +5 -19
- data/lib/scout_apm/background_job_integrations/sneakers.rb +87 -0
- data/lib/scout_apm/config.rb +45 -8
- data/lib/scout_apm/detailed_trace.rb +217 -0
- data/lib/scout_apm/environment.rb +20 -1
- data/lib/scout_apm/error.rb +27 -0
- data/lib/scout_apm/error_service.rb +32 -0
- data/lib/scout_apm/error_service/error_buffer.rb +39 -0
- data/lib/scout_apm/error_service/error_record.rb +211 -0
- data/lib/scout_apm/error_service/ignored_exceptions.rb +66 -0
- data/lib/scout_apm/error_service/middleware.rb +32 -0
- data/lib/scout_apm/error_service/notifier.rb +33 -0
- data/lib/scout_apm/error_service/payload.rb +47 -0
- data/lib/scout_apm/error_service/periodic_work.rb +17 -0
- data/lib/scout_apm/error_service/railtie.rb +11 -0
- data/lib/scout_apm/error_service/sidekiq.rb +80 -0
- data/lib/scout_apm/extensions/transaction_callback_payload.rb +1 -1
- data/lib/scout_apm/fake_store.rb +3 -0
- data/lib/scout_apm/framework_integrations/rails_2.rb +2 -1
- data/lib/scout_apm/framework_integrations/rails_3_or_4.rb +3 -1
- data/lib/scout_apm/git_revision.rb +6 -3
- data/lib/scout_apm/instant/middleware.rb +2 -1
- data/lib/scout_apm/instrument_manager.rb +9 -7
- data/lib/scout_apm/instruments/action_controller_rails_2.rb +3 -1
- data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +56 -55
- data/lib/scout_apm/instruments/action_view.rb +122 -26
- data/lib/scout_apm/instruments/active_record.rb +66 -18
- data/lib/scout_apm/instruments/http.rb +48 -0
- data/lib/scout_apm/instruments/memcached.rb +43 -0
- data/lib/scout_apm/instruments/mongoid.rb +9 -4
- data/lib/scout_apm/instruments/net_http.rb +8 -1
- data/lib/scout_apm/instruments/typhoeus.rb +88 -0
- data/lib/scout_apm/job_record.rb +4 -2
- data/lib/scout_apm/layaway_file.rb +4 -0
- data/lib/scout_apm/layer.rb +6 -57
- data/lib/scout_apm/layer_children_set.rb +9 -8
- data/lib/scout_apm/layer_converters/converter_base.rb +15 -30
- data/lib/scout_apm/layer_converters/slow_job_converter.rb +12 -2
- data/lib/scout_apm/layer_converters/slow_request_converter.rb +14 -4
- data/lib/scout_apm/layer_converters/trace_converter.rb +184 -0
- data/lib/scout_apm/limited_layer.rb +0 -7
- data/lib/scout_apm/metric_stats.rb +0 -8
- data/lib/scout_apm/middleware.rb +1 -1
- data/lib/scout_apm/periodic_work.rb +19 -0
- data/lib/scout_apm/remote/message.rb +4 -0
- data/lib/scout_apm/remote/server.rb +13 -1
- data/lib/scout_apm/reporter.rb +8 -3
- data/lib/scout_apm/reporting.rb +2 -1
- data/lib/scout_apm/request_histograms.rb +8 -0
- data/lib/scout_apm/serializers/app_server_load_serializer.rb +4 -0
- data/lib/scout_apm/serializers/directive_serializer.rb +4 -0
- data/lib/scout_apm/serializers/payload_serializer.rb +2 -2
- data/lib/scout_apm/serializers/payload_serializer_to_json.rb +8 -7
- data/lib/scout_apm/slow_job_record.rb +5 -1
- data/lib/scout_apm/slow_policy/age_policy.rb +33 -0
- data/lib/scout_apm/slow_policy/percent_policy.rb +22 -0
- data/lib/scout_apm/slow_policy/percentile_policy.rb +24 -0
- data/lib/scout_apm/slow_policy/policy.rb +21 -0
- data/lib/scout_apm/slow_policy/speed_policy.rb +16 -0
- data/lib/scout_apm/slow_request_policy.rb +18 -77
- data/lib/scout_apm/slow_transaction.rb +3 -1
- data/lib/scout_apm/store.rb +0 -1
- data/lib/scout_apm/tracer.rb +2 -2
- data/lib/scout_apm/tracked_request.rb +39 -30
- data/lib/scout_apm/utils/backtrace_parser.rb +3 -0
- data/lib/scout_apm/utils/marshal_logging.rb +90 -0
- data/lib/scout_apm/utils/sql_sanitizer.rb +10 -1
- data/lib/scout_apm/utils/sql_sanitizer_regex.rb +8 -1
- data/lib/scout_apm/utils/sql_sanitizer_regex_1_8_7.rb +6 -0
- data/lib/scout_apm/utils/unique_id.rb +27 -0
- data/lib/scout_apm/version.rb +1 -1
- data/scout_apm.gemspec +13 -7
- data/test/test_helper.rb +2 -2
- data/test/unit/agent_context_test.rb +29 -0
- data/test/unit/auto_instrument/assignments-instrumented.rb +31 -0
- data/test/unit/auto_instrument/assignments.rb +31 -0
- data/test/unit/auto_instrument/controller-ast.txt +57 -0
- data/test/unit/auto_instrument/controller-instrumented.rb +49 -0
- data/test/unit/auto_instrument/controller.rb +49 -0
- data/test/unit/auto_instrument/rescue_from-instrumented.rb +13 -0
- data/test/unit/auto_instrument/rescue_from.rb +13 -0
- data/test/unit/auto_instrument_test.rb +54 -0
- data/test/unit/environment_test.rb +2 -2
- data/test/unit/error_service/error_buffer_test.rb +25 -0
- data/test/unit/error_service/ignored_exceptions_test.rb +49 -0
- data/test/unit/instruments/active_record_test.rb +40 -0
- data/test/unit/layer_children_set_test.rb +9 -0
- data/test/unit/request_histograms_test.rb +17 -0
- data/test/unit/serializers/payload_serializer_test.rb +39 -5
- data/test/unit/slow_request_policy_test.rb +41 -13
- data/test/unit/sql_sanitizer_test.rb +78 -0
- data/test/unit/tracer_test.rb +25 -0
- metadata +101 -18
- data/ext/stacks/extconf.rb +0 -38
- data/ext/stacks/scout_atomics.h +0 -86
- data/ext/stacks/stacks.c +0 -814
- data/lib/scout_apm/slow_job_policy.rb +0 -111
- data/lib/scout_apm/trace_compactor.rb +0 -312
- data/lib/scout_apm/utils/fake_stacks.rb +0 -88
- data/test/unit/instruments/active_record_instruments_test.rb +0 -5
- data/test/unit/slow_job_policy_test.rb +0 -6
- data/tester.rb +0 -53
data/lib/scout_apm.rb
CHANGED
@@ -15,7 +15,7 @@ require 'socket'
|
|
15
15
|
require 'thread'
|
16
16
|
require 'time'
|
17
17
|
require 'yaml'
|
18
|
-
require '
|
18
|
+
require 'securerandom'
|
19
19
|
|
20
20
|
#####################################
|
21
21
|
# Gem Requires
|
@@ -45,6 +45,7 @@ require 'scout_apm/layer_converters/database_converter'
|
|
45
45
|
require 'scout_apm/layer_converters/slow_request_converter'
|
46
46
|
require 'scout_apm/layer_converters/request_queue_time_converter'
|
47
47
|
require 'scout_apm/layer_converters/allocation_metric_converter'
|
48
|
+
require 'scout_apm/layer_converters/trace_converter'
|
48
49
|
require 'scout_apm/layer_converters/histograms'
|
49
50
|
require 'scout_apm/layer_converters/find_layer_by_type'
|
50
51
|
|
@@ -57,8 +58,13 @@ require 'scout_apm/server_integrations/webrick'
|
|
57
58
|
require 'scout_apm/server_integrations/null'
|
58
59
|
|
59
60
|
require 'scout_apm/background_job_integrations/sidekiq'
|
61
|
+
require 'scout_apm/background_job_integrations/faktory'
|
60
62
|
require 'scout_apm/background_job_integrations/delayed_job'
|
61
63
|
require 'scout_apm/background_job_integrations/resque'
|
64
|
+
require 'scout_apm/background_job_integrations/shoryuken'
|
65
|
+
require 'scout_apm/background_job_integrations/sneakers'
|
66
|
+
require 'scout_apm/background_job_integrations/que'
|
67
|
+
require 'scout_apm/background_job_integrations/legacy_sneakers'
|
62
68
|
|
63
69
|
require 'scout_apm/framework_integrations/rails_2'
|
64
70
|
require 'scout_apm/framework_integrations/rails_3_or_4'
|
@@ -73,8 +79,10 @@ require 'scout_apm/histogram'
|
|
73
79
|
|
74
80
|
require 'scout_apm/instruments/net_http'
|
75
81
|
require 'scout_apm/instruments/http_client'
|
82
|
+
require 'scout_apm/instruments/typhoeus'
|
76
83
|
require 'scout_apm/instruments/moped'
|
77
84
|
require 'scout_apm/instruments/mongoid'
|
85
|
+
require 'scout_apm/instruments/memcached'
|
78
86
|
require 'scout_apm/instruments/redis'
|
79
87
|
require 'scout_apm/instruments/influxdb'
|
80
88
|
require 'scout_apm/instruments/elasticsearch'
|
@@ -94,12 +102,6 @@ require 'scout_apm/instruments/process/process_memory'
|
|
94
102
|
require 'scout_apm/instruments/percentile_sampler'
|
95
103
|
require 'scout_apm/instruments/samplers'
|
96
104
|
|
97
|
-
begin
|
98
|
-
require 'stacks'
|
99
|
-
rescue LoadError
|
100
|
-
require 'scout_apm/utils/fake_stacks'
|
101
|
-
end
|
102
|
-
|
103
105
|
require 'scout_apm/app_server_load'
|
104
106
|
|
105
107
|
require 'scout_apm/ignored_uris.rb'
|
@@ -113,6 +115,7 @@ require 'scout_apm/utils/time'
|
|
113
115
|
require 'scout_apm/utils/unique_id'
|
114
116
|
require 'scout_apm/utils/numbers'
|
115
117
|
require 'scout_apm/utils/gzip_helper'
|
118
|
+
require 'scout_apm/utils/marshal_logging'
|
116
119
|
|
117
120
|
require 'scout_apm/config'
|
118
121
|
require 'scout_apm/environment'
|
@@ -133,7 +136,6 @@ require 'scout_apm/tracer'
|
|
133
136
|
require 'scout_apm/transaction'
|
134
137
|
require 'scout_apm/context'
|
135
138
|
require 'scout_apm/instant_reporting'
|
136
|
-
require 'scout_apm/trace_compactor'
|
137
139
|
require 'scout_apm/background_recorder'
|
138
140
|
require 'scout_apm/synchronous_recorder'
|
139
141
|
|
@@ -142,9 +144,15 @@ require 'scout_apm/metric_stats'
|
|
142
144
|
require 'scout_apm/db_query_metric_stats'
|
143
145
|
require 'scout_apm/slow_transaction'
|
144
146
|
require 'scout_apm/slow_job_record'
|
147
|
+
require 'scout_apm/detailed_trace'
|
145
148
|
require 'scout_apm/scored_item_set'
|
149
|
+
|
146
150
|
require 'scout_apm/slow_request_policy'
|
147
|
-
require 'scout_apm/
|
151
|
+
require 'scout_apm/slow_policy/age_policy'
|
152
|
+
require 'scout_apm/slow_policy/speed_policy'
|
153
|
+
require 'scout_apm/slow_policy/percent_policy'
|
154
|
+
require 'scout_apm/slow_policy/percentile_policy'
|
155
|
+
|
148
156
|
require 'scout_apm/job_record'
|
149
157
|
require 'scout_apm/request_histograms'
|
150
158
|
require 'scout_apm/transaction_time_consumed'
|
@@ -185,6 +193,16 @@ require 'scout_apm/tasks/support'
|
|
185
193
|
require 'scout_apm/extensions/config'
|
186
194
|
require 'scout_apm/extensions/transaction_callback_payload'
|
187
195
|
|
196
|
+
require 'scout_apm/error_service'
|
197
|
+
require 'scout_apm/error_service/middleware'
|
198
|
+
require 'scout_apm/error_service/notifier'
|
199
|
+
require 'scout_apm/error_service/sidekiq'
|
200
|
+
require 'scout_apm/error_service/ignored_exceptions'
|
201
|
+
require 'scout_apm/error_service/error_buffer'
|
202
|
+
require 'scout_apm/error_service/error_record'
|
203
|
+
require 'scout_apm/error_service/periodic_work'
|
204
|
+
require 'scout_apm/error_service/payload'
|
205
|
+
|
188
206
|
if defined?(Rails) && defined?(Rails::VERSION) && defined?(Rails::VERSION::MAJOR) && Rails::VERSION::MAJOR >= 3 && defined?(Rails::Railtie)
|
189
207
|
module ScoutApm
|
190
208
|
class Railtie < Rails::Railtie
|
@@ -197,6 +215,18 @@ if defined?(Rails) && defined?(Rails::VERSION) && defined?(Rails::VERSION::MAJOR
|
|
197
215
|
# Attempt to start right away, this will work best for preloading apps, Unicorn & Puma & similar
|
198
216
|
ScoutApm::Agent.instance.install
|
199
217
|
|
218
|
+
if ScoutApm::Agent.instance.context.config.value("auto_instruments")
|
219
|
+
ScoutApm::Agent.instance.context.logger.debug("AutoInstruments is enabled.")
|
220
|
+
require 'scout_apm/auto_instrument'
|
221
|
+
else
|
222
|
+
ScoutApm::Agent.instance.context.logger.debug("AutoInstruments is disabled.")
|
223
|
+
end
|
224
|
+
|
225
|
+
if ScoutApm::Agent.instance.context.config.value("errors_enabled")
|
226
|
+
app.config.middleware.insert_after ActionDispatch::DebugExceptions, ScoutApm::ErrorService::Middleware
|
227
|
+
ScoutApm::ErrorService::Sidekiq.new.install
|
228
|
+
end
|
229
|
+
|
200
230
|
# Install the middleware every time in development mode.
|
201
231
|
# The middleware is a noop if dev_trace is not enabled in config
|
202
232
|
if Rails.env.development?
|
data/lib/scout_apm/agent.rb
CHANGED
@@ -37,10 +37,13 @@ module ScoutApm
|
|
37
37
|
|
38
38
|
logger.info "Scout Agent [#{ScoutApm::VERSION}] Initialized"
|
39
39
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
40
|
+
if should_load_instruments? || force
|
41
|
+
instrument_manager.install!
|
42
|
+
install_background_job_integrations
|
43
|
+
install_app_server_integration
|
44
|
+
else
|
45
|
+
logger.info "Not Loading Instruments"
|
46
|
+
end
|
44
47
|
|
45
48
|
logger.info "Scout Agent [#{ScoutApm::VERSION}] Installed"
|
46
49
|
|
@@ -63,6 +66,7 @@ module ScoutApm
|
|
63
66
|
|
64
67
|
if context.started?
|
65
68
|
start_background_worker unless background_worker_running?
|
69
|
+
start_error_service_background_worker unless error_service_background_worker_running?
|
66
70
|
return
|
67
71
|
end
|
68
72
|
|
@@ -78,6 +82,7 @@ module ScoutApm
|
|
78
82
|
@app_server_load ||= AppServerLoad.new(context).run
|
79
83
|
|
80
84
|
start_background_worker
|
85
|
+
start_error_service_background_worker
|
81
86
|
end
|
82
87
|
|
83
88
|
def instrument_manager
|
@@ -165,12 +170,6 @@ module ScoutApm
|
|
165
170
|
|
166
171
|
ScoutApm::Agent::ExitHandler.new(context).install
|
167
172
|
|
168
|
-
if context.config.value('profile')
|
169
|
-
# After we fork, setup the handlers here.
|
170
|
-
ScoutApm::Instruments::Stacks.install
|
171
|
-
ScoutApm::Instruments::Stacks.start
|
172
|
-
end
|
173
|
-
|
174
173
|
periodic_work = ScoutApm::PeriodicWork.new(context)
|
175
174
|
|
176
175
|
@background_worker = ScoutApm::BackgroundWorker.new(context)
|
@@ -201,5 +200,25 @@ module ScoutApm
|
|
201
200
|
@background_worker &&
|
202
201
|
@background_worker.running?
|
203
202
|
end
|
203
|
+
|
204
|
+
# seconds to batch error reports
|
205
|
+
ERROR_SEND_FREQUENCY = 5
|
206
|
+
def start_error_service_background_worker
|
207
|
+
periodic_work = ScoutApm::ErrorService::PeriodicWork.new(context)
|
208
|
+
|
209
|
+
@error_service_background_worker = ScoutApm::BackgroundWorker.new(context, ERROR_SEND_FREQUENCY)
|
210
|
+
@error_service_background_worker_thread = Thread.new do
|
211
|
+
@error_service_background_worker.start {
|
212
|
+
periodic_work.run
|
213
|
+
}
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def error_service_background_worker_running?
|
218
|
+
@error_service_background_worker_thread &&
|
219
|
+
@error_service_background_worker_thread.alive? &&
|
220
|
+
@error_service_background_worker &&
|
221
|
+
@error_service_background_worker.running?
|
222
|
+
end
|
204
223
|
end
|
205
224
|
end
|
@@ -96,11 +96,18 @@ module ScoutApm
|
|
96
96
|
end
|
97
97
|
|
98
98
|
def slow_request_policy
|
99
|
-
@slow_request_policy ||= ScoutApm::SlowRequestPolicy.new(self)
|
99
|
+
@slow_request_policy ||= ScoutApm::SlowRequestPolicy.new(self).tap{|p| p.add_default_policies }
|
100
100
|
end
|
101
101
|
|
102
102
|
def slow_job_policy
|
103
|
-
@slow_job_policy ||= ScoutApm::
|
103
|
+
@slow_job_policy ||= ScoutApm::SlowRequestPolicy.new(self).tap{|p| p.add_default_policies }
|
104
|
+
end
|
105
|
+
|
106
|
+
# Maintains a Histogram of insignificant/significant autoinstrument layers.
|
107
|
+
# significant = 1
|
108
|
+
# insignificant = 0
|
109
|
+
def auto_instruments_layer_histograms
|
110
|
+
@auto_instruments_layer_histograms ||= ScoutApm::RequestHistograms.new
|
104
111
|
end
|
105
112
|
|
106
113
|
# Histogram of the cumulative requests since the start of the process
|
@@ -135,6 +142,18 @@ module ScoutApm
|
|
135
142
|
config.value('dev_trace') && environment.env == "development"
|
136
143
|
end
|
137
144
|
|
145
|
+
###################
|
146
|
+
# Error Service #
|
147
|
+
###################
|
148
|
+
|
149
|
+
def error_buffer
|
150
|
+
@error_buffer ||= ScoutApm::ErrorService::ErrorBuffer.new(self)
|
151
|
+
end
|
152
|
+
|
153
|
+
def ignored_exceptions
|
154
|
+
@ignored_exceptions ||= ScoutApm::ErrorService::IgnoredExceptions.new(self, config.value('errors_ignored_exceptions'))
|
155
|
+
end
|
156
|
+
|
138
157
|
#############
|
139
158
|
# Setters #
|
140
159
|
#############
|
@@ -205,7 +224,7 @@ module ScoutApm
|
|
205
224
|
if !@config.any_keys_found?
|
206
225
|
logger.info("No configuration file loaded, and no configuration found in ENV. " +
|
207
226
|
"For assistance configuring Scout, visit " +
|
208
|
-
"
|
227
|
+
"https://docs.scoutapm.com/#ruby-configuration-options")
|
209
228
|
end
|
210
229
|
end
|
211
230
|
end
|
@@ -29,12 +29,17 @@ module ScoutApm
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def data
|
32
|
-
{
|
32
|
+
{
|
33
|
+
:language => 'ruby',
|
34
|
+
:language_version => RUBY_VERSION,
|
35
|
+
:ruby_version => RUBY_VERSION, # Deprecated.
|
36
|
+
|
33
37
|
:framework => to_s_safe(environment.framework_integration.human_name),
|
34
38
|
:framework_version => to_s_safe(environment.framework_integration.version),
|
39
|
+
|
40
|
+
:server_time => to_s_safe(Time.now),
|
35
41
|
:environment => to_s_safe(environment.framework_integration.env),
|
36
42
|
:app_server => to_s_safe(environment.app_server),
|
37
|
-
:ruby_version => RUBY_VERSION,
|
38
43
|
:hostname => to_s_safe(environment.hostname),
|
39
44
|
:database_engine => to_s_safe(environment.database_engine), # Detected
|
40
45
|
:database_adapter => to_s_safe(environment.raw_database_adapter), # Raw
|
@@ -5,8 +5,6 @@ module ScoutApm
|
|
5
5
|
def self.call(subject, attributes_list)
|
6
6
|
attributes_list.inject({}) do |attribute_hash, attribute|
|
7
7
|
case attribute
|
8
|
-
when :traces
|
9
|
-
attribute_hash[attribute] = subject.traces.to_a
|
10
8
|
when Array
|
11
9
|
attribute_hash[attribute[0]] = subject.send(attribute[1])
|
12
10
|
when :bucket
|
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
require 'scout_apm/auto_instrument/rails'
|
3
|
+
|
4
|
+
module ScoutApm
|
5
|
+
module AutoInstrument
|
6
|
+
module InstructionSequence
|
7
|
+
def load_iseq(path)
|
8
|
+
if Rails.controller_path?(path) & !Rails.ignore?(path)
|
9
|
+
begin
|
10
|
+
new_code = Rails.rewrite(path)
|
11
|
+
return self.compile(new_code, path, path)
|
12
|
+
rescue
|
13
|
+
warn "Failed to apply auto-instrumentation to #{path}: #{$!}"
|
14
|
+
end
|
15
|
+
elsif Rails.ignore?(path)
|
16
|
+
warn "AutoInstruments are ignored for path=#{path}."
|
17
|
+
end
|
18
|
+
|
19
|
+
return self.compile_file(path)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# This should work (https://bugs.ruby-lang.org/issues/15572), but it doesn't.
|
24
|
+
# RubyVM::InstructionSequence.extend(InstructionSequence)
|
25
|
+
|
26
|
+
# So we do this instead:
|
27
|
+
class << ::RubyVM::InstructionSequence
|
28
|
+
prepend InstructionSequence
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
|
2
|
+
module ScoutApm
|
3
|
+
def self.AutoInstrument(name, backtrace)
|
4
|
+
request = ScoutApm::RequestManager.lookup
|
5
|
+
|
6
|
+
file_name, _ = backtrace.first.split(":", 2)
|
7
|
+
|
8
|
+
begin
|
9
|
+
layer = ScoutApm::Layer.new('AutoInstrument', name)
|
10
|
+
layer.backtrace = backtrace
|
11
|
+
layer.file_name = file_name
|
12
|
+
|
13
|
+
request.start_layer(layer)
|
14
|
+
started_layer = true
|
15
|
+
|
16
|
+
result = yield
|
17
|
+
ensure
|
18
|
+
request.stop_layer if started_layer
|
19
|
+
end
|
20
|
+
|
21
|
+
return result
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
require 'parser/current'
|
3
|
+
raise LoadError, "Parser::TreeRewriter was not defined" unless defined?(Parser::TreeRewriter)
|
4
|
+
|
5
|
+
module ScoutApm
|
6
|
+
module AutoInstrument
|
7
|
+
class Cache
|
8
|
+
def initialize
|
9
|
+
@local_assignments = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def local_assignments?(node)
|
13
|
+
unless @local_assignments.key?(node)
|
14
|
+
if node.type == :lvasgn
|
15
|
+
@local_assignments[node] = true
|
16
|
+
elsif node.children.find{|child| child.is_a?(Parser::AST::Node) && self.local_assignments?(child)}
|
17
|
+
@local_assignments[node] = true
|
18
|
+
else
|
19
|
+
@local_assignments[node] = false
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
return @local_assignments[node]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
|
2
|
+
require 'scout_apm/auto_instrument/layer'
|
3
|
+
require 'scout_apm/auto_instrument/parser'
|
4
|
+
|
5
|
+
module ScoutApm
|
6
|
+
module AutoInstrument
|
7
|
+
module Rails
|
8
|
+
# A general pattern to match Rails controller files:
|
9
|
+
CONTROLLER_FILE = /\/app\/controllers\/*\/.*_controller.rb$/.freeze
|
10
|
+
|
11
|
+
# Some gems (Devise) provide controllers that match CONTROLLER_FILE pattern.
|
12
|
+
# Try a simple match to see if it's a Gemfile
|
13
|
+
GEM_FILE = /\/gems?\//.freeze
|
14
|
+
|
15
|
+
# Whether the given path is likely to be a Rails controller and not provided by a Gem.
|
16
|
+
def self.controller_path? path
|
17
|
+
CONTROLLER_FILE.match(path) && !GEM_FILE.match(path)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Autoinstruments increases overhead when applied to many code expressions that perform little work.
|
21
|
+
# You can exclude files from autoinstruments via the `auto_instruments_ignore` option.
|
22
|
+
def self.ignore?(path)
|
23
|
+
res = false
|
24
|
+
ScoutApm::Agent.instance.context.config.value('auto_instruments_ignore').each do |ignored_file_name|
|
25
|
+
if path.include?(ignored_file_name)
|
26
|
+
res = true
|
27
|
+
break
|
28
|
+
end
|
29
|
+
end
|
30
|
+
res
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.rewrite(path, code = nil)
|
34
|
+
code ||= File.read(path)
|
35
|
+
|
36
|
+
ast = ::Parser::CurrentRuby.parse(code)
|
37
|
+
|
38
|
+
# pp ast
|
39
|
+
|
40
|
+
buffer = ::Parser::Source::Buffer.new(path)
|
41
|
+
buffer.source = code
|
42
|
+
|
43
|
+
rewriter = Rewriter.new
|
44
|
+
|
45
|
+
# Rewrite the AST, returns a String with the new form.
|
46
|
+
rewriter.rewrite(buffer, ast)
|
47
|
+
end
|
48
|
+
|
49
|
+
class Rewriter < ::Parser::TreeRewriter
|
50
|
+
def initialize
|
51
|
+
super
|
52
|
+
|
53
|
+
# Keeps track of the parent - child relationship between nodes:
|
54
|
+
@nesting = []
|
55
|
+
|
56
|
+
# The stack of method nodes (type :def):
|
57
|
+
@method = []
|
58
|
+
|
59
|
+
# The stack of class nodes:
|
60
|
+
@scope = []
|
61
|
+
|
62
|
+
@cache = Cache.new
|
63
|
+
end
|
64
|
+
|
65
|
+
def instrument(source, file_name, line)
|
66
|
+
# Don't log huge chunks of code... just the first line:
|
67
|
+
if lines = source.lines and lines.count > 1
|
68
|
+
source = lines.first.chomp + "..."
|
69
|
+
end
|
70
|
+
|
71
|
+
method_name = @method.last.children[0]
|
72
|
+
class_name = @scope.last.children[1]
|
73
|
+
bt = ["#{file_name}:#{line}:in `#{method_name}'"]
|
74
|
+
|
75
|
+
return [
|
76
|
+
"::ScoutApm::AutoInstrument("+ source.dump + ",#{bt}){",
|
77
|
+
"}"
|
78
|
+
]
|
79
|
+
end
|
80
|
+
|
81
|
+
# Look up 1 or more nodes to check if the parent exists and matches the given type.
|
82
|
+
# @param type [Symbol] the symbol type to match.
|
83
|
+
# @param up [Integer] how far up to look.
|
84
|
+
def parent_type?(type, up = 1)
|
85
|
+
parent = @nesting[@nesting.size - up - 1] and parent.type == type
|
86
|
+
end
|
87
|
+
|
88
|
+
def on_block(node)
|
89
|
+
# If we are not in a method, don't do any instrumentation:
|
90
|
+
return if @method.empty?
|
91
|
+
|
92
|
+
line = node.location.line || 'line?'
|
93
|
+
column = node.location.column || 'column?' # not used
|
94
|
+
method_name = node.children[0].children[1] || '*unknown*' # not used
|
95
|
+
file_name = @source_rewriter.source_buffer.name
|
96
|
+
|
97
|
+
wrap(node.location.expression, *instrument(node.location.expression.source, file_name, line))
|
98
|
+
end
|
99
|
+
|
100
|
+
def on_mlhs(node)
|
101
|
+
# Ignore / don't instrument multiple assignment (LHS).
|
102
|
+
return
|
103
|
+
end
|
104
|
+
|
105
|
+
def on_op_asgn(node)
|
106
|
+
process(node.children[2])
|
107
|
+
end
|
108
|
+
|
109
|
+
def on_or_asgn(node)
|
110
|
+
process(node.children[1])
|
111
|
+
end
|
112
|
+
|
113
|
+
def on_and_asgn(node)
|
114
|
+
process(node.children[1])
|
115
|
+
end
|
116
|
+
|
117
|
+
# Handle the method call AST node. If this method doesn't call `super`, no futher rewriting is applied to children.
|
118
|
+
def on_send(node)
|
119
|
+
# We aren't interested in top level function calls:
|
120
|
+
return if @method.empty?
|
121
|
+
|
122
|
+
if @cache.local_assignments?(node)
|
123
|
+
return super
|
124
|
+
end
|
125
|
+
|
126
|
+
# This ignores both initial block method invocation `*x*{}`, and subsequent nested invocations `x{*y*}`:
|
127
|
+
return if parent_type?(:block)
|
128
|
+
|
129
|
+
# Extract useful metadata for instrumentation:
|
130
|
+
line = node.location.line || 'line?'
|
131
|
+
column = node.location.column || 'column?' # not used
|
132
|
+
method_name = node.children[1] || '*unknown*' # not used
|
133
|
+
file_name = @source_rewriter.source_buffer.name
|
134
|
+
|
135
|
+
# Wrap the expression with instrumentation:
|
136
|
+
wrap(node.location.expression, *instrument(node.location.expression.source, file_name, line))
|
137
|
+
end
|
138
|
+
|
139
|
+
# def on_class(node)
|
140
|
+
# class_name = node.children[1]
|
141
|
+
#
|
142
|
+
# Kernel.const_get(class_name).ancestors.include? ActionController::Controller
|
143
|
+
#
|
144
|
+
# if class_name =~ /.../
|
145
|
+
# super # continue processing
|
146
|
+
# end
|
147
|
+
# end
|
148
|
+
|
149
|
+
# Invoked for every AST node as it is processed top to bottom.
|
150
|
+
def process(node)
|
151
|
+
# We are nesting inside this node:
|
152
|
+
@nesting.push(node)
|
153
|
+
|
154
|
+
if node and node.type == :def
|
155
|
+
# If the node is a method, push it on the method stack as well:
|
156
|
+
@method.push(node)
|
157
|
+
super
|
158
|
+
@method.pop
|
159
|
+
elsif node and node.type == :class
|
160
|
+
@scope.push(node.children[0])
|
161
|
+
super
|
162
|
+
@scope.pop
|
163
|
+
else
|
164
|
+
super
|
165
|
+
end
|
166
|
+
|
167
|
+
@nesting.pop
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# Force any lazy loading to occur here, before we patch iseq_load. Otherwise you might end up in an infinite loop when rewriting code.
|
175
|
+
ScoutApm::AutoInstrument::Rails.rewrite('(preload)', '')
|