scout_apm 1.6.8 → 2.0.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- 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
|