scout_apm 3.0.0.pre13 → 3.0.0.pre14

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 (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