scout_apm 2.3.5 → 2.4.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.markdown +0 -23
  3. data/lib/scout_apm.rb +21 -10
  4. data/lib/scout_apm/agent.rb +98 -336
  5. data/lib/scout_apm/agent/exit_handler.rb +64 -0
  6. data/lib/scout_apm/agent/preconditions.rb +69 -0
  7. data/lib/scout_apm/agent_context.rb +226 -0
  8. data/lib/scout_apm/app_server_load.rb +20 -18
  9. data/lib/scout_apm/background_job_integrations/resque.rb +7 -8
  10. data/lib/scout_apm/background_job_integrations/sidekiq.rb +2 -8
  11. data/lib/scout_apm/background_recorder.rb +8 -3
  12. data/lib/scout_apm/background_worker.rb +14 -7
  13. data/lib/scout_apm/config.rb +35 -29
  14. data/lib/scout_apm/context.rb +11 -4
  15. data/lib/scout_apm/db_query_metric_set.rb +17 -5
  16. data/lib/scout_apm/debug.rb +1 -1
  17. data/lib/scout_apm/environment.rb +10 -14
  18. data/lib/scout_apm/framework_integrations/sinatra.rb +1 -1
  19. data/lib/scout_apm/git_revision.rb +13 -8
  20. data/lib/scout_apm/histogram.rb +1 -1
  21. data/lib/scout_apm/instant/middleware.rb +7 -7
  22. data/lib/scout_apm/instant_reporting.rb +7 -7
  23. data/lib/scout_apm/instrument_manager.rb +87 -0
  24. data/lib/scout_apm/instruments/action_controller_rails_2.rb +12 -7
  25. data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +16 -11
  26. data/lib/scout_apm/instruments/action_view.rb +11 -7
  27. data/lib/scout_apm/instruments/active_record.rb +28 -51
  28. data/lib/scout_apm/instruments/elasticsearch.rb +10 -6
  29. data/lib/scout_apm/instruments/grape.rb +12 -8
  30. data/lib/scout_apm/instruments/http_client.rb +11 -10
  31. data/lib/scout_apm/instruments/influxdb.rb +10 -6
  32. data/lib/scout_apm/instruments/middleware_detailed.rb +11 -5
  33. data/lib/scout_apm/instruments/middleware_summary.rb +11 -5
  34. data/lib/scout_apm/instruments/mongoid.rb +10 -5
  35. data/lib/scout_apm/instruments/moped.rb +11 -6
  36. data/lib/scout_apm/instruments/net_http.rb +11 -9
  37. data/lib/scout_apm/instruments/percentile_sampler.rb +8 -6
  38. data/lib/scout_apm/instruments/process/process_cpu.rb +8 -4
  39. data/lib/scout_apm/instruments/process/process_memory.rb +15 -10
  40. data/lib/scout_apm/instruments/rails_router.rb +12 -6
  41. data/lib/scout_apm/instruments/redis.rb +10 -6
  42. data/lib/scout_apm/instruments/samplers.rb +11 -0
  43. data/lib/scout_apm/instruments/sinatra.rb +5 -4
  44. data/lib/scout_apm/layaway.rb +26 -39
  45. data/lib/scout_apm/layaway_file.rb +8 -3
  46. data/lib/scout_apm/layer.rb +1 -1
  47. data/lib/scout_apm/layer_converters/converter_base.rb +4 -2
  48. data/lib/scout_apm/layer_converters/database_converter.rb +1 -1
  49. data/lib/scout_apm/layer_converters/histograms.rb +2 -2
  50. data/lib/scout_apm/layer_converters/slow_job_converter.rb +4 -3
  51. data/lib/scout_apm/layer_converters/slow_request_converter.rb +5 -4
  52. data/lib/scout_apm/logger.rb +143 -0
  53. data/lib/scout_apm/middleware.rb +7 -9
  54. data/lib/scout_apm/periodic_work.rb +28 -0
  55. data/lib/scout_apm/remote/server.rb +0 -2
  56. data/lib/scout_apm/reporter.rb +14 -8
  57. data/lib/scout_apm/reporting.rb +135 -0
  58. data/lib/scout_apm/request_manager.rb +4 -7
  59. data/lib/scout_apm/serializers/payload_serializer.rb +1 -1
  60. data/lib/scout_apm/slow_job_policy.rb +6 -2
  61. data/lib/scout_apm/slow_job_record.rb +5 -5
  62. data/lib/scout_apm/slow_request_policy.rb +6 -2
  63. data/lib/scout_apm/slow_transaction.rb +5 -5
  64. data/lib/scout_apm/store.rb +22 -16
  65. data/lib/scout_apm/synchronous_recorder.rb +7 -3
  66. data/lib/scout_apm/tasks/doctor.rb +75 -0
  67. data/lib/scout_apm/tasks/support.rb +22 -0
  68. data/lib/scout_apm/tracer.rb +5 -5
  69. data/lib/scout_apm/tracked_request.rb +23 -35
  70. data/lib/scout_apm/utils/backtrace_parser.rb +1 -1
  71. data/lib/scout_apm/utils/installed_gems.rb +7 -3
  72. data/lib/scout_apm/utils/klass_helper.rb +8 -2
  73. data/lib/scout_apm/utils/scm.rb +1 -1
  74. data/lib/scout_apm/utils/sql_sanitizer.rb +4 -6
  75. data/lib/scout_apm/version.rb +1 -1
  76. data/lib/tasks/doctor.rake +11 -0
  77. data/test/test_helper.rb +15 -2
  78. data/test/unit/agent_test.rb +1 -54
  79. data/test/unit/config_test.rb +9 -5
  80. data/test/unit/context_test.rb +4 -4
  81. data/test/unit/db_query_metric_set_test.rb +11 -4
  82. data/test/unit/fake_store_test.rb +1 -1
  83. data/test/unit/git_revision_test.rb +3 -3
  84. data/test/unit/instruments/net_http_test.rb +2 -1
  85. data/test/unit/instruments/percentile_sampler_test.rb +5 -9
  86. data/test/unit/layaway_test.rb +10 -5
  87. data/test/unit/layer_converters/metric_converter_test.rb +2 -2
  88. data/test/unit/slow_request_policy_test.rb +7 -3
  89. data/test/unit/sql_sanitizer_test.rb +0 -6
  90. data/test/unit/store_test.rb +11 -8
  91. metadata +15 -7
  92. data/lib/scout_apm/agent/logging.rb +0 -74
  93. data/lib/scout_apm/agent/reporting.rb +0 -129
  94. data/lib/scout_apm/utils/null_logger.rb +0 -13
@@ -0,0 +1,64 @@
1
+ module ScoutApm
2
+ class Agent
3
+ class ExitHandler
4
+ attr_reader :context
5
+
6
+ def initialize(context)
7
+ @context = context
8
+ end
9
+
10
+ def install
11
+ logger.debug "Shutdown handler not supported" and return unless exit_handler_supported?
12
+ logger.debug "Installing Shutdown Handler"
13
+
14
+ at_exit do
15
+ logger.info "Shutting down Scout Agent"
16
+ # MRI 1.9 bug drops exit codes.
17
+ # http://bugs.ruby-lang.org/issues/5218
18
+ if environment.ruby_19?
19
+ status = $!.status if $!.is_a?(SystemExit)
20
+ shutdown
21
+ exit status if status
22
+ else
23
+ shutdown
24
+ end
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ # Called via the at_exit handler, it:
31
+ # (1) Stops the background worker
32
+ # (2) Stores metrics locally (forcing current-minute metrics to be written)
33
+ # It does not attempt to actually report metrics.
34
+ def shutdown
35
+ logger.info "Shutting down ScoutApm"
36
+ return if !context.started?
37
+ ::ScoutApm::Agent.instance.stop_background_worker
38
+ end
39
+
40
+ def exit_handler_supported?
41
+ if environment.sinatra?
42
+ logger.debug "Exit handler not supported for Sinatra"
43
+ false
44
+ elsif environment.jruby?
45
+ logger.debug "Exit handler not supported for JRuby"
46
+ false
47
+ elsif environment.rubinius?
48
+ logger.debug "Exit handler not supported for Rubinius"
49
+ false
50
+ else
51
+ true
52
+ end
53
+ end
54
+
55
+ def logger
56
+ context.logger
57
+ end
58
+
59
+ def environment
60
+ context.environment
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,69 @@
1
+ module ScoutApm
2
+ class Agent
3
+ class Preconditions
4
+ # The preconditions here must be a 2 element hash, with :message and :check.
5
+ # message: Proc that takes the environment, and returns a string
6
+ # check: Proc that takes an AgentContext and returns true if precondition was met, if false, we shouldn't start.
7
+ PRECONDITIONS = [
8
+ PRECONDITION_ENABLED = {
9
+ :message => proc {|environ| "Monitoring isn't enabled for the [#{environ.env}] environment." },
10
+ :check => proc { |context| context.config.value('monitor') },
11
+ },
12
+
13
+ PRECONDITION_APP_NAME = {
14
+ :message => proc {|environ| "An application name could not be determined. Specify the :name value in scout_apm.yml." },
15
+ :check => proc { |context| context.environment.application_name },
16
+ },
17
+
18
+ PRECONDITION_INTERACTIVE = {
19
+ :message => proc {|environ| "Agent attempting to load in interactive mode." },
20
+ :check => proc { |context| ! context.environment.interactive? },
21
+ },
22
+
23
+ PRECONDITION_DETECTED_SERVER = {
24
+ :message => proc {|environ| "Deferring agent start. Standing by for first request" },
25
+ :check => proc { |context| !app_server_missing?(context) && !background_job_missing?(context) },
26
+ },
27
+
28
+ PRECONDITION_ALREADY_STARTED = {
29
+ :message => proc {|environ| "Already started agent." },
30
+ :check => proc { |context| !context.started? },
31
+ },
32
+
33
+ PRECONDITION_OLD_SCOUT_RAILS = {
34
+ :message => proc {|environ| "ScoutAPM is incompatible with the old Scout Rails plugin. Please remove scout_rails from your Gemfile" },
35
+ :check => proc { !defined?(::ScoutRails) },
36
+ },
37
+ ]
38
+
39
+ def self.check?(context)
40
+ failed_preconditions = PRECONDITIONS.inject(Array.new) { |errors, condition|
41
+ met = condition[:check].call(context)
42
+ errors << condition[:message].call(context.environment) unless met
43
+ errors
44
+ }
45
+
46
+ if failed_preconditions.any?
47
+ failed_preconditions.each {|msg| context.logger.warn(msg) }
48
+ force? # if forced, return true anyway
49
+ else
50
+ # No errors, we met preconditions
51
+ true
52
+ end
53
+ end
54
+
55
+ # XXX: Wire up options here and below in the appserver & bg server detections
56
+ def self.force?
57
+ false
58
+ end
59
+
60
+ def self.app_server_missing?(context)
61
+ !context.environment.app_server_integration(true).found? # && !options[:skip_app_server_check]
62
+ end
63
+
64
+ def self.background_job_missing?(context)
65
+ context.environment.background_job_integrations.length == 0
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,226 @@
1
+ module ScoutApm
2
+ class AgentContext
3
+ # Initially start up without attempting to load a configuration file. We
4
+ # need to be able to lookup configuration options like "application_root"
5
+ # which would then in turn influence where the yaml configuration file is
6
+ # located
7
+ #
8
+ # Later in initialization, we set config= to include the file.
9
+ def initialize()
10
+ @logger = LoggerFactory.build_minimal_logger
11
+ @process_start_time = Time.now
12
+ end
13
+
14
+ def marshal_dump
15
+ []
16
+ end
17
+
18
+ def marshal_load(*args)
19
+ @logger = LoggerFactory.build_minimal_logger
20
+ @process_start_time = Time.now
21
+ end
22
+
23
+ #####################################
24
+ # Lifecycle: Remote Server/Client
25
+ #
26
+ # This allows short lived forked processes to communicate back to the parent process.
27
+ # Used in the Resque instrumentation
28
+ #
29
+ # Parent Pre-fork: start_remote_server! once
30
+ # Child Post-fork: become_remote_client! after each fork
31
+ #
32
+ # TODO: Figure out where to extract this to
33
+ #####################################
34
+
35
+ def start_remote_server!(bind, port)
36
+ return if @remote_server && @remote_server.running?
37
+
38
+ logger.info("Starting Remote Agent Server")
39
+
40
+ # Start the listening web server only in parent process.
41
+ @remote_server = ScoutApm::Remote::Server.new(
42
+ bind,
43
+ port,
44
+ ScoutApm::Remote::Router.new(ScoutApm::SynchronousRecorder.new(logger), logger),
45
+ logger
46
+ )
47
+
48
+ @remote_server.start
49
+ end
50
+
51
+ # Execute this in the child process of a remote agent. The parent is
52
+ # expected to have its accepting webserver up and running
53
+ def become_remote_client!(host, port)
54
+ logger.debug("Becoming Remote Agent (reporting to: #{host}:#{port})")
55
+ recorder = ScoutApm::Remote::Recorder.new(host, port, logger)
56
+ store = ScoutApm::FakeStore.new
57
+ end
58
+
59
+
60
+ ###############
61
+ # Accessors #
62
+ ###############
63
+
64
+ attr_reader :process_start_time
65
+
66
+ def config
67
+ @config ||= ScoutApm::Config.without_file(self)
68
+ end
69
+
70
+ def environment
71
+ @environment ||= ScoutApm::Environment.instance
72
+ end
73
+
74
+ def started?
75
+ @started
76
+ end
77
+
78
+ def installed?
79
+ @installed
80
+ end
81
+
82
+ def logger
83
+ @logger ||= LoggerFactory.build(config, environment)
84
+ end
85
+
86
+ def ignored_uris
87
+ @ignored_uris ||= ScoutApm::IgnoredUris.new(config.value('ignore'))
88
+ end
89
+
90
+ def slow_request_policy
91
+ @slow_request_policy ||= ScoutApm::SlowRequestPolicy.new(self)
92
+ end
93
+
94
+ def slow_job_policy
95
+ @slow_job_policy ||= ScoutApm::SlowJobPolicy.new(self)
96
+ end
97
+
98
+ # Histogram of the cumulative requests since the start of the process
99
+ def request_histograms
100
+ @request_histograms ||= ScoutApm::RequestHistograms.new
101
+ end
102
+
103
+ # Histogram of the requests, distinct by reporting period (minute)
104
+ # { StoreReportingPeriodTimestamp => RequestHistograms }
105
+ def request_histograms_by_time
106
+ @request_histograms_by_time ||= Hash.new { |h, k| h[k] = ScoutApm::RequestHistograms.new }
107
+ end
108
+
109
+ def store
110
+ return @store if @store
111
+ self.store = ScoutApm::Store.new(self)
112
+ end
113
+
114
+ def layaway
115
+ @layaway ||= ScoutApm::Layaway.new(self)
116
+ end
117
+
118
+ def recorder
119
+ @recorder ||= RecorderFactory.build(self)
120
+ end
121
+
122
+ def dev_trace_enabled?
123
+ config.value('dev_trace') && environment.env == "development"
124
+ end
125
+
126
+ #############
127
+ # Setters #
128
+ #############
129
+
130
+ # When we set the config for any reason, there are some values we must
131
+ # reinitialize, since the config could have changed their settings, so nil
132
+ # them out here, then let them get lazily reset as needed
133
+ #
134
+ # Don't use this in initializer, since it'll attempt to log immediately
135
+ def config=(config)
136
+ @config = config
137
+
138
+ @logger = nil
139
+
140
+ log_configuration_settings
141
+
142
+ @ignored_uris = nil
143
+ @slow_request_policy = nil
144
+ @slow_job_policy = nil
145
+ @request_histograms = nil
146
+ @request_histograms_by_time = nil
147
+ @store = nil
148
+ @layaway = nil
149
+ @recorder = nil
150
+ end
151
+
152
+ def installed!
153
+ @installed = true
154
+ end
155
+
156
+ def started!
157
+ @started = true
158
+ end
159
+
160
+ def store=(store)
161
+ @store = store
162
+
163
+ # Installs the default samplers
164
+ # Don't install samplers on nil stores
165
+ if store
166
+ ScoutApm::Instruments::Samplers::DEFAULT_SAMPLERS.each { |s| store.add_sampler(s) }
167
+ end
168
+ end
169
+
170
+ def recorder=(recorder)
171
+ @recorder = recorder
172
+ end
173
+
174
+ # I believe this is only useful for testing?
175
+ def environment=(env)
176
+ @environment = env
177
+ end
178
+
179
+ #########################
180
+ # Callbacks & helpers #
181
+ #
182
+ # Should find ways to remove these into external spots
183
+ #########################
184
+
185
+ # Called after config is reset and loaded from file
186
+ def log_configuration_settings
187
+ @config.log_settings(logger)
188
+
189
+ if !@config.any_keys_found?
190
+ logger.info("No configuration file loaded, and no configuration found in ENV. " +
191
+ "For assistance configuring Scout, visit " +
192
+ "http://help.apm.scoutapp.com/#configuration-options")
193
+ end
194
+ end
195
+ end
196
+
197
+ class RecorderFactory
198
+ def self.build(context)
199
+ if context.config.value("async_recording")
200
+ context.logger.debug("Using asynchronous recording")
201
+ ScoutApm::BackgroundRecorder.new(context).start
202
+ else
203
+ context.logger.debug("Using synchronous recording")
204
+ ScoutApm::SynchronousRecorder.new(context).start
205
+ end
206
+ end
207
+ end
208
+
209
+ class LoggerFactory
210
+ def self.build(config, environment)
211
+ ScoutApm::Logger.new(environment.root,
212
+ {
213
+ :log_level => config.value('log_level'),
214
+ :log_file_path => config.value('log_file_path'),
215
+ :stdout => config.value('log_stdout'),
216
+ :stderr => config.value('log_stderr'),
217
+ :logger_class => config.value('log_class'),
218
+ }
219
+ )
220
+ end
221
+
222
+ def self.build_minimal_logger
223
+ ScoutApm::Logger.new(nil, :stdout => true)
224
+ end
225
+ end
226
+ end
@@ -1,9 +1,11 @@
1
1
  module ScoutApm
2
2
  class AppServerLoad
3
3
  attr_reader :logger
4
+ attr_reader :context
4
5
 
5
- def initialize(logger=Agent.instance.logger)
6
- @logger = logger
6
+ def initialize(context)
7
+ @context = context
8
+ @logger = context.logger
7
9
  end
8
10
 
9
11
  def run
@@ -13,7 +15,7 @@ module ScoutApm
13
15
  logger.debug("Full Application Startup Info: #{data.inspect}")
14
16
 
15
17
  payload = ScoutApm::Serializers::AppServerLoadSerializer.serialize(data)
16
- reporter = Reporter.new(:app_server_load)
18
+ reporter = Reporter.new(context, :app_server_load)
17
19
  reporter.report(payload)
18
20
 
19
21
  logger.debug("Finished sending Startup Info")
@@ -28,23 +30,19 @@ module ScoutApm
28
30
 
29
31
  def data
30
32
  { :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),
33
+ :framework => to_s_safe(environment.framework_integration.human_name),
34
+ :framework_version => to_s_safe(environment.framework_integration.version),
35
+ :environment => to_s_safe(environment.framework_integration.env),
36
+ :app_server => to_s_safe(environment.app_server),
35
37
  :ruby_version => RUBY_VERSION,
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
- :libraries => ScoutApm::Utils::InstalledGems.new.run,
41
- :paas => to_s_safe(ScoutApm::Environment.instance.platform_integration.name),
42
- :git_sha => to_s_safe(ScoutApm::Environment.instance.git_revision.sha)
38
+ :hostname => to_s_safe(environment.hostname),
39
+ :database_engine => to_s_safe(environment.database_engine), # Detected
40
+ :database_adapter => to_s_safe(environment.raw_database_adapter), # Raw
41
+ :application_name => to_s_safe(environment.application_name),
42
+ :libraries => ScoutApm::Utils::InstalledGems.new(context).run,
43
+ :paas => to_s_safe(environment.platform_integration.name),
44
+ :git_sha => to_s_safe(environment.git_revision.sha)
43
45
  }
44
- ensure
45
- # Sometimes :database_engine and :database_adapter can cause a reference to an AR connection.
46
- # Make sure we release all AR connections held by this thread.
47
- ActiveRecord::Base.clear_active_connections! if Utils::KlassHelper.defined?("ActiveRecord::Base")
48
46
  end
49
47
 
50
48
  # Calls `.to_s` on the object passed in.
@@ -56,5 +54,9 @@ module ScoutApm
56
54
  'to_s error'
57
55
  end
58
56
  end
57
+
58
+ def environment
59
+ context.environment
60
+ end
59
61
  end
60
62
  end
@@ -26,13 +26,12 @@ module ScoutApm
26
26
  def install_before_fork
27
27
  ::Resque.before_first_fork do
28
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)
29
+ ScoutApm::Agent.instance.start
30
+ ScoutApm::Agent.instance.context.start_remote_server!(bind, port)
32
31
  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"
32
+ ScoutApm::Agent.instance.context.logger.warn "Error while Installing Resque Instruments, Port #{port} already in use. Set via the `remote_agent_port` configuration option"
34
33
  rescue => e
35
- ScoutApm::Agent.instance.logger.warn "Error while Installing Resque before_first_fork: #{e.inspect}"
34
+ ScoutApm::Agent.instance.context.logger.warn "Error while Installing Resque before_first_fork: #{e.inspect}"
36
35
  end
37
36
  end
38
37
  end
@@ -40,10 +39,10 @@ module ScoutApm
40
39
  def install_after_fork
41
40
  ::Resque.after_fork do
42
41
  begin
43
- ScoutApm::Agent.instance.use_remote_recorder(bind, port)
42
+ ScoutApm::Agent.instance.context.become_remote_client!(bind, port)
44
43
  inject_job_instrument
45
44
  rescue => e
46
- ScoutApm::Agent.instance.logger.warn "Error while Installing Resque after_fork: #{e.inspect}"
45
+ ScoutApm::Agent.instance.context.logger.warn "Error while Installing Resque after_fork: #{e.inspect}"
47
46
  end
48
47
  end
49
48
  end
@@ -77,7 +76,7 @@ module ScoutApm
77
76
  end
78
77
 
79
78
  def config
80
- @config || ScoutApm::Agent.instance.config
79
+ @config || ScoutApm::Agent.instance.context.config
81
80
  end
82
81
  end
83
82
  end