scout_apm 3.0.0.pre13 → 3.0.0.pre14

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/lib/scout_apm.rb +20 -10
  3. data/lib/scout_apm/agent.rb +114 -319
  4. data/lib/scout_apm/agent/exit_handler.rb +66 -0
  5. data/lib/scout_apm/agent/preconditions.rb +69 -0
  6. data/lib/scout_apm/agent_context.rb +234 -0
  7. data/lib/scout_apm/app_server_load.rb +24 -14
  8. data/lib/scout_apm/background_job_integrations/resque.rb +7 -8
  9. data/lib/scout_apm/background_job_integrations/sidekiq.rb +2 -2
  10. data/lib/scout_apm/background_recorder.rb +8 -3
  11. data/lib/scout_apm/background_worker.rb +14 -7
  12. data/lib/scout_apm/config.rb +35 -26
  13. data/lib/scout_apm/context.rb +11 -4
  14. data/lib/scout_apm/db_query_metric_set.rb +17 -5
  15. data/lib/scout_apm/debug.rb +1 -1
  16. data/lib/scout_apm/environment.rb +10 -14
  17. data/lib/scout_apm/framework_integrations/sinatra.rb +1 -1
  18. data/lib/scout_apm/git_revision.rb +13 -8
  19. data/lib/scout_apm/histogram.rb +1 -1
  20. data/lib/scout_apm/instant/middleware.rb +7 -7
  21. data/lib/scout_apm/instant_reporting.rb +7 -7
  22. data/lib/scout_apm/instrument_manager.rb +87 -0
  23. data/lib/scout_apm/instruments/action_controller_rails_2.rb +12 -7
  24. data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +17 -12
  25. data/lib/scout_apm/instruments/action_view.rb +11 -7
  26. data/lib/scout_apm/instruments/active_record.rb +25 -11
  27. data/lib/scout_apm/instruments/elasticsearch.rb +10 -6
  28. data/lib/scout_apm/instruments/grape.rb +12 -8
  29. data/lib/scout_apm/instruments/http_client.rb +10 -6
  30. data/lib/scout_apm/instruments/influxdb.rb +10 -6
  31. data/lib/scout_apm/instruments/middleware_detailed.rb +11 -5
  32. data/lib/scout_apm/instruments/middleware_summary.rb +11 -5
  33. data/lib/scout_apm/instruments/mongoid.rb +10 -5
  34. data/lib/scout_apm/instruments/moped.rb +11 -6
  35. data/lib/scout_apm/instruments/net_http.rb +10 -6
  36. data/lib/scout_apm/instruments/percentile_sampler.rb +8 -6
  37. data/lib/scout_apm/instruments/process/process_cpu.rb +8 -4
  38. data/lib/scout_apm/instruments/process/process_memory.rb +15 -10
  39. data/lib/scout_apm/instruments/rails_router.rb +12 -6
  40. data/lib/scout_apm/instruments/redis.rb +10 -6
  41. data/lib/scout_apm/instruments/samplers.rb +11 -0
  42. data/lib/scout_apm/instruments/sinatra.rb +5 -4
  43. data/lib/scout_apm/layaway.rb +21 -20
  44. data/lib/scout_apm/layaway_file.rb +8 -3
  45. data/lib/scout_apm/layer.rb +3 -3
  46. data/lib/scout_apm/layer_converters/converter_base.rb +6 -7
  47. data/lib/scout_apm/layer_converters/database_converter.rb +1 -1
  48. data/lib/scout_apm/layer_converters/histograms.rb +2 -2
  49. data/lib/scout_apm/layer_converters/slow_job_converter.rb +4 -3
  50. data/lib/scout_apm/layer_converters/slow_request_converter.rb +5 -4
  51. data/lib/scout_apm/logger.rb +143 -0
  52. data/lib/scout_apm/middleware.rb +7 -9
  53. data/lib/scout_apm/periodic_work.rb +28 -0
  54. data/lib/scout_apm/reporter.rb +14 -8
  55. data/lib/scout_apm/reporting.rb +135 -0
  56. data/lib/scout_apm/request_manager.rb +4 -6
  57. data/lib/scout_apm/serializers/payload_serializer.rb +1 -1
  58. data/lib/scout_apm/slow_job_policy.rb +6 -2
  59. data/lib/scout_apm/slow_job_record.rb +5 -5
  60. data/lib/scout_apm/slow_request_policy.rb +6 -2
  61. data/lib/scout_apm/slow_transaction.rb +5 -5
  62. data/lib/scout_apm/store.rb +22 -16
  63. data/lib/scout_apm/synchronous_recorder.rb +7 -3
  64. data/lib/scout_apm/tasks/doctor.rb +75 -0
  65. data/lib/scout_apm/tasks/support.rb +22 -0
  66. data/lib/scout_apm/tracer.rb +5 -5
  67. data/lib/scout_apm/tracked_request.rb +43 -19
  68. data/lib/scout_apm/utils/active_record_metric_name.rb +66 -8
  69. data/lib/scout_apm/utils/backtrace_parser.rb +1 -1
  70. data/lib/scout_apm/utils/installed_gems.rb +7 -3
  71. data/lib/scout_apm/utils/klass_helper.rb +8 -2
  72. data/lib/scout_apm/utils/scm.rb +1 -1
  73. data/lib/scout_apm/utils/sql_sanitizer.rb +3 -3
  74. data/lib/scout_apm/version.rb +1 -1
  75. data/lib/tasks/doctor.rake +11 -0
  76. data/scout_apm.gemspec +1 -0
  77. data/test/test_helper.rb +17 -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. data/test/unit/utils/active_record_metric_name_test.rb +45 -7
  92. metadata +27 -5
  93. data/lib/scout_apm/agent/logging.rb +0 -74
  94. data/lib/scout_apm/agent/reporting.rb +0 -129
  95. data/lib/scout_apm/utils/null_logger.rb +0 -13
@@ -0,0 +1,66 @@
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
+ context.shutting_down!
38
+ ScoutApm::Instruments::Stacks.uninstall
39
+ ::ScoutApm::Agent.instance.stop_background_worker
40
+ end
41
+
42
+ def exit_handler_supported?
43
+ if environment.sinatra?
44
+ logger.debug "Exit handler not supported for Sinatra"
45
+ false
46
+ elsif environment.jruby?
47
+ logger.debug "Exit handler not supported for JRuby"
48
+ false
49
+ elsif environment.rubinius?
50
+ logger.debug "Exit handler not supported for Rubinius"
51
+ false
52
+ else
53
+ true
54
+ end
55
+ end
56
+
57
+ def logger
58
+ context.logger
59
+ end
60
+
61
+ def environment
62
+ context.environment
63
+ end
64
+ end
65
+ end
66
+ 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,234 @@
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 shutting_down?
79
+ @shutting_down
80
+ end
81
+
82
+ def installed?
83
+ @installed
84
+ end
85
+
86
+ def logger
87
+ @logger ||= LoggerFactory.build(config, environment)
88
+ end
89
+
90
+ def ignored_uris
91
+ @ignored_uris ||= ScoutApm::IgnoredUris.new(config.value('ignore'))
92
+ end
93
+
94
+ def slow_request_policy
95
+ @slow_request_policy ||= ScoutApm::SlowRequestPolicy.new(self)
96
+ end
97
+
98
+ def slow_job_policy
99
+ @slow_job_policy ||= ScoutApm::SlowJobPolicy.new(self)
100
+ end
101
+
102
+ # Histogram of the cumulative requests since the start of the process
103
+ def request_histograms
104
+ @request_histograms ||= ScoutApm::RequestHistograms.new
105
+ end
106
+
107
+ # Histogram of the requests, distinct by reporting period (minute)
108
+ # { StoreReportingPeriodTimestamp => RequestHistograms }
109
+ def request_histograms_by_time
110
+ @request_histograms_by_time ||= Hash.new { |h, k| h[k] = ScoutApm::RequestHistograms.new }
111
+ end
112
+
113
+ def store
114
+ return @store if @store
115
+ self.store = ScoutApm::Store.new(self)
116
+ end
117
+
118
+ def layaway
119
+ @layaway ||= ScoutApm::Layaway.new(self)
120
+ end
121
+
122
+ def recorder
123
+ @recorder ||= RecorderFactory.build(self)
124
+ end
125
+
126
+ def dev_trace_enabled?
127
+ config.value('dev_trace') && environment.env == "development"
128
+ end
129
+
130
+ #############
131
+ # Setters #
132
+ #############
133
+
134
+ # When we set the config for any reason, there are some values we must
135
+ # reinitialize, since the config could have changed their settings, so nil
136
+ # them out here, then let them get lazily reset as needed
137
+ #
138
+ # Don't use this in initializer, since it'll attempt to log immediately
139
+ def config=(config)
140
+ @config = config
141
+
142
+ @logger = nil
143
+
144
+ log_configuration_settings
145
+
146
+ @ignored_uris = nil
147
+ @slow_request_policy = nil
148
+ @slow_job_policy = nil
149
+ @request_histograms = nil
150
+ @request_histograms_by_time = nil
151
+ @store = nil
152
+ @layaway = nil
153
+ @recorder = nil
154
+ end
155
+
156
+ def installed!
157
+ @installed = true
158
+ end
159
+
160
+ def started!
161
+ @started = true
162
+ end
163
+
164
+ def shutting_down!
165
+ @shutting_down = true
166
+ end
167
+
168
+ def store=(store)
169
+ @store = store
170
+
171
+ # Installs the default samplers
172
+ # Don't install samplers on nil stores
173
+ if store
174
+ ScoutApm::Instruments::Samplers::DEFAULT_SAMPLERS.each { |s| store.add_sampler(s) }
175
+ end
176
+ end
177
+
178
+ def recorder=(recorder)
179
+ @recorder = recorder
180
+ end
181
+
182
+ # I believe this is only useful for testing?
183
+ def environment=(env)
184
+ @environment = env
185
+ end
186
+
187
+ #########################
188
+ # Callbacks & helpers #
189
+ #
190
+ # Should find ways to remove these into external spots
191
+ #########################
192
+
193
+ # Called after config is reset and loaded from file
194
+ def log_configuration_settings
195
+ @config.log_settings(logger)
196
+
197
+ if !@config.any_keys_found?
198
+ logger.info("No configuration file loaded, and no configuration found in ENV. " +
199
+ "For assistance configuring Scout, visit " +
200
+ "http://help.apm.scoutapp.com/#configuration-options")
201
+ end
202
+ end
203
+ end
204
+
205
+ class RecorderFactory
206
+ def self.build(context)
207
+ if context.config.value("async_recording")
208
+ context.logger.debug("Using asynchronous recording")
209
+ ScoutApm::BackgroundRecorder.new(context).start
210
+ else
211
+ context.logger.debug("Using synchronous recording")
212
+ ScoutApm::SynchronousRecorder.new(context).start
213
+ end
214
+ end
215
+ end
216
+
217
+ class LoggerFactory
218
+ def self.build(config, environment)
219
+ ScoutApm::Logger.new(environment.root,
220
+ {
221
+ :log_level => config.value('log_level'),
222
+ :log_file_path => config.value('log_file_path'),
223
+ :stdout => config.value('log_stdout'),
224
+ :stderr => config.value('log_stderr'),
225
+ :logger_class => config.value('log_class'),
226
+ }
227
+ )
228
+ end
229
+
230
+ def self.build_minimal_logger
231
+ ScoutApm::Logger.new(nil, :stdout => true)
232
+ end
233
+ end
234
+ 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,19 +30,23 @@ 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
  }
46
+ ensure
47
+ # Sometimes :database_engine and :database_adapter can cause a reference to an AR connection.
48
+ # Make sure we release all AR connections held by this thread.
49
+ ActiveRecord::Base.clear_active_connections! if Utils::KlassHelper.defined?("ActiveRecord::Base")
44
50
  end
45
51
 
46
52
  # Calls `.to_s` on the object passed in.
@@ -52,5 +58,9 @@ module ScoutApm
52
58
  'to_s error'
53
59
  end
54
60
  end
61
+
62
+ def environment
63
+ context.environment
64
+ end
55
65
  end
56
66
  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