scout_apm 3.0.0.pre10 → 3.0.0.pre11

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