scout_apm 0.1.1 → 0.1.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: dab9d1dfbd7c814de188768159109981c7100a54
4
- data.tar.gz: 85d434f1c0c82b3cb19d3ad72fc3c04de45ad958
3
+ metadata.gz: 5e446ee188c5294111023e7110aa649c3d1e0d6d
4
+ data.tar.gz: 28bf8811e6e819ea847b806ae3f5abf62c2531b2
5
5
  SHA512:
6
- metadata.gz: 45d7d02378d1d103bc55067e67a0c13e3ea16d86bc790bddd8577b6b2ca70ff1d5ece8e01894a1995a40f2ef009b94149a9bd0b7b65443d27894114190fca1d9
7
- data.tar.gz: cce129502472b05706abec00715bda69543049fdadf227f69cd2a5dd7626872a1a6ec86890f4ae400fe74ac3239c5d18da422264f2dfd0d2c2200a77cd4a937d
6
+ metadata.gz: 9b174e8e7a4aa643a6b7ae8491aab27d969b2b64c3513ee32911b2045b2b4d9703cbca2a011404f7fd9c4557c6ed45948dce8d0119f55eafdcbc17950d34a188
7
+ data.tar.gz: 292f67f6b0d69de6aaf2582f53dfb1faae882baaf66127431566ba1c30692f1d2b5fd018e45df2fd9da664dacd037013d97d98805f9908880ec898b558ec1af2
@@ -1,3 +1,17 @@
1
+ # 0.1.3
2
+
3
+ * Adds capacity calculation via "Instance/Capacity" metric.
4
+ * Tweaks tracing to still count a transaction if it results in a 500 error and includes it in accumulated time.
5
+ * Adds per-transaction error tracking (ex: Errors/Controller/widgets/index)
6
+
7
+ # 0.1.2
8
+
9
+ * Adds Heroku support:
10
+ * Detects Heroku via the 'DYNO' environment variable
11
+ * Defaults logger to STDOUT
12
+ * uses the dyno name vs. the hostname as the hostname
13
+ * Environment vars with "SCOUT_" prefix override any settings specified in the config file.
14
+
1
15
  # 0.1.1
2
16
 
3
17
  * Store the start time of slow requests.
@@ -24,6 +24,7 @@ require File.expand_path('../scout_apm/store.rb', __FILE__)
24
24
  require File.expand_path('../scout_apm/tracer.rb', __FILE__)
25
25
  require File.expand_path('../scout_apm/context.rb', __FILE__)
26
26
  require File.expand_path('../scout_apm/slow_transaction.rb', __FILE__)
27
+ require File.expand_path('../scout_apm/capacity.rb', __FILE__)
27
28
  require File.expand_path('../scout_apm/instruments/process/process_cpu.rb', __FILE__)
28
29
  require File.expand_path('../scout_apm/instruments/process/process_memory.rb', __FILE__)
29
30
 
@@ -5,8 +5,6 @@ module ScoutApm
5
5
  # The worker thread wakes up every +Agent#period+, merges in-memory metrics w/those saved to disk,
6
6
  # saves tshe merged data to disk, and sends it to the Scout server.
7
7
  class Agent
8
- # Headers passed up with all API requests.
9
- HTTP_HEADERS = { "Agent-Hostname" => Socket.gethostname }
10
8
  # see self.instance
11
9
  @@instance = nil
12
10
 
@@ -15,7 +13,7 @@ module ScoutApm
15
13
  attr_accessor :layaway
16
14
  attr_accessor :config
17
15
  attr_accessor :environment
18
-
16
+ attr_accessor :capacity
19
17
  attr_accessor :logger
20
18
  attr_accessor :log_file # path to the log file
21
19
  attr_accessor :options # options passed to the agent when +#start+ is called.
@@ -38,6 +36,7 @@ module ScoutApm
38
36
  @metric_lookup = Hash.new
39
37
  @process_cpu=ScoutApm::Instruments::Process::ProcessCpu.new(environment.processors)
40
38
  @process_memory=ScoutApm::Instruments::Process::ProcessMemory.new
39
+ @capacity = ScoutApm::Capacity.new
41
40
  end
42
41
 
43
42
  def environment
@@ -49,10 +48,13 @@ module ScoutApm
49
48
  def start(options = {})
50
49
  @options.merge!(options)
51
50
  init_logger
52
- logger.info "Attempting to start Scout Agent [#{ScoutApm::VERSION}] on [#{Socket.gethostname}]"
53
- if !config.settings['monitor'] and !@options[:force]
51
+ logger.info "Attempting to start Scout Agent [#{ScoutApm::VERSION}] on [#{environment.hostname}]"
52
+ if !config.value('monitor') and !@options[:force]
54
53
  logger.warn "Monitoring isn't enabled for the [#{environment.env}] environment."
55
54
  return false
55
+ elsif !config.value('name')
56
+ logger.warn "An application name is required. Specify the :name value in scout_apm.yml. Not starting agent."
57
+ return false
56
58
  elsif !environment.app_server
57
59
  logger.warn "Couldn't find a supported app server. Not starting agent."
58
60
  return false
@@ -61,7 +63,7 @@ module ScoutApm
61
63
  return false
62
64
  end
63
65
  @started = true
64
- logger.info "Starting monitoring. Framework [#{environment.framework}] App Server [#{environment.app_server}]."
66
+ logger.info "Starting monitoring for [#{config.value('name')}]. Framework [#{environment.framework}] App Server [#{environment.app_server}]."
65
67
  start_instruments
66
68
  if !start_background_worker?
67
69
  logger.debug "Not starting worker thread"
@@ -2,12 +2,13 @@
2
2
  module ScoutApm
3
3
  class Agent
4
4
  module Logging
5
- def log_path
5
+
6
+ def default_log_path
6
7
  "#{environment.root}/log"
7
8
  end
8
9
 
9
10
  def init_logger
10
- @log_file = "#{log_path}/scout_apm.log"
11
+ @log_file = wants_stdout? ? STDOUT : "#{log_file_path}/scout_apm.log"
11
12
  begin
12
13
  @logger = Logger.new(@log_file)
13
14
  @logger.level = log_level
@@ -23,13 +24,13 @@ module ScoutApm
23
24
  def apply_log_format
24
25
  def logger.format_message(severity, timestamp, progname, msg)
25
26
  # since STDOUT isn't exclusive like the scout_apm.log file, apply a prefix.
26
- prefix = @logdev.dev == STDOUT ? "scout_apm " : ''
27
- prefix + "[#{timestamp.strftime("%m/%d/%y %H:%M:%S %z")} #{Socket.gethostname} (#{$$})] #{severity} : #{msg}\n"
27
+ prefix = @logdev.dev == STDOUT ? "[Scout] " : ''
28
+ prefix + "[#{timestamp.strftime("%m/%d/%y %H:%M:%S %z")} #{ScoutApm::Agent.instance.environment.hostname} (#{$$})] #{severity} : #{msg}\n"
28
29
  end
29
30
  end
30
31
 
31
32
  def log_level
32
- case config.settings['log_level'].downcase
33
+ case config.value('log_level').downcase
33
34
  when "debug" then Logger::DEBUG
34
35
  when "info" then Logger::INFO
35
36
  when "warn" then Logger::WARN
@@ -38,6 +39,14 @@ module ScoutApm
38
39
  else Logger::INFO
39
40
  end
40
41
  end
42
+
43
+ def log_file_path
44
+ config.value('log_file_path') || default_log_path
45
+ end
46
+
47
+ def wants_stdout?
48
+ config.value('log_file_path').to_s.upcase == 'STDOUT' || environment.heroku?
49
+ end
41
50
  end # module Logging
42
51
  include Logging
43
52
  end # class Agent
@@ -10,6 +10,7 @@ module ScoutApm
10
10
  def process_metrics
11
11
  logger.debug "Processing metrics"
12
12
  run_samplers
13
+ capacity.process
13
14
  payload = layaway.deposit_and_deliver
14
15
  metrics = payload[:metrics]
15
16
  slow_transactions = payload[:slow_transactions]
@@ -25,7 +26,7 @@ module ScoutApm
25
26
  end
26
27
  payload = Marshal.dump(:metrics => metrics, :slow_transactions => slow_transactions)
27
28
  slow_transactions_kb = Marshal.dump(slow_transactions).size/1024 # just for performance debugging
28
- logger.debug "#{config.settings['name']} Delivering total payload [#{payload.size/1024} KB] for #{controller_count} requests and slow transactions [#{slow_transactions_kb} KB] for #{slow_transactions.size} transactions of durations: #{slow_transactions.map(&:total_call_time).join(',')}."
29
+ logger.debug "#{config.value('name')} Delivering total payload [#{payload.size/1024} KB] for #{controller_count} requests and slow transactions [#{slow_transactions_kb} KB] for #{slow_transactions.size} transactions of durations: #{slow_transactions.map(&:total_call_time).join(',')}."
29
30
  response = post( checkin_uri,
30
31
  payload,
31
32
  "Content-Type" => "application/octet-stream" )
@@ -58,7 +59,7 @@ module ScoutApm
58
59
  end
59
60
 
60
61
  def checkin_uri
61
- URI.parse("#{config.settings['host']}/apps/checkin.scout?key=#{config.settings['key']}&name=#{CGI.escape(config.settings['name'])}")
62
+ URI.parse("#{config.value('host')}/apps/checkin.scout?key=#{config.value('key')}&name=#{CGI.escape(config.value('name'))}")
62
63
  end
63
64
 
64
65
  def post(uri, body, headers = Hash.new)
@@ -66,7 +67,7 @@ module ScoutApm
66
67
  request(uri) do |connection|
67
68
  post = Net::HTTP::Post.new( uri.path +
68
69
  (uri.query ? ('?' + uri.query) : ''),
69
- HTTP_HEADERS.merge(headers) )
70
+ http_headers.merge(headers) )
70
71
  post.body = body
71
72
  response=connection.request(post)
72
73
  end
@@ -81,7 +82,7 @@ module ScoutApm
81
82
  when Net::HTTPSuccess, Net::HTTPNotModified
82
83
  logger.debug "/checkin OK"
83
84
  when Net::HTTPBadRequest
84
- logger.warn "/checkin FAILED: The Account Key [#{config.settings['key']}] is invalid."
85
+ logger.warn "/checkin FAILED: The Account Key [#{config.value('key')}] is invalid."
85
86
  else
86
87
  logger.debug "/checkin FAILED: #{response.inspect}"
87
88
  end
@@ -91,11 +92,16 @@ module ScoutApm
91
92
  response
92
93
  end
93
94
 
95
+ # Headers passed up with all API requests.
96
+ def http_headers
97
+ @http_headers ||= { "Agent-Hostname" => environment.hostname }
98
+ end
99
+
94
100
  # Take care of the http proxy, if specified in config.
95
101
  # Given a blank string, the proxy_uri URI instance's host/port/user/pass will be nil.
96
102
  # Net::HTTP::Proxy returns a regular Net::HTTP class if the first argument (host) is nil.
97
103
  def http(url)
98
- proxy_uri = URI.parse(config.settings['proxy'].to_s)
104
+ proxy_uri = URI.parse(config.value('proxy').to_s)
99
105
  http = Net::HTTP::Proxy(proxy_uri.host,proxy_uri.port,proxy_uri.user,proxy_uri.password).new(url.host, url.port)
100
106
  if url.is_a?(URI::HTTPS)
101
107
  http.use_ssl = true
@@ -0,0 +1,54 @@
1
+ # Encapsulates logic for determining capacity utilization of the Ruby processes.
2
+ class ScoutApm::Capacity
3
+ attr_reader :processing_start_time, :accumulated_time, :transaction_entry_time
4
+
5
+ def initialize
6
+ @processing_start_time = Time.now
7
+ @lock ||= Mutex.new # the transaction_entry_time could be modified while processing a request or when #process is called.
8
+ @accumulated_time = 0.0
9
+ end
10
+
11
+ # Called when a transaction is traced.
12
+ def start_transaction!
13
+ @lock.synchronize do
14
+ @transaction_entry_time = Time.now
15
+ end
16
+ end
17
+
18
+ # Called when a transaction completes to record its time used.
19
+ def finish_transaction!
20
+ @lock.synchronize do
21
+ if transaction_entry_time
22
+ @accumulated_time += (Time.now - transaction_entry_time).to_f
23
+ else
24
+ ScoutApm::Agent.instance.logger.warn "No transaction entry time. Not recording capacity metrics for transaction."
25
+ end
26
+ @transaction_entry_time = nil
27
+ end
28
+ end
29
+
30
+ # Ran when sending metrics to server. Reports capacity usage metrics.
31
+ def process
32
+ process_time = Time.now
33
+ ScoutApm::Agent.instance.logger.debug "Processing capacity usage for [#{@processing_start_time}] to [#{process_time}]. Time Spent: #{@accumulated_time}."
34
+ @lock.synchronize do
35
+ time_spent = @accumulated_time
36
+ @accumulated_time = 0.0
37
+ # If a transaction is still running, capture its running time up to now and
38
+ # reset the +transaction_entry_time+ to now.
39
+ if @transaction_entry_time
40
+ time_spent += (process_time - @transaction_entry_time).to_f
41
+ ScoutApm::Agent.instance.logger.debug "A transaction is running while calculating capacity. Start time: [#{transaction_entry_time}]. Will update the entry time to [#{process_time}]."
42
+ @transaction_entry_time = process_time # prevent from over-counting capacity usage. update the transaction start time to now.
43
+ end
44
+ time_spent = 0.0 if time_spent < 0.0
45
+
46
+ window = (process_time - processing_start_time).to_f # time period we are evaulating capacity usage.
47
+ window = 1.0 if window <= 0.0 # prevent divide-by-zero if clock adjusted.
48
+ capacity = time_spent / window
49
+ ScoutApm::Agent.instance.logger.debug "Instance/Capacity: #{capacity}"
50
+ ScoutApm::Agent.instance.store.track!("Instance/Capacity",capacity,:scope => nil)
51
+ @processing_start_time = process_time
52
+ end
53
+ end
54
+ end
@@ -8,12 +8,15 @@ module ScoutApm
8
8
  def initialize(config_path = nil)
9
9
  @config_path = config_path
10
10
  end
11
-
12
- def settings
13
- return @settings if @settings
14
- load_file
11
+
12
+ # Fetch a config value. It first attempts to fetch an ENV var prefixed with 'SCOUT_', then from the settings file.
13
+ def value(key)
14
+ value = ENV['SCOUT_'+key.upcase] || settings[key]
15
+ value.to_s.strip.length.zero? ? nil : value
15
16
  end
16
-
17
+
18
+ private
19
+
17
20
  def config_path
18
21
  @config_path || File.join(ScoutApm::Agent.instance.environment.root,"config","scout_apm.yml")
19
22
  end
@@ -21,6 +24,11 @@ module ScoutApm
21
24
  def config_file
22
25
  File.expand_path(config_path)
23
26
  end
27
+
28
+ def settings
29
+ return @settings if @settings
30
+ load_file
31
+ end
24
32
 
25
33
  def load_file
26
34
  begin
@@ -50,6 +50,14 @@ module ScoutApm
50
50
  '.'
51
51
  end
52
52
  end
53
+
54
+ def heroku?
55
+ ENV['DYNO']
56
+ end
57
+
58
+ def hostname
59
+ heroku? ? ENV['DYNO'] : Socket.gethostname
60
+ end
53
61
 
54
62
  # This needs to be improved. Frequently, multiple app servers gem are present and which
55
63
  # ever is checked first becomes the designated app server.
@@ -1,7 +1,7 @@
1
1
  # Logic for the serialized file access
2
2
  class ScoutApm::LayawayFile
3
3
  def path
4
- "#{ScoutApm::Agent.instance.log_path}/scout_apm.db"
4
+ "#{ScoutApm::Agent.instance.default_log_path}/scout_apm.db"
5
5
  end
6
6
 
7
7
  def dump(object)
@@ -22,15 +22,23 @@ module ScoutApm::Tracer
22
22
  ScoutApm::Agent.instance.store.reset_transaction!
23
23
  ScoutApm::Context.current.add_user(:ip => options[:ip]) if options[:ip]
24
24
  Thread::current[:scout_apm_trace_time] = Time.now.utc
25
+ ScoutApm::Agent.instance.capacity.start_transaction!
26
+ e = nil
25
27
  instrument(metric_name, options) do
26
28
  Thread::current[:scout_apm_scope_name] = metric_name
27
- yield
29
+ begin
30
+ yield
31
+ rescue Exception => e
32
+ ScoutApm::Agent.instance.store.track!("Errors/#{metric_name}",1, :scope => nil)
33
+ end
28
34
  Thread::current[:scout_apm_scope_name] = nil
29
35
  end
30
36
  Thread::current[:scout_apm_trace_time] = nil
37
+ ScoutApm::Agent.instance.capacity.finish_transaction!
31
38
  # The context is cleared after instrumentation (rather than before) as tracing controller-actions doesn't occur until the controller-action is called.
32
39
  # It does not trace before filters, which is a likely spot to add context. This means that any context applied during before_filters would be cleared.
33
40
  ScoutApm::Context.clear!
41
+ raise e if e
34
42
  end
35
43
 
36
44
  # Options:
@@ -1,3 +1,3 @@
1
1
  module ScoutApm
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.3"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scout_apm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Derek Haynes
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-07-23 00:00:00.000000000 Z
12
+ date: 2015-08-03 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: Monitors Ruby apps and reports detailed metrics on performance to Scout.
15
15
  email:
@@ -29,6 +29,7 @@ files:
29
29
  - lib/scout_apm/agent/logging.rb
30
30
  - lib/scout_apm/agent/reporting.rb
31
31
  - lib/scout_apm/background_worker.rb
32
+ - lib/scout_apm/capacity.rb
32
33
  - lib/scout_apm/config.rb
33
34
  - lib/scout_apm/context.rb
34
35
  - lib/scout_apm/environment.rb
@@ -70,7 +71,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
70
71
  version: '0'
71
72
  requirements: []
72
73
  rubyforge_project: scout_apm
73
- rubygems_version: 2.2.2
74
+ rubygems_version: 2.4.6
74
75
  signing_key:
75
76
  specification_version: 4
76
77
  summary: Ruby application performance monitoring