scout_apm 0.1.6 → 0.1.7
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.rb +48 -23
- data/lib/scout_apm/agent.rb +93 -130
- data/lib/scout_apm/agent/reporting.rb +34 -63
- data/lib/scout_apm/app_server_load.rb +29 -0
- data/lib/scout_apm/background_worker.rb +6 -6
- data/lib/scout_apm/capacity.rb +48 -48
- data/lib/scout_apm/config.rb +5 -5
- data/lib/scout_apm/context.rb +3 -3
- data/lib/scout_apm/environment.rb +64 -100
- data/lib/scout_apm/framework_integrations/rails_2.rb +32 -0
- data/lib/scout_apm/framework_integrations/rails_3_or_4.rb +33 -0
- data/lib/scout_apm/framework_integrations/ruby.rb +26 -0
- data/lib/scout_apm/framework_integrations/sinatra.rb +27 -0
- data/lib/scout_apm/instruments/active_record_instruments.rb +1 -1
- data/lib/scout_apm/instruments/mongoid_instruments.rb +1 -1
- data/lib/scout_apm/instruments/moped_instruments.rb +3 -3
- data/lib/scout_apm/instruments/net_http.rb +2 -2
- data/lib/scout_apm/instruments/process/process_cpu.rb +41 -20
- data/lib/scout_apm/instruments/process/process_memory.rb +45 -30
- data/lib/scout_apm/instruments/rails/action_controller_instruments.rb +20 -18
- data/lib/scout_apm/instruments/rails3_or_4/action_controller_instruments.rb +17 -14
- data/lib/scout_apm/layaway.rb +12 -12
- data/lib/scout_apm/layaway_file.rb +1 -1
- data/lib/scout_apm/metric_meta.rb +9 -9
- data/lib/scout_apm/metric_stats.rb +8 -8
- data/lib/scout_apm/reporter.rb +83 -0
- data/lib/scout_apm/serializers/app_server_load_serializer.rb +15 -0
- data/lib/scout_apm/serializers/directive_serializer.rb +15 -0
- data/lib/scout_apm/serializers/payload_serializer.rb +14 -0
- data/lib/scout_apm/server_integrations/null.rb +30 -0
- data/lib/scout_apm/server_integrations/passenger.rb +35 -0
- data/lib/scout_apm/server_integrations/puma.rb +30 -0
- data/lib/scout_apm/server_integrations/rainbows.rb +36 -0
- data/lib/scout_apm/server_integrations/thin.rb +41 -0
- data/lib/scout_apm/server_integrations/unicorn.rb +35 -0
- data/lib/scout_apm/server_integrations/webrick.rb +25 -0
- data/lib/scout_apm/slow_transaction.rb +3 -3
- data/lib/scout_apm/stack_item.rb +19 -17
- data/lib/scout_apm/store.rb +35 -35
- data/lib/scout_apm/tracer.rb +121 -110
- data/lib/scout_apm/utils/sql_sanitizer.rb +2 -2
- data/lib/scout_apm/version.rb +2 -1
- data/test/unit/environment_test.rb +5 -5
- metadata +18 -2
@@ -0,0 +1,33 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
module FrameworkIntegrations
|
3
|
+
class Rails3Or4
|
4
|
+
def name
|
5
|
+
:rails3_or_4
|
6
|
+
end
|
7
|
+
|
8
|
+
def version
|
9
|
+
Rails::VERSION::STRING
|
10
|
+
end
|
11
|
+
|
12
|
+
def present?
|
13
|
+
defined?(::Rails) &&
|
14
|
+
defined?(ActionController) &&
|
15
|
+
Rails::VERSION::MAJOR >= 3
|
16
|
+
end
|
17
|
+
|
18
|
+
def application_name
|
19
|
+
if defined?(::Rails)
|
20
|
+
::Rails.application.class.to_s
|
21
|
+
.sub(/::Application$/, '')
|
22
|
+
end
|
23
|
+
rescue
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def env
|
28
|
+
::Rails.env
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
module FrameworkIntegrations
|
3
|
+
class Ruby
|
4
|
+
def name
|
5
|
+
:ruby
|
6
|
+
end
|
7
|
+
|
8
|
+
def version
|
9
|
+
"Unknown"
|
10
|
+
end
|
11
|
+
|
12
|
+
def present?
|
13
|
+
true
|
14
|
+
end
|
15
|
+
|
16
|
+
# TODO: Fetch the name (Somehow?)
|
17
|
+
def application_name
|
18
|
+
"Ruby"
|
19
|
+
end
|
20
|
+
|
21
|
+
def env
|
22
|
+
ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
module FrameworkIntegrations
|
3
|
+
class Sinatra
|
4
|
+
def name
|
5
|
+
:sinatra
|
6
|
+
end
|
7
|
+
|
8
|
+
def version
|
9
|
+
Sinatra::VERSION
|
10
|
+
end
|
11
|
+
|
12
|
+
def present?
|
13
|
+
defined?(::Sinatra) &&
|
14
|
+
defined?(::Sinatra::Base)
|
15
|
+
end
|
16
|
+
|
17
|
+
# TODO: Fetch the name
|
18
|
+
def application_name
|
19
|
+
"Sinatra"
|
20
|
+
end
|
21
|
+
|
22
|
+
def env
|
23
|
+
ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -3,7 +3,7 @@ require 'scout_apm/utils/sql_sanitizer'
|
|
3
3
|
module ScoutApm
|
4
4
|
module Instruments
|
5
5
|
# Contains ActiveRecord instrument, aliasing +ActiveRecord::ConnectionAdapters::AbstractAdapter#log+ calls
|
6
|
-
# to trace calls to the database.
|
6
|
+
# to trace calls to the database.
|
7
7
|
module ActiveRecordInstruments
|
8
8
|
def self.included(instrumented_class)
|
9
9
|
ScoutApm::Agent.instance.logger.debug "Instrumenting #{instrumented_class.inspect}"
|
@@ -12,13 +12,13 @@ if defined?(::Moped)
|
|
12
12
|
end
|
13
13
|
alias_method :process_without_scout_instruments, :process
|
14
14
|
alias_method :process, :process_with_scout_instruments
|
15
|
-
|
15
|
+
|
16
16
|
# replaces values w/ ?
|
17
17
|
def scout_sanitize_log(log)
|
18
18
|
return nil if log.length > 1000 # safeguard - don't sanitize large SQL statements
|
19
|
-
log.gsub(/(=>")((?:[^"]|"")*)"/) do
|
19
|
+
log.gsub(/(=>")((?:[^"]|"")*)"/) do
|
20
20
|
$1 + '?' + '"'
|
21
21
|
end
|
22
22
|
end
|
23
23
|
end
|
24
|
-
end
|
24
|
+
end
|
@@ -2,7 +2,7 @@ if defined?(::Net) && defined?(Net::HTTP)
|
|
2
2
|
ScoutApm::Agent.instance.logger.debug "Instrumenting Net::HTTP"
|
3
3
|
Net::HTTP.class_eval do
|
4
4
|
include ScoutApm::Tracer
|
5
|
-
|
5
|
+
|
6
6
|
def request_with_scout_instruments(*args,&block)
|
7
7
|
self.class.instrument("HTTP/request", :desc => "#{(@address+args.first.path.split('?').first)[0..99]}") do
|
8
8
|
request_without_scout_instruments(*args,&block)
|
@@ -11,4 +11,4 @@ if defined?(::Net) && defined?(Net::HTTP)
|
|
11
11
|
alias request_without_scout_instruments request
|
12
12
|
alias request request_with_scout_instruments
|
13
13
|
end
|
14
|
-
end
|
14
|
+
end
|
@@ -1,26 +1,47 @@
|
|
1
|
-
module ScoutApm
|
2
|
-
module
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
1
|
+
module ScoutApm
|
2
|
+
module Instruments
|
3
|
+
module Process
|
4
|
+
class ProcessCpu
|
5
|
+
attr_reader :logger
|
6
|
+
|
7
|
+
def initialize(num_processors, logger)
|
8
|
+
@num_processors = num_processors || 1
|
9
|
+
@logger = logger
|
10
|
+
|
11
|
+
t = ::Process.times
|
12
|
+
@last_run = Time.now
|
13
|
+
@last_utime = t.utime
|
14
|
+
@last_stime = t.stime
|
15
|
+
end
|
16
|
+
|
17
|
+
def metric_name
|
18
|
+
"CPU/Utilization"
|
19
|
+
end
|
20
|
+
|
21
|
+
def human_name
|
22
|
+
"Process CPU"
|
23
|
+
end
|
7
24
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
25
|
+
def run
|
26
|
+
res = nil
|
27
|
+
now = Time.now
|
28
|
+
t = ::Process.times
|
29
|
+
if @last_run
|
30
|
+
elapsed_time = now - @last_run
|
31
|
+
if elapsed_time >= 1
|
32
|
+
user_time_since_last_sample = t.utime - @last_utime
|
33
|
+
system_time_since_last_sample = t.stime - @last_stime
|
34
|
+
res = ((user_time_since_last_sample + system_time_since_last_sample)/(elapsed_time * @num_processors))*100
|
35
|
+
end
|
18
36
|
end
|
37
|
+
@last_utime = t.utime
|
38
|
+
@last_stime = t.stime
|
39
|
+
@last_run = now
|
40
|
+
|
41
|
+
logger.debug "#{human_name}: #{res.inspect} [#{Environment.instance.processors} CPU(s)]"
|
42
|
+
|
43
|
+
return res
|
19
44
|
end
|
20
|
-
@last_utime = t.utime
|
21
|
-
@last_stime = t.stime
|
22
|
-
@last_run = now
|
23
|
-
return res
|
24
45
|
end
|
25
46
|
end
|
26
47
|
end
|
@@ -1,39 +1,54 @@
|
|
1
|
-
module ScoutApm
|
2
|
-
module
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
res = get_mem_from_procfile
|
10
|
-
elsif platform =~ /darwin9/ # 10.5
|
11
|
-
res = get_mem_from_shell("ps -o rsz")
|
12
|
-
elsif platform =~ /darwin1[01]/ # 10.6 & 10.7
|
13
|
-
res = get_mem_from_shell("ps -o rss")
|
1
|
+
module ScoutApm
|
2
|
+
module Instruments
|
3
|
+
module Process
|
4
|
+
class ProcessMemory
|
5
|
+
attr_reader :logger
|
6
|
+
|
7
|
+
def initialize(logger)
|
8
|
+
@logger = logger
|
14
9
|
end
|
15
|
-
return res
|
16
|
-
end
|
17
10
|
|
18
|
-
|
11
|
+
def metric_name
|
12
|
+
"Memory/Physical"
|
13
|
+
end
|
19
14
|
|
20
|
-
|
21
|
-
|
22
|
-
proc_status = File.open(procfile, "r") { |f| f.read_nonblock(4096).strip }
|
23
|
-
if proc_status =~ /RSS:\s*(\d+) kB/i
|
24
|
-
res= $1.to_f / 1024.0
|
15
|
+
def human_name
|
16
|
+
"Process Memory"
|
25
17
|
end
|
26
|
-
res
|
27
|
-
end
|
28
18
|
|
29
|
-
|
30
|
-
|
31
|
-
|
19
|
+
def run
|
20
|
+
case RUBY_PLATFORM.downcase
|
21
|
+
when /linux/
|
22
|
+
get_mem_from_procfile
|
23
|
+
when /darwin9/ # 10.5
|
24
|
+
get_mem_from_shell("ps -o rsz")
|
25
|
+
when /darwin1[0123]/ # 10.6 - 10.10
|
26
|
+
get_mem_from_shell("ps -o rss")
|
27
|
+
else
|
28
|
+
0 # What default? was nil.
|
29
|
+
end.tap { |res| logger.debug "#{human_name}: #{res.inspect}" }
|
30
|
+
end
|
32
31
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
32
|
+
private
|
33
|
+
|
34
|
+
def get_mem_from_procfile
|
35
|
+
res = nil
|
36
|
+
proc_status = File.open(procfile, "r") { |f| f.read_nonblock(4096).strip }
|
37
|
+
if proc_status =~ /RSS:\s*(\d+) kB/i
|
38
|
+
res= $1.to_f / 1024.0
|
39
|
+
end
|
40
|
+
res
|
41
|
+
end
|
42
|
+
|
43
|
+
def procfile
|
44
|
+
"/proc/#{$$}/status"
|
45
|
+
end
|
46
|
+
|
47
|
+
# memory in MB the current process is using
|
48
|
+
def get_mem_from_shell(command)
|
49
|
+
res = `#{command} #{$$}`.split("\n")[1].to_f / 1024.0 #rescue nil
|
50
|
+
res
|
51
|
+
end
|
37
52
|
end
|
38
53
|
end
|
39
54
|
end
|
@@ -1,23 +1,25 @@
|
|
1
|
-
module ScoutApm
|
2
|
-
module
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
1
|
+
module ScoutApm
|
2
|
+
module Instruments
|
3
|
+
module ActionControllerInstruments
|
4
|
+
def self.included(instrumented_class)
|
5
|
+
ScoutApm::Agent.instance.logger.debug "Instrumenting #{instrumented_class.inspect}"
|
6
|
+
instrumented_class.class_eval do
|
7
|
+
unless instrumented_class.method_defined?(:perform_action_without_scout_instruments)
|
8
|
+
alias_method :perform_action_without_scout_instruments, :perform_action
|
9
|
+
alias_method :perform_action, :perform_action_with_scout_instruments
|
10
|
+
private :perform_action
|
11
|
+
end
|
10
12
|
end
|
11
13
|
end
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
14
|
+
|
15
|
+
# In addition to instrumenting actions, this also sets the scope to the controller action name. The scope is later
|
16
|
+
# applied to metrics recorded during this transaction. This lets us associate ActiveRecord calls with
|
17
|
+
# specific controller actions.
|
18
|
+
def perform_action_with_scout_instruments(*args, &block)
|
19
|
+
scout_controller_action = "Controller/#{controller_path}/#{action_name}"
|
20
|
+
self.class.scout_apm_trace(scout_controller_action, :uri => request.request_uri, :ip => request.remote_ip) do
|
21
|
+
perform_action_without_scout_instruments(*args, &block)
|
22
|
+
end
|
21
23
|
end
|
22
24
|
end
|
23
25
|
end
|
@@ -1,18 +1,20 @@
|
|
1
1
|
# Rails 3/4
|
2
|
-
module ScoutApm
|
3
|
-
module
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
2
|
+
module ScoutApm
|
3
|
+
module Instruments
|
4
|
+
module ActionControllerInstruments
|
5
|
+
# Instruments the action and tracks errors.
|
6
|
+
def process_action(*args)
|
7
|
+
scout_controller_action = "Controller/#{controller_path}/#{action_name}"
|
8
|
+
|
9
|
+
self.class.scout_apm_trace(scout_controller_action, :uri => request.fullpath, :ip => request.remote_ip) do
|
10
|
+
begin
|
11
|
+
super
|
12
|
+
rescue Exception
|
13
|
+
ScoutApm::Agent.instance.store.track!("Errors/Request",1, :scope => nil)
|
14
|
+
raise
|
15
|
+
ensure
|
16
|
+
Thread::current[:scout_apm_scope_name] = nil
|
17
|
+
end
|
16
18
|
end
|
17
19
|
end
|
18
20
|
end
|
@@ -36,3 +38,4 @@ if defined?(ActionView) && defined?(ActionView::PartialRenderer)
|
|
36
38
|
instrument_method :render_partial, :metric_name => 'View/#{@template.virtual_path}/Rendering', :scope => true
|
37
39
|
end
|
38
40
|
end
|
41
|
+
|
data/lib/scout_apm/layaway.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# Stores metrics in a file before sending them to the server. Two uses:
|
2
2
|
# 1. A centralized store for multiple Agent processes. This way, only 1 checkin is sent to Scout rather than 1 per-process.
|
3
3
|
# 2. Bundling up reports from multiple timeslices to make updates more efficent server-side.
|
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
7
|
class ScoutApm::Layaway
|
@@ -9,7 +9,7 @@ class ScoutApm::Layaway
|
|
9
9
|
def initialize
|
10
10
|
@file = ScoutApm::LayawayFile.new
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
13
|
def deposit_and_deliver
|
14
14
|
new_metrics = ScoutApm::Agent.instance.store.metric_hash
|
15
15
|
log_deposited_metrics(new_metrics)
|
@@ -18,14 +18,14 @@ class ScoutApm::Layaway
|
|
18
18
|
file.read_and_write do |old_data|
|
19
19
|
old_data ||= Hash.new
|
20
20
|
# merge data
|
21
|
-
# if (1) there's data in the file and (2) there isn't any data yet for the current minute, this means we've
|
21
|
+
# if (1) there's data in the file and (2) there isn't any data yet for the current minute, this means we've
|
22
22
|
# collected all metrics for the previous slots and we're ready to deliver.
|
23
23
|
#
|
24
24
|
# Example w/2 processes:
|
25
25
|
#
|
26
26
|
# 12:00:34 ---
|
27
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.
|
28
|
+
# Process 2: old_data_any? => true and old_data[12:00].nil? => false, so deposits.
|
29
29
|
#
|
30
30
|
# 12:01:34 ---
|
31
31
|
# Process 1: old_data.any? => true and old_data[12:01].nil? => true, so delivers metrics.
|
@@ -44,11 +44,11 @@ class ScoutApm::Layaway
|
|
44
44
|
end
|
45
45
|
to_deliver.any? ? validate_data(to_deliver) : {}
|
46
46
|
end
|
47
|
-
|
48
|
-
# Ensures the data we're sending to the server isn't stale.
|
49
|
-
# This can occur if the agent is collecting data, and app server goes down w/data in the local storage.
|
47
|
+
|
48
|
+
# Ensures the data we're sending to the server isn't stale.
|
49
|
+
# This can occur if the agent is collecting data, and app server goes down w/data in the local storage.
|
50
50
|
# When it is restarted later data will remain in local storage but it won't be for the current reporting interval.
|
51
|
-
#
|
51
|
+
#
|
52
52
|
# If the data is stale, an empty Hash is returned. Otherwise, the data from the most recent slot is returned.
|
53
53
|
def validate_data(data)
|
54
54
|
data = data.to_a.sort
|
@@ -63,14 +63,14 @@ class ScoutApm::Layaway
|
|
63
63
|
ScoutApm::Agent.instance.logger.debug $!.message
|
64
64
|
ScoutApm::Agent.instance.logger.debug $!.backtrace
|
65
65
|
end
|
66
|
-
|
66
|
+
|
67
67
|
# Data is stored under timestamp-keys (without the second).
|
68
68
|
def slot
|
69
69
|
t = Time.now
|
70
70
|
t -= t.sec
|
71
71
|
t.to_i
|
72
72
|
end
|
73
|
-
|
73
|
+
|
74
74
|
def log_deposited_metrics(new_metrics)
|
75
75
|
controller_count = 0
|
76
76
|
new_metrics.each do |meta,stats|
|
@@ -84,7 +84,7 @@ class ScoutApm::Layaway
|
|
84
84
|
def log_deposited_slow_transactions(new_slow_transactions)
|
85
85
|
ScoutApm::Agent.instance.logger.debug "Depositing #{new_slow_transactions.size} slow transactions into #{Time.at(slot).strftime("%m/%d/%y %H:%M:%S %z")} slot."
|
86
86
|
end
|
87
|
-
|
87
|
+
|
88
88
|
def log_saved_data(old_data,new_metrics)
|
89
89
|
ScoutApm::Agent.instance.logger.debug "Saving the following #{old_data.size} time slots locally:"
|
90
90
|
old_data.each do |k,v|
|
@@ -97,4 +97,4 @@ class ScoutApm::Layaway
|
|
97
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
98
|
end
|
99
99
|
end
|
100
|
-
end
|
100
|
+
end
|