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.
Files changed (131) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/test.yml +49 -0
  3. data/.gitignore +1 -1
  4. data/.rubocop.yml +5 -5
  5. data/.travis.yml +19 -14
  6. data/CHANGELOG.markdown +139 -4
  7. data/Gemfile +1 -7
  8. data/README.markdown +13 -4
  9. data/Rakefile +1 -1
  10. data/ext/allocations/allocations.c +2 -0
  11. data/gems/README.md +28 -0
  12. data/gems/octoshark.gemfile +4 -0
  13. data/gems/rails3.gemfile +5 -0
  14. data/gems/rails4.gemfile +4 -0
  15. data/gems/rails5.gemfile +4 -0
  16. data/gems/rails6.gemfile +4 -0
  17. data/lib/scout_apm.rb +38 -9
  18. data/lib/scout_apm/agent.rb +29 -10
  19. data/lib/scout_apm/agent/exit_handler.rb +0 -1
  20. data/lib/scout_apm/agent_context.rb +22 -3
  21. data/lib/scout_apm/app_server_load.rb +7 -2
  22. data/lib/scout_apm/attribute_arranger.rb +0 -2
  23. data/lib/scout_apm/auto_instrument.rb +5 -0
  24. data/lib/scout_apm/auto_instrument/instruction_sequence.rb +31 -0
  25. data/lib/scout_apm/auto_instrument/layer.rb +23 -0
  26. data/lib/scout_apm/auto_instrument/parser.rb +27 -0
  27. data/lib/scout_apm/auto_instrument/rails.rb +175 -0
  28. data/lib/scout_apm/background_job_integrations/legacy_sneakers.rb +55 -0
  29. data/lib/scout_apm/background_job_integrations/que.rb +134 -0
  30. data/lib/scout_apm/background_job_integrations/resque.rb +6 -2
  31. data/lib/scout_apm/background_job_integrations/shoryuken.rb +124 -0
  32. data/lib/scout_apm/background_job_integrations/sidekiq.rb +5 -19
  33. data/lib/scout_apm/background_job_integrations/sneakers.rb +87 -0
  34. data/lib/scout_apm/config.rb +45 -8
  35. data/lib/scout_apm/detailed_trace.rb +217 -0
  36. data/lib/scout_apm/environment.rb +19 -1
  37. data/lib/scout_apm/error.rb +27 -0
  38. data/lib/scout_apm/error_service.rb +32 -0
  39. data/lib/scout_apm/error_service/error_buffer.rb +39 -0
  40. data/lib/scout_apm/error_service/error_record.rb +211 -0
  41. data/lib/scout_apm/error_service/ignored_exceptions.rb +66 -0
  42. data/lib/scout_apm/error_service/middleware.rb +32 -0
  43. data/lib/scout_apm/error_service/notifier.rb +33 -0
  44. data/lib/scout_apm/error_service/payload.rb +47 -0
  45. data/lib/scout_apm/error_service/periodic_work.rb +17 -0
  46. data/lib/scout_apm/error_service/railtie.rb +11 -0
  47. data/lib/scout_apm/error_service/sidekiq.rb +80 -0
  48. data/lib/scout_apm/extensions/transaction_callback_payload.rb +1 -1
  49. data/lib/scout_apm/fake_store.rb +3 -0
  50. data/lib/scout_apm/framework_integrations/rails_2.rb +2 -1
  51. data/lib/scout_apm/framework_integrations/rails_3_or_4.rb +3 -1
  52. data/lib/scout_apm/git_revision.rb +6 -3
  53. data/lib/scout_apm/instant/middleware.rb +2 -1
  54. data/lib/scout_apm/instrument_manager.rb +9 -7
  55. data/lib/scout_apm/instruments/action_controller_rails_2.rb +3 -1
  56. data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +56 -55
  57. data/lib/scout_apm/instruments/action_view.rb +122 -26
  58. data/lib/scout_apm/instruments/active_record.rb +66 -18
  59. data/lib/scout_apm/instruments/http.rb +48 -0
  60. data/lib/scout_apm/instruments/memcached.rb +43 -0
  61. data/lib/scout_apm/instruments/mongoid.rb +9 -4
  62. data/lib/scout_apm/instruments/net_http.rb +8 -1
  63. data/lib/scout_apm/instruments/typhoeus.rb +88 -0
  64. data/lib/scout_apm/job_record.rb +4 -2
  65. data/lib/scout_apm/layaway_file.rb +4 -0
  66. data/lib/scout_apm/layer.rb +6 -57
  67. data/lib/scout_apm/layer_children_set.rb +9 -8
  68. data/lib/scout_apm/layer_converters/converter_base.rb +15 -30
  69. data/lib/scout_apm/layer_converters/slow_job_converter.rb +12 -2
  70. data/lib/scout_apm/layer_converters/slow_request_converter.rb +14 -4
  71. data/lib/scout_apm/layer_converters/trace_converter.rb +184 -0
  72. data/lib/scout_apm/limited_layer.rb +0 -7
  73. data/lib/scout_apm/metric_stats.rb +0 -8
  74. data/lib/scout_apm/middleware.rb +1 -1
  75. data/lib/scout_apm/periodic_work.rb +19 -0
  76. data/lib/scout_apm/remote/message.rb +4 -0
  77. data/lib/scout_apm/remote/server.rb +13 -1
  78. data/lib/scout_apm/reporter.rb +8 -3
  79. data/lib/scout_apm/reporting.rb +2 -1
  80. data/lib/scout_apm/request_histograms.rb +8 -0
  81. data/lib/scout_apm/serializers/app_server_load_serializer.rb +4 -0
  82. data/lib/scout_apm/serializers/directive_serializer.rb +4 -0
  83. data/lib/scout_apm/serializers/payload_serializer.rb +2 -2
  84. data/lib/scout_apm/serializers/payload_serializer_to_json.rb +30 -15
  85. data/lib/scout_apm/slow_job_record.rb +5 -1
  86. data/lib/scout_apm/slow_policy/age_policy.rb +33 -0
  87. data/lib/scout_apm/slow_policy/percent_policy.rb +22 -0
  88. data/lib/scout_apm/slow_policy/percentile_policy.rb +24 -0
  89. data/lib/scout_apm/slow_policy/policy.rb +21 -0
  90. data/lib/scout_apm/slow_policy/speed_policy.rb +16 -0
  91. data/lib/scout_apm/slow_request_policy.rb +18 -77
  92. data/lib/scout_apm/slow_transaction.rb +3 -1
  93. data/lib/scout_apm/store.rb +0 -1
  94. data/lib/scout_apm/tracked_request.rb +39 -30
  95. data/lib/scout_apm/utils/backtrace_parser.rb +3 -0
  96. data/lib/scout_apm/utils/marshal_logging.rb +90 -0
  97. data/lib/scout_apm/utils/sql_sanitizer.rb +10 -1
  98. data/lib/scout_apm/utils/sql_sanitizer_regex.rb +8 -1
  99. data/lib/scout_apm/utils/sql_sanitizer_regex_1_8_7.rb +6 -0
  100. data/lib/scout_apm/utils/unique_id.rb +27 -0
  101. data/lib/scout_apm/version.rb +1 -1
  102. data/scout_apm.gemspec +13 -7
  103. data/test/test_helper.rb +2 -2
  104. data/test/unit/agent_context_test.rb +29 -0
  105. data/test/unit/auto_instrument/assignments-instrumented.rb +31 -0
  106. data/test/unit/auto_instrument/assignments.rb +31 -0
  107. data/test/unit/auto_instrument/controller-ast.txt +57 -0
  108. data/test/unit/auto_instrument/controller-instrumented.rb +49 -0
  109. data/test/unit/auto_instrument/controller.rb +49 -0
  110. data/test/unit/auto_instrument/rescue_from-instrumented.rb +13 -0
  111. data/test/unit/auto_instrument/rescue_from.rb +13 -0
  112. data/test/unit/auto_instrument_test.rb +54 -0
  113. data/test/unit/environment_test.rb +2 -2
  114. data/test/unit/error_service/error_buffer_test.rb +25 -0
  115. data/test/unit/error_service/ignored_exceptions_test.rb +49 -0
  116. data/test/unit/instruments/active_record_test.rb +40 -0
  117. data/test/unit/layer_children_set_test.rb +9 -0
  118. data/test/unit/request_histograms_test.rb +17 -0
  119. data/test/unit/serializers/payload_serializer_test.rb +39 -5
  120. data/test/unit/slow_request_policy_test.rb +41 -13
  121. data/test/unit/sql_sanitizer_test.rb +78 -0
  122. metadata +101 -62
  123. data/ext/stacks/extconf.rb +0 -37
  124. data/ext/stacks/scout_atomics.h +0 -86
  125. data/ext/stacks/stacks.c +0 -814
  126. data/lib/scout_apm/slow_job_policy.rb +0 -111
  127. data/lib/scout_apm/trace_compactor.rb +0 -312
  128. data/lib/scout_apm/utils/fake_stacks.rb +0 -88
  129. data/test/unit/instruments/active_record_instruments_test.rb +0 -5
  130. data/test/unit/slow_job_policy_test.rb +0 -6
  131. 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
@@ -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
- same_job = queue_name == other.queue_name && job_name == other.job_name
36
- raise "Mismatched Merge of Background Job" unless same_job
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)
@@ -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
- attr_reader :backtrace
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
- # ScoutProf - trace_index is an index into the Stack structure in the C
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 || init_limited_layers
51
- @limited_layers[metric_type].absorb(child)
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
- # Timing
178
- timing_stat = metric_hash[meta]
179
- timing_stat.update!(layer.total_call_time, layer.total_exclusive_time)
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
- # Allocations
183
- allocation_stat = allocation_metric_hash[meta]
184
- allocation_stat.update!(layer.total_allocations, layer.total_exclusive_allocations)
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
- meta = MetricMeta.new("#{layer.type}/all")
193
+ meta = MetricMeta.new("#{layer.type}/all")
195
194
 
196
- metric_hash[meta] ||= MetricStats.new(false)
197
- allocation_metric_hash[meta] ||= MetricStats.new(false)
195
+ metric_hash[meta] ||= MetricStats.new(false)
196
+ allocation_metric_hash[meta] ||= MetricStats.new(false)
198
197
 
199
- # timing
200
- timing_stat = metric_hash[meta]
201
- timing_stat.update!(layer.total_call_time, layer.total_exclusive_time)
198
+ # timing
199
+ stat = metric_hash[meta]
200
+ stat.update!(layer.total_call_time, layer.total_exclusive_time)
202
201
 
203
- # allocations
204
- allocation_stat = allocation_metric_hash[meta]
205
- allocation_stat.update!(layer.total_allocations, layer.total_exclusive_allocations)
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
- debug_scoutprof(layer)
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