scout_apm 3.0.0.pre10 → 3.0.0.pre11

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.markdown +47 -0
  4. data/Guardfile +42 -0
  5. data/lib/scout_apm.rb +14 -0
  6. data/lib/scout_apm/agent.rb +58 -1
  7. data/lib/scout_apm/agent/logging.rb +6 -1
  8. data/lib/scout_apm/app_server_load.rb +21 -11
  9. data/lib/scout_apm/background_job_integrations/resque.rb +85 -0
  10. data/lib/scout_apm/background_job_integrations/sidekiq.rb +19 -3
  11. data/lib/scout_apm/background_recorder.rb +43 -0
  12. data/lib/scout_apm/background_worker.rb +6 -6
  13. data/lib/scout_apm/config.rb +14 -3
  14. data/lib/scout_apm/environment.rb +14 -0
  15. data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +112 -70
  16. data/lib/scout_apm/instruments/action_view.rb +49 -0
  17. data/lib/scout_apm/instruments/active_record.rb +2 -2
  18. data/lib/scout_apm/instruments/mongoid.rb +10 -2
  19. data/lib/scout_apm/instruments/resque.rb +40 -0
  20. data/lib/scout_apm/layer_children_set.rb +7 -2
  21. data/lib/scout_apm/rack.rb +26 -0
  22. data/lib/scout_apm/remote/message.rb +23 -0
  23. data/lib/scout_apm/remote/recorder.rb +57 -0
  24. data/lib/scout_apm/remote/router.rb +49 -0
  25. data/lib/scout_apm/remote/server.rb +58 -0
  26. data/lib/scout_apm/request_manager.rb +1 -1
  27. data/lib/scout_apm/synchronous_recorder.rb +26 -0
  28. data/lib/scout_apm/tracked_request.rb +53 -5
  29. data/lib/scout_apm/utils/backtrace_parser.rb +3 -3
  30. data/lib/scout_apm/utils/scm.rb +14 -0
  31. data/lib/scout_apm/version.rb +1 -1
  32. data/scout_apm.gemspec +2 -0
  33. data/test/unit/remote/test_message.rb +13 -0
  34. data/test/unit/remote/test_router.rb +33 -0
  35. data/test/unit/remote/test_server.rb +15 -0
  36. data/test/unit/test_tracked_request.rb +87 -0
  37. data/test/unit/utils/backtrace_parser_test.rb +5 -0
  38. data/test/unit/utils/scm.rb +17 -0
  39. metadata +52 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a1bbc487af5636f30f5768e6bae7e8656c24811b
4
- data.tar.gz: 9e130174afe2ca2c2fb4764b7bc2bfa483ad95e5
3
+ metadata.gz: 36bac9cf47e89f5cec6475b574b6cd948339d627
4
+ data.tar.gz: dd2bc23001811e393950d502f04b2a359dae083d
5
5
  SHA512:
6
- metadata.gz: 49a9d4dc629b9fa48ae63a0f4cefe67ce1eb4a5328213b83b194dfcc4ee745445d72e931ef665ffe78d2f66630e866a10815297b62d89a9e52b56361754990d8
7
- data.tar.gz: c2d3469fa72ce16635ef964b0ed3c2e96e8fcdc2c006414ce098ccee9ec3cb1d4dac5ae3ad02de714b3b563e28dbd05a08e1da9c79fe08290f32beb52f9656c3
6
+ metadata.gz: 0d2e5b8270afe767d63890df65b8ea6f1fba96f4a2187cd58537372e13c7555f94875b3884473f210eb6e96f1815240508a53c50638e0ba74ad96075d9364888
7
+ data.tar.gz: 7b0169e3e311061dc20da5088f7b22247ec3acf66ba145b827a926ef87763bf6e576282a282611c2d5178412de7b8ade5a24a7c50e730399c560e1f846626020
data/.gitignore CHANGED
@@ -18,3 +18,4 @@ coverage/*
18
18
  lib/*.bundle
19
19
  lib/*.so
20
20
  **/.RUBYARCHDIR.time
21
+ log/scout_apm.log
data/CHANGELOG.markdown CHANGED
@@ -1,6 +1,52 @@
1
+ <<<<<<< HEAD
1
2
  # 3.0.0
2
3
 
3
4
  * ScoutProf BETA
5
+ =======
6
+ # 2.1.32
7
+
8
+ * Better naming when using Resque + ActiveJob
9
+ * Better naming when using Sidekiq + DelayedExtension
10
+ >>>>>>> master
11
+
12
+ # 2.1.31
13
+
14
+ * Better detection of Resque queue names
15
+ * Fix passing arguments through Active Record instrumentation. (Thanks to Nick Quaranto for providing the fix)
16
+ * Stricter checks to prevent agent from starting in Rails console
17
+
18
+ # 2.1.30
19
+
20
+ * Add Resque support.
21
+
22
+ # 2.1.29
23
+
24
+ * Add `scm_subdirectory` option. Useful for when your app code does not live in your SCM root directory.
25
+
26
+ # 2.1.28
27
+
28
+ * Changes to app server load data
29
+
30
+ # 2.1.27
31
+
32
+ * Don't attempt to call `current_layer.type` on nil
33
+
34
+ # 2.1.26
35
+
36
+ * Bug fix [4b188d6](https://github.com/scoutapp/scout_apm_ruby/commit/4b188d698852c86b86d8768ea5b37d706ce544fe)
37
+
38
+ # 2.1.25
39
+
40
+ * Automatically instrument API and Metal controllers.
41
+
42
+ # 2.1.24
43
+
44
+ * Capture additional layers of application backtrace frames. (From 3 -> 8)
45
+
46
+ # 2.1.23
47
+
48
+ * Extend Mongoid instrumentation to 6.x
49
+ >>>>>>> master
4
50
 
5
51
  # 2.1.22
6
52
 
@@ -66,6 +112,7 @@
66
112
  # 2.1.9
67
113
 
68
114
  * Send raw histograms of response time, enabling more accurate 95th %iles
115
+ * Raw histograms are used in Apdex calculations
69
116
  * Gzip payloads
70
117
  * Fix Mongoid (5.0) + Mongo (2.1) support
71
118
  * Initial Delayed Job support
data/Guardfile ADDED
@@ -0,0 +1,42 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ ## Uncomment and set this to only include directories you want to watch
5
+ # directories %w(app lib config test spec features) \
6
+ # .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
7
+
8
+ ## Note: if you are using the `directories` clause above and you are not
9
+ ## watching the project directory ('.'), then you will want to move
10
+ ## the Guardfile to a watched dir and symlink it back, e.g.
11
+ #
12
+ # $ mkdir config
13
+ # $ mv Guardfile config/
14
+ # $ ln -s config/Guardfile .
15
+ #
16
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
17
+
18
+ guard :minitest do
19
+ # with Minitest::Unit
20
+ watch(%r{^test/(.*)\/?test_(.*)\.rb$})
21
+ watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
22
+ watch(%r{^test/test_helper\.rb$}) { 'test' }
23
+
24
+ # with Minitest::Spec
25
+ # watch(%r{^spec/(.*)_spec\.rb$})
26
+ # watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
27
+ # watch(%r{^spec/spec_helper\.rb$}) { 'spec' }
28
+
29
+ # Rails 4
30
+ # watch(%r{^app/(.+)\.rb$}) { |m| "test/#{m[1]}_test.rb" }
31
+ # watch(%r{^app/controllers/application_controller\.rb$}) { 'test/controllers' }
32
+ # watch(%r{^app/controllers/(.+)_controller\.rb$}) { |m| "test/integration/#{m[1]}_test.rb" }
33
+ # watch(%r{^app/views/(.+)_mailer/.+}) { |m| "test/mailers/#{m[1]}_mailer_test.rb" }
34
+ # watch(%r{^lib/(.+)\.rb$}) { |m| "test/lib/#{m[1]}_test.rb" }
35
+ # watch(%r{^test/.+_test\.rb$})
36
+ # watch(%r{^test/test_helper\.rb$}) { 'test' }
37
+
38
+ # Rails < 4
39
+ # watch(%r{^app/controllers/(.*)\.rb$}) { |m| "test/functional/#{m[1]}_test.rb" }
40
+ # watch(%r{^app/helpers/(.*)\.rb$}) { |m| "test/helpers/#{m[1]}_test.rb" }
41
+ # watch(%r{^app/models/(.*)\.rb$}) { |m| "test/unit/#{m[1]}_test.rb" }
42
+ end
data/lib/scout_apm.rb CHANGED
@@ -15,6 +15,7 @@ require 'thread'
15
15
  require 'time'
16
16
  require 'yaml'
17
17
  require 'rbconfig'
18
+ require 'webrick'
18
19
 
19
20
  #####################################
20
21
  # Gem Requires
@@ -54,6 +55,7 @@ require 'scout_apm/server_integrations/null'
54
55
 
55
56
  require 'scout_apm/background_job_integrations/sidekiq'
56
57
  require 'scout_apm/background_job_integrations/delayed_job'
58
+ require 'scout_apm/background_job_integrations/resque'
57
59
 
58
60
  require 'scout_apm/framework_integrations/rails_2'
59
61
  require 'scout_apm/framework_integrations/rails_3_or_4'
@@ -84,6 +86,7 @@ require 'scout_apm/instruments/sinatra'
84
86
  require 'scout_apm/instruments/process/process_cpu'
85
87
  require 'scout_apm/instruments/process/process_memory'
86
88
  require 'scout_apm/instruments/percentile_sampler'
89
+ require 'scout_apm/instruments/action_view'
87
90
  require 'allocations'
88
91
 
89
92
  begin
@@ -100,6 +103,7 @@ require 'scout_apm/utils/backtrace_parser'
100
103
  require 'scout_apm/utils/installed_gems'
101
104
  require 'scout_apm/utils/klass_helper'
102
105
  require 'scout_apm/utils/null_logger'
106
+ require 'scout_apm/utils/scm'
103
107
  require 'scout_apm/utils/sql_sanitizer'
104
108
  require 'scout_apm/utils/time'
105
109
  require 'scout_apm/utils/unique_id'
@@ -124,6 +128,8 @@ require 'scout_apm/tracer'
124
128
  require 'scout_apm/context'
125
129
  require 'scout_apm/instant_reporting'
126
130
  require 'scout_apm/trace_compactor'
131
+ require 'scout_apm/background_recorder'
132
+ require 'scout_apm/synchronous_recorder'
127
133
 
128
134
  require 'scout_apm/metric_meta'
129
135
  require 'scout_apm/metric_stats'
@@ -151,6 +157,14 @@ require 'scout_apm/middleware'
151
157
 
152
158
  require 'scout_apm/instant/middleware'
153
159
 
160
+ require 'scout_apm/rack'
161
+
162
+ require 'scout_apm/remote/server'
163
+ require 'scout_apm/remote/router'
164
+ require 'scout_apm/remote/message'
165
+ require 'scout_apm/remote/recorder'
166
+ require 'scout_apm/instruments/resque'
167
+
154
168
  if defined?(Rails) && defined?(Rails::VERSION) && defined?(Rails::VERSION::MAJOR) && Rails::VERSION::MAJOR >= 3 && defined?(Rails::Railtie)
155
169
  module ScoutApm
156
170
  class Railtie < Rails::Railtie
@@ -10,6 +10,7 @@ module ScoutApm
10
10
 
11
11
  # Accessors below are for associated classes
12
12
  attr_accessor :store
13
+ attr_reader :recorder
13
14
  attr_accessor :layaway
14
15
  attr_accessor :config
15
16
  attr_accessor :logger
@@ -41,6 +42,9 @@ module ScoutApm
41
42
  @process_start_time = Time.now
42
43
  @options ||= options
43
44
 
45
+ # until the agent is started, there's no recorder
46
+ @recorder = nil
47
+
44
48
  # Start up without attempting to load a configuration file. We need to be
45
49
  # able to lookup configuration options like "application_root" which would
46
50
  # then in turn influence where the configuration file came from.
@@ -54,6 +58,7 @@ module ScoutApm
54
58
  @request_histograms_by_time = Hash.new { |h, k| h[k] = ScoutApm::RequestHistograms.new }
55
59
 
56
60
  @store = ScoutApm::Store.new
61
+
57
62
  @layaway = ScoutApm::Layaway.new(config, environment)
58
63
  @metric_lookup = Hash.new
59
64
 
@@ -84,6 +89,11 @@ module ScoutApm
84
89
  return false unless force?
85
90
  end
86
91
 
92
+ if environment.interactive?
93
+ logger.warn "Agent attempting to load in interactive mode. #{force? ? 'Forcing agent to start' : 'Not starting agent'}"
94
+ return false unless force?
95
+ end
96
+
87
97
  if app_server_missing?(options) && background_job_missing?
88
98
  if force?
89
99
  logger.warn "Agent starting (forced)"
@@ -111,13 +121,14 @@ module ScoutApm
111
121
  def start(options = {})
112
122
  @options.merge!(options)
113
123
 
114
-
115
124
  @config = ScoutApm::Config.with_file(@config.value("config_file"))
116
125
  layaway.config = config
117
126
 
118
127
  init_logger
119
128
  logger.info "Attempting to start Scout Agent [#{ScoutApm::VERSION}] on [#{environment.hostname}]"
120
129
 
130
+ @recorder = create_recorder
131
+
121
132
  @config.log_settings
122
133
 
123
134
  @ignored_uris = ScoutApm::IgnoredUris.new(config.value('ignore'))
@@ -262,6 +273,9 @@ module ScoutApm
262
273
  ScoutApm::Instruments::Stacks.start
263
274
  end
264
275
 
276
+ @recorder = create_recorder
277
+ logger.info("recorder is now: #{@recorder.class}")
278
+
265
279
  @background_worker = ScoutApm::BackgroundWorker.new
266
280
  @background_worker_thread = Thread.new do
267
281
  @background_worker.start {
@@ -303,6 +317,7 @@ module ScoutApm
303
317
  end
304
318
  end
305
319
 
320
+ install_instrument(ScoutApm::Instruments::ActionView)
306
321
  install_instrument(ScoutApm::Instruments::ActiveRecord)
307
322
  install_instrument(ScoutApm::Instruments::Moped)
308
323
  install_instrument(ScoutApm::Instruments::Mongoid)
@@ -341,5 +356,47 @@ module ScoutApm
341
356
  def background_job_missing?(options = {})
342
357
  environment.background_job_integration.nil? && !options[:skip_background_job_check]
343
358
  end
359
+
360
+ def clear_recorder
361
+ @recorder = nil
362
+ end
363
+
364
+ def create_recorder
365
+ if @recorder
366
+ return @recorder
367
+ end
368
+
369
+ if config.value("async_recording")
370
+ logger.debug("Using asynchronous recording")
371
+ ScoutApm::BackgroundRecorder.new(logger).start
372
+ else
373
+ logger.debug("Using synchronous recording")
374
+ ScoutApm::SynchronousRecorder.new(logger).start
375
+ end
376
+ end
377
+
378
+ def start_remote_server(bind, port)
379
+ return if @remote_server && @remote_server.running?
380
+
381
+ logger.info("Starting Remote Agent Server")
382
+
383
+ # Start the listening web server only in parent process.
384
+ @remote_server = ScoutApm::Remote::Server.new(
385
+ bind,
386
+ port,
387
+ ScoutApm::Remote::Router.new(ScoutApm::SynchronousRecorder.new(logger), logger),
388
+ logger
389
+ )
390
+
391
+ @remote_server.start
392
+ end
393
+
394
+ # Execute this in the child process of a remote agent. The parent is
395
+ # expected to have its accepting webserver up and running
396
+ def use_remote_recorder(host, port)
397
+ logger.debug("Becoming Remote Agent (reporting to: #{host}:#{port})")
398
+ @recorder = ScoutApm::Remote::Recorder.new(host, port, logger)
399
+ @store = ScoutApm::FakeStore.new
400
+ end
344
401
  end
345
402
  end
@@ -6,7 +6,12 @@ module ScoutApm
6
6
  "#{environment.root}/log"
7
7
  end
8
8
 
9
- def init_logger
9
+ def init_logger(opts={})
10
+ if opts[:force]
11
+ @log_file = nil
12
+ @logger = nil
13
+ end
14
+
10
15
  begin
11
16
  @log_file ||= determine_log_destination
12
17
  rescue => e
@@ -27,20 +27,30 @@ module ScoutApm
27
27
  end
28
28
 
29
29
  def data
30
- { :server_time => Time.now,
31
- :framework => ScoutApm::Environment.instance.framework_integration.human_name,
32
- :framework_version => ScoutApm::Environment.instance.framework_integration.version,
33
- :environment => ScoutApm::Environment.instance.framework_integration.env,
34
- :app_server => ScoutApm::Environment.instance.app_server,
30
+ { :server_time => to_s_safe(Time.now),
31
+ :framework => to_s_safe(ScoutApm::Environment.instance.framework_integration.human_name),
32
+ :framework_version => to_s_safe(ScoutApm::Environment.instance.framework_integration.version),
33
+ :environment => to_s_safe(ScoutApm::Environment.instance.framework_integration.env),
34
+ :app_server => to_s_safe(ScoutApm::Environment.instance.app_server),
35
35
  :ruby_version => RUBY_VERSION,
36
- :hostname => ScoutApm::Environment.instance.hostname,
37
- :database_engine => ScoutApm::Environment.instance.database_engine, # Detected
38
- :database_adapter => ScoutApm::Environment.instance.raw_database_adapter, # Raw
39
- :application_name => ScoutApm::Environment.instance.application_name,
36
+ :hostname => to_s_safe(ScoutApm::Environment.instance.hostname),
37
+ :database_engine => to_s_safe(ScoutApm::Environment.instance.database_engine), # Detected
38
+ :database_adapter => to_s_safe(ScoutApm::Environment.instance.raw_database_adapter), # Raw
39
+ :application_name => to_s_safe(ScoutApm::Environment.instance.application_name),
40
40
  :libraries => ScoutApm::Utils::InstalledGems.new.run,
41
- :paas => ScoutApm::Environment.instance.platform_integration.name,
42
- :git_sha => ScoutApm::Environment.instance.git_revision.sha
41
+ :paas => to_s_safe(ScoutApm::Environment.instance.platform_integration.name),
42
+ :git_sha => to_s_safe(ScoutApm::Environment.instance.git_revision.sha)
43
43
  }
44
44
  end
45
+
46
+ # Calls `.to_s` on the object passed in.
47
+ # Returns literal string 'to_s error' if the object does not respond to .to_s
48
+ def to_s_safe(obj)
49
+ if obj.respond_to?(:to_s)
50
+ obj.to_s
51
+ else
52
+ 'to_s error'
53
+ end
54
+ end
45
55
  end
46
56
  end
@@ -0,0 +1,85 @@
1
+ module ScoutApm
2
+ module BackgroundJobIntegrations
3
+ class Resque
4
+ def name
5
+ :resque
6
+ end
7
+
8
+ def present?
9
+ defined?(::Resque) &&
10
+ ::Resque.respond_to?(:before_first_fork) &&
11
+ ::Resque.respond_to?(:after_fork)
12
+ end
13
+
14
+ # Lies. This forks really aggressively, but we have to do handling
15
+ # of it manually here, rather than via any sort of automatic
16
+ # background worker starting
17
+ def forking?
18
+ false
19
+ end
20
+
21
+ def install
22
+ install_before_fork
23
+ install_after_fork
24
+ end
25
+
26
+ def install_before_fork
27
+ ::Resque.before_first_fork do
28
+ begin
29
+ ScoutApm::Agent.instance.start(:skip_app_server_check => true)
30
+ ScoutApm::Agent.instance.start_background_worker
31
+ ScoutApm::Agent.instance.start_remote_server(bind, port)
32
+ rescue Errno::EADDRINUSE
33
+ ScoutApm::Agent.instance.logger.warn "Error while Installing Resque Instruments, Port #{port} already in use. Set via the `remote_agent_port` configuration option"
34
+ rescue => e
35
+ ScoutApm::Agent.instance.logger.warn "Error while Installing Resque before_first_fork: #{e.inspect}"
36
+ end
37
+ end
38
+ end
39
+
40
+ def install_after_fork
41
+ ::Resque.after_fork do
42
+ begin
43
+ ScoutApm::Agent.instance.use_remote_recorder(bind, port)
44
+ inject_job_instrument
45
+ rescue => e
46
+ ScoutApm::Agent.instance.logger.warn "Error while Installing Resque after_fork: #{e.inspect}"
47
+ end
48
+ end
49
+ end
50
+
51
+ # Insert ourselves into the point when resque turns a string "TestJob"
52
+ # into the class constant TestJob, and insert our instrumentation plugin
53
+ # into that constantized class
54
+ #
55
+ # This automates away any need for the user to insert our instrumentation into
56
+ # each of their jobs
57
+ def inject_job_instrument
58
+ ::Resque::Job.class_eval do
59
+ def payload_class_with_scout_instruments
60
+ klass = payload_class_without_scout_instruments
61
+ klass.extend(ScoutApm::Instruments::Resque)
62
+ klass
63
+ end
64
+ alias_method :payload_class_without_scout_instruments, :payload_class
65
+ alias_method :payload_class, :payload_class_with_scout_instruments
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def bind
72
+ config.value("remote_agent_host")
73
+ end
74
+
75
+ def port
76
+ config.value("remote_agent_port")
77
+ end
78
+
79
+ def config
80
+ @config || ScoutApm::Agent.instance.config
81
+ end
82
+ end
83
+ end
84
+ end
85
+
@@ -55,8 +55,6 @@ module ScoutApm
55
55
  # We insert this middleware into the Sidekiq stack, to capture each job,
56
56
  # and time them.
57
57
  class SidekiqMiddleware
58
- ACTIVE_JOB_KLASS = 'ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper'.freeze
59
-
60
58
  def call(_worker, msg, queue)
61
59
  req = ScoutApm::RequestManager.lookup
62
60
  req.job!
@@ -89,12 +87,30 @@ module ScoutApm
89
87
  end
90
88
 
91
89
  UNKNOWN_CLASS_PLACEHOLDER = 'UnknownJob'.freeze
90
+ ACTIVE_JOB_KLASS = 'ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper'.freeze
91
+ DELAYED_WRAPPER_KLASS = 'Sidekiq::Extensions::DelayedClass'.freeze
92
+
92
93
 
93
94
  def job_class(msg)
94
95
  job_class = msg.fetch('class', UNKNOWN_CLASS_PLACEHOLDER)
96
+
95
97
  if job_class == ACTIVE_JOB_KLASS && msg.key?('wrapped')
96
- job_class = msg['wrapped']
98
+ begin
99
+ job_class = msg['wrapped']
100
+ rescue
101
+ ACTIVE_JOB_KLASS
102
+ end
103
+ elsif job_class == DELAYED_WRAPPER_KLASS
104
+ begin
105
+ yml = msg['args'].first
106
+ deserialized_args = YAML.load(yml)
107
+ klass, method, *rest = deserialized_args
108
+ job_class = [klass,method].map(&:to_s).join(".")
109
+ rescue
110
+ DELAYED_WRAPPER_KLASS
111
+ end
97
112
  end
113
+
98
114
  job_class
99
115
  rescue
100
116
  UNKNOWN_CLASS_PLACEHOLDER