scout_apm 3.0.0.pre1 → 3.0.0.pre2
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 +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
|