scout_apm 3.0.0.pre28 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +1 -1
- data/.rubocop.yml +3 -4
- data/.travis.yml +17 -14
- data/CHANGELOG.markdown +124 -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 +37 -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/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 +3 -0
- 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 +8 -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 +114 -26
- data/lib/scout_apm/instruments/active_record.rb +62 -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/job_record.rb +4 -2
- data/lib/scout_apm/layaway_file.rb +4 -0
- data/lib/scout_apm/layer.rb +5 -56
- 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/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 +30 -15
- 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/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 +7 -0
- 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/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 +47 -0
- metadata +99 -19
- 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
@@ -3,43 +3,29 @@
|
|
3
3
|
|
4
4
|
module ScoutApm
|
5
5
|
class SlowRequestPolicy
|
6
|
-
CAPTURE_TYPES = [
|
7
|
-
CAPTURE_DETAIL = "capture_detail",
|
8
|
-
CAPTURE_NONE = "capture_none",
|
9
|
-
]
|
10
|
-
|
11
|
-
# Adjust speed points. See the function
|
12
|
-
POINT_MULTIPLIER_SPEED = 0.25
|
13
|
-
|
14
|
-
# For each minute we haven't seen an endpoint
|
15
|
-
POINT_MULTIPLIER_AGE = 0.25
|
16
|
-
|
17
|
-
# Outliers are worth up to "1000ms" of weight
|
18
|
-
POINT_MULTIPLIER_PERCENTILE = 1.0
|
19
|
-
|
20
|
-
# Points for an endpoint's who's throughput * response time is a large % of
|
21
|
-
# overall time spent processing requests
|
22
|
-
POINT_MULTIPLIER_PERCENT_TIME = 2.5
|
23
|
-
|
24
|
-
# A hash of Endpoint Name to the last time we stored a slow transaction for it.
|
25
|
-
#
|
26
|
-
# Defaults to a start time that is pretty close to application boot time.
|
27
|
-
# So the "age" of an endpoint we've never seen is the time the application
|
28
|
-
# has been running.
|
29
|
-
attr_reader :last_seen
|
30
|
-
|
31
6
|
# The AgentContext we're running in
|
32
7
|
attr_reader :context
|
8
|
+
attr_reader :policies
|
33
9
|
|
34
10
|
def initialize(context)
|
35
11
|
@context = context
|
12
|
+
@policies = []
|
13
|
+
end
|
36
14
|
|
37
|
-
|
38
|
-
|
15
|
+
def add_default_policies
|
16
|
+
add(SlowPolicy::SpeedPolicy.new(context))
|
17
|
+
add(SlowPolicy::PercentilePolicy.new(context))
|
18
|
+
add(SlowPolicy::AgePolicy.new(context))
|
19
|
+
add(SlowPolicy::PercentilePolicy.new(context))
|
39
20
|
end
|
40
21
|
|
41
|
-
|
42
|
-
|
22
|
+
# policy is an object that behaves like a policy (responds to .call(req) for the score, and .store!(req))
|
23
|
+
def add(policy)
|
24
|
+
unless policy.respond_to?(:call) && policy.respond_to?(:stored!)
|
25
|
+
raise "SlowRequestPolicy must implement policy api call(req) and stored!(req)"
|
26
|
+
end
|
27
|
+
|
28
|
+
@policies << policy
|
43
29
|
end
|
44
30
|
|
45
31
|
# Determine if this request trace should be fully analyzed by scoring it
|
@@ -56,56 +42,11 @@ module ScoutApm
|
|
56
42
|
return -1 # A negative score, should never be good enough to store.
|
57
43
|
end
|
58
44
|
|
59
|
-
|
60
|
-
|
61
|
-
# How long has it been since we've seen this?
|
62
|
-
age = Time.now - last_seen[unique_name]
|
63
|
-
|
64
|
-
# What approximate percentile was this request?
|
65
|
-
percentile = context.request_histograms.approximate_quantile_of_value(unique_name, total_time)
|
66
|
-
|
67
|
-
percent_of_total_time = context.transaction_time_consumed.percent_of_total(unique_name)
|
68
|
-
|
69
|
-
return speed_points(total_time) + percentile_points(percentile) + age_points(age) + percent_time_points(percent_of_total_time)
|
70
|
-
end
|
71
|
-
|
72
|
-
private
|
73
|
-
|
74
|
-
# Time in seconds
|
75
|
-
# Logarithm keeps huge times from swamping the other metrics.
|
76
|
-
# 1+ is necessary to keep the log function in positive territory.
|
77
|
-
def speed_points(time)
|
78
|
-
Math.log(1 + time) * POINT_MULTIPLIER_SPEED
|
79
|
-
end
|
80
|
-
|
81
|
-
def percentile_points(percentile)
|
82
|
-
if percentile < 40
|
83
|
-
0.4 # Don't put much emphasis on capturing low percentiles.
|
84
|
-
elsif percentile < 60
|
85
|
-
1.4 # Highest here to get mean traces
|
86
|
-
elsif percentile < 90
|
87
|
-
0.7 # Between 60 & 90% is fine.
|
88
|
-
elsif percentile >= 90
|
89
|
-
1.4 # Highest here to get 90+%ile traces
|
90
|
-
else
|
91
|
-
# impossible.
|
92
|
-
percentile
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
def age_points(age)
|
97
|
-
age / 60.0 * POINT_MULTIPLIER_AGE
|
45
|
+
policies.map{ |p| p.call(request) }.sum
|
98
46
|
end
|
99
47
|
|
100
|
-
|
101
|
-
|
102
|
-
#
|
103
|
-
# A: 20 calls @ 100ms each => 2 seconds of total time
|
104
|
-
# B: 10 calls @ 100ms each => 1 second of total time
|
105
|
-
#
|
106
|
-
# Then A is 66% of the total call time
|
107
|
-
def percent_time_points(percent) # Scale 0.0 - 1.0
|
108
|
-
percent * POINT_MULTIPLIER_PERCENT_TIME
|
48
|
+
def stored!(request)
|
49
|
+
policies.each{ |p| p.stored!(request) }
|
109
50
|
end
|
110
51
|
end
|
111
52
|
end
|
@@ -13,13 +13,14 @@ module ScoutApm
|
|
13
13
|
attr_reader :prof
|
14
14
|
attr_reader :mem_delta
|
15
15
|
attr_reader :allocations
|
16
|
+
attr_reader :span_trace
|
16
17
|
attr_accessor :hostname # hack - we need to reset these server side.
|
17
18
|
attr_accessor :seconds_since_startup # hack - we need to reset these server side.
|
18
19
|
attr_accessor :git_sha # hack - we need to reset these server side.
|
19
20
|
|
20
21
|
attr_reader :truncated_metrics # True/False that says if we had to truncate the metrics of this trace
|
21
22
|
|
22
|
-
def initialize(agent_context, uri, metric_name, total_call_time, metrics, allocation_metrics, context, time, raw_stackprof, mem_delta, allocations, score, truncated_metrics)
|
23
|
+
def initialize(agent_context, uri, metric_name, total_call_time, metrics, allocation_metrics, context, time, raw_stackprof, mem_delta, allocations, score, truncated_metrics, span_trace)
|
23
24
|
@uri = uri
|
24
25
|
@metric_name = metric_name
|
25
26
|
@total_call_time = total_call_time
|
@@ -35,6 +36,7 @@ module ScoutApm
|
|
35
36
|
@score = score
|
36
37
|
@git_sha = agent_context.environment.git_revision.sha
|
37
38
|
@truncated_metrics = truncated_metrics
|
39
|
+
@span_trace = span_trace
|
38
40
|
|
39
41
|
agent_context.logger.debug { "Slow Request [#{uri}] - Call Time: #{total_call_time} Mem Delta: #{mem_delta} Score: #{score}"}
|
40
42
|
end
|
data/lib/scout_apm/store.rb
CHANGED
@@ -42,6 +42,9 @@ module ScoutApm
|
|
42
42
|
# the name is determined from the name of the Controller or Job layer.
|
43
43
|
attr_accessor :name_override
|
44
44
|
|
45
|
+
# A unique, but otherwise meaningless String to identify this request. UUID
|
46
|
+
attr_reader :transaction_id
|
47
|
+
|
45
48
|
# When we see these layers, it means a real request is going through the
|
46
49
|
# system. We toggle a flag to turn on some slightly more expensive
|
47
50
|
# instrumentation (backtrace collection and the like) that would be too
|
@@ -49,6 +52,10 @@ module ScoutApm
|
|
49
52
|
# see that on Sidekiq.
|
50
53
|
REQUEST_TYPES = ["Controller", "Job"]
|
51
54
|
|
55
|
+
# Layers of type 'AutoInstrument' are not recorded if their total_call_time doesn't exceed this threshold.
|
56
|
+
# AutoInstrument layers are frequently of short duration. This throws out this deadweight that is unlikely to be optimized.
|
57
|
+
AUTO_INSTRUMENT_TIMING_THRESHOLD = 5/1_000.0 # units = seconds
|
58
|
+
|
52
59
|
def initialize(agent_context, store)
|
53
60
|
@agent_context = agent_context
|
54
61
|
@store = store #this is passed in so we can use a real store (normal operation) or fake store (instant mode only)
|
@@ -64,6 +71,7 @@ module ScoutApm
|
|
64
71
|
@mem_start = mem_usage
|
65
72
|
@recorder = agent_context.recorder
|
66
73
|
@real_request = false
|
74
|
+
@transaction_id = ScoutApm::Utils::TransactionId.new.to_s
|
67
75
|
ignore_request! if @recorder.nil?
|
68
76
|
end
|
69
77
|
|
@@ -75,8 +83,6 @@ module ScoutApm
|
|
75
83
|
|
76
84
|
return ignoring_start_layer if ignoring_request?
|
77
85
|
|
78
|
-
layer.start_sampling
|
79
|
-
|
80
86
|
start_request(layer) unless @root_layer
|
81
87
|
|
82
88
|
if REQUEST_TYPES.include?(layer.type)
|
@@ -105,10 +111,12 @@ module ScoutApm
|
|
105
111
|
return
|
106
112
|
end
|
107
113
|
|
108
|
-
layer.record_traces!
|
109
114
|
layer.record_stop_time!
|
110
115
|
layer.record_allocations!
|
111
116
|
|
117
|
+
# Must follow layer.record_stop_time! as the total_call_time is used to determine if the layer is significant.
|
118
|
+
return if layer_insignificant?(layer)
|
119
|
+
|
112
120
|
@layers[-1].add_child(layer) if @layers.any?
|
113
121
|
|
114
122
|
# This must be called before checking if a backtrace should be collected as the call count influences our capture logic.
|
@@ -121,8 +129,6 @@ module ScoutApm
|
|
121
129
|
|
122
130
|
if finalized?
|
123
131
|
stop_request
|
124
|
-
else
|
125
|
-
continue_sampling_for_layers if @agent_context.config.value('profile')
|
126
132
|
end
|
127
133
|
end
|
128
134
|
|
@@ -151,6 +157,10 @@ module ScoutApm
|
|
151
157
|
def capture_backtrace?(layer)
|
152
158
|
return if ignoring_request?
|
153
159
|
|
160
|
+
# A backtrace has already been recorded. This happens with autoinstruments as
|
161
|
+
# the partial backtrace is set when creating the layer.
|
162
|
+
return false if layer.backtrace
|
163
|
+
|
154
164
|
# Never capture backtraces for this kind of layer. The backtrace will
|
155
165
|
# always be 100% framework code.
|
156
166
|
return false if BACKTRACE_BLACKLIST.include?(layer.type)
|
@@ -170,6 +180,20 @@ module ScoutApm
|
|
170
180
|
false
|
171
181
|
end
|
172
182
|
|
183
|
+
# Returns +true+ if the total call time of AutoInstrument layers exceeds +AUTO_INSTRUMENT_TIMING_THRESHOLD+ and
|
184
|
+
# records a Histogram of insignificant / significant layers by file name.
|
185
|
+
def layer_insignificant?(layer)
|
186
|
+
result = false # default is significant
|
187
|
+
if layer.type == 'AutoInstrument'
|
188
|
+
if layer.total_call_time < AUTO_INSTRUMENT_TIMING_THRESHOLD
|
189
|
+
result = true # not significant
|
190
|
+
end
|
191
|
+
# 0 = not significant, 1 = significant
|
192
|
+
@agent_context.auto_instruments_layer_histograms.add(layer.file_name, (result ? 0 : 1))
|
193
|
+
end
|
194
|
+
result
|
195
|
+
end
|
196
|
+
|
173
197
|
# Maintains a lookup Hash of call counts by layer name. Used to determine if we should capture a backtrace.
|
174
198
|
def update_call_counts!(layer)
|
175
199
|
@call_set[layer.name].update!(layer.desc)
|
@@ -200,13 +224,6 @@ module ScoutApm
|
|
200
224
|
@layers.none?
|
201
225
|
end
|
202
226
|
|
203
|
-
def continue_sampling_for_layers
|
204
|
-
if last_traced_layer = @layers.select{|layer| layer.traced?}.last
|
205
|
-
ScoutApm::Instruments::Stacks.update_indexes(@layers.last.frame_index, @layers.last.trace_index)
|
206
|
-
ScoutApm::Instruments::Stacks.start_sampling
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
227
|
# Run at the beginning of the whole request
|
211
228
|
#
|
212
229
|
# * Capture the first layer as the root_layer
|
@@ -220,13 +237,8 @@ module ScoutApm
|
|
220
237
|
def stop_request
|
221
238
|
@stopping = true
|
222
239
|
|
223
|
-
if @
|
224
|
-
|
225
|
-
ScoutApm::Instruments::Stacks.update_indexes(0, 0)
|
226
|
-
end
|
227
|
-
|
228
|
-
if recorder
|
229
|
-
recorder.record!(self)
|
240
|
+
if @recorder
|
241
|
+
@recorder.record!(self)
|
230
242
|
end
|
231
243
|
end
|
232
244
|
|
@@ -234,16 +246,6 @@ module ScoutApm
|
|
234
246
|
@stopping
|
235
247
|
end
|
236
248
|
|
237
|
-
# Enable ScoutProf for this thread
|
238
|
-
def enable_profiled_thread!
|
239
|
-
ScoutApm::Instruments::Stacks.add_profiled_thread
|
240
|
-
end
|
241
|
-
|
242
|
-
# Disable ScoutProf for this thread
|
243
|
-
def disable_profiled_thread!
|
244
|
-
ScoutApm::Instruments::Stacks.remove_profiled_thread
|
245
|
-
end
|
246
|
-
|
247
249
|
###################################
|
248
250
|
# Annotations
|
249
251
|
###################################
|
@@ -302,6 +304,8 @@ module ScoutApm
|
|
302
304
|
|
303
305
|
@agent_context.transaction_time_consumed.add(unique_name, root_layer.total_call_time)
|
304
306
|
|
307
|
+
context.add(:transaction_id => transaction_id)
|
308
|
+
|
305
309
|
# Make a constant, then call converters.dup.each so it isn't inline?
|
306
310
|
converters = {
|
307
311
|
:histograms => LayerConverters::Histograms,
|
@@ -314,6 +318,11 @@ module ScoutApm
|
|
314
318
|
|
315
319
|
:slow_job => LayerConverters::SlowJobConverter,
|
316
320
|
:slow_req => LayerConverters::SlowRequestConverter,
|
321
|
+
|
322
|
+
# This is now integrated into the slow_job and slow_req converters, so that
|
323
|
+
# we get the exact same set of traces either way. We can call it
|
324
|
+
# directly when we move away from the legacy trace styles.
|
325
|
+
# :traces => LayerConverters::TraceConverter,
|
317
326
|
}
|
318
327
|
|
319
328
|
walker = LayerConverters::DepthFirstWalker.new(self.root_layer)
|
@@ -324,7 +333,7 @@ module ScoutApm
|
|
324
333
|
memo
|
325
334
|
end
|
326
335
|
walker.walk
|
327
|
-
converter_results = converter_instances.inject({}) do |memo, (slug,i)|
|
336
|
+
converter_results = converter_instances.inject({}) do |memo, (slug,i)|
|
328
337
|
memo[slug] = i.record!
|
329
338
|
memo
|
330
339
|
end
|
@@ -11,6 +11,9 @@ module ScoutApm
|
|
11
11
|
|
12
12
|
attr_reader :call_stack
|
13
13
|
|
14
|
+
# call_stack - an +Array+ of calls, typically generated via the +caller+ method.
|
15
|
+
# Example single line:
|
16
|
+
# "/Users/dlite/.rvm/rubies/ruby-2.4.5/lib/ruby/2.4.0/irb/workspace.rb:87:in `eval'"
|
14
17
|
def initialize(call_stack, root=ScoutApm::Agent.instance.context.environment.root)
|
15
18
|
@call_stack = call_stack
|
16
19
|
# We can't use a constant as it'd be too early to fetch environment info
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
module Utils
|
3
|
+
class Error < StandardError; end
|
4
|
+
|
5
|
+
class InstanceVar
|
6
|
+
attr_reader :name
|
7
|
+
attr_reader :obj
|
8
|
+
|
9
|
+
def initialize(name, obj, parent)
|
10
|
+
@name = name
|
11
|
+
@obj = obj
|
12
|
+
@parent = parent
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
"#{@name} - #{obj.class}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def history
|
20
|
+
(@parent.nil? ? [] : @parent.history) + [to_s]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class MarshalLogging
|
25
|
+
def initialize(base_obj)
|
26
|
+
@base_obj = base_obj
|
27
|
+
end
|
28
|
+
|
29
|
+
def dive
|
30
|
+
to_investigate = [InstanceVar.new('Root', @base_obj, nil)]
|
31
|
+
max_to_check = 10000
|
32
|
+
checked = 0
|
33
|
+
|
34
|
+
while (var = to_investigate.shift)
|
35
|
+
checked += 1
|
36
|
+
if checked > max_to_check
|
37
|
+
return "Limiting Checks (max = #{max_to_check})"
|
38
|
+
end
|
39
|
+
|
40
|
+
obj = var.obj
|
41
|
+
|
42
|
+
if offending_hash?(obj)
|
43
|
+
return "Found undumpable object: #{var.history}"
|
44
|
+
end
|
45
|
+
|
46
|
+
if !dumps?(obj)
|
47
|
+
if obj.is_a? Hash
|
48
|
+
keys = obj.keys
|
49
|
+
keys.each do |key|
|
50
|
+
to_investigate.push(
|
51
|
+
InstanceVar.new(key.to_s, obj[key], var)
|
52
|
+
)
|
53
|
+
end
|
54
|
+
elsif obj.is_a? Array
|
55
|
+
obj.each_with_index do |value, idx|
|
56
|
+
to_investigate.push(
|
57
|
+
InstanceVar.new("Index #{idx}", value, var)
|
58
|
+
)
|
59
|
+
end
|
60
|
+
else
|
61
|
+
symbols = obj.instance_variables
|
62
|
+
if !symbols.any?
|
63
|
+
return "Found undumpable object: #{var.history}"
|
64
|
+
end
|
65
|
+
|
66
|
+
symbols.each do |sym|
|
67
|
+
to_investigate.push(
|
68
|
+
InstanceVar.new(sym, obj.instance_variable_get(sym), var)
|
69
|
+
)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
true
|
76
|
+
end
|
77
|
+
|
78
|
+
def dumps?(obj)
|
79
|
+
Marshal.dump(obj)
|
80
|
+
true
|
81
|
+
rescue TypeError
|
82
|
+
false
|
83
|
+
end
|
84
|
+
|
85
|
+
def offending_hash?(obj)
|
86
|
+
obj.is_a?(Hash) && !obj.default_proc.nil?
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -34,15 +34,24 @@ module ScoutApm
|
|
34
34
|
when :postgres then to_s_postgres
|
35
35
|
when :mysql then to_s_mysql
|
36
36
|
when :sqlite then to_s_sqlite
|
37
|
+
when :sqlserver then to_s_sqlserver
|
37
38
|
end
|
38
39
|
end
|
39
40
|
|
40
41
|
private
|
41
42
|
|
43
|
+
def to_s_sqlserver
|
44
|
+
sql.gsub!(SQLSERVER_EXECUTESQL, '\1')
|
45
|
+
sql.gsub!(SQLSERVER_REMOVE_INTEGERS, '?')
|
46
|
+
sql.gsub!(SQLSERVER_IN_CLAUSE, 'IN (?)')
|
47
|
+
sql
|
48
|
+
end
|
49
|
+
|
42
50
|
def to_s_postgres
|
43
51
|
sql.gsub!(PSQL_PLACEHOLDER, '?')
|
44
52
|
sql.gsub!(PSQL_VAR_INTERPOLATION, '')
|
45
|
-
sql.gsub!(PSQL_REMOVE_STRINGS, '?')
|
53
|
+
sql.gsub!(PSQL_AFTER_WHERE) {|c| c.gsub(PSQL_REMOVE_STRINGS, '?')}
|
54
|
+
sql.gsub!(PSQL_AFTER_SET) {|c| c.gsub(PSQL_REMOVE_STRINGS, '?')}
|
46
55
|
sql.gsub!(PSQL_REMOVE_INTEGERS, '?')
|
47
56
|
sql.gsub!(PSQL_IN_CLAUSE, 'IN (?)')
|
48
57
|
sql.gsub!(MULTIPLE_SPACES, ' ')
|
@@ -10,6 +10,8 @@ module ScoutApm
|
|
10
10
|
PSQL_REMOVE_INTEGERS = /(?<!LIMIT )\b\d+\b/.freeze
|
11
11
|
PSQL_PLACEHOLDER = /\$\d+/.freeze
|
12
12
|
PSQL_IN_CLAUSE = /IN\s+\(\?[^\)]*\)/.freeze
|
13
|
+
PSQL_AFTER_WHERE = /(?:WHERE\s+).*?(?:SELECT|$)/i.freeze
|
14
|
+
PSQL_AFTER_SET = /(?:SET\s+).*?(?:WHERE|$)/i.freeze
|
13
15
|
|
14
16
|
MYSQL_VAR_INTERPOLATION = %r|\[\[.*\]\]\s*$|.freeze
|
15
17
|
MYSQL_REMOVE_INTEGERS = /(?<!LIMIT )\b\d+\b/.freeze
|
@@ -20,6 +22,11 @@ module ScoutApm
|
|
20
22
|
SQLITE_VAR_INTERPOLATION = %r|\[\[.*\]\]\s*$|.freeze
|
21
23
|
SQLITE_REMOVE_STRINGS = /'(?:[^']|'')*'/.freeze
|
22
24
|
SQLITE_REMOVE_INTEGERS = /(?<!LIMIT )\b\d+\b/.freeze
|
25
|
+
|
26
|
+
# => "EXEC sp_executesql N'SELECT [users].* FROM [users] WHERE (age > 50) ORDER BY [users].[id] ASC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY', N'@0 int', @0 = 10"
|
27
|
+
SQLSERVER_EXECUTESQL = /EXEC sp_executesql N'(.*?)'.*/
|
28
|
+
SQLSERVER_REMOVE_INTEGERS = /(?<!LIMIT )\b(?<!@)\d+\b/.freeze
|
29
|
+
SQLSERVER_IN_CLAUSE = /IN\s+\(\?[^\)]*\)/.freeze
|
23
30
|
end
|
24
31
|
end
|
25
32
|
end
|