scout_apm 3.0.0.pre28 → 4.0.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 +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)', '')
|