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
@@ -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}"
@@ -7,4 +7,4 @@ if defined?(::Mongoid) and !defined?(::Moped)
7
7
  instrument_method method, :metric_name => "MongoDB/\#{@klass}/#{method}"
8
8
  end
9
9
  end
10
- end
10
+ end
@@ -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::Instruments
2
- module Process
3
- class ProcessCpu
4
- def initialize(num_processors)
5
- @num_processors = num_processors || 1
6
- end
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
- def run
9
- res=nil
10
- now = Time.now
11
- t = ::Process.times
12
- if @last_run
13
- elapsed_time = now - @last_run
14
- if elapsed_time >= 1
15
- user_time_since_last_sample = t.utime - @last_utime
16
- system_time_since_last_sample = t.stime - @last_stime
17
- res = ((user_time_since_last_sample + system_time_since_last_sample)/(elapsed_time * @num_processors))*100
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::Instruments
2
- module Process
3
- class ProcessMemory
4
- def run
5
- res=nil
6
- platform = RUBY_PLATFORM.downcase
7
-
8
- if platform =~ /linux/
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
- private
11
+ def metric_name
12
+ "Memory/Physical"
13
+ end
19
14
 
20
- def get_mem_from_procfile
21
- res = nil
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
- def procfile
30
- "/proc/#{$$}/status"
31
- end
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
- # memory in MB the current process is using
34
- def get_mem_from_shell(command)
35
- res = `#{command} #{$$}`.split("\n")[1].to_f / 1024.0 #rescue nil
36
- res
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::Instruments
2
- module ActionControllerInstruments
3
- def self.included(instrumented_class)
4
- ScoutApm::Agent.instance.logger.debug "Instrumenting #{instrumented_class.inspect}"
5
- instrumented_class.class_eval do
6
- unless instrumented_class.method_defined?(:perform_action_without_scout_instruments)
7
- alias_method :perform_action_without_scout_instruments, :perform_action
8
- alias_method :perform_action, :perform_action_with_scout_instruments
9
- private :perform_action
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
- end # self.included
13
-
14
- # In addition to instrumenting actions, this also sets the scope to the controller action name. The scope is later
15
- # applied to metrics recorded during this transaction. This lets us associate ActiveRecord calls with
16
- # specific controller actions.
17
- def perform_action_with_scout_instruments(*args, &block)
18
- scout_controller_action = "Controller/#{controller_path}/#{action_name}"
19
- self.class.scout_apm_trace(scout_controller_action, :uri => request.request_uri, :ip => request.remote_ip) do
20
- perform_action_without_scout_instruments(*args, &block)
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::Instruments
3
- module ActionControllerInstruments
4
- # Instruments the action and tracks errors.
5
- def process_action(*args)
6
- scout_controller_action = "Controller/#{controller_path}/#{action_name}"
7
- #ScoutApm::Agent.instance.logger.debug "Processing #{scout_controller_action}"
8
- self.class.scout_apm_trace(scout_controller_action, :uri => request.fullpath, :ip => request.remote_ip) do
9
- begin
10
- super
11
- rescue Exception => e
12
- ScoutApm::Agent.instance.store.track!("Errors/Request",1, :scope => nil)
13
- raise
14
- ensure
15
- Thread::current[:scout_apm_scope_name] = nil
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
+
@@ -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