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.
- 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'
|