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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.markdown +4 -0
  3. data/lib/scout_apm.rb +48 -23
  4. data/lib/scout_apm/agent.rb +93 -130
  5. data/lib/scout_apm/agent/reporting.rb +34 -63
  6. data/lib/scout_apm/app_server_load.rb +29 -0
  7. data/lib/scout_apm/background_worker.rb +6 -6
  8. data/lib/scout_apm/capacity.rb +48 -48
  9. data/lib/scout_apm/config.rb +5 -5
  10. data/lib/scout_apm/context.rb +3 -3
  11. data/lib/scout_apm/environment.rb +64 -100
  12. data/lib/scout_apm/framework_integrations/rails_2.rb +32 -0
  13. data/lib/scout_apm/framework_integrations/rails_3_or_4.rb +33 -0
  14. data/lib/scout_apm/framework_integrations/ruby.rb +26 -0
  15. data/lib/scout_apm/framework_integrations/sinatra.rb +27 -0
  16. data/lib/scout_apm/instruments/active_record_instruments.rb +1 -1
  17. data/lib/scout_apm/instruments/mongoid_instruments.rb +1 -1
  18. data/lib/scout_apm/instruments/moped_instruments.rb +3 -3
  19. data/lib/scout_apm/instruments/net_http.rb +2 -2
  20. data/lib/scout_apm/instruments/process/process_cpu.rb +41 -20
  21. data/lib/scout_apm/instruments/process/process_memory.rb +45 -30
  22. data/lib/scout_apm/instruments/rails/action_controller_instruments.rb +20 -18
  23. data/lib/scout_apm/instruments/rails3_or_4/action_controller_instruments.rb +17 -14
  24. data/lib/scout_apm/layaway.rb +12 -12
  25. data/lib/scout_apm/layaway_file.rb +1 -1
  26. data/lib/scout_apm/metric_meta.rb +9 -9
  27. data/lib/scout_apm/metric_stats.rb +8 -8
  28. data/lib/scout_apm/reporter.rb +83 -0
  29. data/lib/scout_apm/serializers/app_server_load_serializer.rb +15 -0
  30. data/lib/scout_apm/serializers/directive_serializer.rb +15 -0
  31. data/lib/scout_apm/serializers/payload_serializer.rb +14 -0
  32. data/lib/scout_apm/server_integrations/null.rb +30 -0
  33. data/lib/scout_apm/server_integrations/passenger.rb +35 -0
  34. data/lib/scout_apm/server_integrations/puma.rb +30 -0
  35. data/lib/scout_apm/server_integrations/rainbows.rb +36 -0
  36. data/lib/scout_apm/server_integrations/thin.rb +41 -0
  37. data/lib/scout_apm/server_integrations/unicorn.rb +35 -0
  38. data/lib/scout_apm/server_integrations/webrick.rb +25 -0
  39. data/lib/scout_apm/slow_transaction.rb +3 -3
  40. data/lib/scout_apm/stack_item.rb +19 -17
  41. data/lib/scout_apm/store.rb +35 -35
  42. data/lib/scout_apm/tracer.rb +121 -110
  43. data/lib/scout_apm/utils/sql_sanitizer.rb +2 -2
  44. data/lib/scout_apm/version.rb +2 -1
  45. data/test/unit/environment_test.rb +5 -5
  46. metadata +18 -2
@@ -69,4 +69,4 @@ class ScoutApm::LayawayFile
69
69
  rescue EOFError
70
70
  contents
71
71
  end
72
- end
72
+ end
@@ -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
- 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
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