scout_apm 1.6.8 → 2.0.0.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +8 -1
- data/CHANGELOG.markdown +7 -57
- data/ext/allocations/allocations.c +84 -0
- data/ext/allocations/extconf.rb +3 -0
- data/lib/scout_apm/agent/reporting.rb +9 -32
- data/lib/scout_apm/agent.rb +45 -31
- data/lib/scout_apm/app_server_load.rb +1 -2
- data/lib/scout_apm/attribute_arranger.rb +0 -4
- data/lib/scout_apm/background_worker.rb +6 -9
- data/lib/scout_apm/bucket_name_splitter.rb +3 -3
- data/lib/scout_apm/call_set.rb +1 -0
- data/lib/scout_apm/config.rb +110 -66
- data/lib/scout_apm/environment.rb +16 -10
- data/lib/scout_apm/framework_integrations/rails_2.rb +12 -14
- data/lib/scout_apm/framework_integrations/rails_3_or_4.rb +5 -17
- data/lib/scout_apm/framework_integrations/ruby.rb +0 -4
- data/lib/scout_apm/framework_integrations/sinatra.rb +0 -4
- data/lib/scout_apm/histogram.rb +0 -20
- data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +1 -4
- data/lib/scout_apm/instruments/active_record.rb +149 -8
- data/lib/scout_apm/instruments/mongoid.rb +5 -78
- data/lib/scout_apm/instruments/process/process_cpu.rb +0 -12
- data/lib/scout_apm/instruments/process/process_memory.rb +14 -43
- data/lib/scout_apm/layaway.rb +34 -134
- data/lib/scout_apm/layaway_file.rb +50 -27
- data/lib/scout_apm/layer.rb +45 -1
- data/lib/scout_apm/layer_converters/allocation_metric_converter.rb +17 -0
- data/lib/scout_apm/layer_converters/converter_base.rb +4 -6
- data/lib/scout_apm/layer_converters/job_converter.rb +1 -0
- data/lib/scout_apm/layer_converters/metric_converter.rb +2 -1
- data/lib/scout_apm/layer_converters/slow_job_converter.rb +42 -21
- data/lib/scout_apm/layer_converters/slow_request_converter.rb +58 -37
- data/lib/scout_apm/metric_meta.rb +1 -5
- data/lib/scout_apm/metric_set.rb +6 -15
- data/lib/scout_apm/reporter.rb +4 -6
- data/lib/scout_apm/serializers/metrics_to_json_serializer.rb +5 -1
- data/lib/scout_apm/serializers/payload_serializer_to_json.rb +1 -3
- data/lib/scout_apm/serializers/slow_jobs_serializer_to_json.rb +5 -3
- data/lib/scout_apm/slow_job_policy.rb +19 -89
- data/lib/scout_apm/slow_job_record.rb +12 -20
- data/lib/scout_apm/slow_request_policy.rb +12 -80
- data/lib/scout_apm/slow_transaction.rb +16 -20
- data/lib/scout_apm/stackprof_tree_collapser.rb +103 -0
- data/lib/scout_apm/store.rb +16 -78
- data/lib/scout_apm/tracked_request.rb +53 -36
- data/lib/scout_apm/utils/active_record_metric_name.rb +2 -0
- data/lib/scout_apm/utils/fake_stack_prof.rb +40 -0
- data/lib/scout_apm/utils/klass_helper.rb +26 -0
- data/lib/scout_apm/utils/sql_sanitizer.rb +1 -1
- data/lib/scout_apm/utils/sql_sanitizer_regex.rb +2 -2
- data/lib/scout_apm/utils/sql_sanitizer_regex_1_8_7.rb +2 -2
- data/lib/scout_apm/version.rb +1 -1
- data/lib/scout_apm.rb +13 -7
- data/scout_apm.gemspec +3 -1
- data/test/test_helper.rb +3 -4
- data/test/unit/layaway_test.rb +8 -5
- data/test/unit/serializers/payload_serializer_test.rb +2 -2
- data/test/unit/slow_item_set_test.rb +1 -2
- data/test/unit/sql_sanitizer_test.rb +0 -6
- metadata +28 -20
- data/LICENSE.md +0 -27
- data/lib/scout_apm/instruments/grape.rb +0 -69
- data/lib/scout_apm/instruments/percentile_sampler.rb +0 -37
- data/lib/scout_apm/request_histograms.rb +0 -46
- data/lib/scout_apm/scored_item_set.rb +0 -79
- data/test/unit/metric_set_test.rb +0 -101
- data/test/unit/scored_item_set_test.rb +0 -65
- data/test/unit/slow_request_policy_test.rb +0 -42
@@ -17,89 +17,16 @@ module ScoutApm
|
|
17
17
|
|
18
18
|
# Mongoid versions that use Moped should instrument Moped.
|
19
19
|
if defined?(::Mongoid) and !defined?(::Moped)
|
20
|
-
ScoutApm::Agent.instance.logger.info "Instrumenting Mongoid
|
20
|
+
ScoutApm::Agent.instance.logger.info "Instrumenting Mongoid"
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
::Mongoid::
|
25
|
-
|
26
|
-
(::Mongoid::Collections::Operations::ALL - [:<<, :[]]).each do |method|
|
27
|
-
instrument_method method, :type => "MongoDB", :name => '#{@klass}/' + method.to_s
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
### See moped instrument for Moped driven deploys
|
33
|
-
|
34
|
-
### 5.x Mongoid
|
35
|
-
if mongoid_v5? && defined?(::Mongoid::Contextual::Mongo)
|
36
|
-
ScoutApm::Agent.instance.logger.info "Instrumenting Mongoid 5.x"
|
37
|
-
# All the public methods from Mongoid::Contextual::Mongo.
|
38
|
-
# TODO: Geo and MapReduce support (?). They are in other Contextual::* classes
|
39
|
-
methods = [
|
40
|
-
:count, :delete, :destroy, :distinct, :each,
|
41
|
-
:explain, :find_first, :find_one_and_delete, :find_one_and_replace,
|
42
|
-
:find_one_and_update, :first, :geo_near, :initialize, :last,
|
43
|
-
:length, :limit, :map, :map_reduce, :pluck,
|
44
|
-
:skip, :sort, :update, :update_all,
|
45
|
-
]
|
46
|
-
# :exists?,
|
47
|
-
|
48
|
-
methods.each do |method|
|
49
|
-
if ::Mongoid::Contextual::Mongo.method_defined?(method)
|
50
|
-
with_scout_instruments = %Q[
|
51
|
-
def #{method}_with_scout_instruments(*args, &block)
|
52
|
-
|
53
|
-
req = ScoutApm::RequestManager.lookup
|
54
|
-
*db, collection = view.collection.namespace.split(".")
|
55
|
-
|
56
|
-
name = collection + "/#{method}"
|
57
|
-
filter = ScoutApm::Instruments::Mongoid.anonymize_filter(view.filter)
|
58
|
-
|
59
|
-
layer = ScoutApm::Layer.new("MongoDB", name)
|
60
|
-
layer.desc = filter.inspect
|
61
|
-
|
62
|
-
req.start_layer( layer )
|
63
|
-
begin
|
64
|
-
#{method}_without_scout_instruments(*args, &block)
|
65
|
-
ensure
|
66
|
-
req.stop_layer
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
alias_method :#{method}_without_scout_instruments, :#{method}
|
71
|
-
alias_method :#{method}, :#{method}_with_scout_instruments
|
72
|
-
]
|
73
|
-
|
74
|
-
::Mongoid::Contextual::Mongo.class_eval(with_scout_instruments)
|
75
|
-
end
|
22
|
+
::Mongoid::Collection.class_eval do
|
23
|
+
include ScoutApm::Tracer
|
24
|
+
(::Mongoid::Collections::Operations::ALL - [:<<, :[]]).each do |method|
|
25
|
+
instrument_method method, :type => "MongoDB", :name => '#{@klass}/' + method.to_s
|
76
26
|
end
|
77
27
|
end
|
78
28
|
end
|
79
29
|
end
|
80
|
-
|
81
|
-
def mongoid_v5?
|
82
|
-
if defined?(::Mongoid::VERSION)
|
83
|
-
::Mongoid::VERSION =~ /\A5/
|
84
|
-
else
|
85
|
-
false
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
|
90
|
-
# Example of what a filter looks like: => {"founded"=>{"$gte"=>"1980-1-1"}, "name"=>{"$in"=>["Tool", "Deftones", "Melvins"]}}
|
91
|
-
# Approach: find every leaf-node, clear it. inspect the whole thing when done.
|
92
|
-
def self.anonymize_filter(filter)
|
93
|
-
Hash[
|
94
|
-
filter.map do |k,v|
|
95
|
-
if v.is_a? Hash
|
96
|
-
[k, anonymize_filter(v)]
|
97
|
-
else
|
98
|
-
[k, "?"]
|
99
|
-
end
|
100
|
-
end
|
101
|
-
]
|
102
|
-
end
|
103
30
|
end
|
104
31
|
end
|
105
32
|
end
|
@@ -29,18 +29,6 @@ module ScoutApm
|
|
29
29
|
"Process CPU"
|
30
30
|
end
|
31
31
|
|
32
|
-
def metrics(_time)
|
33
|
-
result = run
|
34
|
-
if result
|
35
|
-
meta = MetricMeta.new("#{metric_type}/#{metric_name}")
|
36
|
-
stat = MetricStats.new(false)
|
37
|
-
stat.update!(result)
|
38
|
-
{ meta => stat }
|
39
|
-
else
|
40
|
-
{}
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
32
|
# TODO: Figure out a good default instead of nil
|
45
33
|
def run
|
46
34
|
res = nil
|
@@ -4,6 +4,19 @@ module ScoutApm
|
|
4
4
|
class ProcessMemory
|
5
5
|
attr_reader :logger
|
6
6
|
|
7
|
+
# Account for Darwin returning maxrss in bytes and Linux in KB. Used by the slow converters. Doesn't feel like this should go here though...more of a utility.
|
8
|
+
def self.rss_to_mb(rss)
|
9
|
+
rss.to_f/1024/(ScoutApm::Agent.instance.environment.os == 'darwin' ? 1024 : 1)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.rss
|
13
|
+
::Process.rusage.maxrss
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.rss_in_mb
|
17
|
+
rss_to_mb(rss)
|
18
|
+
end
|
19
|
+
|
7
20
|
def initialize(logger)
|
8
21
|
@logger = logger
|
9
22
|
end
|
@@ -20,50 +33,8 @@ module ScoutApm
|
|
20
33
|
"Process Memory"
|
21
34
|
end
|
22
35
|
|
23
|
-
def metrics(_time)
|
24
|
-
result = run
|
25
|
-
if result
|
26
|
-
meta = MetricMeta.new("#{metric_type}/#{metric_name}")
|
27
|
-
stat = MetricStats.new(false)
|
28
|
-
stat.update!(result)
|
29
|
-
{ meta => stat }
|
30
|
-
else
|
31
|
-
{}
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
36
|
def run
|
36
|
-
|
37
|
-
when /linux/
|
38
|
-
get_mem_from_procfile
|
39
|
-
when /darwin9/ # 10.5
|
40
|
-
get_mem_from_shell("ps -o rsz")
|
41
|
-
when /darwin1[01234]/ # 10.6 - 10.11
|
42
|
-
get_mem_from_shell("ps -o rss")
|
43
|
-
else
|
44
|
-
0 # What default? was nil.
|
45
|
-
end.tap { |res| logger.debug "#{human_name}: #{res.inspect}" }
|
46
|
-
end
|
47
|
-
|
48
|
-
private
|
49
|
-
|
50
|
-
def get_mem_from_procfile
|
51
|
-
res = nil
|
52
|
-
proc_status = File.open(procfile, "r") { |f| f.read_nonblock(4096).strip }
|
53
|
-
if proc_status =~ /RSS:\s*(\d+) kB/i
|
54
|
-
res= $1.to_f / 1024.0
|
55
|
-
end
|
56
|
-
res
|
57
|
-
end
|
58
|
-
|
59
|
-
def procfile
|
60
|
-
"/proc/#{$$}/status"
|
61
|
-
end
|
62
|
-
|
63
|
-
# memory in MB the current process is using
|
64
|
-
def get_mem_from_shell(command)
|
65
|
-
res = `#{command} #{$$}`.split("\n")[1].to_f / 1024.0 #rescue nil
|
66
|
-
res
|
37
|
+
self.class.rss_in_mb.tap { |res| logger.debug "#{human_name}: #{res.inspect}" }
|
67
38
|
end
|
68
39
|
end
|
69
40
|
end
|
data/lib/scout_apm/layaway.rb
CHANGED
@@ -1,156 +1,56 @@
|
|
1
|
-
# Stores StoreReportingPeriod objects in a
|
2
|
-
#
|
3
|
-
#
|
4
|
-
# Each layaway file is named basedir/scout_#{timestamp}_#{pid}.data
|
5
|
-
# Where timestamp is in the format:
|
6
|
-
# And PID is the process id of the running process
|
7
|
-
#
|
1
|
+
# Stores StoreReportingPeriod objects in a file before sending them to the server.
|
2
|
+
# 1. A centralized store for multiple Agent processes. This way, only 1 checkin is sent to Scout rather than 1 per-process.
|
8
3
|
module ScoutApm
|
9
4
|
class Layaway
|
10
|
-
|
11
|
-
REPORTING_AGE = 120
|
5
|
+
attr_accessor :file
|
12
6
|
|
13
|
-
|
14
|
-
|
15
|
-
STALE_AGE = 10 * 60
|
16
|
-
|
17
|
-
# A strftime format string for how we render timestamps in filenames.
|
18
|
-
# Must be sortable as an integer
|
19
|
-
TIME_FORMAT = "%Y%m%d%H%M"
|
20
|
-
|
21
|
-
def initialize(directory=nil)
|
22
|
-
@directory = directory
|
23
|
-
end
|
24
|
-
|
25
|
-
# Returns a Pathname object with the fully qualified directory where the layaway files can be placed.
|
26
|
-
# That directory must be writable by this process.
|
27
|
-
#
|
28
|
-
# Don't set this in initializer, since it relies on agent instance existing to figure out the value.
|
29
|
-
#
|
30
|
-
def directory
|
31
|
-
return @directory if @directory
|
32
|
-
|
33
|
-
data_file = ScoutApm::Agent.instance.config.value("data_file")
|
34
|
-
data_file = File.dirname(data_file) if data_file && !File.directory?
|
35
|
-
|
36
|
-
candidates = [
|
37
|
-
data_file,
|
38
|
-
"#{ScoutApm::Agent.instance.environment.root}/tmp",
|
39
|
-
"/tmp"
|
40
|
-
].compact
|
41
|
-
|
42
|
-
found = candidates.detect { |dir| File.writable?(dir) }
|
43
|
-
ScoutApm::Agent.instance.logger.debug("Storing Layaway Files in #{found}")
|
44
|
-
@directory = Pathname.new(found)
|
45
|
-
end
|
46
|
-
|
47
|
-
def write_reporting_period(reporting_period)
|
48
|
-
filename = file_for(reporting_period.timestamp)
|
49
|
-
layaway_file = LayawayFile.new(filename)
|
50
|
-
layaway_file.write(reporting_period)
|
7
|
+
def initialize
|
8
|
+
@file = ScoutApm::LayawayFile.new
|
51
9
|
end
|
52
10
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
coordinator_file = glob_pattern(timestamp, :coordinator)
|
58
|
-
|
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)
|
62
|
-
begin
|
63
|
-
# Nonblocking, Exclusive lock.
|
64
|
-
if f.flock(File::LOCK_EX | File::LOCK_NB)
|
65
|
-
|
66
|
-
ScoutApm::Agent.instance.logger.debug("Obtained Reporting Lock")
|
11
|
+
def add_reporting_period(time, reporting_period)
|
12
|
+
file.read_and_write do |existing_data|
|
13
|
+
existing_data ||= Hash.new
|
14
|
+
ScoutApm::Agent.instance.logger.debug("AddReportingPeriod: Adding a reporting_period with timestamp: #{reporting_period.timestamp.to_s}, and #{reporting_period.request_count} requests")
|
67
15
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
16
|
+
existing_data = existing_data.merge(time => reporting_period) {|key, old_val, new_val|
|
17
|
+
old_req = old_val.request_count
|
18
|
+
new_req = new_val.request_count
|
19
|
+
ScoutApm::Agent.instance.logger.debug("Merging Two reporting periods (#{old_val.timestamp.to_s}, #{new_val.timestamp.to_s}): old req #{old_req}, new req #{new_req}")
|
72
20
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
end
|
21
|
+
old_val.
|
22
|
+
merge_metrics!(new_val.metrics_payload).
|
23
|
+
merge_slow_transactions!(new_val.slow_transactions).
|
24
|
+
merge_jobs!(new_val.jobs)
|
25
|
+
}
|
79
26
|
|
80
|
-
|
81
|
-
|
82
|
-
f.close
|
83
|
-
true
|
84
|
-
else
|
85
|
-
# Didn't obtain lock, another process is reporting. Return false from this function, but otherwise no work
|
86
|
-
f.close
|
87
|
-
false
|
88
|
-
end
|
27
|
+
ScoutApm::Agent.instance.logger.debug("AddReportingPeriod: AfterMerge Timestamps: #{existing_data.keys.map(&:to_s).inspect}")
|
28
|
+
existing_data
|
89
29
|
end
|
90
30
|
end
|
91
31
|
|
92
|
-
|
93
|
-
all_files_for(timestamp).each { |layaway| File.unlink(layaway) }
|
94
|
-
end
|
95
|
-
|
96
|
-
def delete_stale_files(older_than)
|
97
|
-
all_files_for(:all).
|
98
|
-
map { |filename| timestamp_from_filename(filename) }.
|
99
|
-
compact.
|
100
|
-
uniq.
|
101
|
-
select { |timestamp| timestamp.to_i < older_than.strftime(TIME_FORMAT).to_i }.
|
102
|
-
tap { |timestamps| ScoutApm::Agent.instance.logger.debug("Deleting stale layaway files with timestamps: #{timestamps.inspect}") }.
|
103
|
-
map { |timestamp| delete_files_for(timestamp) }
|
104
|
-
end
|
32
|
+
REPORTING_INTERVAL = 60 # seconds
|
105
33
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
def file_for(timestamp)
|
112
|
-
glob_pattern(timestamp)
|
113
|
-
end
|
34
|
+
# Returns an array of ReportingPeriod objects that are ready to be pushed to the server
|
35
|
+
def periods_ready_for_delivery
|
36
|
+
ready_for_delivery = []
|
37
|
+
file.read_and_write do |existing_data|
|
38
|
+
existing_data ||= {}
|
114
39
|
|
115
|
-
|
116
|
-
Dir[glob_pattern(timestamp, :all)]
|
117
|
-
end
|
40
|
+
ScoutApm::Agent.instance.logger.debug("PeriodsReadyForDeliver: All Timestamps: #{existing_data.keys.map(&:to_s).inspect}")
|
118
41
|
|
119
|
-
|
120
|
-
# if timestamp == :all then find all timestamps, otherwise format it.
|
121
|
-
# if pid == :all, get the files for all
|
122
|
-
def glob_pattern(timestamp, pid=$$)
|
123
|
-
timestamp_pattern = format_timestamp(timestamp)
|
124
|
-
pid_pattern = format_pid(pid)
|
125
|
-
directory + "scout_#{timestamp_pattern}_#{pid_pattern}.data"
|
126
|
-
end
|
42
|
+
ready_for_delivery = existing_data.to_a.select {|time, rp| should_send?(rp) } # Select off the values we want. to_a is needed for compatibility with Ruby 1.8.7.
|
127
43
|
|
128
|
-
|
129
|
-
|
130
|
-
"*"
|
131
|
-
elsif timestamp.respond_to?(:strftime)
|
132
|
-
timestamp.strftime(TIME_FORMAT)
|
133
|
-
else
|
134
|
-
timestamp.to_s
|
44
|
+
# Rewrite anything not plucked out back to the file
|
45
|
+
existing_data.reject {|k, v| ready_for_delivery.map(&:first).include?(k) }
|
135
46
|
end
|
136
|
-
end
|
137
47
|
|
138
|
-
|
139
|
-
if pid == :all
|
140
|
-
"*"
|
141
|
-
else
|
142
|
-
pid.to_s
|
143
|
-
end
|
48
|
+
return ready_for_delivery.map(&:last)
|
144
49
|
end
|
145
50
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
match[1]
|
150
|
-
else
|
151
|
-
nil
|
152
|
-
end
|
51
|
+
# We just want to send anything older than X
|
52
|
+
def should_send?(reporting_period)
|
53
|
+
reporting_period.timestamp.age_in_seconds > (REPORTING_INTERVAL * 2)
|
153
54
|
end
|
154
55
|
end
|
155
56
|
end
|
156
|
-
|
@@ -1,36 +1,70 @@
|
|
1
|
-
#
|
1
|
+
# Logic for the serialized file access
|
2
2
|
module ScoutApm
|
3
3
|
class LayawayFile
|
4
|
-
|
4
|
+
def path
|
5
|
+
ScoutApm::Agent.instance.config.value("data_file") ||
|
6
|
+
"#{ScoutApm::Agent.instance.default_log_path}/scout_apm.db"
|
7
|
+
end
|
5
8
|
|
6
|
-
def
|
7
|
-
|
9
|
+
def dump(object)
|
10
|
+
Marshal.dump(object)
|
8
11
|
end
|
9
12
|
|
10
|
-
def load
|
11
|
-
|
12
|
-
|
13
|
+
def load(dump)
|
14
|
+
if dump.size == 0
|
15
|
+
ScoutApm::Agent.instance.logger.debug("No data in layaway file.")
|
16
|
+
return nil
|
17
|
+
end
|
18
|
+
Marshal.load(dump)
|
13
19
|
rescue NameError, ArgumentError, TypeError => e
|
14
|
-
# Marshal error
|
15
20
|
ScoutApm::Agent.instance.logger.info("Unable to load data from Layaway file, resetting.")
|
16
21
|
ScoutApm::Agent.instance.logger.debug("#{e.message}, #{e.backtrace.join("\n\t")}")
|
17
22
|
nil
|
18
23
|
end
|
19
24
|
|
20
|
-
def
|
21
|
-
|
22
|
-
|
25
|
+
def read_and_write
|
26
|
+
File.open(path, File::RDWR | File::CREAT) do |f|
|
27
|
+
f.flock(File::LOCK_EX)
|
28
|
+
begin
|
29
|
+
result = (yield get_data(f))
|
30
|
+
f.rewind
|
31
|
+
f.truncate(0)
|
32
|
+
if result
|
33
|
+
write(f, dump(result))
|
34
|
+
end
|
35
|
+
ensure
|
36
|
+
f.flock(File::LOCK_UN)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
rescue Errno::ENOENT, Exception => e
|
40
|
+
ScoutApm::Agent.instance.logger.error("Unable to access the layaway file [#{e.class} - #{e.message}]. " +
|
41
|
+
"The user running the app must have read & write access. " +
|
42
|
+
"Change the path by setting the `data_file` key in scout_apm.yml"
|
43
|
+
)
|
44
|
+
ScoutApm::Agent.instance.logger.debug(e.backtrace.join("\n\t"))
|
45
|
+
|
46
|
+
# ensure the in-memory metric hash is cleared so data doesn't continue to accumulate.
|
47
|
+
# ScoutApm::Agent.instance.store.metric_hash = {}
|
23
48
|
end
|
24
49
|
|
25
|
-
def
|
26
|
-
|
50
|
+
def get_data(f)
|
51
|
+
data = read_until_end(f)
|
52
|
+
result = load(data)
|
53
|
+
f.truncate(0)
|
54
|
+
result
|
27
55
|
end
|
28
56
|
|
29
|
-
def
|
30
|
-
|
57
|
+
def write(f, string)
|
58
|
+
result = 0
|
59
|
+
while (result < string.length)
|
60
|
+
result += f.write_nonblock(string)
|
61
|
+
end
|
62
|
+
rescue Errno::EAGAIN, Errno::EINTR
|
63
|
+
IO.select(nil, [f])
|
64
|
+
retry
|
31
65
|
end
|
32
66
|
|
33
|
-
def
|
67
|
+
def read_until_end(f)
|
34
68
|
contents = ""
|
35
69
|
while true
|
36
70
|
contents << f.read_nonblock(10_000)
|
@@ -41,16 +75,5 @@ module ScoutApm
|
|
41
75
|
rescue EOFError
|
42
76
|
contents
|
43
77
|
end
|
44
|
-
|
45
|
-
def write_raw(f, data)
|
46
|
-
result = 0
|
47
|
-
while (result < data.length)
|
48
|
-
result += f.write_nonblock(data)
|
49
|
-
end
|
50
|
-
rescue Errno::EAGAIN, Errno::EINTR
|
51
|
-
IO.select(nil, [f])
|
52
|
-
retry
|
53
|
-
end
|
54
78
|
end
|
55
79
|
end
|
56
|
-
|
data/lib/scout_apm/layer.rb
CHANGED
@@ -7,7 +7,11 @@ module ScoutApm
|
|
7
7
|
|
8
8
|
# Name: a more specific name of this single item
|
9
9
|
# Examples: "Rack::Cache", "User#find", "users/index", "users/index.html.erb"
|
10
|
-
|
10
|
+
#
|
11
|
+
# Accessor, so we can update a layer if multiple pieces of instrumentation work
|
12
|
+
# together at different layers to fill in the full data. See the ActiveRecord
|
13
|
+
# instrumentation for an example of how this is useful
|
14
|
+
attr_accessor :name
|
11
15
|
|
12
16
|
# An array of children layers, in call order.
|
13
17
|
# For instance, if we are in a middleware, there will likely be only a single
|
@@ -30,12 +34,21 @@ module ScoutApm
|
|
30
34
|
# backtrace of where it occurred.
|
31
35
|
attr_reader :backtrace
|
32
36
|
|
37
|
+
# As we go through a part of a request, instrumentation can store additional data
|
38
|
+
# Known Keys:
|
39
|
+
# :record_count - The number of rows returned by an AR query (From notification instantiation.active_record)
|
40
|
+
# :class_name - The ActiveRecord class name (From notification instantiation.active_record)
|
41
|
+
attr_reader :annotations
|
42
|
+
|
33
43
|
BACKTRACE_CALLER_LIMIT = 30 # maximum number of lines to send thru for backtrace analysis
|
34
44
|
|
35
45
|
def initialize(type, name, start_time = Time.now)
|
36
46
|
@type = type
|
37
47
|
@name = name
|
48
|
+
@annotations = {}
|
38
49
|
@start_time = start_time
|
50
|
+
@allocations_start = ScoutApm::Instruments::Allocations.count
|
51
|
+
@allocations_stop = 0
|
39
52
|
@children = [] # In order of calls
|
40
53
|
@desc = nil
|
41
54
|
end
|
@@ -48,10 +61,20 @@ module ScoutApm
|
|
48
61
|
@stop_time = stop_time
|
49
62
|
end
|
50
63
|
|
64
|
+
# Fetch the current number of allocated objects. This will always increment - we fetch when initializing and when stopping the layer.
|
65
|
+
def record_allocations!
|
66
|
+
@allocations_stop = ScoutApm::Instruments::Allocations.count
|
67
|
+
end
|
68
|
+
|
51
69
|
def desc=(desc)
|
52
70
|
@desc = desc
|
53
71
|
end
|
54
72
|
|
73
|
+
# This data is internal to ScoutApm, to add custom information, use the Context api.
|
74
|
+
def annotate_layer(hsh)
|
75
|
+
@annotations.merge!(hsh)
|
76
|
+
end
|
77
|
+
|
55
78
|
def subscopable!
|
56
79
|
@subscopable = true
|
57
80
|
end
|
@@ -123,5 +146,26 @@ module ScoutApm
|
|
123
146
|
map { |child| child.total_call_time }.
|
124
147
|
inject(0) { |sum, time| sum + time }
|
125
148
|
end
|
149
|
+
|
150
|
+
######################################
|
151
|
+
# Allocation Calculations
|
152
|
+
######################################
|
153
|
+
|
154
|
+
# These are almost identical to the timing metrics.
|
155
|
+
|
156
|
+
def total_allocations
|
157
|
+
allocations = (@allocations_stop - @allocations_start)
|
158
|
+
allocations < 0 ? 0 : allocations
|
159
|
+
end
|
160
|
+
|
161
|
+
def total_exclusive_allocations
|
162
|
+
total_allocations - child_allocations
|
163
|
+
end
|
164
|
+
|
165
|
+
def child_allocations
|
166
|
+
children.
|
167
|
+
map { |child| child.total_allocations }.
|
168
|
+
inject(0) { |sum, obj| sum + obj }
|
169
|
+
end
|
126
170
|
end
|
127
171
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
module LayerConverters
|
3
|
+
class AllocationMetricConverter < ConverterBase
|
4
|
+
def call
|
5
|
+
scope = scope_layer
|
6
|
+
return {} unless scope
|
7
|
+
return {} unless ScoutApm::Instruments::Allocations::ENABLED
|
8
|
+
|
9
|
+
meta = MetricMeta.new("ObjectAllocations", {:scope => scope.legacy_metric_name})
|
10
|
+
stat = MetricStats.new
|
11
|
+
stat.update!(root_layer.total_allocations)
|
12
|
+
|
13
|
+
{ meta => stat }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -19,12 +19,10 @@ module ScoutApm
|
|
19
19
|
# render :update
|
20
20
|
# end
|
21
21
|
def scope_layer
|
22
|
-
@scope_layer ||=
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
walker.walk do |layer|
|
27
|
-
return layer if layer.type == layer_type
|
22
|
+
@scope_layer ||= walker.walk do |layer|
|
23
|
+
if layer.type == "Controller"
|
24
|
+
break layer
|
25
|
+
end
|
28
26
|
end
|
29
27
|
end
|
30
28
|
end
|
@@ -57,6 +57,7 @@ module ScoutApm
|
|
57
57
|
walker.walk do |layer|
|
58
58
|
next if layer == job_layer
|
59
59
|
next if layer == queue_layer
|
60
|
+
next if layer.annotations[:ignorable]
|
60
61
|
|
61
62
|
# we don't need to use the full metric name for scoped metrics as we
|
62
63
|
# only display metrics aggregrated by type, just use "ActiveRecord"
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# Take a TrackedRequest and turn it into a hash of:
|
2
2
|
# MetricMeta => MetricStats
|
3
|
-
|
4
3
|
module ScoutApm
|
5
4
|
module LayerConverters
|
6
5
|
class MetricConverter < ConverterBase
|
@@ -22,6 +21,8 @@ module ScoutApm
|
|
22
21
|
metric_hash = Hash.new
|
23
22
|
|
24
23
|
walker.walk do |layer|
|
24
|
+
next if layer.annotations[:ignorable]
|
25
|
+
|
25
26
|
meta_options = if layer == scope_layer # We don't scope the controller under itself
|
26
27
|
{}
|
27
28
|
else
|