scout_apm 2.4.0.pre3 → 2.4.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 29cede8ad70dd0d7cb0be6a777da5d346309ff24
4
- data.tar.gz: 17f98116601cc9b75c3d0da0e9f315f2250af174
3
+ metadata.gz: 35153897d05053c188035cea90a9c9821abcd4cf
4
+ data.tar.gz: 94fe5aee4fa214256fe31ffe643973274f2d66fb
5
5
  SHA512:
6
- metadata.gz: 4b5717805b91c5f13ab00e22c5739b4bba32ab7f4bf1eae18118607019937cfba7ad53dfd76086ddc1c5d8fcd2f2350686b594cbb5b6b3a44a61b4759305cad3
7
- data.tar.gz: 60b82a60a84001d6a480f2ebdbf16cb5c964e729b30bcbc82c47b0cd526aea71802e3b753bb568176d5a4beca02a1a2d5bdf9b012a0bb48318fc6fb6b2908307
6
+ metadata.gz: efef5649e203008c653a8fba69e1c28de35f510d831ff3e47a4483fda8e92116cd8aa5e1e64d3ab7ffe63cef9f03113987f953cc78f65a05af076be04fa1fb7b
7
+ data.tar.gz: 37966ab847e9c50ce354c9f8a2fe3c4ecfda397e091fc2d89f02b7816b2b14bfdd8e4bf56528a1f6572e9bb92b26ab6438b7a4bb83709969c887a8f904a810a0
data/CHANGELOG.markdown CHANGED
@@ -1,10 +1,27 @@
1
1
  # 2.4.0
2
2
 
3
3
  * Rework agent startup sequence
4
+ * Install all background job instrumentations if you're running more than one
5
+ * Capture longer individual SQL statements
6
+ * Capture multiple SQL statements if multiple are run during a single AR call.
7
+
8
+ # 2.3.5
9
+
10
+ * More robust recovery from stale layaway files
11
+ * Quiet logging when hitting unusual layaway file limits
12
+ * Better naming for Sidekiq delayed method jobs
13
+ * Webrick is only required if actually needed
14
+
15
+ # 2.3.4
16
+
17
+ * Capture 300 characters of a url from net/http and httpclient instruments (up from 100).
4
18
 
5
19
  # 2.3.3
6
20
 
7
21
  * Capture ActiveRecord calls that generate more complex queries
22
+ * More aggressively determine names of complex queries (to determine "User/find", "Account/create" and similar)
23
+ * Increases the maximum size of SQL queries that are sanitized to 16KB from 4 KB
24
+ * Captures all SQL individual queries generated in a given AR call (previous only a single query was captured)
8
25
 
9
26
  # 2.3.2
10
27
 
data/README.markdown CHANGED
@@ -1,14 +1,21 @@
1
- # ScoutApm
1
+ # ScoutApm Ruby Agent
2
2
 
3
- A Ruby gem for detailed Rails application performance analysis. Metrics are reported to [Scout](https://scoutapp.com), a hosted application monitoring service.
3
+ A Ruby gem for detailed Rails application performance analysis. Metrics are
4
+ reported to [Scout](https://scoutapp.com), a hosted application monitoring
5
+ service.
4
6
 
5
7
  ## Getting Started
6
8
 
7
- Install the gem:
9
+ Add the gem to your Gemfile
8
10
 
9
- gem install scout_apm
10
-
11
- Signup for a [Scout](https://apm.scoutapp.com) account and copy the config file to `RAILS_ROOT/config/scout_apm.yml`.
11
+ gem 'scout_apm'
12
+
13
+ Update your Gemfile
14
+
15
+ bundle install
16
+
17
+ Signup for a [Scout](https://apm.scoutapp.com) account and put the provided
18
+ config file at `RAILS_ROOT/config/scout_apm.yml`.
12
19
 
13
20
  Your config file should look like:
14
21
 
@@ -22,7 +29,8 @@ Your config file should look like:
22
29
 
23
30
  ## Docs
24
31
 
25
- For the complete list of supported frameworks, Rubies, etc, see our [help site](http://help.apm.scoutapp.com/).
32
+ For the complete list of supported frameworks, Rubies, configuration options
33
+ and more, see our [help site](http://help.apm.scoutapp.com/).
26
34
 
27
35
  ## Help
28
36
 
data/lib/scout_apm.rb CHANGED
@@ -14,7 +14,6 @@ require 'socket'
14
14
  require 'thread'
15
15
  require 'time'
16
16
  require 'yaml'
17
- require 'webrick'
18
17
 
19
18
  #####################################
20
19
  # Gem Requires
@@ -43,18 +43,18 @@ module ScoutApm
43
43
  install_background_job_integrations
44
44
  install_app_server_integration
45
45
 
46
- # XXX: Should this happen at application start?
47
- # Should this ever happen after fork?
48
- # We start a thread in this, which can screw stuff up when we then fork.
49
- #
50
- # Save it into a variable to prevent it from ever running twice
51
- @app_server_load ||= AppServerLoad.new(context).run
52
-
53
46
  logger.info "Scout Agent [#{ScoutApm::VERSION}] installed"
54
47
 
55
48
  context.installed!
56
49
 
57
50
  if ScoutApm::Agent::Preconditions.check?(context) || force
51
+ # XXX: Should this happen at application start?
52
+ # Should this ever happen after fork?
53
+ # We start a thread in this, which can screw stuff up when we then fork.
54
+ #
55
+ # Save it into a variable to prevent it from ever running twice
56
+ @app_server_load ||= AppServerLoad.new(context).run
57
+
58
58
  start
59
59
  end
60
60
  end
@@ -124,9 +124,8 @@ module ScoutApm
124
124
 
125
125
  def should_load_instruments?
126
126
  return true if context.config.value('dev_trace')
127
- # XXX: If monitor is true, we want to install, right?
128
- # return false if context.config.value('monitor')
129
- context.environment.app_server_integration.found? || context.environment.background_job_integration
127
+ return false unless context.config.value('monitor')
128
+ context.environment.app_server_integration.found? || context.environment.background_job_integrations.any?
130
129
  end
131
130
 
132
131
  #################################
@@ -51,7 +51,6 @@ module ScoutApm
51
51
  queue = job.queue || "default"
52
52
 
53
53
  req = ScoutApm::RequestManager.lookup
54
- req.job!
55
54
 
56
55
  begin
57
56
  latency = Time.now - job.created_at
@@ -57,7 +57,6 @@ module ScoutApm
57
57
  class SidekiqMiddleware
58
58
  def call(_worker, msg, queue)
59
59
  req = ScoutApm::RequestManager.lookup
60
- req.job!
61
60
  req.annotate_request(:queue_latency => latency(msg))
62
61
 
63
62
  begin
@@ -92,10 +91,16 @@ module ScoutApm
92
91
  end
93
92
  elsif job_class == DELAYED_WRAPPER_KLASS
94
93
  begin
94
+ # Extract the info out of the wrapper
95
95
  yml = msg['args'].first
96
96
  deserialized_args = YAML.load(yml)
97
97
  klass, method, *rest = deserialized_args
98
- job_class = [klass,method].map(&:to_s).join(".")
98
+
99
+ # If this is an instance of a class, get the class itself
100
+ # Prevents instances from coming through named like "#<Foo:0x007ffd7a9dd8a0>"
101
+ klass = klass.class unless klass.is_a? Module
102
+
103
+ job_class = [klass, method].map(&:to_s).join(".")
99
104
  rescue
100
105
  DELAYED_WRAPPER_KLASS
101
106
  end
@@ -66,6 +66,7 @@ module ScoutApm
66
66
  'report_format',
67
67
  'scm_subdirectory',
68
68
  'uri_reporting',
69
+ 'instrument_http_url_length',
69
70
  ]
70
71
 
71
72
  ################################################################################
@@ -153,6 +154,7 @@ module ScoutApm
153
154
  "monitor" => BooleanCoercion.new,
154
155
  'database_metric_limit' => IntegerCoercion.new,
155
156
  'database_metric_report_limit' => IntegerCoercion.new,
157
+ 'instrument_http_url_length' => IntegerCoercion.new,
156
158
  }
157
159
 
158
160
 
@@ -256,6 +258,7 @@ module ScoutApm
256
258
  'remote_agent_port' => 7721, # picked at random
257
259
  'database_metric_limit' => 5000, # The hard limit on db metrics
258
260
  'database_metric_report_limit' => 1000,
261
+ 'instrument_http_url_length' => 300,
259
262
  }.freeze
260
263
 
261
264
  def value(key)
@@ -57,7 +57,6 @@ module ScoutApm
57
57
  req.context.add_user(:ip => request.remote_ip)
58
58
  req.set_headers(request.headers)
59
59
  req.start_layer( ScoutApm::Layer.new("Controller", "#{controller_path}/#{action_name}") )
60
- req.web!
61
60
 
62
61
  begin
63
62
  perform_action_without_scout_instruments(*args, &block)
@@ -77,8 +77,6 @@ module ScoutApm
77
77
  req.context.add_user(:ip => request.remote_ip) rescue nil
78
78
  req.set_headers(request.headers)
79
79
 
80
- req.web!
81
-
82
80
  resolved_name = scout_action_name(*args)
83
81
  req.start_layer( ScoutApm::Layer.new("Controller", "#{controller_path}/#{resolved_name}") )
84
82
  begin
@@ -44,7 +44,6 @@ module ScoutApm
44
44
  req.context.add_user(:ip => request.ip) rescue nil
45
45
 
46
46
  req.set_headers(request.headers)
47
- req.web!
48
47
 
49
48
  begin
50
49
  name = ["Grape",
@@ -26,9 +26,12 @@ module ScoutApm
26
26
  include ScoutApm::Tracer
27
27
 
28
28
  def request_with_scout_instruments(*args, &block)
29
+
29
30
  method = args[0].to_s
30
31
  url = args[1]
31
- url = url && url.to_s[0..99]
32
+
33
+ max_length = ScoutApm::Agent.instance.context.config.value('instrument_http_url_length')
34
+ url = url && url.to_s[0..(max_length - 1)]
32
35
 
33
36
  self.class.instrument("HTTP", method, :desc => url) do
34
37
  request_without_scout_instruments(*args, &block)
@@ -34,7 +34,9 @@ module ScoutApm
34
34
  def request_scout_description(req)
35
35
  path = req.path
36
36
  path = path.path if path.respond_to?(:path)
37
- (@address + path.split('?').first)[0..99]
37
+
38
+ max_length = ScoutApm::Agent.instance.context.config.value('instrument_http_url_length')
39
+ (@address + path.split('?').first)[0..(max_length - 1)]
38
40
  end
39
41
 
40
42
  alias request_without_scout_instruments request
@@ -11,7 +11,6 @@ module ScoutApm
11
11
  end
12
12
 
13
13
  req = ScoutApm::RequestManager.lookup
14
- req.job!
15
14
 
16
15
  begin
17
16
  req.start_layer(ScoutApm::Layer.new('Queue', queue))
@@ -51,7 +51,8 @@ module ScoutApm
51
51
 
52
52
  def write_reporting_period(reporting_period, files_limit = MAX_FILES_LIMIT)
53
53
  if at_layaway_file_limit?(files_limit)
54
- logger.error("Hit layaway file limit. Not writing to layaway file")
54
+ # This will happen constantly once we hit this case, so only log the first time
55
+ @wrote_layaway_limit_error_message ||= logger.error("Hit layaway file limit. Not writing to layaway file")
55
56
  return false
56
57
  end
57
58
  filename = file_for(reporting_period.timestamp)
@@ -59,9 +60,22 @@ module ScoutApm
59
60
  layaway_file.write(reporting_period)
60
61
  end
61
62
 
62
- # Claims a given timestamp (getting a lock on a particular filename),
63
- # then yields ReportingPeriods collected up from all the files.
64
- # If the yield returns truthy, delete the layaway files that made it up.
63
+ # Claims a given timestamp by getting an exclusive lock on a timestamped
64
+ # coordinator file. The coordinator file never contains data, it's just a
65
+ # syncronization mechanism.
66
+ #
67
+ # Once the 'claim' is obtained:
68
+ # * load and yield each ReportingPeriod from the layaway files.
69
+ # * if there are reporting periods:
70
+ # * yields any ReportingPeriods collected up from all the files.
71
+ # * deletes all of the layaway files (including the coordinator) for the timestamp
72
+ # * if not
73
+ # * delete the coordinator
74
+ # * remove any stale layaway files that may be hanging around.
75
+ # * Finally unlock and ensure the coordinator file is cleared.
76
+ #
77
+ # If a claim file can't be obtained, return false without doing any work
78
+ # Another process is handling the reporting.
65
79
  def with_claim(timestamp)
66
80
  coordinator_file = glob_pattern(timestamp, :coordinator)
67
81
 
@@ -86,14 +100,14 @@ module ScoutApm
86
100
 
87
101
  logger.debug("Deleting the now-reported layaway files for #{timestamp.to_s}")
88
102
  delete_files_for(timestamp) # also removes the coodinator_file
89
-
90
- logger.debug("Checking for any Stale layaway files")
91
- delete_stale_files(timestamp.to_time - STALE_AGE)
92
103
  else
93
104
  File.unlink(coordinator_file)
94
105
  logger.debug("No layaway files to report")
95
106
  end
96
107
 
108
+ logger.debug("Checking for any Stale layaway files")
109
+ delete_stale_files(timestamp.to_time - STALE_AGE)
110
+
97
111
  true
98
112
  rescue Exception => e
99
113
  logger.debug("Caught an exception in with_claim, with the coordination file locked: #{e.message}, #{e.backtrace.inspect}")
@@ -16,6 +16,10 @@ module ScoutApm
16
16
  @scope ||= call(["Controller", "Job"])
17
17
  end
18
18
 
19
+ def controller
20
+ @controller ||= call(["Controller"])
21
+ end
22
+
19
23
  def job
20
24
  @job ||= call(["Job"])
21
25
  end
@@ -10,8 +10,8 @@ module ScoutApm
10
10
  req.annotate_request(:uri => env["REQUEST_PATH"]) rescue nil
11
11
  req.context.add_user(:ip => env["REMOTE_ADDR"]) rescue nil
12
12
 
13
- req.web!
14
- req.start_layer(ScoutApm::Layer.new('Controller', endpoint_name))
13
+ layer = ScoutApm::Layer.new('Controller', endpoint_name)
14
+ req.start_layer(layer)
15
15
 
16
16
  begin
17
17
  yield
@@ -17,6 +17,8 @@ module ScoutApm
17
17
  end
18
18
 
19
19
  def start
20
+ require 'webrick'
21
+
20
22
  @server = WEBrick::HTTPServer.new(
21
23
  :BindAddress => bind,
22
24
  :Port => port,
@@ -26,10 +26,6 @@ module ScoutApm
26
26
  # Can be nil if we never reach a Rails Controller
27
27
  attr_reader :headers
28
28
 
29
- # What kind of request is this? A trace of a web request, or a background job?
30
- # Use job! and web! to set, and job? and web? to query
31
- attr_reader :request_type
32
-
33
29
  # This maintains a lookup hash of Layer names and call counts. It's used to trigger fetching a backtrace on n+1 calls.
34
30
  # Note that layer names might not be Strings - can alse be Utils::ActiveRecordMetricName. Also, this would fail for layers
35
31
  # with same names across multiple types.
@@ -222,20 +218,14 @@ module ScoutApm
222
218
  @headers = headers
223
219
  end
224
220
 
225
- def job!
226
- @request_type = "job"
227
- end
228
-
221
+ # This request is a job transaction iff it has a 'Job' layer
229
222
  def job?
230
- request_type == "job"
231
- end
232
-
233
- def web!
234
- @request_type = "web"
223
+ layer_finder.job != nil
235
224
  end
236
225
 
226
+ # This request is a web transaction iff it has a 'Controller' layer
237
227
  def web?
238
- request_type == "web"
228
+ layer_finder.controller != nil
239
229
  end
240
230
 
241
231
  def instant?
@@ -279,7 +269,6 @@ module ScoutApm
279
269
  LayerConverters::SlowRequestConverter,
280
270
  ]
281
271
 
282
- layer_finder = LayerConverters::FindLayerByType.new(self)
283
272
  walker = LayerConverters::DepthFirstWalker.new(self.root_layer)
284
273
  converters = converters.map do |klass|
285
274
  instance = klass.new(@agent_context, self, layer_finder, @store)
@@ -301,6 +290,10 @@ module ScoutApm
301
290
  end
302
291
  end
303
292
 
293
+ def layer_finder
294
+ @layer_finder ||= LayerConverters::FindLayerByType.new(self)
295
+ end
296
+
304
297
  # Ensure the background worker thread is up & running - a fallback if other
305
298
  # detection doesn't achieve this at boot.
306
299
  def ensure_background_worker
@@ -1,4 +1,4 @@
1
1
  module ScoutApm
2
- VERSION = "2.4.0.pre3"
2
+ VERSION = "2.4.0"
3
3
  end
4
4
 
@@ -11,7 +11,6 @@ class SidekiqTest < Minitest::Test
11
11
  def test_middleware_call_happy_path
12
12
  fake_request = mock
13
13
  fake_request.expects(:annotate_request)
14
- fake_request.expects(:job!)
15
14
  fake_request.expects(:start_layer).twice
16
15
  fake_request.expects(:stop_layer).twice
17
16
  fake_request.expects(:error!).never
@@ -29,7 +28,6 @@ class SidekiqTest < Minitest::Test
29
28
  def test_middleware_call_job_exception
30
29
  fake_request = mock
31
30
  fake_request.expects(:annotate_request)
32
- fake_request.expects(:job!)
33
31
  fake_request.expects(:start_layer).twice
34
32
  fake_request.expects(:stop_layer).twice
35
33
  fake_request.expects(:error!)
@@ -47,7 +45,6 @@ class SidekiqTest < Minitest::Test
47
45
  def test_middleware_call_edge_cases
48
46
  fake_request = mock
49
47
  fake_request.expects(:annotate_request)
50
- fake_request.expects(:job!)
51
48
  fake_request.expects(:start_layer).twice
52
49
  fake_request.expects(:stop_layer).twice
53
50
  fake_request.expects(:error!)
@@ -3,7 +3,7 @@ require 'test_helper'
3
3
  class TrackedRequestDumpAndLoadTest < Minitest::Test
4
4
  # TrackedRequest must be marshalable
5
5
  def test_marshal_dump_load
6
- tr = ScoutApm::TrackedRequest.new(ScoutApm::FakeStore.new)
6
+ tr = ScoutApm::TrackedRequest.new(ScoutApm::AgentContext.new, ScoutApm::FakeStore.new)
7
7
  tr.prepare_to_dump!
8
8
 
9
9
  dumped = Marshal.dump(tr)
@@ -12,58 +12,22 @@ class TrackedRequestDumpAndLoadTest < Minitest::Test
12
12
  end
13
13
 
14
14
  def test_restore_store
15
- faux = ScoutApm::FakeStore.new
16
- tr = ScoutApm::TrackedRequest.new(faux)
17
- assert_equal faux, tr.instance_variable_get("@store")
15
+ faux_store = ScoutApm::FakeStore.new
16
+ context = ScoutApm::AgentContext.new
17
+ tr = ScoutApm::TrackedRequest.new(context, faux_store)
18
+ assert_equal faux_store, tr.instance_variable_get("@store")
18
19
 
19
20
  tr.prepare_to_dump!
20
21
  assert_nil tr.instance_variable_get("@store")
21
22
 
22
23
  tr.restore_store
23
- assert_equal ScoutApm::Agent.instance.store, tr.instance_variable_get("@store")
24
- end
25
- end
26
-
27
- class TrackedRequestFlagsTest < Minitest::Test
28
- def test_set_web
29
- tr = ScoutApm::TrackedRequest.new(ScoutApm::FakeStore.new)
30
- assert_false tr.web?
31
- tr.web!
32
- assert tr.web?
33
- end
34
-
35
- def test_set_job
36
- tr = ScoutApm::TrackedRequest.new(ScoutApm::FakeStore.new)
37
- assert ! tr.job?
38
- tr.job!
39
- assert tr.job?
40
- end
41
-
42
- def test_set_error
43
- tr = ScoutApm::TrackedRequest.new(ScoutApm::FakeStore.new)
44
- assert_false tr.error?
45
- tr.error!
46
- assert tr.error?
47
- end
48
-
49
- def test_set_error_and_web
50
- tr = ScoutApm::TrackedRequest.new(ScoutApm::FakeStore.new)
51
- assert_false tr.error?
52
- assert_false tr.web?
53
-
54
- tr.web!
55
- assert_false tr.error?
56
- assert tr.web?
57
-
58
- tr.error!
59
- assert tr.error?
60
- assert tr.web?
24
+ assert_equal context.store, tr.instance_variable_get("@store")
61
25
  end
62
26
  end
63
27
 
64
28
  class TrackedRequestLayerManipulationTest < Minitest::Test
65
29
  def test_start_layer
66
- tr = ScoutApm::TrackedRequest.new(ScoutApm::FakeStore.new)
30
+ tr = ScoutApm::TrackedRequest.new(ScoutApm::AgentContext.new, ScoutApm::FakeStore.new)
67
31
  tr.start_layer(ScoutApm::Layer.new("Foo", "Bar"))
68
32
 
69
33
  assert_equal "Foo", tr.current_layer.type
@@ -74,7 +38,7 @@ class TrackedRequestLayerManipulationTest < Minitest::Test
74
38
  controller_layer = ScoutApm::Layer.new("Controller", "users/index")
75
39
  ar_layer = ScoutApm::Layer.new("ActiveRecord", "Users#find")
76
40
 
77
- tr = ScoutApm::TrackedRequest.new(ScoutApm::FakeStore.new)
41
+ tr = ScoutApm::TrackedRequest.new(ScoutApm::AgentContext.new, ScoutApm::FakeStore.new)
78
42
  tr.start_layer(controller_layer)
79
43
  tr.start_layer(ar_layer)
80
44
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scout_apm
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.0.pre3
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Derek Haynes
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2017-12-21 00:00:00.000000000 Z
12
+ date: 2018-01-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: minitest
@@ -360,9 +360,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
360
360
  version: '0'
361
361
  required_rubygems_version: !ruby/object:Gem::Requirement
362
362
  requirements:
363
- - - ">"
363
+ - - ">="
364
364
  - !ruby/object:Gem::Version
365
- version: 1.3.1
365
+ version: '0'
366
366
  requirements: []
367
367
  rubyforge_project: scout_apm
368
368
  rubygems_version: 2.4.5.2