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
@@ -42,7 +42,7 @@ module ScoutApm
42
42
  ::Sidekiq::Processor.class_eval do
43
43
  def initialize_with_scout(boss)
44
44
  agent = ::ScoutApm::Agent.instance
45
- agent.start_background_worker
45
+ agent.start
46
46
  initialize_without_scout(boss)
47
47
  end
48
48
 
@@ -63,7 +63,7 @@ module ScoutApm
63
63
  queue_layer = ScoutApm::Layer.new('Queue', queue)
64
64
  job_layer = ScoutApm::Layer.new('Job', job_class(msg))
65
65
 
66
- if ScoutApm::Agent.instance.config.value('profile') && SidekiqMiddleware.version_supports_profiling?
66
+ if ScoutApm::Agent.instance.context.config.value('profile') && SidekiqMiddleware.version_supports_profiling?
67
67
  # Capture ScoutProf if we can
68
68
  #req.enable_profiled_thread!
69
69
  #job_layer.set_root_class(job_class)
@@ -4,15 +4,20 @@
4
4
 
5
5
  module ScoutApm
6
6
  class BackgroundRecorder
7
+ attr_reader :context
8
+
7
9
  attr_reader :queue
8
10
  attr_reader :thread
9
- attr_reader :logger
10
11
 
11
- def initialize(logger)
12
- @logger = logger
12
+ def initialize(context)
13
+ @context = context
13
14
  @queue = Queue.new
14
15
  end
15
16
 
17
+ def logger
18
+ context.logger
19
+ end
20
+
16
21
  def start
17
22
  logger.info("Starting BackgroundRecorder")
18
23
  @thread = Thread.new(&method(:thread_func))
@@ -6,17 +6,24 @@ module ScoutApm
6
6
 
7
7
  attr_reader :period
8
8
 
9
- def initialize(period=DEFAULT_PERIOD)
9
+ attr_reader :context
10
+
11
+ def initialize(context, period=DEFAULT_PERIOD)
12
+ @context = context
10
13
  @period = period
11
14
  @keep_running = true
12
15
  end
13
16
 
17
+ def logger
18
+ context.logger
19
+ end
20
+
14
21
  def running?
15
22
  @keep_running
16
23
  end
17
24
 
18
25
  def stop
19
- ScoutApm::Agent.instance.logger.debug "Background Worker: stop requested"
26
+ logger.debug "Background Worker: stop requested"
20
27
  @keep_running = false
21
28
  end
22
29
 
@@ -29,7 +36,7 @@ module ScoutApm
29
36
  def start(&block)
30
37
  @task = block
31
38
 
32
- ScoutApm::Agent.instance.logger.debug "Background Worker: Starting Background Worker, running every #{period} seconds"
39
+ logger.debug "Background Worker: starting to run every #{period} seconds"
33
40
 
34
41
  # The first run should be 1 period of time from now
35
42
  next_time = Time.now + period
@@ -47,7 +54,7 @@ module ScoutApm
47
54
 
48
55
  # Bail out if @keep_running is false
49
56
  unless @keep_running
50
- ScoutApm::Agent.instance.logger.debug "Background Worker: breaking from loop"
57
+ logger.debug "Background Worker: breaking from loop"
51
58
  break
52
59
  end
53
60
 
@@ -58,9 +65,9 @@ module ScoutApm
58
65
  next_time += period
59
66
  end
60
67
  rescue
61
- ScoutApm::Agent.instance.logger.debug "Background Worker Exception!"
62
- ScoutApm::Agent.instance.logger.debug $!.message
63
- ScoutApm::Agent.instance.logger.debug $!.backtrace
68
+ logger.debug "Background Worker Exception!"
69
+ logger.debug $!.message
70
+ logger.debug $!.backtrace
64
71
  end
65
72
  end
66
73
  end
@@ -52,8 +52,11 @@ module ScoutApm
52
52
  'hostname',
53
53
  'ignore',
54
54
  'key',
55
+ 'log_class',
55
56
  'log_file_path',
56
57
  'log_level',
58
+ 'log_stderr',
59
+ 'log_stdout',
57
60
  'monitor',
58
61
  'name',
59
62
  'profile',
@@ -159,29 +162,30 @@ module ScoutApm
159
162
 
160
163
  # Load up a config instance without attempting to load a file.
161
164
  # Useful for bootstrapping.
162
- def self.without_file
165
+ def self.without_file(context)
163
166
  overlays = [
164
167
  ConfigEnvironment.new,
165
168
  ConfigDefaults.new,
166
169
  ConfigNull.new,
167
170
  ]
168
- new(overlays)
171
+ new(context, overlays)
169
172
  end
170
173
 
171
174
  # Load up a config instance, attempting to load a yaml file. Allows a
172
175
  # definite location if requested, or will attempt to load the default
173
176
  # configuration file: APP_ROOT/config/scout_apm.yml
174
- def self.with_file(file_path=nil, config={})
177
+ def self.with_file(context, file_path=nil, config={})
175
178
  overlays = [
176
179
  ConfigEnvironment.new,
177
- ConfigFile.new(file_path, config),
180
+ ConfigFile.new(context, file_path, config),
178
181
  ConfigDefaults.new,
179
182
  ConfigNull.new,
180
183
  ]
181
- new(overlays)
184
+ new(context, overlays)
182
185
  end
183
186
 
184
- def initialize(overlays)
187
+ def initialize(context, overlays)
188
+ @context = context
185
189
  @overlays = Array(overlays)
186
190
  end
187
191
 
@@ -192,7 +196,7 @@ module ScoutApm
192
196
 
193
197
  def value(key)
194
198
  if ! KNOWN_CONFIG_OPTIONS.include?(key)
195
- ScoutApm::Agent.instance.logger.debug("Requested looking up a unknown configuration key: #{key} (not a problem. Evaluate and add to config.rb)")
199
+ logger.debug("Requested looking up a unknown configuration key: #{key} (not a problem. Evaluate and add to config.rb)")
196
200
  end
197
201
 
198
202
  o = overlay_for_key(key)
@@ -212,12 +216,25 @@ module ScoutApm
212
216
  @overlays.any? { |overlay| overlay.any_keys_found? }
213
217
  end
214
218
 
215
- def log_settings
216
- messages = KNOWN_CONFIG_OPTIONS.inject([]) do |memo, key|
219
+ # Returns an array of config keys, values, and source
220
+ # {key: "monitor", value: "true", source: "environment"}
221
+ #
222
+ def all_settings
223
+ KNOWN_CONFIG_OPTIONS.inject([]) do |memo, key|
217
224
  o = overlay_for_key(key)
218
- memo << "#{o.name} - #{key}: #{value(key).inspect}"
225
+ memo << {:key => key, :value => value(key).inspect, :source => o.name}
219
226
  end
220
- ScoutApm::Agent.instance.logger.debug("Resolved Setting Values:\n" + messages.join("\n"))
227
+ end
228
+
229
+ def log_settings(logger)
230
+ logger.debug(
231
+ "Resolved Setting Values:\n" +
232
+ all_settings.map{|hsh| "#{hsh[:source]} - #{hsh[:key]}: #{hsh[:value]}"}.join("\n")
233
+ )
234
+ end
235
+
236
+ def logger
237
+ @context.logger
221
238
  end
222
239
 
223
240
  class ConfigDefaults
@@ -310,7 +327,8 @@ module ScoutApm
310
327
  # is not found, inaccessbile, or unparsable, log a message to that effect,
311
328
  # and move on.
312
329
  class ConfigFile
313
- def initialize(file_path=nil, config={})
330
+ def initialize(context, file_path=nil, config={})
331
+ @context = context
314
332
  @config = config || {}
315
333
  @resolved_file_path = file_path || determine_file_path
316
334
  load_file(@resolved_file_path)
@@ -341,6 +359,8 @@ module ScoutApm
341
359
 
342
360
  private
343
361
 
362
+ attr_reader :context
363
+
344
364
  def load_file(file)
345
365
  @settings = {}
346
366
  if !File.exist?(@resolved_file_path)
@@ -376,26 +396,15 @@ module ScoutApm
376
396
  end
377
397
 
378
398
  def determine_file_path
379
- File.join(ScoutApm::Environment.instance.root, "config", "scout_apm.yml")
399
+ File.join(context.environment.root, "config", "scout_apm.yml")
380
400
  end
381
401
 
382
402
  def app_environment
383
- @config[:environment] || ScoutApm::Environment.instance.env
403
+ @config[:environment] || context.environment.env
384
404
  end
385
405
 
386
406
  def logger
387
- if ScoutApm::Agent.instance.logger
388
- return ScoutApm::Agent.instance.logger
389
- else
390
- l = Logger.new(STDOUT)
391
- if ENV["SCOUT_LOG_LEVEL"] == "debug"
392
- l.level = Logger::DEBUG
393
- else
394
- l.level = Logger::INFO
395
- end
396
-
397
- return l
398
- end
407
+ context.logger
399
408
  end
400
409
  end
401
410
  end
@@ -5,11 +5,18 @@
5
5
  # For misc context, use @Context#add@.
6
6
  module ScoutApm
7
7
  class Context
8
- def initialize
8
+ attr_reader :context
9
+
10
+ def initialize(context)
11
+ @context = context
9
12
  @extra = {}
10
13
  @user = {}
11
14
  end
12
15
 
16
+ def logger
17
+ context.logger
18
+ end
19
+
13
20
  # Generates a hash representation of the Context.
14
21
  # Example: {:monthly_spend => 100, :user => {:ip => '127.0.0.1'}}
15
22
  def to_hash
@@ -78,7 +85,7 @@ module ScoutApm
78
85
  if value.nil?
79
86
  false # don't log this ... easy to happen
80
87
  elsif !valid_type?([String, Symbol, Numeric, Time, Date, TrueClass, FalseClass],value)
81
- ScoutApm::Agent.instance.logger.info "The value for [#{key_value.keys.first}] is not a valid type [#{value.class}]."
88
+ logger.info "The value for [#{key_value.keys.first}] is not a valid type [#{value.class}]."
82
89
  false
83
90
  else
84
91
  true
@@ -92,12 +99,12 @@ module ScoutApm
92
99
  false # don't log this ... easy to happen
93
100
  # ensure a string or a symbol
94
101
  elsif !valid_type?([String, Symbol],key)
95
- ScoutApm::Agent.instance.logger.info "The key [#{key}] is not a valid type [#{key.class}]."
102
+ logger.info "The key [#{key}] is not a valid type [#{key.class}]."
96
103
  return false
97
104
  end
98
105
  # only alphanumeric, dash, and underscore allowed.
99
106
  if key.to_s.match(/[^\w-]/)
100
- ScoutApm::Agent.instance.logger.info "They key name [#{key}] is not valid."
107
+ logger.info "They key name [#{key}] is not valid."
101
108
  return false
102
109
  end
103
110
  true
@@ -1,14 +1,25 @@
1
+ # Note, this class must be Marshal Dumpable
1
2
  module ScoutApm
2
3
  class DbQueryMetricSet
3
4
  include Enumerable
4
5
 
5
6
  attr_reader :metrics # the raw metrics. You probably want #metrics_to_report
6
- attr_reader :config # A ScoutApm::Config instance
7
+ attr_reader :context
8
+
9
+ def marshal_dump
10
+ [ @metrics ]
11
+ end
12
+
13
+ def marshal_load(array)
14
+ @metrics = array.first
15
+ @context = ScoutApm::Agent.instance.context
16
+ end
17
+
18
+ def initialize(context)
19
+ @context = context
7
20
 
8
- def initialize(config=ScoutApm::Agent.instance.config)
9
21
  # A hash of DbQueryMetricStats values, keyed by DbQueryMetricStats.key
10
22
  @metrics = Hash.new
11
- @config = config
12
23
  end
13
24
 
14
25
  def each
@@ -54,7 +65,7 @@ module ScoutApm
54
65
  end
55
66
 
56
67
  def metrics_to_report
57
- report_limit = config.value('database_metric_report_limit')
68
+ report_limit = context.config.value('database_metric_report_limit')
58
69
  if metrics.size > report_limit
59
70
  metrics.
60
71
  values.
@@ -73,8 +84,9 @@ module ScoutApm
73
84
  end
74
85
 
75
86
  def at_limit?
76
- @limit ||= config.value('database_metric_limit')
87
+ @limit ||= context.config.value('database_metric_limit')
77
88
  metrics.size >= @limit
78
89
  end
90
+
79
91
  end
80
92
  end
@@ -31,7 +31,7 @@ module ScoutApm
31
31
  end
32
32
 
33
33
  def logger
34
- ScoutApm::Agent.instance.logger
34
+ ScoutApm::Agent.instance.context.logger
35
35
  end
36
36
  end
37
37
  end
@@ -59,7 +59,7 @@ module ScoutApm
59
59
  end
60
60
 
61
61
  def application_name
62
- Agent.instance.config.value("name") ||
62
+ Agent.instance.context.config.value("name") ||
63
63
  framework_integration.application_name ||
64
64
  "App"
65
65
  end
@@ -86,10 +86,10 @@ module ScoutApm
86
86
  end
87
87
 
88
88
  def scm_subdirectory
89
- @scm_subdirectory ||= if Agent.instance.config.value('scm_subdirectory').empty?
89
+ @scm_subdirectory ||= if Agent.instance.context.config.value('scm_subdirectory').empty?
90
90
  ''
91
91
  else
92
- Agent.instance.config.value('scm_subdirectory').sub(/^\//, '') # Trim any leading slash
92
+ Agent.instance.context.config.value('scm_subdirectory').sub(/^\//, '') # Trim any leading slash
93
93
  end
94
94
  end
95
95
 
@@ -98,7 +98,7 @@ module ScoutApm
98
98
  end
99
99
 
100
100
  def framework_root
101
- if override_root = Agent.instance.config.value("application_root")
101
+ if override_root = Agent.instance.context.config.value("application_root")
102
102
  return override_root
103
103
  end
104
104
  if framework == :rails
@@ -113,11 +113,11 @@ module ScoutApm
113
113
  end
114
114
 
115
115
  def hostname
116
- @hostname ||= Agent.instance.config.value("hostname") || platform_integration.hostname
116
+ @hostname ||= Agent.instance.context.config.value("hostname") || platform_integration.hostname
117
117
  end
118
118
 
119
119
  def git_revision
120
- @git_revision ||= ScoutApm::GitRevision.new
120
+ @git_revision ||= ScoutApm::GitRevision.new(Agent.instance.context)
121
121
  end
122
122
 
123
123
  # Returns the whole integration object
@@ -141,18 +141,14 @@ module ScoutApm
141
141
  app_server_integration.forking? || (background_job_integration && background_job_integration.forking?)
142
142
  end
143
143
 
144
- def background_job_integration
145
- if Agent.instance.config.value("enable_background_jobs")
146
- @background_job_integration ||= BACKGROUND_JOB_INTEGRATIONS.detect {|integration| integration.present?}
144
+ def background_job_integrations
145
+ if Agent.instance.context.config.value("enable_background_jobs")
146
+ @background_job_integrations ||= BACKGROUND_JOB_INTEGRATIONS.select {|integration| integration.present?}
147
147
  else
148
- nil
148
+ []
149
149
  end
150
150
  end
151
151
 
152
- def background_job_name
153
- background_job_integration && background_job_integration.name
154
- end
155
-
156
152
  # If both stdin & stdout are interactive and the Rails::Console constant is defined
157
153
  def interactive?
158
154
  defined?(::Rails::Console) && $stdout.isatty && $stdin.isatty
@@ -25,7 +25,7 @@ module ScoutApm
25
25
  "Sinatra"
26
26
  end
27
27
  rescue => e
28
- ScoutApm::Agent.instance.logger.debug "Failed getting Sinatra Application Name: #{e.message}\n#{e.backtrace.join("\n\t")}"
28
+ ScoutApm::Agent.instance.context.logger.debug "Failed getting Sinatra Application Name: #{e.message}\n#{e.backtrace.join("\n\t")}"
29
29
  "Sinatra"
30
30
  end
31
31
 
@@ -1,11 +1,17 @@
1
1
  module ScoutApm
2
2
  class GitRevision
3
-
4
3
  attr_accessor :sha
5
4
 
6
- def initialize
5
+ attr_reader :context
6
+
7
+ def initialize(context)
8
+ @context = context
7
9
  @sha = detect
8
- ScoutApm::Agent.instance.logger.debug "Detected Git Revision [#{@sha}]"
10
+ logger.debug "Detected Git Revision [#{@sha}]"
11
+ end
12
+
13
+ def logger
14
+ context.logger
9
15
  end
10
16
 
11
17
  private
@@ -30,7 +36,7 @@ module ScoutApm
30
36
  # Capistrano 3.0 - 3.1.x
31
37
  version || File.open(File.join(app_root, '..', 'revisions.log')).to_a.last.strip.sub(/.*as release ([0-9]+).*/, '\1')
32
38
  rescue
33
- ScoutApm::Agent.instance.logger.debug "Unable to detect Git Revision from Capistrano: #{$!.message}"
39
+ logger.debug "Unable to detect Git Revision from Capistrano: #{$!.message}"
34
40
  nil
35
41
  end
36
42
 
@@ -39,13 +45,12 @@ module ScoutApm
39
45
  `git rev-parse --short HEAD`.strip
40
46
  end
41
47
  rescue
42
- ScoutApm::Agent.instance.logger.debug "Unable to detect Git Revision from Git: #{$!.message}"
48
+ logger.debug "Unable to detect Git Revision from Git: #{$!.message}"
43
49
  nil
44
50
  end
45
51
 
46
52
  def app_root
47
- ScoutApm::Environment.instance.root
53
+ context.environment.root
48
54
  end
49
-
50
55
  end
51
- end
56
+ end