scout_apm 1.4.6 → 1.5.0.pre

Sign up to get free protection for your applications and to get access to all the features.
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'