scout_apm 0.1.1 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
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