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