scout_apm 1.4.6 → 1.5.0.pre

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.markdown +9 -0
  3. data/lib/scout_apm/agent/reporting.rb +8 -6
  4. data/lib/scout_apm/agent.rb +10 -6
  5. data/lib/scout_apm/background_job_integrations/sidekiq.rb +23 -11
  6. data/lib/scout_apm/call_set.rb +61 -0
  7. data/lib/scout_apm/config.rb +2 -1
  8. data/lib/scout_apm/environment.rb +12 -7
  9. data/lib/scout_apm/histogram.rb +124 -0
  10. data/lib/scout_apm/instruments/.DS_Store +0 -0
  11. data/lib/scout_apm/instruments/action_controller_rails_2.rb +1 -0
  12. data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +1 -0
  13. data/lib/scout_apm/instruments/delayed_job.rb +1 -0
  14. data/lib/scout_apm/instruments/process/process_memory.rb +1 -1
  15. data/lib/scout_apm/instruments/sinatra.rb +1 -1
  16. data/lib/scout_apm/job_record.rb +76 -0
  17. data/lib/scout_apm/layaway.rb +4 -1
  18. data/lib/scout_apm/layaway_file.rb +4 -4
  19. data/lib/scout_apm/layer.rb +14 -4
  20. data/lib/scout_apm/layer_converters/converter_base.rb +30 -0
  21. data/lib/scout_apm/layer_converters/depth_first_walker.rb +36 -0
  22. data/lib/scout_apm/layer_converters/error_converter.rb +20 -0
  23. data/lib/scout_apm/layer_converters/job_converter.rb +84 -0
  24. data/lib/scout_apm/layer_converters/metric_converter.rb +45 -0
  25. data/lib/scout_apm/layer_converters/request_queue_time_converter.rb +60 -0
  26. data/lib/scout_apm/layer_converters/slow_job_converter.rb +88 -0
  27. data/lib/scout_apm/layer_converters/slow_request_converter.rb +111 -0
  28. data/lib/scout_apm/metric_meta.rb +9 -0
  29. data/lib/scout_apm/metric_set.rb +44 -0
  30. data/lib/scout_apm/reporter.rb +12 -5
  31. data/lib/scout_apm/serializers/jobs_serializer_to_json.rb +28 -0
  32. data/lib/scout_apm/serializers/metrics_to_json_serializer.rb +54 -0
  33. data/lib/scout_apm/serializers/payload_serializer.rb +5 -3
  34. data/lib/scout_apm/serializers/payload_serializer_to_json.rb +9 -4
  35. data/lib/scout_apm/serializers/slow_jobs_serializer_to_json.rb +29 -0
  36. data/lib/scout_apm/slow_item_set.rb +80 -0
  37. data/lib/scout_apm/slow_job_policy.rb +29 -0
  38. data/lib/scout_apm/slow_job_record.rb +33 -0
  39. data/lib/scout_apm/slow_transaction.rb +0 -22
  40. data/lib/scout_apm/stackprof_tree_collapser.rb +7 -8
  41. data/lib/scout_apm/store.rb +55 -35
  42. data/lib/scout_apm/tracked_request.rb +67 -10
  43. data/lib/scout_apm/utils/active_record_metric_name.rb +13 -0
  44. data/lib/scout_apm/utils/backtrace_parser.rb +31 -0
  45. data/lib/scout_apm/utils/fake_stack_prof.rb +1 -1
  46. data/lib/scout_apm/utils/sql_sanitizer.rb +6 -0
  47. data/lib/scout_apm/version.rb +1 -1
  48. data/lib/scout_apm.rb +25 -5
  49. data/test/unit/histogram_test.rb +93 -0
  50. data/test/unit/serializers/payload_serializer_test.rb +5 -5
  51. data/test/unit/{slow_transaction_set_test.rb → slow_item_set_test.rb} +8 -8
  52. data/test/unit/slow_job_policy_test.rb +55 -0
  53. metadata +30 -9
  54. data/lib/scout_apm/layer_converter.rb +0 -222
  55. data/lib/scout_apm/request_queue_time.rb +0 -57
  56. data/lib/scout_apm/slow_transaction_set.rb +0 -67
@@ -0,0 +1,29 @@
1
+ module ScoutApm
2
+ module Serializers
3
+ class SlowJobsSerializerToJson
4
+ attr_reader :jobs
5
+
6
+ # Jobs is a series of slow job records
7
+ def initialize(jobs)
8
+ @jobs = jobs
9
+ end
10
+
11
+ # An array of job records
12
+ def as_json
13
+ jobs.map do |job|
14
+ {
15
+ "queue" => job.queue_name,
16
+ "name" => job.job_name,
17
+ "time" => job.time,
18
+ "total_time" => job.total_time,
19
+ "exclusive_time" => job.exclusive_time,
20
+
21
+ "metrics" => MetricsToJsonSerializer.new(job.metrics).as_json, # New style of metrics
22
+ "context" => job.context,
23
+ }
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+
@@ -0,0 +1,80 @@
1
+ # In order to keep load down, only record a sample of Slow Items (Transactions
2
+ # or Jobs). In order to make that sampling as fair as possible, follow a basic
3
+ # algorithm:
4
+ #
5
+ # When adding a new Slow Item:
6
+ # * Just add it if there is an open spot
7
+ # * If there isn't an open spot, attempt to remove an over-represented
8
+ # item instead ("attempt_to_evict"). Overrepresented is simply "has more
9
+ # than @fair number of Matching Items in the set". The fastest of the
10
+ # overrepresented items is removed.
11
+ # * If there isn't an open spot, and no Item is valid to evict, drop the
12
+ # incoming Item without adding.
13
+ #
14
+ # There is no way to remove Items from this set, create a new object
15
+ # for each reporting period.
16
+ #
17
+ # Item must respond to:
18
+ # #metric_name - string - grouping key to see if one kind of thing is overrepresented
19
+ # #total_call_time - float - duration of the item
20
+
21
+ module ScoutApm
22
+ class SlowItemSet
23
+ include Enumerable
24
+
25
+ DEFAULT_TOTAL = 10
26
+ DEFAULT_FAIR = 1
27
+
28
+ attr_reader :total
29
+ attr_reader :fair
30
+
31
+ def initialize(total=DEFAULT_TOTAL, fair=DEFAULT_FAIR)
32
+ @total = total
33
+ @fair = fair
34
+ @items = []
35
+ end
36
+
37
+ def each
38
+ @items.each { |s| yield s }
39
+ end
40
+
41
+ def <<(item)
42
+ return if attempt_append(item)
43
+ attempt_to_evict
44
+ attempt_append(item)
45
+ end
46
+
47
+ def empty_slot?
48
+ @items.length < total
49
+ end
50
+
51
+ def attempt_append(item)
52
+ if empty_slot?
53
+ @items.push(item)
54
+ true
55
+ else
56
+ false
57
+ end
58
+ end
59
+
60
+ def attempt_to_evict
61
+ return if @items.length == 0
62
+
63
+ overrepresented = @items.
64
+ group_by { |item| unique_name_for(item) }.
65
+ to_a.
66
+ sort_by { |(_, items)| items.length }.
67
+ last
68
+
69
+ if overrepresented[1].length > fair
70
+ fastest = overrepresented[1].sort_by { |item| item.total_call_time }.first
71
+ @items.delete(fastest)
72
+ end
73
+ end
74
+
75
+ # Determine this items' "hash key"
76
+ def unique_name_for(item)
77
+ item.metric_name
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,29 @@
1
+ # Create one of these at startup time, and ask it if a certain worker's
2
+ # processing time is slow enough for us to collect a slow trace.
3
+ #
4
+ # Keeps track of a histogram of times for each worker class (spearately), and
5
+ # uses a percentile of normal to mark individual runs as "slow".
6
+ #
7
+ # This assumes that all worker calls will be requested once to `slow?`, so that
8
+ # the data can be stored
9
+ module ScoutApm
10
+ class SlowJobPolicy
11
+ DEFAULT_HISTOGRAM_SIZE = 50
12
+
13
+ QUANTILE = 95
14
+
15
+ def initialize(histogram_size = DEFAULT_HISTOGRAM_SIZE)
16
+ @histograms = Hash.new { |h, k| h[k] = NumericHistogram.new(histogram_size) }
17
+ end
18
+
19
+ # worker: just the worker class name. "PasswordResetJob" or similar
20
+ # total_time: runtime of the job in seconds
21
+ # returns true if this request should be stored in higher trace detail, false otherwise
22
+ def slow?(worker, total_time)
23
+ @histograms[worker].add(total_time)
24
+ return false if @histograms[worker].total == 1 # First call is never slow
25
+
26
+ total_time >= @histograms[worker].quantile(QUANTILE)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,33 @@
1
+ module ScoutApm
2
+ class SlowJobRecord
3
+ attr_reader :queue_name
4
+ attr_reader :job_name
5
+
6
+ # When did this job occur
7
+ attr_reader :time
8
+
9
+ # What else interesting did we learn?
10
+ attr_reader :context
11
+
12
+ attr_reader :total_time
13
+ attr_reader :exclusive_time
14
+ alias_method :total_call_time, :total_time
15
+
16
+ attr_reader :metrics
17
+
18
+ def initialize(queue_name, job_name, time, total_time, exclusive_time, context, metrics)
19
+ @queue_name = queue_name
20
+ @job_name = job_name
21
+ @time = time
22
+ @total_time = total_time
23
+ @exclusive_time = exclusive_time
24
+ @context = context
25
+ @metrics = metrics
26
+ end
27
+
28
+ def metric_name
29
+ "Job/#{queue_name}/#{job_name}"
30
+ end
31
+
32
+ end
33
+ end
@@ -2,10 +2,6 @@ module ScoutApm
2
2
  class SlowTransaction
3
3
  include ScoutApm::BucketNameSplitter
4
4
 
5
- BACKTRACE_THRESHOLD = 0.5 # the minimum threshold in seconds to record the backtrace for a metric.
6
- BACKTRACE_LIMIT = 5 # Max length of callers to display
7
- MAX_SIZE = 100 # Limits the size of the metric hash to prevent a metric explosion.
8
-
9
5
  attr_reader :metric_name
10
6
  attr_reader :total_call_time
11
7
  attr_reader :metrics
@@ -16,24 +12,6 @@ module ScoutApm
16
12
  attr_reader :prof
17
13
  attr_reader :raw_prof
18
14
 
19
- # TODO: Move this out of SlowTransaction, it doesn't have much to do w/
20
- # slow trans other than being a piece of data that ends up in it.
21
- #
22
- # Given a call stack, generates a filtered backtrace that:
23
- # * Limits to the app/models, app/controllers, or app/views directories
24
- # * Limits to 5 total callers
25
- # * Makes the app folder the top-level folder used in trace info
26
- def self.backtrace_parser(backtrace)
27
- stack = []
28
- backtrace.each do |c|
29
- if m=c.match(/(\/app\/(controllers|models|views)\/.+)/)
30
- stack << m[1]
31
- break if stack.size == BACKTRACE_LIMIT
32
- end
33
- end
34
- stack
35
- end
36
-
37
15
  def initialize(uri, metric_name, total_call_time, metrics, context, time, raw_stackprof)
38
16
  @uri = uri
39
17
  @metric_name = metric_name
@@ -7,14 +7,13 @@ module ScoutApm
7
7
  @raw_stackprof = raw_stackprof
8
8
 
9
9
  # Log the raw stackprof info
10
- unless StackProf.respond_to?(:fake?) && StackProf.fake?
11
- begin
12
- ScoutApm::Agent.instance.logger.debug("StackProf - Samples: #{raw_stackprof[:samples]}, GC: #{raw_stackprof[:gc_samples]}, missed: #{raw_stackprof[:missed_samples]}, Interval: #{raw_stackprof[:interval]}")
13
- rescue
14
- # Not a useful log message, currently stackprof is disabled
15
- # ScoutApm::Agent.instance.logger.debug("StackProf Raw - #{raw_stackprof.inspect}")
16
- end
17
- end
10
+ #unless StackProf.respond_to?(:fake?) && StackProf.fake?
11
+ # begin
12
+ # ScoutApm::Agent.instance.logger.debug("StackProf - Samples: #{raw_stackprof[:samples]}, GC: #{raw_stackprof[:gc_samples]}, missed: #{raw_stackprof[:missed_samples]}, Interval: #{raw_stackprof[:interval]}")
13
+ # rescue
14
+ # ScoutApm::Agent.instance.logger.debug("StackProf Raw - #{raw_stackprof.inspect}")
15
+ # end
16
+ #end
18
17
  end
19
18
 
20
19
  def call
@@ -41,6 +41,20 @@ module ScoutApm
41
41
  }
42
42
  end
43
43
 
44
+ def track_job!(job)
45
+ return if job.nil?
46
+ @mutex.synchronize {
47
+ current_period.merge_jobs!(Array(job))
48
+ }
49
+ end
50
+
51
+ def track_slow_job!(job)
52
+ return if job.nil?
53
+ @mutex.synchronize {
54
+ current_period.merge_slow_jobs!(Array(job))
55
+ }
56
+ end
57
+
44
58
  # Take each completed reporting_period, and write it to the layaway passed
45
59
  #
46
60
  # force - a boolean argument that forces this function to write
@@ -93,45 +107,79 @@ module ScoutApm
93
107
 
94
108
  # One period of Storage. Typically 1 minute
95
109
  class StoreReportingPeriod
96
- # A SlowTransactionSet object.
110
+ # A SlowItemSet to store slow transactions in
97
111
  attr_reader :slow_transactions
98
112
 
113
+ # A SlowItemSet to store slow jobs in
114
+ attr_reader :slow_jobs
115
+
99
116
  # A StoreReportingPeriodTimestamp representing the time that this
100
117
  # collection of metrics is for
101
118
  attr_reader :timestamp
102
119
 
120
+ attr_reader :metric_set
121
+
103
122
  def initialize(timestamp)
104
123
  @timestamp = timestamp
105
124
 
106
- @slow_transactions = SlowTransactionSet.new
107
- @aggregate_metrics = Hash.new
125
+ @slow_transactions = SlowItemSet.new
126
+ @slow_jobs = SlowItemSet.new
127
+
128
+ @metric_set = MetricSet.new
129
+ @jobs = Hash.new
108
130
  end
109
131
 
110
132
  #################################
111
133
  # Add metrics as they are recorded
112
134
  #################################
113
135
  def merge_metrics!(metrics)
114
- metrics.each { |metric| absorb(metric) }
136
+ metric_set.absorb_all(metrics)
115
137
  self
116
138
  end
117
139
 
118
140
  def merge_slow_transactions!(new_transactions)
119
141
  Array(new_transactions).each do |one_transaction|
120
- @slow_transactions << one_transaction
142
+ slow_transactions << one_transaction
143
+ end
144
+
145
+ self
146
+ end
147
+
148
+ def merge_jobs!(jobs)
149
+ jobs.each do |job|
150
+ if @jobs.has_key?(job)
151
+ @jobs[job].combine!(job)
152
+ else
153
+ @jobs[job] = job
154
+ end
121
155
  end
122
156
 
123
157
  self
124
158
  end
125
159
 
160
+ def merge_slow_jobs!(new_jobs)
161
+ Array(new_jobs).each do |job|
162
+ slow_jobs << job
163
+ end
164
+ end
165
+
126
166
  #################################
127
167
  # Retrieve Metrics for reporting
128
168
  #################################
129
169
  def metrics_payload
130
- @aggregate_metrics
170
+ metric_set.metrics
131
171
  end
132
172
 
133
173
  def slow_transactions_payload
134
- @slow_transactions.to_a
174
+ slow_transactions.to_a
175
+ end
176
+
177
+ def jobs
178
+ @jobs.values
179
+ end
180
+
181
+ def slow_jobs_payload
182
+ slow_jobs.to_a
135
183
  end
136
184
 
137
185
  #################################
@@ -143,34 +191,6 @@ module ScoutApm
143
191
  select { |meta,stats| meta.metric_name =~ /\AController/ }.
144
192
  inject(0) {|sum, (_, stat)| sum + stat.call_count }
145
193
  end
146
-
147
- private
148
-
149
- # We can't aggregate CPU, Memory, Capacity, or Controller, so pass through these metrics directly
150
- # TODO: Figure out a way to not have this duplicate what's in Samplers, and also on server's ingest
151
- PASSTHROUGH_METRICS = ["CPU", "Memory", "Instance", "Controller", "SlowTransaction"]
152
-
153
- # Absorbs a single new metric into the aggregates
154
- def absorb(metric)
155
- meta, stat = metric
156
-
157
- if PASSTHROUGH_METRICS.include?(meta.type) # Leave as-is, don't attempt to combine
158
- @aggregate_metrics[meta] ||= MetricStats.new
159
- @aggregate_metrics[meta].combine!(stat)
160
-
161
- elsif meta.type == "Errors" # Sadly special cased, we want both raw and aggregate values
162
- @aggregate_metrics[meta] ||= MetricStats.new
163
- @aggregate_metrics[meta].combine!(stat)
164
- agg_meta = MetricMeta.new("Errors/Request", :scope => meta.scope)
165
- @aggregate_metrics[agg_meta] ||= MetricStats.new
166
- @aggregate_metrics[agg_meta].combine!(stat)
167
-
168
- else # Combine down to a single /all key
169
- agg_meta = MetricMeta.new("#{meta.type}/all", :scope => meta.scope)
170
- @aggregate_metrics[agg_meta] ||= MetricStats.new
171
- @aggregate_metrics[agg_meta].combine!(stat)
172
- end
173
- end
174
194
  end
175
195
  end
176
196
 
@@ -19,6 +19,7 @@ module ScoutApm
19
19
  # As we go through a request, instrumentation can mark more general data into the Request
20
20
  # Known Keys:
21
21
  # :uri - the full URI requested by the user
22
+ # :queue_latency - how long a background Job spent in the queue before starting processing
22
23
  attr_reader :annotations
23
24
 
24
25
  # Nil until the request is finalized, at which point it will hold the
@@ -29,8 +30,20 @@ module ScoutApm
29
30
  # Can be nil if we never reach a Rails Controller
30
31
  attr_reader :headers
31
32
 
33
+ # What kind of request is this? A trace of a web request, or a background job?
34
+ # Use job! and web! to set, and job? and web? to query
35
+ attr_reader :request_type
36
+
37
+ # This maintains a lookup hash of Layer names and call counts. It's used to trigger fetching a backtrace on n+1 calls.
38
+ # Note that layer names might not be Strings - can alse be Utils::ActiveRecordMetricName. Also, this would fail for layers
39
+ # with same names across multiple types.
40
+ attr_accessor :call_counts
41
+
42
+ BACKTRACE_THRESHOLD = 0.5 # the minimum threshold in seconds to record the backtrace for a metric.
43
+
32
44
  def initialize
33
45
  @layers = []
46
+ @call_counts = Hash.new { |h, k| h[k] = CallSet.new }
34
47
  @annotations = {}
35
48
  @ignoring_children = false
36
49
  @context = Context.new
@@ -41,12 +54,11 @@ module ScoutApm
41
54
 
42
55
  def start_layer(layer)
43
56
  if ignoring_children?
44
- ScoutApm::Agent.instance.logger.info("Skipping layer because we're ignoring children: #{layer.inspect}")
45
57
  return
46
58
  end
47
59
 
48
60
  start_request(layer) unless @root_layer
49
-
61
+ update_call_counts!(layer)
50
62
  @layers[-1].add_child(layer) if @layers.any?
51
63
  @layers.push(layer)
52
64
  end
@@ -57,9 +69,8 @@ module ScoutApm
57
69
  layer = @layers.pop
58
70
  layer.record_stop_time!
59
71
 
60
- # Do this here, rather than in the layer because we need this caller. Maybe able to move it?
61
- if layer.total_exclusive_time > ScoutApm::SlowTransaction::BACKTRACE_THRESHOLD
62
- layer.store_backtrace(caller)
72
+ if capture_backtrace?(layer)
73
+ layer.capture_backtrace!
63
74
  end
64
75
 
65
76
  if finalized?
@@ -67,6 +78,32 @@ module ScoutApm
67
78
  end
68
79
  end
69
80
 
81
+ BACKTRACE_BLACKLIST = ["Controller", "Job"]
82
+ def capture_backtrace?(layer)
83
+ # Never capture backtraces for this kind of layer. The backtrace will
84
+ # always be 100% framework code.
85
+ return false if BACKTRACE_BLACKLIST.include?(layer.type)
86
+
87
+ # Only capture backtraces if we're in a real "request". Otherwise we
88
+ # can spend lot of time capturing backtraces from the internals of
89
+ # Sidekiq, only to throw them away immediately.
90
+ return false unless (web? || job?)
91
+
92
+ # Capture any individually slow layer.
93
+ return true if layer.total_exclusive_time > BACKTRACE_THRESHOLD
94
+
95
+ # Capture any layer that we've seen many times. Captures n+1 problems
96
+ return true if @call_counts[layer.name].capture_backtrace?
97
+
98
+ # Don't capture otherwise
99
+ false
100
+ end
101
+
102
+ # Maintains a lookup Hash of call counts by layer name. Used to determine if we should capture a backtrace.
103
+ def update_call_counts!(layer)
104
+ @call_counts[layer.name].update!(layer.desc)
105
+ end
106
+
70
107
  ###################################
71
108
  # Request Lifecycle
72
109
  ###################################
@@ -124,6 +161,22 @@ module ScoutApm
124
161
  @headers = headers
125
162
  end
126
163
 
164
+ def job!
165
+ @request_type = "job"
166
+ end
167
+
168
+ def job?
169
+ request_type == "job"
170
+ end
171
+
172
+ def web!
173
+ @request_type = "web"
174
+ end
175
+
176
+ def web?
177
+ request_type == "web"
178
+ end
179
+
127
180
  ###################################
128
181
  # Persist the Request
129
182
  ###################################
@@ -133,20 +186,24 @@ module ScoutApm
133
186
  def record!
134
187
  @recorded = true
135
188
 
136
- metrics = LayerMetricConverter.new(self).call
189
+ metrics = LayerConverters::MetricConverter.new(self).call
137
190
  ScoutApm::Agent.instance.store.track!(metrics)
138
191
 
139
- slow, slow_metrics = LayerSlowTransactionConverter.new(self).call
192
+ slow, slow_metrics = LayerConverters::SlowRequestConverter.new(self).call
140
193
  ScoutApm::Agent.instance.store.track_slow_transaction!(slow)
141
194
  ScoutApm::Agent.instance.store.track!(slow_metrics)
142
195
 
143
- error_metrics = LayerErrorConverter.new(self).call
196
+ error_metrics = LayerConverters::ErrorConverter.new(self).call
144
197
  ScoutApm::Agent.instance.store.track!(error_metrics)
145
198
 
146
- queue_time_metrics = RequestQueueTime.new(self).call
199
+ queue_time_metrics = LayerConverters::RequestQueueTimeConverter.new(self).call
147
200
  ScoutApm::Agent.instance.store.track!(queue_time_metrics)
148
201
 
149
- # ScoutApm::Agent.instance.logger.debug("Finished recording request") if metrics.any?
202
+ job = LayerConverters::JobConverter.new(self).call
203
+ ScoutApm::Agent.instance.store.track_job!(job)
204
+
205
+ slow_job = LayerConverters::SlowJobConverter.new(self).call
206
+ ScoutApm::Agent.instance.store.track_slow_job!(slow_job)
150
207
  end
151
208
 
152
209
  # Have we already persisted this request?
@@ -27,6 +27,19 @@ module ScoutApm
27
27
  end
28
28
  end
29
29
 
30
+ # For the layer lookup.
31
+ def hash
32
+ h = name.downcase.hash
33
+ h
34
+ end
35
+
36
+ # For the layer lookup.
37
+ # Reminder: #eql? is for Hash equality: returns true if obj and other refer to the same hash key.
38
+ def eql?(o)
39
+ self.class == o.class &&
40
+ name.downcase == o.name.downcase
41
+ end
42
+
30
43
  private
31
44
 
32
45
  def model
@@ -0,0 +1,31 @@
1
+ require 'scout_apm/environment'
2
+
3
+ # Removes actual values from SQL. Used to both obfuscate the SQL and group
4
+ # similar queries in the UI.
5
+ module ScoutApm
6
+ module Utils
7
+ class BacktraceParser
8
+
9
+ def initialize(call_stack)
10
+ @call_stack = call_stack
11
+ # We can't use a constant as it'd be too early to fetch environment info
12
+ @@app_dir_regex ||= /\A(#{ScoutApm::Environment.instance.root.to_s.gsub('/','\/')}\/)(app\/(.+))/.freeze
13
+ end
14
+
15
+ # Given a call stack Array, grabs the first call within the application root directory.
16
+ def call
17
+ # We used to return an array of up to 5 elements...this will return a single element-array for backwards compatibility.
18
+ # Only the first element is used in Github code display.
19
+ stack = []
20
+ @call_stack.each_with_index do |c,i|
21
+ if m = c.match(@@app_dir_regex)
22
+ stack << m[2]
23
+ break
24
+ end
25
+ end
26
+ stack
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -1,5 +1,5 @@
1
1
  # A fake implementation of stackprof, for systems that don't support it.
2
- module StackProf
2
+ class StackProf
3
3
  def self.start(*args)
4
4
  @running = true
5
5
  end
@@ -17,6 +17,7 @@ module ScoutApm
17
17
  def initialize(sql)
18
18
  @raw_sql = sql
19
19
  @database_engine = ScoutApm::Environment.instance.database_engine
20
+ @sanitized = false # only sanitize once.
20
21
  end
21
22
 
22
23
  def sql
@@ -24,6 +25,11 @@ module ScoutApm
24
25
  end
25
26
 
26
27
  def to_s
28
+ if @sanitized
29
+ sql
30
+ else
31
+ @sanitized = true
32
+ end
27
33
  case database_engine
28
34
  when :postgres then to_s_postgres
29
35
  when :mysql then to_s_mysql
@@ -1,4 +1,4 @@
1
1
  module ScoutApm
2
- VERSION = "1.4.6"
2
+ VERSION = "1.5.0.pre"
3
3
  end
4
4
 
data/lib/scout_apm.rb CHANGED
@@ -32,8 +32,16 @@ require 'scout_apm/version'
32
32
  require 'scout_apm/tracked_request'
33
33
  require 'scout_apm/layer'
34
34
  require 'scout_apm/request_manager'
35
- require 'scout_apm/layer_converter'
36
- require 'scout_apm/request_queue_time'
35
+ require 'scout_apm/call_set'
36
+
37
+ require 'scout_apm/layer_converters/converter_base'
38
+ require 'scout_apm/layer_converters/depth_first_walker'
39
+ require 'scout_apm/layer_converters/error_converter'
40
+ require 'scout_apm/layer_converters/job_converter'
41
+ require 'scout_apm/layer_converters/slow_job_converter'
42
+ require 'scout_apm/layer_converters/metric_converter'
43
+ require 'scout_apm/layer_converters/slow_request_converter'
44
+ require 'scout_apm/layer_converters/request_queue_time_converter'
37
45
 
38
46
  require 'scout_apm/server_integrations/passenger'
39
47
  require 'scout_apm/server_integrations/puma'
@@ -55,6 +63,8 @@ require 'scout_apm/platform_integrations/heroku'
55
63
  require 'scout_apm/platform_integrations/cloud_foundry'
56
64
  require 'scout_apm/platform_integrations/server'
57
65
 
66
+ require 'scout_apm/histogram'
67
+
58
68
  require 'scout_apm/deploy_integrations/capistrano_3'
59
69
  #require 'scout_apm/deploy_integrations/capistrano_2'
60
70
 
@@ -79,6 +89,7 @@ require 'scout_apm/instruments/process/process_memory'
79
89
  require 'scout_apm/app_server_load'
80
90
 
81
91
  require 'scout_apm/utils/sql_sanitizer'
92
+ require 'scout_apm/utils/backtrace_parser'
82
93
  require 'scout_apm/utils/active_record_metric_name'
83
94
  require 'scout_apm/utils/null_logger'
84
95
  require 'scout_apm/utils/installed_gems'
@@ -95,21 +106,30 @@ require 'scout_apm/layaway_file'
95
106
  require 'scout_apm/reporter'
96
107
  require 'scout_apm/background_worker'
97
108
  require 'scout_apm/bucket_name_splitter'
98
- require 'scout_apm/metric_meta'
99
- require 'scout_apm/metric_stats'
100
109
  require 'scout_apm/stack_item'
110
+ require 'scout_apm/metric_set'
101
111
  require 'scout_apm/store'
102
112
  require 'scout_apm/tracer'
103
113
  require 'scout_apm/context'
104
114
  require 'scout_apm/stackprof_tree_collapser'
115
+
116
+ require 'scout_apm/metric_meta'
117
+ require 'scout_apm/metric_stats'
105
118
  require 'scout_apm/slow_transaction'
119
+ require 'scout_apm/slow_job_record'
120
+ require 'scout_apm/slow_item_set'
106
121
  require 'scout_apm/slow_request_policy'
107
- require 'scout_apm/slow_transaction_set'
122
+ require 'scout_apm/slow_job_policy'
123
+ require 'scout_apm/job_record'
124
+
108
125
  require 'scout_apm/capacity'
109
126
  require 'scout_apm/attribute_arranger'
110
127
 
111
128
  require 'scout_apm/serializers/payload_serializer'
112
129
  require 'scout_apm/serializers/payload_serializer_to_json'
130
+ require 'scout_apm/serializers/jobs_serializer_to_json'
131
+ require 'scout_apm/serializers/slow_jobs_serializer_to_json'
132
+ require 'scout_apm/serializers/metrics_to_json_serializer'
113
133
  require 'scout_apm/serializers/directive_serializer'
114
134
  require 'scout_apm/serializers/app_server_load_serializer'
115
135
  require 'scout_apm/serializers/deploy_serializer'