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.
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