scout_apm 0.1.6 → 0.1.7
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.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
|