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
@@ -11,24 +11,24 @@ class ScoutApm::MetricMeta
|
|
11
11
|
attr_accessor :scope
|
12
12
|
attr_accessor :client_id
|
13
13
|
attr_accessor :desc, :extra
|
14
|
-
|
14
|
+
|
15
15
|
# To avoid conflicts with different JSON libaries
|
16
16
|
def to_json(*a)
|
17
17
|
%Q[{"metric_id":#{metric_id || 'null'},"metric_name":#{metric_name.to_json},"scope":#{scope.to_json || 'null'}}]
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
def ==(o)
|
21
21
|
self.eql?(o)
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
def hash
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
25
|
+
h = metric_name.downcase.hash
|
26
|
+
h ^= scope.downcase.hash unless scope.nil?
|
27
|
+
h ^= desc.downcase.hash unless desc.nil?
|
28
|
+
h
|
29
|
+
end
|
30
30
|
|
31
31
|
def eql?(o)
|
32
32
|
self.class == o.class && metric_name.downcase.eql?(o.metric_name.downcase) && scope == o.scope && client_id == o.client_id && desc == o.desc
|
33
33
|
end
|
34
|
-
end # class MetricMeta
|
34
|
+
end # class MetricMeta
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Stats that are associated with each instrumented method.
|
1
|
+
# Stats that are associated with each instrumented method.
|
2
2
|
class ScoutApm::MetricStats
|
3
3
|
attr_accessor :call_count
|
4
4
|
attr_accessor :min_call_time
|
@@ -6,7 +6,7 @@ class ScoutApm::MetricStats
|
|
6
6
|
attr_accessor :total_call_time
|
7
7
|
attr_accessor :total_exclusive_time
|
8
8
|
attr_accessor :sum_of_squares
|
9
|
-
|
9
|
+
|
10
10
|
def initialize(scoped = false)
|
11
11
|
@scoped = scoped
|
12
12
|
self.call_count = 0
|
@@ -16,20 +16,20 @@ class ScoutApm::MetricStats
|
|
16
16
|
self.max_call_time = 0.0
|
17
17
|
self.sum_of_squares = 0.0
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
def update!(call_time,exclusive_time)
|
21
21
|
# If this metric is scoped inside another, use exclusive time for min/max and sum_of_squares. Non-scoped metrics
|
22
22
|
# (like controller actions) track the total call time.
|
23
23
|
t = (@scoped ? exclusive_time : call_time)
|
24
24
|
self.min_call_time = t if self.call_count == 0 or t < min_call_time
|
25
|
-
self.max_call_time = t if self.call_count == 0 or t > max_call_time
|
25
|
+
self.max_call_time = t if self.call_count == 0 or t > max_call_time
|
26
26
|
self.call_count +=1
|
27
27
|
self.total_call_time += call_time
|
28
28
|
self.total_exclusive_time += exclusive_time
|
29
29
|
self.sum_of_squares += (t * t)
|
30
30
|
self
|
31
31
|
end
|
32
|
-
|
32
|
+
|
33
33
|
# combines data from another MetricStats object
|
34
34
|
def combine!(other)
|
35
35
|
self.call_count += other.call_count
|
@@ -40,10 +40,10 @@ class ScoutApm::MetricStats
|
|
40
40
|
self.sum_of_squares += other.sum_of_squares
|
41
41
|
self
|
42
42
|
end
|
43
|
-
|
44
|
-
# To avoid conflicts with different JSON libaries handle JSON ourselves.
|
43
|
+
|
44
|
+
# To avoid conflicts with different JSON libaries handle JSON ourselves.
|
45
45
|
# Time-based metrics are converted to milliseconds from seconds.
|
46
46
|
def to_json(*a)
|
47
47
|
%Q[{"total_exclusive_time":#{total_exclusive_time*1000},"min_call_time":#{min_call_time*1000},"call_count":#{call_count},"sum_of_squares":#{sum_of_squares*1000},"total_call_time":#{total_call_time*1000},"max_call_time":#{max_call_time*1000}}]
|
48
48
|
end
|
49
|
-
end # class MetricStats
|
49
|
+
end # class MetricStats
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
class Reporter
|
3
|
+
CA_FILE = File.join( File.dirname(__FILE__), *%w[.. .. data cacert.pem] )
|
4
|
+
VERIFY_MODE = OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
5
|
+
|
6
|
+
attr_reader :config
|
7
|
+
attr_reader :logger
|
8
|
+
attr_reader :type
|
9
|
+
|
10
|
+
def initialize(config=Agent.instance.config, logger=Agent.instance.logger, type: :checkin)
|
11
|
+
@config = config
|
12
|
+
@logger = logger
|
13
|
+
@type = type
|
14
|
+
end
|
15
|
+
|
16
|
+
# TODO: Parse & return a real response object, not the HTTP Response object
|
17
|
+
def report(payload)
|
18
|
+
post(uri, payload)
|
19
|
+
end
|
20
|
+
|
21
|
+
def uri
|
22
|
+
case type
|
23
|
+
when :checkin
|
24
|
+
URI.parse("#{config.value('host')}/apps/checkin.scout?key=#{config.value('key')}&name=#{CGI.escape(Environment.instance.application_name)}")
|
25
|
+
when :app_server_load
|
26
|
+
URI.parse("#{config.value('host')}/apps/app_server_load.scout?key=#{config.value('key')}&name=#{CGI.escape(Environment.instance.application_name)}")
|
27
|
+
end.tap{|u| logger.debug("Posting to #{u.to_s}")}
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def post(uri, body, headers = Hash.new)
|
33
|
+
response = nil
|
34
|
+
request(uri) do |connection|
|
35
|
+
post = Net::HTTP::Post.new( uri.path +
|
36
|
+
(uri.query ? ('?' + uri.query) : ''),
|
37
|
+
default_http_headers.merge(headers) )
|
38
|
+
post.body = body
|
39
|
+
response=connection.request(post)
|
40
|
+
end
|
41
|
+
response
|
42
|
+
end
|
43
|
+
|
44
|
+
def request(uri, &connector)
|
45
|
+
response = nil
|
46
|
+
response = http(uri).start(&connector)
|
47
|
+
logger.debug "got response: #{response.inspect}"
|
48
|
+
case response
|
49
|
+
when Net::HTTPSuccess, Net::HTTPNotModified
|
50
|
+
logger.debug "/#{type} OK"
|
51
|
+
when Net::HTTPBadRequest
|
52
|
+
logger.warn "/#{type} FAILED: The Account Key [#{config.value('key')}] is invalid."
|
53
|
+
else
|
54
|
+
logger.debug "/#{type} FAILED: #{response.inspect}"
|
55
|
+
end
|
56
|
+
rescue Exception
|
57
|
+
logger.debug "Exception sending request to server: #{$!.message}\n#{$!.backtrace}"
|
58
|
+
ensure
|
59
|
+
response
|
60
|
+
end
|
61
|
+
|
62
|
+
# Headers passed up with all API requests.
|
63
|
+
def default_http_headers
|
64
|
+
{ "Agent-Hostname" => ScoutApm::Environment.instance.hostname,
|
65
|
+
"Content-Type" => "application/octet-stream"
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
# Take care of the http proxy, if specified in config.
|
70
|
+
# Given a blank string, the proxy_uri URI instance's host/port/user/pass will be nil.
|
71
|
+
# Net::HTTP::Proxy returns a regular Net::HTTP class if the first argument (host) is nil.
|
72
|
+
def http(url)
|
73
|
+
proxy_uri = URI.parse(config.value('proxy').to_s)
|
74
|
+
http = Net::HTTP::Proxy(proxy_uri.host,proxy_uri.port,proxy_uri.user,proxy_uri.password).new(url.host, url.port)
|
75
|
+
if url.is_a?(URI::HTTPS)
|
76
|
+
http.use_ssl = true
|
77
|
+
http.ca_file = CA_FILE
|
78
|
+
http.verify_mode = VERIFY_MODE
|
79
|
+
end
|
80
|
+
http
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# Serialize & deserialize commands from the APM server to the instrumented app
|
2
|
+
|
3
|
+
module ScoutApm
|
4
|
+
module Serializers
|
5
|
+
class AppServerLoadSerializer
|
6
|
+
def self.serialize(data)
|
7
|
+
Marshal.dump(data)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.deserialize(data)
|
11
|
+
Marshal.load(data)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# Serialize & deserialize commands from the APM server to the instrumented app
|
2
|
+
|
3
|
+
module ScoutApm
|
4
|
+
module Serializers
|
5
|
+
class DirectiveSerializer
|
6
|
+
def self.serialize(data)
|
7
|
+
Marshal.dump(data)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.deserialize(data)
|
11
|
+
Marshal.load(data)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Serialize & deserialize data from the instrumented app up to the APM server
|
2
|
+
module ScoutApm
|
3
|
+
module Serializers
|
4
|
+
class PayloadSerializer
|
5
|
+
def self.serialize(metrics, slow_transactions)
|
6
|
+
Marshal.dump(:metrics => metrics, :slow_transactions => slow_transactions)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.deserialize(data)
|
10
|
+
Marshal.load(data)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# A lack of app server to integrate with.
|
2
|
+
# Null Object pattern
|
3
|
+
|
4
|
+
module ScoutApm
|
5
|
+
module ServerIntegrations
|
6
|
+
class Null
|
7
|
+
attr_reader :logger
|
8
|
+
|
9
|
+
def initialize(logger)
|
10
|
+
@logger = logger
|
11
|
+
end
|
12
|
+
|
13
|
+
def name
|
14
|
+
:null
|
15
|
+
end
|
16
|
+
|
17
|
+
def present?
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
def install
|
22
|
+
# Nothing to do.
|
23
|
+
end
|
24
|
+
|
25
|
+
def forking?
|
26
|
+
false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
module ServerIntegrations
|
3
|
+
class Passenger
|
4
|
+
attr_reader :logger
|
5
|
+
|
6
|
+
def initialize(logger)
|
7
|
+
@logger = logger
|
8
|
+
end
|
9
|
+
|
10
|
+
def name
|
11
|
+
:passenger
|
12
|
+
end
|
13
|
+
|
14
|
+
def forking?; true; end
|
15
|
+
|
16
|
+
def present?
|
17
|
+
(defined?(::Passenger) && defined?(::Passenger::AbstractServer)) || defined?(::PhusionPassenger)
|
18
|
+
end
|
19
|
+
|
20
|
+
def install
|
21
|
+
PhusionPassenger.on_event(:starting_worker_process) do |forked|
|
22
|
+
logger.debug "Passenger is starting a worker process. Starting worker thread."
|
23
|
+
ScoutApm::Agent.instance.start_background_worker
|
24
|
+
end
|
25
|
+
|
26
|
+
# The agent's at_exit hook doesn't run when a Passenger process stops.
|
27
|
+
# This does run when a process stops.
|
28
|
+
PhusionPassenger.on_event(:stopping_worker_process) do
|
29
|
+
logger.debug "Passenger is stopping a worker process, shutting down the agent."
|
30
|
+
ScoutApm::Agent.instance.shutdown
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
module ServerIntegrations
|
3
|
+
class Puma
|
4
|
+
attr_reader :logger
|
5
|
+
|
6
|
+
def initialize(logger)
|
7
|
+
@logger = logger
|
8
|
+
end
|
9
|
+
|
10
|
+
def name
|
11
|
+
:puma
|
12
|
+
end
|
13
|
+
|
14
|
+
def forking?; true; end
|
15
|
+
|
16
|
+
def present?
|
17
|
+
defined?(::Puma) && File.basename($0) == 'puma'
|
18
|
+
end
|
19
|
+
|
20
|
+
def install
|
21
|
+
Puma.cli_config.options[:before_worker_boot] << Proc.new do
|
22
|
+
logger.debug "Installing Puma worker loop."
|
23
|
+
ScoutApm::Agent.instance.start_background_worker
|
24
|
+
end
|
25
|
+
rescue
|
26
|
+
logger.warn "Unable to install Puma worker loop: #{$!.message}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
module ServerIntegrations
|
3
|
+
class Rainbows
|
4
|
+
attr_reader :logger
|
5
|
+
|
6
|
+
def initialize(logger)
|
7
|
+
@logger = logger
|
8
|
+
end
|
9
|
+
|
10
|
+
def name
|
11
|
+
:rainbows
|
12
|
+
end
|
13
|
+
|
14
|
+
def forking?; true; end
|
15
|
+
|
16
|
+
def present?
|
17
|
+
if defined?(::Rainbows) && defined?(::Rainbows::HttpServer)
|
18
|
+
ObjectSpace.each_object(::Rainbows::HttpServer) { |x| return true }
|
19
|
+
false
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def install
|
24
|
+
logger.debug "Installing Rainbows worker loop."
|
25
|
+
|
26
|
+
Rainbows::HttpServer.class_eval do
|
27
|
+
old = instance_method(:worker_loop)
|
28
|
+
define_method(:worker_loop) do |worker|
|
29
|
+
ScoutApm::Agent.instance.start_background_worker
|
30
|
+
old.bind(self).call(worker)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
module ServerIntegrations
|
3
|
+
class Thin
|
4
|
+
attr_reader :logger
|
5
|
+
|
6
|
+
def initialize(logger)
|
7
|
+
@logger = logger
|
8
|
+
end
|
9
|
+
|
10
|
+
def name
|
11
|
+
:thin
|
12
|
+
end
|
13
|
+
|
14
|
+
def forking?; false; end
|
15
|
+
|
16
|
+
def present?
|
17
|
+
found_thin = false
|
18
|
+
|
19
|
+
# This code block detects when thin is run as:
|
20
|
+
# `thin start`
|
21
|
+
if defined?(::Thin) && defined?(::Thin::Server)
|
22
|
+
# Ensure Thin is actually initialized. It could just be required and not running.
|
23
|
+
ObjectSpace.each_object(::Thin::Server) { |x| found_thin = true }
|
24
|
+
end
|
25
|
+
|
26
|
+
# This code block detects when thin is run as:
|
27
|
+
# `rails server`
|
28
|
+
if defined?(::Rails::Server)
|
29
|
+
ObjectSpace.each_object(::Rails::Server) { |x| found_thin ||= (x.instance_variable_get(:@_server).to_s == "Rack::Handler::Thin") }
|
30
|
+
end
|
31
|
+
|
32
|
+
found_thin
|
33
|
+
end
|
34
|
+
|
35
|
+
# TODO: What does it mean to install on a non-forking env?
|
36
|
+
def install
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
module ServerIntegrations
|
3
|
+
class Unicorn
|
4
|
+
attr_reader :logger
|
5
|
+
|
6
|
+
def initialize(logger)
|
7
|
+
@logger = logger
|
8
|
+
end
|
9
|
+
|
10
|
+
def name
|
11
|
+
:unicorn
|
12
|
+
end
|
13
|
+
|
14
|
+
def forking?; true; end
|
15
|
+
|
16
|
+
def present?
|
17
|
+
if defined?(::Unicorn) && defined?(::Unicorn::HttpServer)
|
18
|
+
# Ensure Unicorn is actually initialized. It could just be required and not running.
|
19
|
+
ObjectSpace.each_object(::Unicorn::HttpServer) { |x| return true }
|
20
|
+
false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def install
|
25
|
+
::Unicorn::HttpServer.class_eval do
|
26
|
+
old = instance_method(:worker_loop)
|
27
|
+
define_method(:worker_loop) do |worker|
|
28
|
+
ScoutApm::Agent.instance.start_background_worker
|
29
|
+
old.bind(self).call(worker)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|