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