scout_apm 2.1.32 → 2.2.0.pre0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/CHANGELOG.markdown +2 -161
- data/Rakefile +2 -2
- data/ext/allocations/allocations.c +0 -6
- data/ext/allocations/extconf.rb +0 -1
- data/ext/stacks/extconf.rb +33 -0
- data/ext/stacks/scout_atomics.h +86 -0
- data/ext/stacks/stacks.c +744 -0
- data/lib/scout_apm.rb +16 -24
- data/lib/scout_apm/agent.rb +38 -93
- data/lib/scout_apm/agent/logging.rb +1 -6
- data/lib/scout_apm/agent/reporting.rb +6 -8
- data/lib/scout_apm/app_server_load.rb +10 -21
- data/lib/scout_apm/attribute_arranger.rb +2 -0
- data/lib/scout_apm/background_job_integrations/delayed_job.rb +1 -71
- data/lib/scout_apm/background_job_integrations/sidekiq.rb +27 -66
- data/lib/scout_apm/background_worker.rb +15 -19
- data/lib/scout_apm/capacity.rb +57 -0
- data/lib/scout_apm/config.rb +29 -135
- data/lib/scout_apm/context.rb +5 -9
- data/lib/scout_apm/deploy_integrations/capistrano_2.cap +12 -0
- data/lib/scout_apm/deploy_integrations/capistrano_2.rb +83 -0
- data/lib/scout_apm/deploy_integrations/capistrano_3.cap +12 -0
- data/lib/scout_apm/deploy_integrations/capistrano_3.rb +88 -0
- data/lib/scout_apm/environment.rb +15 -22
- data/lib/scout_apm/histogram.rb +2 -11
- data/lib/scout_apm/instant/assets/xmlhttp_instrumentation.html +2 -2
- data/lib/scout_apm/instant/middleware.rb +57 -198
- data/lib/scout_apm/instruments/action_controller_rails_2.rb +2 -1
- data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +59 -90
- data/lib/scout_apm/instruments/active_record.rb +5 -7
- data/lib/scout_apm/instruments/delayed_job.rb +57 -0
- data/lib/scout_apm/instruments/grape.rb +3 -4
- data/lib/scout_apm/instruments/middleware_detailed.rb +6 -4
- data/lib/scout_apm/instruments/middleware_summary.rb +1 -39
- data/lib/scout_apm/instruments/mongoid.rb +3 -24
- data/lib/scout_apm/instruments/net_http.rb +2 -7
- data/lib/scout_apm/instruments/percentile_sampler.rb +19 -36
- data/lib/scout_apm/instruments/process/process_cpu.rb +2 -3
- data/lib/scout_apm/instruments/process/process_memory.rb +3 -3
- data/lib/scout_apm/layaway.rb +33 -76
- data/lib/scout_apm/layer.rb +59 -16
- data/lib/scout_apm/layer_converters/converter_base.rb +0 -199
- data/lib/scout_apm/layer_converters/job_converter.rb +1 -1
- data/lib/scout_apm/layer_converters/metric_converter.rb +1 -1
- data/lib/scout_apm/layer_converters/slow_job_converter.rb +90 -15
- data/lib/scout_apm/layer_converters/slow_request_converter.rb +101 -13
- data/lib/scout_apm/metric_set.rb +1 -9
- data/lib/scout_apm/metric_stats.rb +8 -8
- data/lib/scout_apm/reporter.rb +15 -51
- data/lib/scout_apm/request_histograms.rb +0 -4
- data/lib/scout_apm/request_manager.rb +1 -2
- data/lib/scout_apm/scored_item_set.rb +0 -7
- data/lib/scout_apm/serializers/deploy_serializer.rb +16 -0
- data/lib/scout_apm/serializers/payload_serializer.rb +3 -9
- data/lib/scout_apm/serializers/payload_serializer_to_json.rb +5 -2
- data/lib/scout_apm/serializers/slow_jobs_serializer_to_json.rb +1 -2
- data/lib/scout_apm/server_integrations/puma.rb +2 -5
- data/lib/scout_apm/slow_item_set.rb +80 -0
- data/lib/scout_apm/slow_job_record.rb +1 -6
- data/lib/scout_apm/slow_transaction.rb +2 -20
- data/lib/scout_apm/store.rb +12 -50
- data/lib/scout_apm/trace_compactor.rb +311 -0
- data/lib/scout_apm/tracked_request.rb +37 -128
- data/lib/scout_apm/utils/backtrace_parser.rb +5 -7
- data/lib/scout_apm/utils/fake_stacks.rb +83 -0
- data/lib/scout_apm/version.rb +1 -1
- data/scout_apm.gemspec +4 -6
- data/test/test_helper.rb +0 -56
- data/test/unit/config_test.rb +9 -60
- data/test/unit/histogram_test.rb +0 -14
- data/test/unit/layaway_test.rb +16 -31
- data/test/unit/serializers/payload_serializer_test.rb +105 -3
- data/test/unit/slow_item_set_test.rb +94 -0
- data/test/unit/slow_job_policy_test.rb +49 -0
- data/test/unit/slow_request_policy_test.rb +5 -4
- data/test/unit/utils/backtrace_parser_test.rb +0 -19
- data/tester.rb +53 -0
- metadata +29 -124
- data/.rubocop.yml +0 -8
- data/Guardfile +0 -42
- data/ext/rusage/README.md +0 -26
- data/ext/rusage/extconf.rb +0 -5
- data/ext/rusage/rusage.c +0 -52
- data/lib/scout_apm/background_job_integrations/resque.rb +0 -85
- data/lib/scout_apm/background_recorder.rb +0 -43
- data/lib/scout_apm/debug.rb +0 -37
- data/lib/scout_apm/git_revision.rb +0 -51
- data/lib/scout_apm/instruments/action_view.rb +0 -49
- data/lib/scout_apm/instruments/resque.rb +0 -40
- data/lib/scout_apm/layer_children_set.rb +0 -77
- data/lib/scout_apm/limited_layer.rb +0 -122
- data/lib/scout_apm/rack.rb +0 -26
- data/lib/scout_apm/remote/message.rb +0 -23
- data/lib/scout_apm/remote/recorder.rb +0 -57
- data/lib/scout_apm/remote/router.rb +0 -49
- data/lib/scout_apm/remote/server.rb +0 -58
- data/lib/scout_apm/serializers/histograms_serializer_to_json.rb +0 -21
- data/lib/scout_apm/synchronous_recorder.rb +0 -26
- data/lib/scout_apm/utils/gzip_helper.rb +0 -24
- data/lib/scout_apm/utils/numbers.rb +0 -14
- data/lib/scout_apm/utils/scm.rb +0 -14
- data/test/unit/background_job_integrations/sidekiq_test.rb +0 -104
- data/test/unit/context_test.rb +0 -30
- data/test/unit/git_revision_test.rb +0 -15
- data/test/unit/instruments/net_http_test.rb +0 -21
- data/test/unit/instruments/percentile_sampler_test.rb +0 -137
- data/test/unit/layer_children_set_test.rb +0 -88
- data/test/unit/limited_layer_test.rb +0 -53
- data/test/unit/remote/test_message.rb +0 -13
- data/test/unit/remote/test_router.rb +0 -33
- data/test/unit/remote/test_server.rb +0 -15
- data/test/unit/store_test.rb +0 -89
- data/test/unit/test_tracked_request.rb +0 -87
- data/test/unit/utils/numbers_test.rb +0 -15
- data/test/unit/utils/scm.rb +0 -17
@@ -1,53 +1,36 @@
|
|
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
|
-
|
20
3
|
class PercentileSampler
|
21
4
|
attr_reader :logger
|
22
5
|
|
23
|
-
|
24
|
-
attr_reader :histograms
|
6
|
+
attr_reader :percentiles
|
25
7
|
|
26
|
-
def initialize(logger,
|
8
|
+
def initialize(logger, percentiles)
|
27
9
|
@logger = logger
|
28
|
-
@
|
10
|
+
@percentiles = Array(percentiles)
|
29
11
|
end
|
30
12
|
|
31
13
|
def human_name
|
32
|
-
|
14
|
+
"Percentiles"
|
33
15
|
end
|
34
16
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
histogram.each_name do |name|
|
47
|
-
result << HistogramReport.new(name, histogram.raw(name))
|
17
|
+
# Gets the 95th%ile for the time requested
|
18
|
+
def metrics(time)
|
19
|
+
ms = {}
|
20
|
+
histos = ScoutApm::Agent.instance.request_histograms_by_time[time]
|
21
|
+
histos.each_name do |name|
|
22
|
+
percentiles.each do |percentile|
|
23
|
+
meta = MetricMeta.new("Percentile/#{percentile}/#{name}")
|
24
|
+
stat = MetricStats.new
|
25
|
+
stat.update!(histos.quantile(name, percentile))
|
26
|
+
ms[meta] = stat
|
27
|
+
end
|
48
28
|
end
|
49
29
|
|
50
|
-
|
30
|
+
# Wipe the histograms we just collected data on
|
31
|
+
ScoutApm::Agent.instance.request_histograms_by_time.delete(time)
|
32
|
+
|
33
|
+
ms
|
51
34
|
end
|
52
35
|
end
|
53
36
|
end
|
@@ -29,17 +29,16 @@ module ScoutApm
|
|
29
29
|
"Process CPU"
|
30
30
|
end
|
31
31
|
|
32
|
-
def metrics(
|
32
|
+
def metrics(_time)
|
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
|
-
|
38
|
+
{ meta => stat }
|
39
39
|
else
|
40
40
|
{}
|
41
41
|
end
|
42
|
-
|
43
42
|
end
|
44
43
|
|
45
44
|
# 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(_time)
|
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
|
-
|
42
|
+
{ meta => stat }
|
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
|
data/lib/scout_apm/layaway.rb
CHANGED
@@ -7,23 +7,19 @@
|
|
7
7
|
#
|
8
8
|
module ScoutApm
|
9
9
|
class Layaway
|
10
|
+
# How old a file needs to be in Seconds before it gets reported.
|
11
|
+
REPORTING_AGE = 120
|
12
|
+
|
10
13
|
# How long to let a stale file sit before deleting it.
|
11
14
|
# Letting it sit a bit may be useful for debugging
|
12
15
|
STALE_AGE = 10 * 60
|
13
16
|
|
14
|
-
# Failsafe to prevent writing layaway files if for some reason they are not being cleaned up
|
15
|
-
MAX_FILES_LIMIT = 5000
|
16
|
-
|
17
17
|
# A strftime format string for how we render timestamps in filenames.
|
18
18
|
# Must be sortable as an integer
|
19
19
|
TIME_FORMAT = "%Y%m%d%H%M"
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
def initialize(config, environment)
|
25
|
-
@config = config
|
26
|
-
@environment = environment
|
21
|
+
def initialize(directory=nil)
|
22
|
+
@directory = directory
|
27
23
|
end
|
28
24
|
|
29
25
|
# Returns a Pathname object with the fully qualified directory where the layaway files can be placed.
|
@@ -34,12 +30,12 @@ module ScoutApm
|
|
34
30
|
def directory
|
35
31
|
return @directory if @directory
|
36
32
|
|
37
|
-
data_file = config.value("data_file")
|
38
|
-
data_file = File.dirname(data_file) if data_file && !File.directory?
|
33
|
+
data_file = ScoutApm::Agent.instance.config.value("data_file")
|
34
|
+
data_file = File.dirname(data_file) if data_file && !File.directory?
|
39
35
|
|
40
36
|
candidates = [
|
41
37
|
data_file,
|
42
|
-
"#{environment.root}/tmp",
|
38
|
+
"#{ScoutApm::Agent.instance.environment.root}/tmp",
|
43
39
|
"/tmp"
|
44
40
|
].compact
|
45
41
|
|
@@ -48,11 +44,7 @@ module ScoutApm
|
|
48
44
|
@directory = Pathname.new(found)
|
49
45
|
end
|
50
46
|
|
51
|
-
def write_reporting_period(reporting_period
|
52
|
-
if at_layaway_file_limit?(files_limit)
|
53
|
-
ScoutApm::Agent.instance.logger.error("Hit layaway file limit. Not writing to layaway file")
|
54
|
-
return false
|
55
|
-
end
|
47
|
+
def write_reporting_period(reporting_period)
|
56
48
|
filename = file_for(reporting_period.timestamp)
|
57
49
|
layaway_file = LayawayFile.new(filename)
|
58
50
|
layaway_file.write(reporting_period)
|
@@ -64,56 +56,41 @@ module ScoutApm
|
|
64
56
|
def with_claim(timestamp)
|
65
57
|
coordinator_file = glob_pattern(timestamp, :coordinator)
|
66
58
|
|
67
|
-
begin
|
68
|
-
# This file gets deleted only by a process that successfully created and obtained the exclusive lock
|
69
|
-
f = File.open(coordinator_file, File::RDWR | File::CREAT | File::EXCL | File::NONBLOCK)
|
70
|
-
rescue Errno::EEXIST
|
71
|
-
false
|
72
|
-
end
|
73
59
|
|
60
|
+
# This file gets deleted only by a process that successfully obtained a lock
|
61
|
+
f = File.open(coordinator_file, File::RDWR | File::CREAT)
|
74
62
|
begin
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
delete_stale_files(timestamp.to_time - STALE_AGE)
|
91
|
-
else
|
92
|
-
File.unlink(coordinator_file)
|
93
|
-
ScoutApm::Agent.instance.logger.debug("No layaway files to report")
|
94
|
-
end
|
95
|
-
|
96
|
-
true
|
97
|
-
rescue Exception => e
|
98
|
-
ScoutApm::Agent.instance.logger.debug("Caught an exception in with_claim, with the coordination file locked: #{e.message}, #{e.backtrace.inspect}")
|
99
|
-
raise
|
100
|
-
ensure
|
101
|
-
# Unlock the file when done!
|
102
|
-
f.flock(File::LOCK_UN | File::LOCK_NB)
|
103
|
-
f.close
|
63
|
+
# Nonblocking, Exclusive lock.
|
64
|
+
if f.flock(File::LOCK_EX | File::LOCK_NB)
|
65
|
+
|
66
|
+
ScoutApm::Agent.instance.logger.debug("Obtained Reporting Lock")
|
67
|
+
|
68
|
+
files = all_files_for(timestamp).reject{|l| l.to_s == coordinator_file.to_s }
|
69
|
+
rps = files.map{ |layaway| LayawayFile.new(layaway).load }.compact
|
70
|
+
if rps.any?
|
71
|
+
yield rps
|
72
|
+
|
73
|
+
delete_files_for(timestamp) # also removes the coodinator_file
|
74
|
+
delete_stale_files(timestamp.to_time - STALE_AGE)
|
75
|
+
else
|
76
|
+
File.unlink(coordinator_file)
|
77
|
+
ScoutApm::Agent.instance.logger.debug("No layaway files to report")
|
104
78
|
end
|
79
|
+
|
80
|
+
# Unlock the file when done!
|
81
|
+
f.flock(File::LOCK_UN | File::LOCK_NB)
|
82
|
+
f.close
|
83
|
+
true
|
105
84
|
else
|
106
85
|
# Didn't obtain lock, another process is reporting. Return false from this function, but otherwise no work
|
86
|
+
f.close
|
107
87
|
false
|
108
88
|
end
|
109
89
|
end
|
110
90
|
end
|
111
91
|
|
112
92
|
def delete_files_for(timestamp)
|
113
|
-
all_files_for(timestamp).each { |layaway|
|
114
|
-
ScoutApm::Agent.instance.logger.debug("Deleting layaway file: #{layaway}")
|
115
|
-
File.unlink(layaway)
|
116
|
-
}
|
93
|
+
all_files_for(timestamp).each { |layaway| File.unlink(layaway) }
|
117
94
|
end
|
118
95
|
|
119
96
|
def delete_stale_files(older_than)
|
@@ -124,8 +101,6 @@ module ScoutApm
|
|
124
101
|
select { |timestamp| timestamp.to_i < older_than.strftime(TIME_FORMAT).to_i }.
|
125
102
|
tap { |timestamps| ScoutApm::Agent.instance.logger.debug("Deleting stale layaway files with timestamps: #{timestamps.inspect}") }.
|
126
103
|
map { |timestamp| delete_files_for(timestamp) }
|
127
|
-
rescue => e
|
128
|
-
ScoutApm::Agent.instance.logger.debug("Problem deleting stale files: #{e.message}, #{e.backtrace.inspect}")
|
129
104
|
end
|
130
105
|
|
131
106
|
private
|
@@ -176,24 +151,6 @@ module ScoutApm
|
|
176
151
|
nil
|
177
152
|
end
|
178
153
|
end
|
179
|
-
|
180
|
-
def at_layaway_file_limit?(files_limit = MAX_FILES_LIMIT)
|
181
|
-
all_files_for(:all).count >= files_limit
|
182
|
-
end
|
183
|
-
|
184
|
-
def log_layaway_file_information
|
185
|
-
files_in_temp = Dir["#{directory}/*"].count
|
186
|
-
|
187
|
-
all_filenames = all_files_for(:all)
|
188
|
-
count_per_timestamp = Hash[
|
189
|
-
all_filenames.
|
190
|
-
group_by {|f| timestamp_from_filename(f) }.
|
191
|
-
map{ |timestamp, list| [timestamp, list.length] }
|
192
|
-
]
|
193
|
-
|
194
|
-
|
195
|
-
ScoutApm::Agent.instance.logger.debug("Total in #{directory}: #{files_in_temp}. Total Layaway Files: #{all_filenames.size}. By Timestamp: #{count_per_timestamp.inspect}")
|
196
|
-
end
|
197
154
|
end
|
198
155
|
end
|
199
156
|
|
data/lib/scout_apm/layer.rb
CHANGED
@@ -13,17 +13,13 @@ module ScoutApm
|
|
13
13
|
# instrumentation for an example of how this is useful
|
14
14
|
attr_accessor :name
|
15
15
|
|
16
|
-
# An array of children layers
|
16
|
+
# An array of children layers, in call order.
|
17
17
|
# For instance, if we are in a middleware, there will likely be only a single
|
18
18
|
# child, which is another middleware. In a Controller, we may have a handful
|
19
19
|
# of children: [ActiveRecord, ActiveRecord, View, HTTP Call].
|
20
20
|
#
|
21
21
|
# This useful to get actual time spent in this layer vs. children time
|
22
|
-
|
23
|
-
# TODO: Check callers for compatibility w/ nil to avoid making an empty array
|
24
|
-
def children
|
25
|
-
@children || LayerChildrenSet.new
|
26
|
-
end
|
22
|
+
attr_reader :children
|
27
23
|
|
28
24
|
# Time objects recording the start & stop times of this layer
|
29
25
|
attr_reader :start_time, :stop_time
|
@@ -42,27 +38,41 @@ module ScoutApm
|
|
42
38
|
# Known Keys:
|
43
39
|
# :record_count - The number of rows returned by an AR query (From notification instantiation.active_record)
|
44
40
|
# :class_name - The ActiveRecord class name (From notification instantiation.active_record)
|
45
|
-
#
|
46
|
-
# If no annotations are ever set, this will return nil
|
47
41
|
attr_reader :annotations
|
48
42
|
|
43
|
+
# ScoutProf - trace_index is an index into the Stack structure in the C
|
44
|
+
# code, used to store captured traces.
|
45
|
+
attr_reader :trace_index
|
46
|
+
|
47
|
+
# ScoutProf - frame_index is an optimization to not capture a few frames
|
48
|
+
# during scoutprof instrumentation
|
49
|
+
attr_reader :frame_index
|
50
|
+
|
51
|
+
# Captured backtraces from ScoutProf. This is distinct from the backtrace
|
52
|
+
# attribute, which gets the ruby backtrace of any given layer. StackProf
|
53
|
+
# focuses on Controller layers, and requires a native extension and a
|
54
|
+
# reasonably recent Ruby.
|
55
|
+
attr_reader :traces
|
56
|
+
|
49
57
|
BACKTRACE_CALLER_LIMIT = 50 # maximum number of lines to send thru for backtrace analysis
|
50
58
|
|
51
59
|
def initialize(type, name, start_time = Time.now)
|
52
60
|
@type = type
|
53
61
|
@name = name
|
62
|
+
@annotations = {}
|
54
63
|
@start_time = start_time
|
55
64
|
@allocations_start = ScoutApm::Instruments::Allocations.count
|
56
65
|
@allocations_stop = 0
|
57
|
-
|
58
|
-
# initialize these only on first use
|
59
|
-
@children = nil
|
60
|
-
@annotations = nil
|
66
|
+
@children = [] # In order of calls
|
61
67
|
@desc = nil
|
68
|
+
|
69
|
+
@traces = ScoutApm::TraceSet.new
|
70
|
+
@raw_frames = []
|
71
|
+
@frame_index = ScoutApm::Instruments::Stacks.current_frame_index # For efficiency sake, try to skip the bottom X frames when collecting traces
|
72
|
+
@trace_index = ScoutApm::Instruments::Stacks.current_trace_index
|
62
73
|
end
|
63
74
|
|
64
75
|
def add_child(child)
|
65
|
-
@children ||= LayerChildrenSet.new
|
66
76
|
@children << child
|
67
77
|
end
|
68
78
|
|
@@ -81,7 +91,6 @@ module ScoutApm
|
|
81
91
|
|
82
92
|
# This data is internal to ScoutApm, to add custom information, use the Context api.
|
83
93
|
def annotate_layer(hsh)
|
84
|
-
@annotations ||= {}
|
85
94
|
@annotations.merge!(hsh)
|
86
95
|
end
|
87
96
|
|
@@ -93,6 +102,14 @@ module ScoutApm
|
|
93
102
|
@subscopable
|
94
103
|
end
|
95
104
|
|
105
|
+
def traced!
|
106
|
+
@traced = true
|
107
|
+
end
|
108
|
+
|
109
|
+
def traced?
|
110
|
+
@traced
|
111
|
+
end
|
112
|
+
|
96
113
|
# This is the old style name. This function is used for now, but should be
|
97
114
|
# removed, and the new type & name split should be enforced through the
|
98
115
|
# app.
|
@@ -114,6 +131,34 @@ module ScoutApm
|
|
114
131
|
end
|
115
132
|
end
|
116
133
|
|
134
|
+
# Set the name of the file that this action is coming from.
|
135
|
+
# TraceSet uses this to more accurately filter backtraces
|
136
|
+
def set_root_class(klass_name)
|
137
|
+
@traces.set_root_class(klass_name)
|
138
|
+
end
|
139
|
+
|
140
|
+
def start_sampling
|
141
|
+
if ScoutApm::Agent.instance.config.value('profile') && traced?
|
142
|
+
ScoutApm::Instruments::Stacks.update_indexes(frame_index, trace_index)
|
143
|
+
ScoutApm::Instruments::Stacks.start_sampling
|
144
|
+
else
|
145
|
+
ScoutApm::Instruments::Stacks.stop_sampling(false)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def record_traces!
|
150
|
+
if ScoutApm::Agent.instance.config.value('profile')
|
151
|
+
ScoutApm::Instruments::Stacks.stop_sampling(false)
|
152
|
+
if traced?
|
153
|
+
traces.raw_traces = ScoutApm::Instruments::Stacks.profile_frames
|
154
|
+
traces.skipped_in_gc = ScoutApm::Instruments::Stacks.skipped_in_gc
|
155
|
+
traces.skipped_in_handler = ScoutApm::Instruments::Stacks.skipped_in_handler
|
156
|
+
traces.skipped_in_job_registered = ScoutApm::Instruments::Stacks.skipped_in_job_registered
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
|
117
162
|
######################################
|
118
163
|
# Debugging Helpers
|
119
164
|
######################################
|
@@ -154,7 +199,6 @@ module ScoutApm
|
|
154
199
|
map { |child| child.total_call_time }.
|
155
200
|
inject(0) { |sum, time| sum + time }
|
156
201
|
end
|
157
|
-
private :child_time
|
158
202
|
|
159
203
|
######################################
|
160
204
|
# Allocation Calculations
|
@@ -180,6 +224,5 @@ module ScoutApm
|
|
180
224
|
map { |child| child.total_allocations }.
|
181
225
|
inject(0) { |sum, obj| sum + obj }
|
182
226
|
end
|
183
|
-
private :child_allocations
|
184
227
|
end
|
185
228
|
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
module ScoutApm
|
2
2
|
module LayerConverters
|
3
3
|
class ConverterBase
|
4
|
-
|
5
4
|
attr_reader :walker
|
6
5
|
attr_reader :request
|
7
6
|
attr_reader :root_layer
|
@@ -9,10 +8,7 @@ module ScoutApm
|
|
9
8
|
def initialize(request)
|
10
9
|
@request = request
|
11
10
|
@root_layer = request.root_layer
|
12
|
-
@backtraces = []
|
13
11
|
@walker = DepthFirstWalker.new(root_layer)
|
14
|
-
|
15
|
-
@limited = false
|
16
12
|
end
|
17
13
|
|
18
14
|
# Scope is determined by the first Controller we hit. Most of the time
|
@@ -31,201 +27,6 @@ module ScoutApm
|
|
31
27
|
return layer if layer.type == layer_type
|
32
28
|
end
|
33
29
|
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
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
# Call this after you finish walking the layers, and want to take the
|
95
|
-
# set-aside backtraces and place them into the metas they match
|
96
|
-
def attach_backtraces(metric_hash)
|
97
|
-
@backtraces.each do |meta_with_backtrace|
|
98
|
-
metric_hash.keys.find { |k| k == meta_with_backtrace }.backtrace = meta_with_backtrace.backtrace
|
99
|
-
end
|
100
|
-
metric_hash
|
101
|
-
end
|
102
|
-
|
103
|
-
|
104
|
-
################################################################################
|
105
|
-
# Limit Handling
|
106
|
-
################################################################################
|
107
|
-
|
108
|
-
# To prevent huge traces from being generated, we should stop collecting
|
109
|
-
# detailed metrics as we go beyond some reasonably large count.
|
110
|
-
#
|
111
|
-
# We should still add up the /all aggregates.
|
112
|
-
|
113
|
-
MAX_METRICS = 500
|
114
|
-
|
115
|
-
def over_metric_limit?(metric_hash)
|
116
|
-
if metric_hash.size > MAX_METRICS
|
117
|
-
@limited = true
|
118
|
-
else
|
119
|
-
false
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
def limited?
|
124
|
-
!! @limited
|
125
|
-
end
|
126
|
-
|
127
|
-
################################################################################
|
128
|
-
# Meta Scope
|
129
|
-
################################################################################
|
130
|
-
|
131
|
-
# When we make MetricMeta records, we need to determine a few things from layer.
|
132
|
-
def make_meta_options(layer)
|
133
|
-
scope_hash = make_meta_options_scope(layer)
|
134
|
-
desc_hash = make_meta_options_desc_hash(layer)
|
135
|
-
|
136
|
-
scope_hash.merge(desc_hash)
|
137
|
-
end
|
138
|
-
|
139
|
-
def make_meta_options_scope(layer)
|
140
|
-
# This layer is scoped under another thing. Typically that means this is a layer under a view.
|
141
|
-
# Like: Controller -> View/users/show -> ActiveRecord/user/find
|
142
|
-
# in that example, the scope is the View/users/show
|
143
|
-
if subscoped?(layer)
|
144
|
-
{:scope => subscope_name}
|
145
|
-
|
146
|
-
# We don't scope the controller under itself
|
147
|
-
elsif layer == scope_layer
|
148
|
-
{}
|
149
|
-
|
150
|
-
# This layer is a top level metric ("ActiveRecord", or "HTTP" or
|
151
|
-
# whatever, directly under the controller), so scope to the
|
152
|
-
# Controller
|
153
|
-
else
|
154
|
-
{:scope => scope_layer.legacy_metric_name}
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
def make_meta_options_desc_hash(layer, max_desc_length=1000)
|
159
|
-
if layer.desc
|
160
|
-
desc_s = layer.desc.to_s
|
161
|
-
trimmed_desc = desc_s[0 .. max_desc_length]
|
162
|
-
{:desc => trimmed_desc}
|
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) if 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
|
-
stat = metric_hash[meta]
|
189
|
-
stat.update!(layer.total_call_time, layer.total_exclusive_time)
|
190
|
-
|
191
|
-
# allocations
|
192
|
-
stat = allocation_metric_hash[meta]
|
193
|
-
stat.update!(layer.total_allocations, layer.total_exclusive_allocations)
|
194
|
-
|
195
|
-
if LimitedLayer === layer
|
196
|
-
metric_hash[meta].call_count = layer.count
|
197
|
-
allocation_metric_hash[meta].call_count = layer.count
|
198
|
-
end
|
199
|
-
end
|
200
|
-
|
201
|
-
# Merged Metric - no specifics, just sum up by type (ActiveRecord, View, HTTP, etc)
|
202
|
-
def store_aggregate_metric(layer, metric_hash, allocation_metric_hash)
|
203
|
-
meta = MetricMeta.new("#{layer.type}/all")
|
204
|
-
|
205
|
-
metric_hash[meta] ||= MetricStats.new(false)
|
206
|
-
allocation_metric_hash[meta] ||= MetricStats.new(false)
|
207
|
-
|
208
|
-
# timing
|
209
|
-
stat = metric_hash[meta]
|
210
|
-
stat.update!(layer.total_call_time, layer.total_exclusive_time)
|
211
|
-
|
212
|
-
# allocations
|
213
|
-
stat = allocation_metric_hash[meta]
|
214
|
-
stat.update!(layer.total_allocations, layer.total_exclusive_allocations)
|
215
|
-
end
|
216
|
-
|
217
|
-
################################################################################
|
218
|
-
# Misc Helpers
|
219
|
-
################################################################################
|
220
|
-
|
221
|
-
# Sometimes we start capturing a layer without knowing if we really
|
222
|
-
# want to make an entry for it. See ActiveRecord instrumentation for
|
223
|
-
# an example. We start capturing before we know if a query is cached
|
224
|
-
# or not, and want to skip any cached queries.
|
225
|
-
def skip_layer?(layer)
|
226
|
-
return false if layer.annotations.nil?
|
227
|
-
return true if layer.annotations[:ignorable]
|
228
|
-
end
|
229
30
|
end
|
230
31
|
end
|
231
32
|
end
|