scout_apm 3.0.0.pre27 → 4.0.3
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 +139 -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 +38 -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 +19 -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 +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 +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
- metadata +101 -62
- data/ext/stacks/extconf.rb +0 -37
- 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
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
module ScoutApm
|
|
2
|
+
module Instruments
|
|
3
|
+
class Typhoeus
|
|
4
|
+
attr_reader :context
|
|
5
|
+
|
|
6
|
+
def initialize(context)
|
|
7
|
+
@context = context
|
|
8
|
+
@installed = false
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def logger
|
|
12
|
+
context.logger
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def installed?
|
|
16
|
+
@installed
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def install
|
|
20
|
+
if defined?(::Typhoeus)
|
|
21
|
+
@installed = true
|
|
22
|
+
|
|
23
|
+
logger.info "Instrumenting Typhoeus"
|
|
24
|
+
|
|
25
|
+
::Typhoeus::Request.send(:prepend, TyphoeusInstrumentation)
|
|
26
|
+
::Typhoeus::Hydra.send(:prepend, TyphoeusHydraInstrumentation)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
module TyphoeusHydraInstrumentation
|
|
31
|
+
def run(*args, &block)
|
|
32
|
+
req = ScoutApm::RequestManager.lookup
|
|
33
|
+
req.start_layer(ScoutApm::Layer.new("HTTP", "Hydra"))
|
|
34
|
+
current_layer = req.current_layer
|
|
35
|
+
current_layer.desc = scout_desc if current_layer
|
|
36
|
+
|
|
37
|
+
begin
|
|
38
|
+
super(*args, &block)
|
|
39
|
+
ensure
|
|
40
|
+
req.stop_layer
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def scout_desc
|
|
45
|
+
"#{self.queued_requests.count} requests"
|
|
46
|
+
rescue
|
|
47
|
+
""
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
module TyphoeusInstrumentation
|
|
52
|
+
def run(*args, &block)
|
|
53
|
+
req = ScoutApm::RequestManager.lookup
|
|
54
|
+
req.start_layer(ScoutApm::Layer.new("HTTP", scout_request_verb))
|
|
55
|
+
current_layer = req.current_layer
|
|
56
|
+
current_layer.desc = scout_desc(scout_request_verb, scout_request_url) if current_layer
|
|
57
|
+
|
|
58
|
+
begin
|
|
59
|
+
super(*args, &block)
|
|
60
|
+
ensure
|
|
61
|
+
req.stop_layer
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def scout_desc(verb, uri)
|
|
66
|
+
max_length = ScoutApm::Agent.instance.context.config.value('instrument_http_url_length')
|
|
67
|
+
(String(uri).split('?').first)[0..(max_length - 1)]
|
|
68
|
+
rescue
|
|
69
|
+
""
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def scout_request_url
|
|
73
|
+
self.url
|
|
74
|
+
rescue
|
|
75
|
+
""
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def scout_request_verb
|
|
79
|
+
self.options[:method].to_s
|
|
80
|
+
rescue
|
|
81
|
+
""
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
data/lib/scout_apm/job_record.rb
CHANGED
|
@@ -32,8 +32,10 @@ module ScoutApm
|
|
|
32
32
|
|
|
33
33
|
# Modifies self and returns self, after merging in `other`.
|
|
34
34
|
def combine!(other)
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
if !self.eql?(other)
|
|
36
|
+
ScoutApm::Agent.instance.logger.debug("Mismatched Merge of Background Job: (Queue #{queue_name} == #{other.queue_name}) (Name #{job_name} == #{other.job_name}) (Hash #{hash} == #{other.hash})")
|
|
37
|
+
return self
|
|
38
|
+
end
|
|
37
39
|
|
|
38
40
|
@errors += other.errors
|
|
39
41
|
@metric_set = metric_set.combine!(other.metric_set)
|
|
@@ -30,6 +30,10 @@ module ScoutApm
|
|
|
30
30
|
|
|
31
31
|
def serialize(data)
|
|
32
32
|
Marshal.dump(data)
|
|
33
|
+
rescue
|
|
34
|
+
ScoutApm::Agent.instance.logger.info("Failed Marshalling LayawayFile")
|
|
35
|
+
ScoutApm::Agent.instance.logger.info(ScoutApm::Utils::MarshalLogging.new(data).dive) rescue nil
|
|
36
|
+
raise
|
|
33
37
|
end
|
|
34
38
|
|
|
35
39
|
def deserialize(data)
|
data/lib/scout_apm/layer.rb
CHANGED
|
@@ -36,7 +36,10 @@ module ScoutApm
|
|
|
36
36
|
|
|
37
37
|
# If this layer took longer than a fixed amount of time, store the
|
|
38
38
|
# backtrace of where it occurred.
|
|
39
|
-
|
|
39
|
+
attr_accessor :backtrace
|
|
40
|
+
|
|
41
|
+
# The file name associated with the layer. Only used for autoinstruments overhead logging.
|
|
42
|
+
attr_accessor :file_name
|
|
40
43
|
|
|
41
44
|
# As we go through a part of a request, instrumentation can store additional data
|
|
42
45
|
# Known Keys:
|
|
@@ -46,19 +49,7 @@ module ScoutApm
|
|
|
46
49
|
# If no annotations are ever set, this will return nil
|
|
47
50
|
attr_reader :annotations
|
|
48
51
|
|
|
49
|
-
|
|
50
|
-
# code, used to store captured traces.
|
|
51
|
-
attr_reader :trace_index
|
|
52
|
-
|
|
53
|
-
# ScoutProf - frame_index is an optimization to not capture a few frames
|
|
54
|
-
# during scoutprof instrumentation
|
|
55
|
-
attr_reader :frame_index
|
|
56
|
-
|
|
57
|
-
# Captured backtraces from ScoutProf. This is distinct from the backtrace
|
|
58
|
-
# attribute, which gets the ruby backtrace of any given layer. StackProf
|
|
59
|
-
# focuses on Controller layers, and requires a native extension and a
|
|
60
|
-
# reasonably recent Ruby.
|
|
61
|
-
attr_reader :traces
|
|
52
|
+
attr_reader :allocations_start, :allocations_stop
|
|
62
53
|
|
|
63
54
|
BACKTRACE_CALLER_LIMIT = 50 # maximum number of lines to send thru for backtrace analysis
|
|
64
55
|
|
|
@@ -73,11 +64,6 @@ module ScoutApm
|
|
|
73
64
|
@children = nil
|
|
74
65
|
@annotations = nil
|
|
75
66
|
@desc = nil
|
|
76
|
-
|
|
77
|
-
@traces = ScoutApm::TraceSet.new
|
|
78
|
-
@raw_frames = []
|
|
79
|
-
@frame_index = ScoutApm::Instruments::Stacks.current_frame_index # For efficiency sake, try to skip the bottom X frames when collecting traces
|
|
80
|
-
@trace_index = ScoutApm::Instruments::Stacks.current_trace_index
|
|
81
67
|
end
|
|
82
68
|
|
|
83
69
|
def limited?
|
|
@@ -116,14 +102,6 @@ module ScoutApm
|
|
|
116
102
|
@subscopable
|
|
117
103
|
end
|
|
118
104
|
|
|
119
|
-
def traced!
|
|
120
|
-
@traced = true
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
def traced?
|
|
124
|
-
@traced
|
|
125
|
-
end
|
|
126
|
-
|
|
127
105
|
# This is the old style name. This function is used for now, but should be
|
|
128
106
|
# removed, and the new type & name split should be enforced through the
|
|
129
107
|
# app.
|
|
@@ -138,42 +116,13 @@ module ScoutApm
|
|
|
138
116
|
# In Ruby 2.0+, we can pass the range directly to the caller to reduce the memory footprint.
|
|
139
117
|
def caller_array
|
|
140
118
|
# omits the first several callers which are in the ScoutAPM stack.
|
|
141
|
-
if ScoutApm::Agent.instance.context.environment.ruby_2?
|
|
119
|
+
if ScoutApm::Agent.instance.context.environment.ruby_2? || ScoutApm::Agent.instance.context.environment.ruby_3?
|
|
142
120
|
caller(3...BACKTRACE_CALLER_LIMIT)
|
|
143
121
|
else
|
|
144
122
|
caller[3...BACKTRACE_CALLER_LIMIT]
|
|
145
123
|
end
|
|
146
124
|
end
|
|
147
125
|
|
|
148
|
-
# Set the name of the file that this action is coming from.
|
|
149
|
-
# TraceSet uses this to more accurately filter backtraces
|
|
150
|
-
def set_root_class(klass_name)
|
|
151
|
-
@traces.set_root_class(klass_name)
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
def start_sampling
|
|
155
|
-
if ScoutApm::Agent.instance.context.config.value('profile') && traced?
|
|
156
|
-
ScoutApm::Instruments::Stacks.update_indexes(frame_index, trace_index)
|
|
157
|
-
ScoutApm::Instruments::Stacks.start_sampling
|
|
158
|
-
else
|
|
159
|
-
ScoutApm::Instruments::Stacks.stop_sampling(false)
|
|
160
|
-
end
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
def record_traces!
|
|
164
|
-
if ScoutApm::Agent.instance.context.config.value('profile')
|
|
165
|
-
ScoutApm::Instruments::Stacks.stop_sampling(false)
|
|
166
|
-
if traced?
|
|
167
|
-
traces.raw_traces = ScoutApm::Instruments::Stacks.profile_frames
|
|
168
|
-
traces.skipped_in_gc = ScoutApm::Instruments::Stacks.skipped_in_gc
|
|
169
|
-
traces.skipped_in_handler = ScoutApm::Instruments::Stacks.skipped_in_handler
|
|
170
|
-
traces.skipped_in_job_registered = ScoutApm::Instruments::Stacks.skipped_in_job_registered
|
|
171
|
-
traces.skipped_in_not_running = ScoutApm::Instruments::Stacks.skipped_in_not_running
|
|
172
|
-
end
|
|
173
|
-
end
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
|
|
177
126
|
######################################
|
|
178
127
|
# Debugging Helpers
|
|
179
128
|
######################################
|
|
@@ -46,9 +46,15 @@ module ScoutApm
|
|
|
46
46
|
set = child_set(metric_type)
|
|
47
47
|
|
|
48
48
|
if set.size >= unique_cutoff
|
|
49
|
-
# find limited_layer
|
|
50
|
-
@limited_layers
|
|
51
|
-
@limited_layers
|
|
49
|
+
# find or create limited_layer
|
|
50
|
+
@limited_layers ||= Hash.new
|
|
51
|
+
layer = if @limited_layers.has_key?(metric_type)
|
|
52
|
+
@limited_layers[metric_type]
|
|
53
|
+
else
|
|
54
|
+
@limited_layers[metric_type] = LimitedLayer.new(metric_type)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
layer.absorb(child)
|
|
52
58
|
else
|
|
53
59
|
# we have space just add it
|
|
54
60
|
set << child
|
|
@@ -76,10 +82,5 @@ module ScoutApm
|
|
|
76
82
|
def size
|
|
77
83
|
@children.size
|
|
78
84
|
end
|
|
79
|
-
|
|
80
|
-
# hold off initializing this until we know we need it
|
|
81
|
-
def init_limited_layers
|
|
82
|
-
@limited_layers ||= Hash.new { |hash, key| hash[key] = LimitedLayer.new(key) }
|
|
83
|
-
end
|
|
84
85
|
end
|
|
85
86
|
end
|
|
@@ -174,14 +174,13 @@ module ScoutApm
|
|
|
174
174
|
metric_hash[meta] ||= MetricStats.new(meta_options.has_key?(:scope))
|
|
175
175
|
allocation_metric_hash[meta] ||= MetricStats.new(meta_options.has_key?(:scope))
|
|
176
176
|
|
|
177
|
-
#
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
timing_stat.add_traces(layer.traces.as_json)
|
|
177
|
+
# timing
|
|
178
|
+
stat = metric_hash[meta]
|
|
179
|
+
stat.update!(layer.total_call_time, layer.total_exclusive_time)
|
|
181
180
|
|
|
182
|
-
#
|
|
183
|
-
|
|
184
|
-
|
|
181
|
+
# allocations
|
|
182
|
+
stat = allocation_metric_hash[meta]
|
|
183
|
+
stat.update!(layer.total_allocations, layer.total_exclusive_allocations)
|
|
185
184
|
|
|
186
185
|
if LimitedLayer === layer
|
|
187
186
|
metric_hash[meta].call_count = layer.count
|
|
@@ -191,18 +190,18 @@ module ScoutApm
|
|
|
191
190
|
|
|
192
191
|
# Merged Metric - no specifics, just sum up by type (ActiveRecord, View, HTTP, etc)
|
|
193
192
|
def store_aggregate_metric(layer, metric_hash, allocation_metric_hash)
|
|
194
|
-
|
|
193
|
+
meta = MetricMeta.new("#{layer.type}/all")
|
|
195
194
|
|
|
196
|
-
|
|
197
|
-
|
|
195
|
+
metric_hash[meta] ||= MetricStats.new(false)
|
|
196
|
+
allocation_metric_hash[meta] ||= MetricStats.new(false)
|
|
198
197
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
198
|
+
# timing
|
|
199
|
+
stat = metric_hash[meta]
|
|
200
|
+
stat.update!(layer.total_call_time, layer.total_exclusive_time)
|
|
202
201
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
202
|
+
# allocations
|
|
203
|
+
stat = allocation_metric_hash[meta]
|
|
204
|
+
stat.update!(layer.total_allocations, layer.total_exclusive_allocations)
|
|
206
205
|
end
|
|
207
206
|
|
|
208
207
|
################################################################################
|
|
@@ -217,20 +216,6 @@ module ScoutApm
|
|
|
217
216
|
return false if layer.annotations.nil?
|
|
218
217
|
return true if layer.annotations[:ignorable]
|
|
219
218
|
end
|
|
220
|
-
|
|
221
|
-
# Debug logging for scoutprof traces
|
|
222
|
-
def debug_scoutprof(layer)
|
|
223
|
-
if layer.type =~ %r{^(Controller|Queue|Job)$}.freeze
|
|
224
|
-
if context.config.value('profile')
|
|
225
|
-
context.logger.debug do
|
|
226
|
-
traces_inspect = layer.traces.inspect
|
|
227
|
-
"****** Slow Request #{layer.type} Traces (#{layer.name}, tet: #{layer.total_exclusive_time}, tct: #{layer.total_call_time}), total raw traces: #{layer.traces.cube.total_count}, total clean traces: #{layer.traces.total_count}, skipped gc: #{layer.traces.skipped_in_gc}, skipped handler: #{layer.traces.skipped_in_handler}, skipped registered #{layer.traces.skipped_in_job_registered}, skipped not_running #{layer.traces.skipped_in_not_running}:\n#{traces_inspect}"
|
|
228
|
-
end
|
|
229
|
-
else
|
|
230
|
-
context.logger.debug "****** Slow Request #{layer.type} Traces: Scoutprof is not enabled"
|
|
231
|
-
end
|
|
232
|
-
end
|
|
233
|
-
end
|
|
234
219
|
end
|
|
235
220
|
end
|
|
236
221
|
end
|
|
@@ -15,6 +15,7 @@ module ScoutApm
|
|
|
15
15
|
# Let the store know we're here, and if it wants our data, it will call
|
|
16
16
|
# back into #call
|
|
17
17
|
@store.track_slow_job!(self)
|
|
18
|
+
|
|
18
19
|
nil # not returning anything in the layer results ... not used
|
|
19
20
|
end
|
|
20
21
|
|
|
@@ -54,7 +55,8 @@ module ScoutApm
|
|
|
54
55
|
mem_delta,
|
|
55
56
|
job_layer.total_allocations,
|
|
56
57
|
score,
|
|
57
|
-
limited
|
|
58
|
+
limited?,
|
|
59
|
+
span_trace
|
|
58
60
|
)
|
|
59
61
|
end
|
|
60
62
|
|
|
@@ -69,7 +71,9 @@ module ScoutApm
|
|
|
69
71
|
walker.on do |layer|
|
|
70
72
|
next if skip_layer?(layer)
|
|
71
73
|
|
|
72
|
-
|
|
74
|
+
# The queue_layer is useful to capture for other reasons, but doesn't
|
|
75
|
+
# create a MetricMeta/Stat of its own
|
|
76
|
+
next if layer == queue_layer
|
|
73
77
|
|
|
74
78
|
store_specific_metric(layer, metric_hash, allocation_metric_hash)
|
|
75
79
|
store_aggregate_metric(layer, metric_hash, allocation_metric_hash)
|
|
@@ -87,6 +91,12 @@ module ScoutApm
|
|
|
87
91
|
def skip_layer?(layer); super(layer) || layer == queue_layer; end
|
|
88
92
|
def queue_layer; layer_finder.queue; end
|
|
89
93
|
def job_layer; layer_finder.job; end
|
|
94
|
+
|
|
95
|
+
def span_trace
|
|
96
|
+
ScoutApm::LayerConverters::TraceConverter.
|
|
97
|
+
new(@context, @request, @layer_finder, @store).
|
|
98
|
+
call
|
|
99
|
+
end
|
|
90
100
|
end
|
|
91
101
|
end
|
|
92
102
|
end
|
|
@@ -11,6 +11,7 @@ module ScoutApm
|
|
|
11
11
|
# Let the store know we're here, and if it wants our data, it will call
|
|
12
12
|
# back into #call
|
|
13
13
|
@store.track_slow_transaction!(self)
|
|
14
|
+
|
|
14
15
|
nil # not returning anything in the layer results ... not used
|
|
15
16
|
end
|
|
16
17
|
|
|
@@ -51,7 +52,8 @@ module ScoutApm
|
|
|
51
52
|
mem_delta,
|
|
52
53
|
root_layer.total_allocations,
|
|
53
54
|
@points,
|
|
54
|
-
limited
|
|
55
|
+
limited?,
|
|
56
|
+
span_trace)
|
|
55
57
|
end
|
|
56
58
|
|
|
57
59
|
# Full metrics from this request. These get stored permanently in a SlowTransaction.
|
|
@@ -69,9 +71,6 @@ module ScoutApm
|
|
|
69
71
|
|
|
70
72
|
walker.on do |layer|
|
|
71
73
|
next if skip_layer?(layer)
|
|
72
|
-
|
|
73
|
-
debug_scoutprof(layer)
|
|
74
|
-
|
|
75
74
|
store_specific_metric(layer, metric_hash, allocation_metric_hash)
|
|
76
75
|
store_aggregate_metric(layer, metric_hash, allocation_metric_hash)
|
|
77
76
|
end
|
|
@@ -84,6 +83,17 @@ module ScoutApm
|
|
|
84
83
|
|
|
85
84
|
[metric_hash, allocation_metric_hash]
|
|
86
85
|
end
|
|
86
|
+
|
|
87
|
+
###########################################################
|
|
88
|
+
# Also create a new style trace. This is not a good #
|
|
89
|
+
# spot for this long term, but fixes an issue for now. #
|
|
90
|
+
###########################################################
|
|
91
|
+
|
|
92
|
+
def span_trace
|
|
93
|
+
ScoutApm::LayerConverters::TraceConverter.
|
|
94
|
+
new(@context, @request, @layer_finder, @store).
|
|
95
|
+
call
|
|
96
|
+
end
|
|
87
97
|
end
|
|
88
98
|
end
|
|
89
99
|
end
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
module ScoutApm
|
|
2
|
+
module LayerConverters
|
|
3
|
+
class TraceConverter < ConverterBase
|
|
4
|
+
###################
|
|
5
|
+
# Converter API #
|
|
6
|
+
###################
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def record!
|
|
10
|
+
@points = context.slow_request_policy.score(request)
|
|
11
|
+
|
|
12
|
+
# Let the store know we're here, and if it wants our data, it will call
|
|
13
|
+
# back into #call
|
|
14
|
+
@store.track_trace!(self)
|
|
15
|
+
|
|
16
|
+
nil # not returning anything in the layer results ... not used
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
#####################
|
|
20
|
+
# ScoreItemSet API #
|
|
21
|
+
#####################
|
|
22
|
+
def name; request.unique_name; end
|
|
23
|
+
def score; @points; end
|
|
24
|
+
|
|
25
|
+
# Unconditionally attempts to convert this into a DetailedTrace object.
|
|
26
|
+
# Can return nil if the request didn't have any scope_layer or if `timeline_traces` aren't enabled.
|
|
27
|
+
def call
|
|
28
|
+
return nil unless scope_layer
|
|
29
|
+
return nil unless context.config.value('timeline_traces')
|
|
30
|
+
|
|
31
|
+
# Since this request is being stored, update the needed counters
|
|
32
|
+
context.slow_request_policy.stored!(request)
|
|
33
|
+
|
|
34
|
+
# record the change in memory usage
|
|
35
|
+
mem_delta = ScoutApm::Instruments::Process::ProcessMemory.new(context).rss_to_mb(@request.capture_mem_delta!)
|
|
36
|
+
|
|
37
|
+
transaction_id = request.transaction_id
|
|
38
|
+
revision = context.environment.git_revision.sha
|
|
39
|
+
start_instant = request.root_layer.start_time
|
|
40
|
+
stop_instant = request.root_layer.stop_time
|
|
41
|
+
type = if request.web?
|
|
42
|
+
"Web"
|
|
43
|
+
elsif request.job?
|
|
44
|
+
"Job"
|
|
45
|
+
else
|
|
46
|
+
"Unknown"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Create request tags
|
|
50
|
+
#
|
|
51
|
+
tags = {
|
|
52
|
+
:allocations => request.root_layer.total_allocations,
|
|
53
|
+
:mem_delta => mem_delta,
|
|
54
|
+
}.merge(request.context.to_flat_hash)
|
|
55
|
+
|
|
56
|
+
host = context.environment.hostname
|
|
57
|
+
path = request.annotations[:uri] || ""
|
|
58
|
+
code = "" # User#index for instance
|
|
59
|
+
|
|
60
|
+
spans = create_spans(request.root_layer)
|
|
61
|
+
if limited?
|
|
62
|
+
tags[:"scout.reached_span_cap"] = true
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
DetailedTrace.new(
|
|
66
|
+
transaction_id,
|
|
67
|
+
revision,
|
|
68
|
+
host,
|
|
69
|
+
start_instant,
|
|
70
|
+
stop_instant,
|
|
71
|
+
type,
|
|
72
|
+
|
|
73
|
+
path,
|
|
74
|
+
code,
|
|
75
|
+
|
|
76
|
+
spans,
|
|
77
|
+
tags
|
|
78
|
+
|
|
79
|
+
# total_score = 0,
|
|
80
|
+
# percentile_score = 0,
|
|
81
|
+
# age_score = 0,
|
|
82
|
+
# memory_delta_score = 0,
|
|
83
|
+
# memory_allocations_score = 0
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Returns an array of span objects. Uses recursion to get all children
|
|
88
|
+
# wired up w/ correct parent_ids
|
|
89
|
+
def create_spans(layer, parent_id = nil)
|
|
90
|
+
span_id = ScoutApm::Utils::SpanId.new.to_s
|
|
91
|
+
|
|
92
|
+
start_instant = layer.start_time
|
|
93
|
+
stop_instant = layer.stop_time
|
|
94
|
+
operation = layer.legacy_metric_name
|
|
95
|
+
tags = {
|
|
96
|
+
:start_allocations => layer.allocations_start,
|
|
97
|
+
:stop_allocations => layer.allocations_stop,
|
|
98
|
+
}
|
|
99
|
+
if layer.desc
|
|
100
|
+
tags[:desc] = layer.desc.to_s
|
|
101
|
+
end
|
|
102
|
+
if layer.annotations && layer.annotations[:record_count]
|
|
103
|
+
tags["db.record_count"] = layer.annotations[:record_count]
|
|
104
|
+
end
|
|
105
|
+
if layer.annotations && layer.annotations[:class_name]
|
|
106
|
+
tags["db.class_name"] = layer.annotations[:class_name]
|
|
107
|
+
end
|
|
108
|
+
if layer.backtrace
|
|
109
|
+
tags[:backtrace] = backtrace_parser(layer.backtrace) rescue nil
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Collect up self, and all children into result array
|
|
113
|
+
result = []
|
|
114
|
+
result << DetailedTraceSpan.new(
|
|
115
|
+
span_id.to_s,
|
|
116
|
+
parent_id.to_s,
|
|
117
|
+
start_instant,
|
|
118
|
+
stop_instant,
|
|
119
|
+
operation,
|
|
120
|
+
tags)
|
|
121
|
+
|
|
122
|
+
layer.children.each do |child|
|
|
123
|
+
# Don't create spans from limited layers. These don't have start/stop times and our processing can't
|
|
124
|
+
# handle these yet.
|
|
125
|
+
unless over_span_limit?(result) || child.is_a?(LimitedLayer)
|
|
126
|
+
result += create_spans(child, span_id)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
return result
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Take an array of ruby backtrace lines and split it into an array of hashes like:
|
|
134
|
+
# ["/Users/cschneid/.rvm/rubies/ruby-2.2.7/lib/ruby/2.2.0/irb/workspace.rb:86:in `eval'", ...]
|
|
135
|
+
# turns into:
|
|
136
|
+
# [ {
|
|
137
|
+
# "file": "app/controllers/users_controller.rb",
|
|
138
|
+
# "line": 10,
|
|
139
|
+
# "function": "index"
|
|
140
|
+
# },
|
|
141
|
+
# ]
|
|
142
|
+
def backtrace_parser(lines)
|
|
143
|
+
bt = ScoutApm::Utils::BacktraceParser.new(lines).call
|
|
144
|
+
|
|
145
|
+
bt.map do |line|
|
|
146
|
+
match = line.match(/(.*):(\d+):in `(.*)'/)
|
|
147
|
+
{
|
|
148
|
+
"file" => match[1],
|
|
149
|
+
"line" => match[2],
|
|
150
|
+
"function" => match[3],
|
|
151
|
+
}
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
################################################################################
|
|
157
|
+
# Limit Handling
|
|
158
|
+
################################################################################
|
|
159
|
+
|
|
160
|
+
# To prevent huge traces from being generated, we stop collecting
|
|
161
|
+
# spans as we go beyond some reasonably large count.
|
|
162
|
+
MAX_SPANS = 1500
|
|
163
|
+
|
|
164
|
+
def over_span_limit?(spans)
|
|
165
|
+
if spans.size > MAX_SPANS
|
|
166
|
+
log_over_span_limit
|
|
167
|
+
@limited = true
|
|
168
|
+
else
|
|
169
|
+
false
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def log_over_span_limit
|
|
174
|
+
unless limited?
|
|
175
|
+
context.logger.debug "Not recording additional spans for #{name}. Over the span limit."
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def limited?
|
|
180
|
+
!! @limited
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|