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.
- checksums.yaml +4 -4
- data/CHANGELOG.markdown +9 -0
- data/lib/scout_apm/agent/reporting.rb +8 -6
- data/lib/scout_apm/agent.rb +10 -6
- data/lib/scout_apm/background_job_integrations/sidekiq.rb +23 -11
- data/lib/scout_apm/call_set.rb +61 -0
- data/lib/scout_apm/config.rb +2 -1
- data/lib/scout_apm/environment.rb +12 -7
- data/lib/scout_apm/histogram.rb +124 -0
- data/lib/scout_apm/instruments/.DS_Store +0 -0
- data/lib/scout_apm/instruments/action_controller_rails_2.rb +1 -0
- data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +1 -0
- data/lib/scout_apm/instruments/delayed_job.rb +1 -0
- data/lib/scout_apm/instruments/process/process_memory.rb +1 -1
- data/lib/scout_apm/instruments/sinatra.rb +1 -1
- data/lib/scout_apm/job_record.rb +76 -0
- data/lib/scout_apm/layaway.rb +4 -1
- data/lib/scout_apm/layaway_file.rb +4 -4
- data/lib/scout_apm/layer.rb +14 -4
- data/lib/scout_apm/layer_converters/converter_base.rb +30 -0
- data/lib/scout_apm/layer_converters/depth_first_walker.rb +36 -0
- data/lib/scout_apm/layer_converters/error_converter.rb +20 -0
- data/lib/scout_apm/layer_converters/job_converter.rb +84 -0
- data/lib/scout_apm/layer_converters/metric_converter.rb +45 -0
- data/lib/scout_apm/layer_converters/request_queue_time_converter.rb +60 -0
- data/lib/scout_apm/layer_converters/slow_job_converter.rb +88 -0
- data/lib/scout_apm/layer_converters/slow_request_converter.rb +111 -0
- data/lib/scout_apm/metric_meta.rb +9 -0
- data/lib/scout_apm/metric_set.rb +44 -0
- data/lib/scout_apm/reporter.rb +12 -5
- data/lib/scout_apm/serializers/jobs_serializer_to_json.rb +28 -0
- data/lib/scout_apm/serializers/metrics_to_json_serializer.rb +54 -0
- data/lib/scout_apm/serializers/payload_serializer.rb +5 -3
- data/lib/scout_apm/serializers/payload_serializer_to_json.rb +9 -4
- data/lib/scout_apm/serializers/slow_jobs_serializer_to_json.rb +29 -0
- data/lib/scout_apm/slow_item_set.rb +80 -0
- data/lib/scout_apm/slow_job_policy.rb +29 -0
- data/lib/scout_apm/slow_job_record.rb +33 -0
- data/lib/scout_apm/slow_transaction.rb +0 -22
- data/lib/scout_apm/stackprof_tree_collapser.rb +7 -8
- data/lib/scout_apm/store.rb +55 -35
- data/lib/scout_apm/tracked_request.rb +67 -10
- data/lib/scout_apm/utils/active_record_metric_name.rb +13 -0
- data/lib/scout_apm/utils/backtrace_parser.rb +31 -0
- data/lib/scout_apm/utils/fake_stack_prof.rb +1 -1
- data/lib/scout_apm/utils/sql_sanitizer.rb +6 -0
- data/lib/scout_apm/version.rb +1 -1
- data/lib/scout_apm.rb +25 -5
- data/test/unit/histogram_test.rb +93 -0
- data/test/unit/serializers/payload_serializer_test.rb +5 -5
- data/test/unit/{slow_transaction_set_test.rb → slow_item_set_test.rb} +8 -8
- data/test/unit/slow_job_policy_test.rb +55 -0
- metadata +30 -9
- data/lib/scout_apm/layer_converter.rb +0 -222
- data/lib/scout_apm/request_queue_time.rb +0 -57
- 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
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
data/lib/scout_apm/store.rb
CHANGED
@@ -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
|
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 =
|
107
|
-
@
|
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
|
-
|
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
|
-
|
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
|
-
|
170
|
+
metric_set.metrics
|
131
171
|
end
|
132
172
|
|
133
173
|
def slow_transactions_payload
|
134
|
-
|
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
|
-
|
61
|
-
|
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 =
|
189
|
+
metrics = LayerConverters::MetricConverter.new(self).call
|
137
190
|
ScoutApm::Agent.instance.store.track!(metrics)
|
138
191
|
|
139
|
-
slow, slow_metrics =
|
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 =
|
196
|
+
error_metrics = LayerConverters::ErrorConverter.new(self).call
|
144
197
|
ScoutApm::Agent.instance.store.track!(error_metrics)
|
145
198
|
|
146
|
-
queue_time_metrics =
|
199
|
+
queue_time_metrics = LayerConverters::RequestQueueTimeConverter.new(self).call
|
147
200
|
ScoutApm::Agent.instance.store.track!(queue_time_metrics)
|
148
201
|
|
149
|
-
|
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
|
@@ -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
|
data/lib/scout_apm/version.rb
CHANGED
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/
|
36
|
-
|
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/
|
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'
|