scout_apm 3.0.0.pre1 → 3.0.0.pre2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.markdown +12 -1
- data/lib/scout_apm.rb +3 -0
- data/lib/scout_apm/agent.rb +9 -6
- data/lib/scout_apm/agent/reporting.rb +5 -3
- data/lib/scout_apm/background_job_integrations/delayed_job.rb +47 -1
- data/lib/scout_apm/background_worker.rb +4 -0
- data/lib/scout_apm/config.rb +2 -1
- data/lib/scout_apm/environment.rb +1 -1
- data/lib/scout_apm/histogram.rb +11 -2
- data/lib/scout_apm/instruments/mongoid.rb +14 -1
- data/lib/scout_apm/instruments/percentile_sampler.rb +36 -19
- data/lib/scout_apm/instruments/process/process_cpu.rb +3 -2
- data/lib/scout_apm/instruments/process/process_memory.rb +3 -3
- data/lib/scout_apm/layer_converters/converter_base.rb +213 -0
- data/lib/scout_apm/layer_converters/slow_job_converter.rb +19 -93
- data/lib/scout_apm/layer_converters/slow_request_converter.rb +15 -100
- data/lib/scout_apm/metric_set.rb +6 -0
- data/lib/scout_apm/reporter.rb +53 -15
- data/lib/scout_apm/request_histograms.rb +4 -0
- data/lib/scout_apm/scored_item_set.rb +7 -0
- data/lib/scout_apm/serializers/histograms_serializer_to_json.rb +21 -0
- data/lib/scout_apm/serializers/payload_serializer.rb +9 -3
- data/lib/scout_apm/serializers/payload_serializer_to_json.rb +2 -1
- data/lib/scout_apm/serializers/slow_jobs_serializer_to_json.rb +1 -1
- data/lib/scout_apm/slow_job_record.rb +4 -1
- data/lib/scout_apm/slow_transaction.rb +18 -2
- data/lib/scout_apm/store.rb +42 -11
- data/lib/scout_apm/tracked_request.rb +1 -1
- data/lib/scout_apm/utils/gzip_helper.rb +24 -0
- data/lib/scout_apm/utils/numbers.rb +14 -0
- data/lib/scout_apm/version.rb +2 -2
- data/test/test_helper.rb +10 -0
- data/test/unit/config_test.rb +7 -9
- data/test/unit/histogram_test.rb +14 -0
- data/test/unit/instruments/percentile_sampler_test.rb +137 -0
- data/test/unit/serializers/payload_serializer_test.rb +3 -3
- data/test/unit/store_test.rb +51 -0
- data/test/unit/utils/numbers_test.rb +15 -0
- metadata +10 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 83b62021065f23753ffcd91484e74ccd6dbf2058
|
4
|
+
data.tar.gz: 7163784d52c0547588f4cda9247eca9a634d29d1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f711a6661dee811a41f5a92845f004288ca6eb931b0d49f5bf691d6f1fe0fd9b845d2292519c62e6137cfc49386cf77b43add139381531c6c9b351484e208ac8
|
7
|
+
data.tar.gz: d23b7ba32077adc95a23abead06bcbe8d452c2ff845c49bd2ad376ae069feadaed5836ad6c1bd46a075300962c2812d14109c2526804d606baf8d41dc581afd5
|
data/CHANGELOG.markdown
CHANGED
@@ -1,7 +1,19 @@
|
|
1
|
+
# Dev
|
2
|
+
|
3
|
+
* Adds Git revision detection, which is reported on app load and associated with transaction traces
|
4
|
+
|
1
5
|
# 3.0.0
|
2
6
|
|
3
7
|
* ScoutProf BETA
|
4
8
|
|
9
|
+
# 2.1.9
|
10
|
+
|
11
|
+
* Send raw histograms of response time, enabling more accurate 95th %iles
|
12
|
+
* Gzip payloads
|
13
|
+
* Fix Mongoid (5.0) + Mongo (2.1) support
|
14
|
+
* Initial Delayed Job support
|
15
|
+
* Limit max metric size of a trace to 500.
|
16
|
+
|
5
17
|
# 2.1.8
|
6
18
|
|
7
19
|
* Adds Git revision detection, which is reported on app load and associated with transaction traces
|
@@ -9,7 +21,6 @@
|
|
9
21
|
# 2.1.7
|
10
22
|
|
11
23
|
* Fix allocations extension compilation on Ruby 1.8.7
|
12
|
-
>>>>>>> master
|
13
24
|
|
14
25
|
# 2.1.6
|
15
26
|
|
data/lib/scout_apm.rb
CHANGED
@@ -101,6 +101,8 @@ require 'scout_apm/utils/null_logger'
|
|
101
101
|
require 'scout_apm/utils/sql_sanitizer'
|
102
102
|
require 'scout_apm/utils/time'
|
103
103
|
require 'scout_apm/utils/unique_id'
|
104
|
+
require 'scout_apm/utils/numbers'
|
105
|
+
require 'scout_apm/utils/gzip_helper'
|
104
106
|
|
105
107
|
require 'scout_apm/config'
|
106
108
|
require 'scout_apm/environment'
|
@@ -140,6 +142,7 @@ require 'scout_apm/serializers/payload_serializer_to_json'
|
|
140
142
|
require 'scout_apm/serializers/jobs_serializer_to_json'
|
141
143
|
require 'scout_apm/serializers/slow_jobs_serializer_to_json'
|
142
144
|
require 'scout_apm/serializers/metrics_to_json_serializer'
|
145
|
+
require 'scout_apm/serializers/histograms_serializer_to_json'
|
143
146
|
require 'scout_apm/serializers/directive_serializer'
|
144
147
|
require 'scout_apm/serializers/app_server_load_serializer'
|
145
148
|
|
data/lib/scout_apm/agent.rb
CHANGED
@@ -129,7 +129,7 @@ module ScoutApm
|
|
129
129
|
|
130
130
|
[ ScoutApm::Instruments::Process::ProcessCpu.new(environment.processors, logger),
|
131
131
|
ScoutApm::Instruments::Process::ProcessMemory.new(logger),
|
132
|
-
ScoutApm::Instruments::PercentileSampler.new(logger,
|
132
|
+
ScoutApm::Instruments::PercentileSampler.new(logger, request_histograms_by_time),
|
133
133
|
].each { |s| store.add_sampler(s) }
|
134
134
|
|
135
135
|
app_server_load_hook
|
@@ -231,7 +231,10 @@ module ScoutApm
|
|
231
231
|
end
|
232
232
|
|
233
233
|
def background_worker_running?
|
234
|
-
|
234
|
+
@background_worker_thread &&
|
235
|
+
@background_worker_thread.alive? &&
|
236
|
+
@background_worker &&
|
237
|
+
@background_worker.running?
|
235
238
|
end
|
236
239
|
|
237
240
|
# Creates the worker thread. The worker thread is a loop that runs continuously. It sleeps for +Agent#period+ and when it wakes,
|
@@ -279,10 +282,10 @@ module ScoutApm
|
|
279
282
|
# Loads the instrumention logic.
|
280
283
|
def load_instruments
|
281
284
|
if !background_job_missing?
|
282
|
-
case environment.background_job_name
|
283
|
-
when :delayed_job
|
284
|
-
install_instrument(ScoutApm::Instruments::DelayedJob)
|
285
|
-
end
|
285
|
+
# case environment.background_job_name
|
286
|
+
# when :delayed_job
|
287
|
+
# install_instrument(ScoutApm::Instruments::DelayedJob)
|
288
|
+
# end
|
286
289
|
else
|
287
290
|
case environment.framework
|
288
291
|
when :rails then install_instrument(ScoutApm::Instruments::ActionControllerRails2)
|
@@ -58,6 +58,7 @@ module ScoutApm
|
|
58
58
|
slow_transactions = reporting_period.slow_transactions_payload
|
59
59
|
jobs = reporting_period.jobs
|
60
60
|
slow_jobs = reporting_period.slow_jobs_payload
|
61
|
+
histograms = reporting_period.histograms
|
61
62
|
|
62
63
|
metadata = {
|
63
64
|
:app_root => ScoutApm::Environment.instance.root.to_s,
|
@@ -68,9 +69,9 @@ module ScoutApm
|
|
68
69
|
:platform => "ruby",
|
69
70
|
}
|
70
71
|
|
71
|
-
log_deliver(metrics, slow_transactions, metadata, slow_jobs)
|
72
|
+
log_deliver(metrics, slow_transactions, metadata, slow_jobs, histograms)
|
72
73
|
|
73
|
-
payload = ScoutApm::Serializers::PayloadSerializer.serialize(metadata, metrics, slow_transactions, jobs, slow_jobs)
|
74
|
+
payload = ScoutApm::Serializers::PayloadSerializer.serialize(metadata, metrics, slow_transactions, jobs, slow_jobs, histograms)
|
74
75
|
# logger.debug("Payload: #{payload}")
|
75
76
|
|
76
77
|
reporter.report(payload, headers)
|
@@ -80,7 +81,7 @@ module ScoutApm
|
|
80
81
|
logger.debug e.backtrace
|
81
82
|
end
|
82
83
|
|
83
|
-
def log_deliver(metrics, slow_transactions, metadata, jobs_traces)
|
84
|
+
def log_deliver(metrics, slow_transactions, metadata, jobs_traces, histograms)
|
84
85
|
total_request_count = metrics.
|
85
86
|
select { |meta,stats| meta.metric_name =~ /\AController/ }.
|
86
87
|
inject(0) {|sum, (_, stat)| sum + stat.call_count }
|
@@ -97,6 +98,7 @@ module ScoutApm
|
|
97
98
|
metrics_clause = "#{metrics.length} Metrics for #{total_request_count} requests"
|
98
99
|
slow_trans_clause = "#{slow_transactions.length} Slow Transaction Traces"
|
99
100
|
job_clause = "#{jobs_traces.length} Job Traces"
|
101
|
+
histogram_clause = "#{histograms.length} Histograms"
|
100
102
|
|
101
103
|
logger.info "#{time_clause} Delivering #{metrics_clause} and #{slow_trans_clause} and #{job_clause}, #{process_log_str}."
|
102
104
|
# logger.debug("Metrics: #{metrics.pretty_inspect}\nSlowTrans: #{slow_transactions.pretty_inspect}\nMetadata: #{metadata.inspect.pretty_inspect}")
|
@@ -8,12 +8,58 @@ module ScoutApm
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def present?
|
11
|
-
defined?(::Delayed::Job) &&
|
11
|
+
defined?(::Delayed::Job) && File.basename($PROGRAM_NAME).start_with?('delayed_job')
|
12
12
|
end
|
13
13
|
|
14
14
|
def forking?
|
15
15
|
false
|
16
16
|
end
|
17
|
+
|
18
|
+
def install
|
19
|
+
plugin = Class.new(Delayed::Plugin) do
|
20
|
+
require 'delayed_job'
|
21
|
+
|
22
|
+
callbacks do |lifecycle|
|
23
|
+
lifecycle.around(:invoke_job) do |job, *args, &block|
|
24
|
+
ScoutApm::Agent.instance.start_background_worker unless ScoutApm::Agent.instance.background_worker_running?
|
25
|
+
|
26
|
+
name = job.name
|
27
|
+
queue = job.queue || "default"
|
28
|
+
|
29
|
+
req = ScoutApm::RequestManager.lookup
|
30
|
+
req.job!
|
31
|
+
|
32
|
+
begin
|
33
|
+
latency = Time.now - job.created_at
|
34
|
+
req.annotate_request(:queue_latency => latency)
|
35
|
+
rescue
|
36
|
+
end
|
37
|
+
|
38
|
+
queue_layer = ScoutApm::Layer.new('Queue', queue)
|
39
|
+
job_layer = ScoutApm::Layer.new('Job', name)
|
40
|
+
|
41
|
+
begin
|
42
|
+
req.start_layer(queue_layer)
|
43
|
+
started_queue = true
|
44
|
+
req.start_layer(job_layer)
|
45
|
+
started_job = true
|
46
|
+
|
47
|
+
# Call the job itself.
|
48
|
+
block.call(job, *args)
|
49
|
+
rescue
|
50
|
+
req.error!
|
51
|
+
raise
|
52
|
+
ensure
|
53
|
+
req.stop_layer if started_job
|
54
|
+
req.stop_layer if started_queue
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
Delayed::Worker.plugins << plugin # ScoutApm::BackgroundJobIntegrations::DelayedJobPlugin
|
61
|
+
end
|
17
62
|
end
|
18
63
|
end
|
19
64
|
end
|
65
|
+
|
data/lib/scout_apm/config.rb
CHANGED
data/lib/scout_apm/histogram.rb
CHANGED
@@ -90,7 +90,11 @@ module ScoutApm
|
|
90
90
|
def combine!(other)
|
91
91
|
mutex.synchronize do
|
92
92
|
other.mutex.synchronize do
|
93
|
-
@bins = (other.bins + @bins).
|
93
|
+
@bins = (other.bins + @bins).
|
94
|
+
group_by {|b| b.value }.
|
95
|
+
map {|val, bs| [val, bs.inject(0) {|sum, b| sum + b.count }] }.
|
96
|
+
map {|val, sum| HistogramBin.new(val,sum) }.
|
97
|
+
sort_by { |b| b.value }
|
94
98
|
@total += other.total
|
95
99
|
trim
|
96
100
|
self
|
@@ -100,7 +104,12 @@ module ScoutApm
|
|
100
104
|
|
101
105
|
def as_json
|
102
106
|
mutex.synchronize do
|
103
|
-
bins.map{|b|
|
107
|
+
bins.map{ |b|
|
108
|
+
[
|
109
|
+
ScoutApm::Utils::Numbers.round(b.value, 4),
|
110
|
+
b.count
|
111
|
+
]
|
112
|
+
}
|
104
113
|
end
|
105
114
|
end
|
106
115
|
|
@@ -50,11 +50,24 @@ module ScoutApm
|
|
50
50
|
with_scout_instruments = %Q[
|
51
51
|
def #{method}_with_scout_instruments(*args, &block)
|
52
52
|
|
53
|
+
|
53
54
|
req = ScoutApm::RequestManager.lookup
|
54
55
|
*db, collection = view.collection.namespace.split(".")
|
55
56
|
|
56
57
|
name = collection + "/#{method}"
|
57
|
-
|
58
|
+
|
59
|
+
# Between Mongo gem version 2.1 and 2.3, this method name was
|
60
|
+
# changed. Accomodate both. If for some reason neither is
|
61
|
+
# there, try to continue with an empty "filter" hash.
|
62
|
+
raw_filter = if view.respond_to?(:selector)
|
63
|
+
view.selector
|
64
|
+
elsif view.respond_to?(:filter)
|
65
|
+
view.filter
|
66
|
+
else
|
67
|
+
{}
|
68
|
+
end
|
69
|
+
|
70
|
+
filter = ScoutApm::Instruments::Mongoid.anonymize_filter(raw_filter)
|
58
71
|
|
59
72
|
layer = ScoutApm::Layer.new("MongoDB", name)
|
60
73
|
layer.desc = filter.inspect
|
@@ -1,36 +1,53 @@
|
|
1
1
|
module ScoutApm
|
2
2
|
module Instruments
|
3
|
+
|
4
|
+
class HistogramReport
|
5
|
+
attr_reader :name
|
6
|
+
attr_reader :histogram
|
7
|
+
|
8
|
+
def initialize(name, histogram)
|
9
|
+
@name = name
|
10
|
+
@histogram = histogram
|
11
|
+
end
|
12
|
+
|
13
|
+
def combine!(other)
|
14
|
+
raise "Mismatched Histogram Names" unless name == other.name
|
15
|
+
histogram.combine!(other.histogram)
|
16
|
+
self
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
3
20
|
class PercentileSampler
|
4
21
|
attr_reader :logger
|
5
22
|
|
6
|
-
|
23
|
+
# A hash of { time => RequestHistograms }
|
24
|
+
attr_reader :histograms
|
7
25
|
|
8
|
-
def initialize(logger,
|
26
|
+
def initialize(logger, histograms)
|
9
27
|
@logger = logger
|
10
|
-
@
|
28
|
+
@histograms = histograms
|
11
29
|
end
|
12
30
|
|
13
31
|
def human_name
|
14
|
-
|
32
|
+
'Percentiles'
|
15
33
|
end
|
16
34
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
meta = MetricMeta.new("Percentile/#{percentile}/#{name}")
|
24
|
-
stat = MetricStats.new
|
25
|
-
stat.update!(histos.quantile(name, percentile))
|
26
|
-
ms[meta] = stat
|
27
|
-
end
|
28
|
-
end
|
35
|
+
def metrics(timestamp, store)
|
36
|
+
store.track_histograms!(percentiles(timestamp), :timestamp => timestamp)
|
37
|
+
end
|
38
|
+
|
39
|
+
def percentiles(time)
|
40
|
+
result = []
|
29
41
|
|
30
|
-
|
31
|
-
|
42
|
+
histogram = histograms.delete(time)
|
43
|
+
|
44
|
+
return result unless histogram
|
45
|
+
|
46
|
+
histogram.each_name do |name|
|
47
|
+
result << HistogramReport.new(name, histogram.raw(name))
|
48
|
+
end
|
32
49
|
|
33
|
-
|
50
|
+
result
|
34
51
|
end
|
35
52
|
end
|
36
53
|
end
|
@@ -29,16 +29,17 @@ module ScoutApm
|
|
29
29
|
"Process CPU"
|
30
30
|
end
|
31
31
|
|
32
|
-
def metrics(
|
32
|
+
def metrics(timestamp, store)
|
33
33
|
result = run
|
34
34
|
if result
|
35
35
|
meta = MetricMeta.new("#{metric_type}/#{metric_name}")
|
36
36
|
stat = MetricStats.new(false)
|
37
37
|
stat.update!(result)
|
38
|
-
{ meta => stat }
|
38
|
+
store.track!({ meta => stat }, :timestamp => timestamp)
|
39
39
|
else
|
40
40
|
{}
|
41
41
|
end
|
42
|
+
|
42
43
|
end
|
43
44
|
|
44
45
|
# TODO: Figure out a good default instead of nil
|
@@ -33,20 +33,20 @@ module ScoutApm
|
|
33
33
|
"Process Memory"
|
34
34
|
end
|
35
35
|
|
36
|
-
def metrics(
|
36
|
+
def metrics(timestamp, store)
|
37
37
|
result = run
|
38
38
|
if result
|
39
39
|
meta = MetricMeta.new("#{metric_type}/#{metric_name}")
|
40
40
|
stat = MetricStats.new(false)
|
41
41
|
stat.update!(result)
|
42
|
-
{ meta => stat }
|
42
|
+
store.track!({ meta => stat }, :timestamp => timestamp)
|
43
43
|
else
|
44
44
|
{}
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
48
|
def run
|
49
|
-
self.class.rss_in_mb.tap { |res| logger.debug "#{human_name}: #{res.inspect}" }
|
49
|
+
self.class.rss_in_mb.tap { |res| logger.debug "#{human_name}: #{res.inspect}" }
|
50
50
|
end
|
51
51
|
end
|
52
52
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module ScoutApm
|
2
2
|
module LayerConverters
|
3
3
|
class ConverterBase
|
4
|
+
|
4
5
|
attr_reader :walker
|
5
6
|
attr_reader :request
|
6
7
|
attr_reader :root_layer
|
@@ -8,7 +9,10 @@ module ScoutApm
|
|
8
9
|
def initialize(request)
|
9
10
|
@request = request
|
10
11
|
@root_layer = request.root_layer
|
12
|
+
@backtraces = []
|
11
13
|
@walker = DepthFirstWalker.new(root_layer)
|
14
|
+
|
15
|
+
@limited = false
|
12
16
|
end
|
13
17
|
|
14
18
|
# Scope is determined by the first Controller we hit. Most of the time
|
@@ -27,6 +31,215 @@ module ScoutApm
|
|
27
31
|
return layer if layer.type == layer_type
|
28
32
|
end
|
29
33
|
end
|
34
|
+
|
35
|
+
################################################################################
|
36
|
+
# Subscoping
|
37
|
+
################################################################################
|
38
|
+
#
|
39
|
+
# Keep a list of subscopes, but only ever use the front one. The rest
|
40
|
+
# get pushed/popped in cases when we have many levels of subscopable
|
41
|
+
# layers. This lets us push/pop without otherwise keeping track very closely.
|
42
|
+
def setup_subscopable_callbacks
|
43
|
+
@subscope_layers = []
|
44
|
+
|
45
|
+
walker.before do |layer|
|
46
|
+
if layer.subscopable?
|
47
|
+
@subscope_layers.push(layer)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
walker.after do |layer|
|
52
|
+
if layer.subscopable?
|
53
|
+
@subscope_layers.pop
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def subscoped?(layer)
|
59
|
+
@subscope_layers.first && layer != @subscope_layers.first # Don't scope under ourself.
|
60
|
+
end
|
61
|
+
|
62
|
+
def subscope_name
|
63
|
+
@subscope_layers.first.legacy_metric_name
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
################################################################################
|
68
|
+
# Backtrace Handling
|
69
|
+
################################################################################
|
70
|
+
#
|
71
|
+
# Because we get several layers for the same thing if you call an
|
72
|
+
# instrumented thing repeatedly, and only some of them may have
|
73
|
+
# backtraces captured, we store the backtraces off into another spot
|
74
|
+
# during processing, then at the end, we loop over those saved
|
75
|
+
# backtraces, putting them back into the metrics hash.
|
76
|
+
#
|
77
|
+
# This comes up most often when capturing n+1 backtraces. Because the
|
78
|
+
# query may be fast enough to evade our time-limit based backtrace
|
79
|
+
# capture, only the Nth item (see TrackedRequest for more detail) has a
|
80
|
+
# backtrack captured. This sequence makes sure that we report up that
|
81
|
+
# backtrace in the aggregated set of metrics around that call.
|
82
|
+
|
83
|
+
# Call this as you are processing each layer. It will store off backtraces
|
84
|
+
def store_backtrace(layer, meta)
|
85
|
+
return unless layer.backtrace
|
86
|
+
|
87
|
+
bt = ScoutApm::Utils::BacktraceParser.new(layer.backtrace).call
|
88
|
+
if bt.any?
|
89
|
+
meta.backtrace = bt
|
90
|
+
@backtraces << meta
|
91
|
+
else
|
92
|
+
ScoutApm::Agent.instance.logger.debug { "Unable to capture an app-specific backtrace for #{meta.inspect}\n#{layer.backtrace}" }
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Call this after you finish walking the layers, and want to take the
|
97
|
+
# set-aside backtraces and place them into the metas they match
|
98
|
+
def attach_backtraces(metric_hash)
|
99
|
+
@backtraces.each do |meta_with_backtrace|
|
100
|
+
metric_hash.keys.find { |k| k == meta_with_backtrace }.backtrace = meta_with_backtrace.backtrace
|
101
|
+
end
|
102
|
+
metric_hash
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
################################################################################
|
107
|
+
# Limit Handling
|
108
|
+
################################################################################
|
109
|
+
|
110
|
+
# To prevent huge traces from being generated, we should stop collecting
|
111
|
+
# detailed metrics as we go beyond some reasonably large count.
|
112
|
+
#
|
113
|
+
# We should still add up the /all aggregates.
|
114
|
+
|
115
|
+
MAX_METRICS = 500
|
116
|
+
|
117
|
+
def over_metric_limit?(metric_hash)
|
118
|
+
if metric_hash.size > MAX_METRICS
|
119
|
+
@limited = true
|
120
|
+
else
|
121
|
+
false
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def limited?
|
126
|
+
!! @limited
|
127
|
+
end
|
128
|
+
|
129
|
+
################################################################################
|
130
|
+
# Meta Scope
|
131
|
+
################################################################################
|
132
|
+
|
133
|
+
# When we make MetricMeta records, we need to determine a few things from layer.
|
134
|
+
def make_meta_options(layer)
|
135
|
+
scope_hash = make_meta_options_scope(layer)
|
136
|
+
desc_hash = make_meta_options_desc_hash(layer)
|
137
|
+
|
138
|
+
scope_hash.merge(desc_hash)
|
139
|
+
end
|
140
|
+
|
141
|
+
def make_meta_options_scope(layer)
|
142
|
+
# This layer is scoped under another thing. Typically that means this is a layer under a view.
|
143
|
+
# Like: Controller -> View/users/show -> ActiveRecord/user/find
|
144
|
+
# in that example, the scope is the View/users/show
|
145
|
+
if subscoped?(layer)
|
146
|
+
{:scope => subscope_name}
|
147
|
+
|
148
|
+
# We don't scope the controller under itself
|
149
|
+
elsif layer == scope_layer
|
150
|
+
{}
|
151
|
+
|
152
|
+
# This layer is a top level metric ("ActiveRecord", or "HTTP" or
|
153
|
+
# whatever, directly under the controller), so scope to the
|
154
|
+
# Controller
|
155
|
+
else
|
156
|
+
{:scope => scope_layer.legacy_metric_name}
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def make_meta_options_desc_hash(layer)
|
161
|
+
if layer.desc
|
162
|
+
{:desc => layer.desc.to_s}
|
163
|
+
else
|
164
|
+
{}
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
################################################################################
|
170
|
+
# Storing metrics into the hashes
|
171
|
+
################################################################################
|
172
|
+
|
173
|
+
# This is the detailed metric - type, name, backtrace, annotations, etc.
|
174
|
+
def store_specific_metric(layer, metric_hash, allocation_metric_hash)
|
175
|
+
return false if over_metric_limit?(metric_hash)
|
176
|
+
|
177
|
+
meta_options = make_meta_options(layer)
|
178
|
+
|
179
|
+
meta = MetricMeta.new(layer.legacy_metric_name, meta_options)
|
180
|
+
meta.extra.merge!(layer.annotations)
|
181
|
+
|
182
|
+
store_backtrace(layer, meta)
|
183
|
+
|
184
|
+
metric_hash[meta] ||= MetricStats.new(meta_options.has_key?(:scope))
|
185
|
+
allocation_metric_hash[meta] ||= MetricStats.new(meta_options.has_key?(:scope))
|
186
|
+
|
187
|
+
# Timing
|
188
|
+
timing_stat = metric_hash[meta]
|
189
|
+
timing_stat.update!(layer.total_call_time, layer.total_exclusive_time)
|
190
|
+
|
191
|
+
# Allocations
|
192
|
+
allocation_stat = allocation_metric_hash[meta]
|
193
|
+
allocation_stat.update!(layer.total_allocations, layer.total_exclusive_allocations)
|
194
|
+
|
195
|
+
# Attach Scoutprof Traces
|
196
|
+
timing_stat.add_traces(layer.traces.as_json)
|
197
|
+
end
|
198
|
+
|
199
|
+
# Merged Metric - no specifics, just sum up by type (ActiveRecord, View, HTTP, etc)
|
200
|
+
def store_aggregate_metric(layer, metric_hash, allocation_metric_hash)
|
201
|
+
meta = MetricMeta.new("#{layer.type}/all")
|
202
|
+
|
203
|
+
metric_hash[meta] ||= MetricStats.new(false)
|
204
|
+
allocation_metric_hash[meta] ||= MetricStats.new(false)
|
205
|
+
|
206
|
+
# timing
|
207
|
+
timing_stat = metric_hash[meta]
|
208
|
+
timing_stat.update!(layer.total_call_time, layer.total_exclusive_time)
|
209
|
+
|
210
|
+
# allocations
|
211
|
+
allocation_stat = allocation_metric_hash[meta]
|
212
|
+
allocation_stat.update!(layer.total_allocations, layer.total_exclusive_allocations)
|
213
|
+
end
|
214
|
+
|
215
|
+
################################################################################
|
216
|
+
# Misc Helpers
|
217
|
+
################################################################################
|
218
|
+
|
219
|
+
# Sometimes we start capturing a layer without knowing if we really
|
220
|
+
# want to make an entry for it. See ActiveRecord instrumentation for
|
221
|
+
# an example. We start capturing before we know if a query is cached
|
222
|
+
# or not, and want to skip any cached queries.
|
223
|
+
def skip_layer?(layer)
|
224
|
+
return true if layer.annotations[:ignorable]
|
225
|
+
end
|
226
|
+
|
227
|
+
# Debug logging for scoutprof traces
|
228
|
+
def debug_scoutprof(layer)
|
229
|
+
agent = ScoutApm::Agent.instance
|
230
|
+
config = agent.config
|
231
|
+
|
232
|
+
if layer.type =~ %r{^(Controller|Queue|Job)$}.freeze
|
233
|
+
if config.value('profile')
|
234
|
+
agent.logger.debug do
|
235
|
+
traces_inspect = layer.traces.inspect
|
236
|
+
"****** Slow Request #{layer.type} Traces (#{layer.name}, tet: #{layer.total_exclusive_time}, tct: #{layer.total_call_time}), total raw traces: #{layer.traces.cube.total_count}, total clean traces: #{layer.traces.total_count}, skipped gc: #{layer.traces.skipped_in_gc}, skipped handler: #{layer.traces.skipped_in_handler}, skipped registered #{layer.traces.skipped_in_job_registered}, skipped not_running #{layer.traces.skipped_in_not_running}:\n#{traces_inspect}"
|
237
|
+
end
|
238
|
+
else
|
239
|
+
agent.logger.debug "****** Slow Request #{layer.type} Traces: Scoutprof is not enabled"
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
30
243
|
end
|
31
244
|
end
|
32
245
|
end
|