scout_apm 0.1.12 → 0.1.13
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 +4 -0
- data/lib/scout_apm/agent/logging.rb +1 -1
- data/lib/scout_apm/agent.rb +14 -8
- data/lib/scout_apm/background_worker.rb +37 -32
- data/lib/scout_apm/capacity.rb +44 -42
- data/lib/scout_apm/environment.rb +1 -19
- data/lib/scout_apm/framework_integrations/rails_2.rb +24 -0
- data/lib/scout_apm/framework_integrations/rails_3_or_4.rb +21 -0
- data/lib/scout_apm/framework_integrations/ruby.rb +5 -0
- data/lib/scout_apm/framework_integrations/sinatra.rb +5 -0
- data/lib/scout_apm/instruments/action_controller_rails_2.rb +68 -0
- data/lib/scout_apm/instruments/action_controller_rails_3.rb +64 -0
- data/lib/scout_apm/instruments/{active_record_instruments.rb → active_record.rb} +43 -22
- data/lib/scout_apm/instruments/mongoid.rb +34 -0
- data/lib/scout_apm/instruments/moped.rb +49 -0
- data/lib/scout_apm/instruments/net_http.rb +33 -10
- data/lib/scout_apm/layaway.rb +82 -78
- data/lib/scout_apm/layaway_file.rb +59 -57
- data/lib/scout_apm/server_integrations/unicorn.rb +9 -1
- data/lib/scout_apm/store.rb +193 -184
- data/lib/scout_apm/utils/time.rb +12 -0
- data/lib/scout_apm/version.rb +1 -1
- data/lib/scout_apm.rb +11 -3
- metadata +8 -7
- data/lib/scout_apm/instruments/mongoid_instruments.rb +0 -10
- data/lib/scout_apm/instruments/moped_instruments.rb +0 -24
- data/lib/scout_apm/instruments/rails/action_controller_instruments.rb +0 -48
- data/lib/scout_apm/instruments/rails3_or_4/action_controller_instruments.rb +0 -41
@@ -1,14 +1,37 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
1
|
+
module ScoutApm
|
2
|
+
module Instruments
|
3
|
+
class NetHttp
|
4
|
+
attr_reader :logger
|
5
|
+
|
6
|
+
def initalize(logger=ScoutApm::Agent.instance.logger)
|
7
|
+
@logger = logger
|
8
|
+
@installed = false
|
9
|
+
end
|
10
|
+
|
11
|
+
def installed?
|
12
|
+
@installed
|
13
|
+
end
|
14
|
+
|
15
|
+
def install
|
16
|
+
@installed = true
|
17
|
+
|
18
|
+
if defined?(::Net) && defined?(::Net::HTTP)
|
19
|
+
ScoutApm::Agent.instance.logger.debug "Instrumenting Net::HTTP"
|
20
|
+
|
21
|
+
::Net::HTTP.class_eval do
|
22
|
+
include ScoutApm::Tracer
|
23
|
+
|
24
|
+
def request_with_scout_instruments(*args,&block)
|
25
|
+
self.class.instrument("HTTP/request", :desc => "#{(@address+args.first.path.split('?').first)[0..99]}") do
|
26
|
+
request_without_scout_instruments(*args,&block)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
alias request_without_scout_instruments request
|
30
|
+
alias request request_with_scout_instruments
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
9
34
|
end
|
10
35
|
end
|
11
|
-
alias request_without_scout_instruments request
|
12
|
-
alias request request_with_scout_instruments
|
13
36
|
end
|
14
37
|
end
|
data/lib/scout_apm/layaway.rb
CHANGED
@@ -4,97 +4,101 @@
|
|
4
4
|
#
|
5
5
|
# Data is stored in a Hash, where the keys are Time.to_i on the minute. The value is a Hash {:metrics => Hash, :slow_transactions => Array}.
|
6
6
|
# When depositing data, the new data is either merged with an existing time or placed in a new key.
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
module ScoutApm
|
8
|
+
class Layaway
|
9
|
+
attr_accessor :file
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@file = ScoutApm::LayawayFile.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def deposit_and_deliver
|
16
|
+
new_metrics = ScoutApm::Agent.instance.store.metric_hash
|
17
|
+
log_deposited_metrics(new_metrics)
|
18
|
+
log_deposited_slow_transactions(ScoutApm::Agent.instance.store.slow_transactions)
|
19
|
+
to_deliver = {}
|
20
|
+
file.read_and_write do |old_data|
|
21
|
+
old_data ||= Hash.new
|
22
|
+
# merge data
|
23
|
+
# if (1) there's data in the file and (2) there isn't any data yet for the current minute, this means we've
|
24
|
+
# collected all metrics for the previous slots and we're ready to deliver.
|
25
|
+
#
|
26
|
+
# Example w/2 processes:
|
27
|
+
#
|
28
|
+
# 12:00:34 ---
|
29
|
+
# Process 1: old_data.any? => false, so deposits.
|
30
|
+
# Process 2: old_data_any? => true and old_data[12:00].nil? => false, so deposits.
|
31
|
+
#
|
32
|
+
# 12:01:34 ---
|
33
|
+
# Process 1: old_data.any? => true and old_data[12:01].nil? => true, so delivers metrics.
|
34
|
+
# Process 2: old_data.any? => true and old_data[12:01].nil? => false, so deposits.
|
35
|
+
if old_data.any? and old_data[slot].nil?
|
36
|
+
to_deliver = old_data
|
37
|
+
old_data = Hash.new
|
38
|
+
elsif old_data.any?
|
39
|
+
ScoutApm::Agent.instance.logger.debug "Not yet time to deliver payload for slot [#{Utils::Time.to_s(old_data.keys.sort.last)}]"
|
40
|
+
else
|
41
|
+
ScoutApm::Agent.instance.logger.debug "There is no data in the layaway file to deliver."
|
42
|
+
end
|
43
|
+
old_data[slot]=ScoutApm::Agent.instance.store.merge_data_and_clear(old_data[slot] || {:metrics => {}, :slow_transactions => []})
|
44
|
+
log_saved_data(old_data,new_metrics)
|
45
|
+
old_data
|
46
|
+
end
|
47
|
+
to_deliver.any? ? validate_data(to_deliver) : {}
|
48
|
+
end
|
12
49
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
# Example w/2 processes:
|
25
|
-
#
|
26
|
-
# 12:00:34 ---
|
27
|
-
# Process 1: old_data.any? => false, so deposits.
|
28
|
-
# Process 2: old_data_any? => true and old_data[12:00].nil? => false, so deposits.
|
29
|
-
#
|
30
|
-
# 12:01:34 ---
|
31
|
-
# Process 1: old_data.any? => true and old_data[12:01].nil? => true, so delivers metrics.
|
32
|
-
# Process 2: old_data.any? => true and old_data[12:01].nil? => false, so deposits.
|
33
|
-
if old_data.any? and old_data[slot].nil?
|
34
|
-
to_deliver = old_data
|
35
|
-
old_data = Hash.new
|
36
|
-
elsif old_data.any?
|
37
|
-
ScoutApm::Agent.instance.logger.debug "Not yet time to deliver payload for slot [#{Time.at(old_data.keys.sort.last).strftime("%m/%d/%y %H:%M:%S %z")}]"
|
50
|
+
# Ensures the data we're sending to the server isn't stale.
|
51
|
+
# This can occur if the agent is collecting data, and app server goes down w/data in the local storage.
|
52
|
+
# When it is restarted later data will remain in local storage but it won't be for the current reporting interval.
|
53
|
+
#
|
54
|
+
# If the data is stale, an empty Hash is returned. Otherwise, the data from the most recent slot is returned.
|
55
|
+
def validate_data(data)
|
56
|
+
data = data.to_a.sort
|
57
|
+
now = Time.now
|
58
|
+
if (most_recent = data.first.first) < now.to_i - 2*60
|
59
|
+
ScoutApm::Agent.instance.logger.debug "Local Storage is stale (#{Utils::Time.to_s(most_recent)}). Not sending data."
|
60
|
+
{}
|
38
61
|
else
|
39
|
-
|
62
|
+
data.first.last
|
40
63
|
end
|
41
|
-
|
42
|
-
|
43
|
-
|
64
|
+
rescue
|
65
|
+
ScoutApm::Agent.instance.logger.debug $!.message
|
66
|
+
ScoutApm::Agent.instance.logger.debug $!.backtrace
|
44
67
|
end
|
45
|
-
to_deliver.any? ? validate_data(to_deliver) : {}
|
46
|
-
end
|
47
68
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
data = data.to_a.sort
|
55
|
-
now = Time.now
|
56
|
-
if (most_recent = data.first.first) < now.to_i - 2*60
|
57
|
-
ScoutApm::Agent.instance.logger.debug "Local Storage is stale (#{Time.at(most_recent).strftime("%m/%d/%y %H:%M:%S %z")}). Not sending data."
|
58
|
-
{}
|
59
|
-
else
|
60
|
-
data.first.last
|
69
|
+
# Data is stored under timestamp-keys, aligned to the beginning of the
|
70
|
+
# current minute
|
71
|
+
def slot
|
72
|
+
t = Time.now
|
73
|
+
t -= t.sec
|
74
|
+
t.to_i
|
61
75
|
end
|
62
|
-
rescue
|
63
|
-
ScoutApm::Agent.instance.logger.debug $!.message
|
64
|
-
ScoutApm::Agent.instance.logger.debug $!.backtrace
|
65
|
-
end
|
66
76
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
77
|
+
def log_deposited_metrics(new_metrics)
|
78
|
+
request_count = new_metrics.
|
79
|
+
to_a.
|
80
|
+
select { |meta, stats| meta.metric_name =~ /\AController/ }.
|
81
|
+
map { |meta, stats| stats.call_count }.
|
82
|
+
inject(0) { |total, i| total + i }
|
73
83
|
|
74
|
-
|
75
|
-
controller_count = 0
|
76
|
-
new_metrics.each do |meta,stats|
|
77
|
-
if meta.metric_name =~ /\AController/
|
78
|
-
controller_count += stats.call_count
|
79
|
-
end
|
84
|
+
ScoutApm::Agent.instance.logger.debug "Depositing #{request_count} requests into #{Utils::Time.to_s(slot)} slot."
|
80
85
|
end
|
81
|
-
ScoutApm::Agent.instance.logger.debug "Depositing #{controller_count} requests into #{Time.at(slot).strftime("%m/%d/%y %H:%M:%S %z")} slot."
|
82
|
-
end
|
83
86
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
+
def log_deposited_slow_transactions(new_slow_transactions)
|
88
|
+
ScoutApm::Agent.instance.logger.debug "Depositing #{new_slow_transactions.size} slow transactions into #{Utils::Time.to_s(slot)} slot."
|
89
|
+
end
|
87
90
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
91
|
+
def log_saved_data(old_data,new_metrics)
|
92
|
+
ScoutApm::Agent.instance.logger.debug "Saving the following #{old_data.size} time slots locally:"
|
93
|
+
old_data.each do |k,v|
|
94
|
+
controller_count = 0
|
95
|
+
new_metrics.each do |meta,stats|
|
96
|
+
if meta.metric_name =~ /\AController/
|
97
|
+
controller_count += stats.call_count
|
98
|
+
end
|
95
99
|
end
|
100
|
+
ScoutApm::Agent.instance.logger.debug "#{Utils::Time.to_s(k)} => #{controller_count} requests and #{v[:slow_transactions].size} slow transactions"
|
96
101
|
end
|
97
|
-
ScoutApm::Agent.instance.logger.debug "#{Time.at(k).strftime("%m/%d/%y %H:%M:%S %z")} => #{controller_count} requests and #{v[:slow_transactions].size} slow transactions"
|
98
102
|
end
|
99
103
|
end
|
100
104
|
end
|
@@ -1,72 +1,74 @@
|
|
1
1
|
# Logic for the serialized file access
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
2
|
+
module ScoutApm
|
3
|
+
class LayawayFile
|
4
|
+
def path
|
5
|
+
"#{ScoutApm::Agent.instance.default_log_path}/scout_apm.db"
|
6
|
+
end
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
8
|
+
def dump(object)
|
9
|
+
Marshal.dump(object)
|
10
|
+
end
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
def load(dump)
|
13
|
+
if dump.size == 0
|
14
|
+
ScoutApm::Agent.instance.logger.debug("No data in layaway file.")
|
15
|
+
return nil
|
16
|
+
end
|
17
|
+
Marshal.load(dump)
|
18
|
+
rescue ArgumentError, TypeError => e
|
19
|
+
ScoutApm::Agent.instance.logger.debug("Error loading data from layaway file: #{e.inspect}")
|
20
|
+
ScoutApm::Agent.instance.logger.debug(e.backtrace.inspect)
|
21
|
+
nil
|
15
22
|
end
|
16
|
-
Marshal.load(dump)
|
17
|
-
rescue ArgumentError, TypeError => e
|
18
|
-
ScoutApm::Agent.instance.logger.debug("Error loading data from layaway file: #{e.inspect}")
|
19
|
-
ScoutApm::Agent.instance.logger.debug(e.backtrace.inspect)
|
20
|
-
nil
|
21
|
-
end
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
24
|
+
def read_and_write
|
25
|
+
File.open(path, File::RDWR | File::CREAT) do |f|
|
26
|
+
f.flock(File::LOCK_EX)
|
27
|
+
begin
|
28
|
+
result = (yield get_data(f))
|
29
|
+
f.rewind
|
30
|
+
f.truncate(0)
|
31
|
+
if result
|
32
|
+
write(f, dump(result))
|
33
|
+
end
|
34
|
+
ensure
|
35
|
+
f.flock(File::LOCK_UN)
|
32
36
|
end
|
33
|
-
ensure
|
34
|
-
f.flock(File::LOCK_UN)
|
35
37
|
end
|
38
|
+
rescue Errno::ENOENT, Exception => e
|
39
|
+
ScoutApm::Agent.instance.logger.error("Unable to access the layaway file [#{e.message}]. The user running the app must have read+write access.")
|
40
|
+
ScoutApm::Agent.instance.logger.debug(e.backtrace.split("\n"))
|
41
|
+
# ensure the in-memory metric hash is cleared so data doesn't continue to accumulate.
|
42
|
+
ScoutApm::Agent.instance.store.metric_hash = {}
|
36
43
|
end
|
37
|
-
rescue Errno::ENOENT, Exception => e
|
38
|
-
ScoutApm::Agent.instance.logger.error("Unable to access the layaway file [#{e.message}]. The user running the app must have read+write access.")
|
39
|
-
ScoutApm::Agent.instance.logger.debug(e.backtrace.split("\n"))
|
40
|
-
# ensure the in-memory metric hash is cleared so data doesn't continue to accumulate.
|
41
|
-
ScoutApm::Agent.instance.store.metric_hash = {}
|
42
|
-
end
|
43
44
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
45
|
+
def get_data(f)
|
46
|
+
data = read_until_end(f)
|
47
|
+
result = load(data)
|
48
|
+
f.truncate(0)
|
49
|
+
result
|
50
|
+
end
|
50
51
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
52
|
+
def write(f, string)
|
53
|
+
result = 0
|
54
|
+
while (result < string.length)
|
55
|
+
result += f.write_nonblock(string)
|
56
|
+
end
|
57
|
+
rescue Errno::EAGAIN, Errno::EINTR
|
58
|
+
IO.select(nil, [f])
|
59
|
+
retry
|
55
60
|
end
|
56
|
-
rescue Errno::EAGAIN, Errno::EINTR
|
57
|
-
IO.select(nil, [f])
|
58
|
-
retry
|
59
|
-
end
|
60
61
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
62
|
+
def read_until_end(f)
|
63
|
+
contents = ""
|
64
|
+
while true
|
65
|
+
contents << f.read_nonblock(10_000)
|
66
|
+
end
|
67
|
+
rescue Errno::EAGAIN, Errno::EINTR
|
68
|
+
IO.select([f])
|
69
|
+
retry
|
70
|
+
rescue EOFError
|
71
|
+
contents
|
65
72
|
end
|
66
|
-
rescue Errno::EAGAIN, Errno::EINTR
|
67
|
-
IO.select([f])
|
68
|
-
retry
|
69
|
-
rescue EOFError
|
70
|
-
contents
|
71
73
|
end
|
72
74
|
end
|
@@ -11,7 +11,14 @@ module ScoutApm
|
|
11
11
|
:unicorn
|
12
12
|
end
|
13
13
|
|
14
|
-
def forking
|
14
|
+
def forking?
|
15
|
+
return true unless (defined?(::Unicorn) && defined?(::Unicorn::Configurator))
|
16
|
+
ObjectSpace.each_object(::Unicorn::Configurator).first[:preload_app].tap {|x|
|
17
|
+
logger.info "Unicorn is forking? #{x}"
|
18
|
+
}
|
19
|
+
rescue
|
20
|
+
true
|
21
|
+
end
|
15
22
|
|
16
23
|
def present?
|
17
24
|
if defined?(::Unicorn) && defined?(::Unicorn::HttpServer)
|
@@ -37,3 +44,4 @@ module ScoutApm
|
|
37
44
|
end
|
38
45
|
end
|
39
46
|
end
|
47
|
+
|