scout_apm 2.1.2 → 2.1.3

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: c4c35583425f2cec75988d311df46da4b1cea195
4
- data.tar.gz: 3e2b125f355a1a5e89037ecf20e2c2d71e7674e6
3
+ metadata.gz: ee80eaab8042686350c223a715ff7a12ff7b5eee
4
+ data.tar.gz: 13d93be3815d1b014716081d130466764a79aeed
5
5
  SHA512:
6
- metadata.gz: f9a78acc7da99ee83c64974b0b32703016acf296855558c09bf7e72b640cef692d9e366f019e687b8d7cf1f337b8085712efa8d2e8a31b7530d314d8a4f483b2
7
- data.tar.gz: 876fd43670907912db636a647cdc858205b73733a4f06ee739bf4c592f70aceb1ed3e153b4a58437cbe63146f2af28a44d15e08924d6f1ab6f58f8b0cc325db8
6
+ metadata.gz: bba413fa76099cdede351d9fa6263ea53e93448a8235918ddac4a9c1072174aff0fb084a5e3cd591079e656d779c369838bc6866b2644c75772e4ab0dc85442c
7
+ data.tar.gz: ccbc7c25bc0aae891b79563b14c4bc4d72ca8f1ebe92b1a32c19e7e74861e61a3003208117a5d9a26dba3c2d83f7b49b7fc351d5df3670ff189c59cacd865869
data/CHANGELOG.markdown CHANGED
@@ -1,5 +1,14 @@
1
1
  # master
2
2
 
3
+ # 2.1.3
4
+
5
+ * Less noisy output on errors with Context
6
+ * Not logging errors w/nil keys or values
7
+ * Bumping log level down from WARN => INFO on errors
8
+ * Fix error with complicated AR queries
9
+ * Caused high log noise
10
+ * Sidekiq instrumentation changes to handle a variety of edge cases
11
+
3
12
  # 2.1.2
4
13
 
5
14
  * Applies `Rails.application.config.filter_parameters` settings to reported transaction trace uris
@@ -8,7 +8,7 @@ module ScoutApm
8
8
  end
9
9
 
10
10
  def present?
11
- defined?(::Sidekiq) && (File.basename($0) =~ /\Asidekiq/)
11
+ defined?(::Sidekiq) && File.basename($PROGRAM_NAME).start_with?('sidekiq')
12
12
  end
13
13
 
14
14
  def forking?
@@ -16,22 +16,33 @@ module ScoutApm
16
16
  end
17
17
 
18
18
  def install
19
+ install_tracer
20
+ add_middleware
21
+ install_processor
22
+ end
23
+
24
+ def install_tracer
19
25
  # ScoutApm::Tracer is not available when this class is defined
20
26
  SidekiqMiddleware.class_eval do
21
27
  include ScoutApm::Tracer
22
28
  end
29
+ end
23
30
 
31
+ def add_middleware
24
32
  ::Sidekiq.configure_server do |config|
25
33
  config.server_middleware do |chain|
26
34
  chain.add SidekiqMiddleware
27
35
  end
28
36
  end
37
+ end
29
38
 
39
+ def install_processor
30
40
  require 'sidekiq/processor' # sidekiq v4 has not loaded this file by this point
31
41
 
32
42
  ::Sidekiq::Processor.class_eval do
33
43
  def initialize_with_scout(boss)
34
- ::ScoutApm::Agent.instance.start_background_worker unless ::ScoutApm::Agent.instance.background_worker_running?
44
+ agent = ::ScoutApm::Agent.instance
45
+ agent.start_background_worker
35
46
  initialize_without_scout(boss)
36
47
  end
37
48
 
@@ -41,31 +52,53 @@ module ScoutApm
41
52
  end
42
53
  end
43
54
 
55
+ # We insert this middleware into the Sidekiq stack, to capture each job,
56
+ # and time them.
44
57
  class SidekiqMiddleware
45
- def call(worker, msg, queue)
46
- job_class = msg["class"] # TODO: Validate this across different versions of Sidekiq
47
- if job_class == "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper" && msg.has_key?("wrapped")
48
- job_class = msg["wrapped"]
49
- end
50
-
51
- latency = (Time.now.to_f - (msg['enqueued_at'] || msg['created_at']))
58
+ ACTIVE_JOB_KLASS = 'ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper'.freeze
52
59
 
60
+ def call(_worker, msg, queue)
53
61
  req = ScoutApm::RequestManager.lookup
54
62
  req.job!
55
- req.annotate_request(:queue_latency => latency)
56
-
57
- req.start_layer( ScoutApm::Layer.new("Queue", queue) )
58
- req.start_layer( ScoutApm::Layer.new("Job", job_class) )
63
+ req.annotate_request(:queue_latency => latency(msg))
59
64
 
60
65
  begin
66
+ req.start_layer(ScoutApm::Layer.new('Queue', queue))
67
+ started_queue = true
68
+ req.start_layer(ScoutApm::Layer.new('Job', job_class(msg)))
69
+ started_job = true
70
+
61
71
  yield
62
72
  rescue
63
73
  req.error!
64
74
  raise
75
+ ensure
76
+ req.stop_layer if started_job
77
+ req.stop_layer if started_queue
78
+ end
79
+ end
80
+
81
+ UNKNOWN_CLASS_PLACEHOLDER = 'UnknownJob'.freeze
82
+
83
+ def job_class(msg)
84
+ job_class = msg.fetch('class', UNKNOWN_CLASS_PLACEHOLDER)
85
+ if job_class == ACTIVE_JOB_KLASS && msg.key?('wrapped')
86
+ job_class = msg['wrapped']
87
+ end
88
+ job_class
89
+ rescue
90
+ UNKNOWN_CLASS_PLACEHOLDER
91
+ end
92
+
93
+ def latency(msg, time = Time.now.to_f)
94
+ created_at = msg['enqueued_at'] || msg['created_at']
95
+ if created_at
96
+ (time - created_at)
97
+ else
98
+ 0
65
99
  end
66
- ensure
67
- req.stop_layer # Job
68
- req.stop_layer # Queue
100
+ rescue
101
+ 0
69
102
  end
70
103
  end
71
104
  end
@@ -75,8 +75,10 @@ module ScoutApm
75
75
  def value_valid?(key_value)
76
76
  # ensure one of our accepted types.
77
77
  value = key_value.values.last
78
- if !valid_type?([String, Symbol, Numeric, Time, Date, TrueClass, FalseClass],value)
79
- ScoutApm::Agent.instance.logger.warn "The value for [#{key_value.keys.first}] is not a valid type [#{value.class}]."
78
+ if value.nil?
79
+ false # don't log this ... easy to happen
80
+ 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}]."
80
82
  false
81
83
  else
82
84
  true
@@ -86,14 +88,16 @@ module ScoutApm
86
88
  # for consistently with #value_valid?, takes a hash eventhough the value isn't yet used.
87
89
  def key_valid?(key_value)
88
90
  key = key_value.keys.first
91
+ if key.nil?
92
+ false # don't log this ... easy to happen
89
93
  # ensure a string or a symbol
90
- if !valid_type?([String, Symbol],key)
91
- ScoutApm::Agent.instance.logger.warn "The key [#{key}] is not a valid type [#{key.class}]."
94
+ elsif !valid_type?([String, Symbol],key)
95
+ ScoutApm::Agent.instance.logger.info "The key [#{key}] is not a valid type [#{key.class}]."
92
96
  return false
93
97
  end
94
98
  # only alphanumeric, dash, and underscore allowed.
95
99
  if key.to_s.match(/[^\w-]/)
96
- ScoutApm::Agent.instance.logger.warn "They key name [#{key}] is not valid."
100
+ ScoutApm::Agent.instance.logger.info "They key name [#{key}] is not valid."
97
101
  return false
98
102
  end
99
103
  true
@@ -47,7 +47,7 @@ module ScoutApm
47
47
  @layers = []
48
48
  @call_set = Hash.new { |h, k| h[k] = CallSet.new }
49
49
  @annotations = {}
50
- @ignoring_children = false
50
+ @ignoring_children = 0
51
51
  @context = Context.new
52
52
  @root_layer = nil
53
53
  @error = false
@@ -297,27 +297,41 @@ module ScoutApm
297
297
  # specific, and useful than the fact that InfluxDB happens to use Net::HTTP
298
298
  # internally
299
299
  #
300
- # When enabled, new layers won't be added to the current Request.
300
+ # When enabled, new layers won't be added to the current Request, and calls
301
+ # to stop_layer will be ignored.
301
302
  #
302
303
  # Do not forget to turn if off when leaving a layer, it is the
303
304
  # instrumentation's task to do that.
305
+ #
306
+ # When you use this in code, be sure to use it in this order:
307
+ #
308
+ # start_layer
309
+ # ignore_children
310
+ # -> call
311
+ # acknowledge_children
312
+ # stop_layer
313
+ #
314
+ # If you don't call it in this order, it's possible to get out of sync, and
315
+ # have an ignored start and an actually-executed stop, causing layers to
316
+ # get out of sync
304
317
 
305
318
  def ignore_children!
306
- @ignoring_children = true
319
+ @ignoring_children += 1
307
320
  end
308
321
 
309
322
  def acknowledge_children!
310
- @ignoring_children = false
323
+ if @ignoring_children > 0
324
+ @ignoring_children -= 1
325
+ end
311
326
  end
312
327
 
313
328
  def ignoring_children?
314
- @ignoring_children
329
+ @ignoring_children > 0
315
330
  end
316
331
 
317
332
  # Grab backtraces more aggressively when running in dev trace mode
318
333
  def backtrace_threshold
319
334
  dev_trace ? 0.05 : 0.5 # the minimum threshold in seconds to record the backtrace for a metric.
320
335
  end
321
-
322
336
  end
323
337
  end
@@ -1,4 +1,4 @@
1
1
  module ScoutApm
2
- VERSION = "2.1.2"
2
+ VERSION = "2.1.3"
3
3
  end
4
4
 
data/scout_apm.gemspec CHANGED
@@ -23,6 +23,7 @@ Gem::Specification.new do |s|
23
23
  s.add_runtime_dependency "rusage", '~> 0.2.0'
24
24
 
25
25
  s.add_development_dependency "minitest"
26
+ s.add_development_dependency 'mocha'
26
27
  s.add_development_dependency "pry"
27
28
  s.add_development_dependency "m"
28
29
  s.add_development_dependency "simplecov"
data/test/test_helper.rb CHANGED
@@ -5,6 +5,7 @@ SimpleCov.start
5
5
  require 'minitest/autorun'
6
6
  require 'minitest/unit'
7
7
  require 'minitest/pride'
8
+ require 'mocha/mini_test'
8
9
  require 'pry'
9
10
 
10
11
 
@@ -82,4 +83,3 @@ class Minitest::Test
82
83
  DATA_FILE_PATH = "#{DATA_FILE_DIR}/scout_apm.db"
83
84
  end
84
85
 
85
-
@@ -0,0 +1,104 @@
1
+ require 'test_helper'
2
+ require 'scout_apm/request_manager'
3
+ require 'scout_apm/background_job_integrations/sidekiq'
4
+
5
+ class SidekiqTest < Minitest::Test
6
+ SidekiqMiddleware = ScoutApm::BackgroundJobIntegrations::SidekiqMiddleware
7
+
8
+ ########################################
9
+ # Middleware
10
+ ########################################
11
+ def test_middleware_call_happy_path
12
+ fake_request = mock
13
+ fake_request.expects(:annotate_request)
14
+ fake_request.expects(:job!)
15
+ fake_request.expects(:start_layer).twice
16
+ fake_request.expects(:stop_layer).twice
17
+ fake_request.expects(:error!).never
18
+
19
+ ScoutApm::RequestManager.stubs(:lookup).returns(fake_request)
20
+
21
+ block_called = false
22
+ msg = { 'class' => 'MyJobClass',
23
+ 'created_at' => Time.now }
24
+
25
+ SidekiqMiddleware.new.call(nil, msg, "defaultqueue") { block_called = true }
26
+ assert_equal true, block_called
27
+ end
28
+
29
+ def test_middleware_call_job_exception
30
+ fake_request = mock
31
+ fake_request.expects(:annotate_request)
32
+ fake_request.expects(:job!)
33
+ fake_request.expects(:start_layer).twice
34
+ fake_request.expects(:stop_layer).twice
35
+ fake_request.expects(:error!)
36
+
37
+ ScoutApm::RequestManager.stubs(:lookup).returns(fake_request)
38
+
39
+ msg = { 'class' => 'MyJobClass',
40
+ 'created_at' => Time.now }
41
+
42
+ assert_raises RuntimeError do
43
+ SidekiqMiddleware.new.call(nil, msg, "defaultqueue") { raise "TheJobFailed" }
44
+ end
45
+ end
46
+
47
+ def test_middleware_call_edge_cases
48
+ fake_request = mock
49
+ fake_request.expects(:annotate_request)
50
+ fake_request.expects(:job!)
51
+ fake_request.expects(:start_layer).twice
52
+ fake_request.expects(:stop_layer).twice
53
+ fake_request.expects(:error!)
54
+
55
+ ScoutApm::RequestManager.stubs(:lookup).returns(fake_request)
56
+
57
+ # msg doesn't have anything
58
+ msg = { }
59
+
60
+ assert_raises RuntimeError do
61
+ SidekiqMiddleware.new.call(nil, msg, "defaultqueue") { raise "TheJobFailed" }
62
+ end
63
+ end
64
+
65
+ ########################################
66
+ # Job Class Determination
67
+ ########################################
68
+ def test_job_class_name_normally
69
+ msg = { 'class' => 'AGreatJob' }
70
+ assert_equal 'AGreatJob', SidekiqMiddleware.new.job_class(msg)
71
+ end
72
+
73
+ def test_job_class_name_activejob
74
+ msg = { 'class' => 'ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper',
75
+ 'wrapped' => 'RealJobClass' }
76
+ assert_equal 'RealJobClass', SidekiqMiddleware.new.job_class(msg)
77
+ end
78
+
79
+ def test_job_class_name_error_default
80
+ msg = {}
81
+ assert_equal 'UnknownJob', SidekiqMiddleware.new.job_class(msg)
82
+ end
83
+
84
+ ########################################
85
+ # Latency Calculation
86
+ ########################################
87
+ def test_latency_from_created_at
88
+ # Created at time 80, but now it is 200. Latency was 120
89
+ msg = { 'created_at' => 80 }
90
+ assert_equal 120, SidekiqMiddleware.new.latency(msg, 200)
91
+ end
92
+
93
+ def test_latency_from_enqueued_at
94
+ # Created at time 80, but now it is 200. Latency was 120
95
+ msg = { 'enqueued_at' => 80 }
96
+ assert_equal 120, SidekiqMiddleware.new.latency(msg, 200)
97
+ end
98
+
99
+ def test_latency_fallback
100
+ # No created at time, so fall back to 0
101
+ msg = {}
102
+ assert_equal 0, SidekiqMiddleware.new.latency(msg, 200)
103
+ end
104
+ end
@@ -0,0 +1,30 @@
1
+ require 'test_helper'
2
+
3
+ require 'scout_apm/context'
4
+
5
+ class ContextText < Minitest::Test
6
+ def test_ignore_nil_value
7
+ context = ScoutApm::Context.new
8
+ assert hash = context.add(:nil_key => nil, :org => 'org')
9
+ assert_equal ({:org => 'org'}), hash
10
+ end
11
+
12
+ def test_ignore_nil_key
13
+ context = ScoutApm::Context.new
14
+ assert hash = context.add(nil => nil, :org => 'org')
15
+ assert_equal ({:org => 'org'}), hash
16
+ end
17
+
18
+ def test_ignore_unsupported_value_type
19
+ context = ScoutApm::Context.new
20
+ assert hash = context.add(:array => [1,2,3,4], :org => 'org')
21
+ assert_equal ({:org => 'org'}), hash
22
+ end
23
+
24
+ def test_ignore_unsupported_key_type
25
+ context = ScoutApm::Context.new
26
+ assert hash = context.add([1,2,3,4] => 'hey', :org => 'org')
27
+ assert_equal ({:org => 'org'}), hash
28
+ end
29
+ end
30
+
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.1.2
4
+ version: 2.1.3
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: 2016-08-22 00:00:00.000000000 Z
12
+ date: 2016-08-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rusage
@@ -39,6 +39,20 @@ dependencies:
39
39
  - - ">="
40
40
  - !ruby/object:Gem::Version
41
41
  version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: mocha
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
42
56
  - !ruby/object:Gem::Dependency
43
57
  name: pry
44
58
  requirement: !ruby/object:Gem::Requirement
@@ -223,7 +237,9 @@ files:
223
237
  - test/data/config_test_1.yml
224
238
  - test/test_helper.rb
225
239
  - test/unit/agent_test.rb
240
+ - test/unit/background_job_integrations/sidekiq_test.rb
226
241
  - test/unit/config_test.rb
242
+ - test/unit/context_test.rb
227
243
  - test/unit/environment_test.rb
228
244
  - test/unit/histogram_test.rb
229
245
  - test/unit/ignored_uris_test.rb
@@ -267,7 +283,9 @@ test_files:
267
283
  - test/data/config_test_1.yml
268
284
  - test/test_helper.rb
269
285
  - test/unit/agent_test.rb
286
+ - test/unit/background_job_integrations/sidekiq_test.rb
270
287
  - test/unit/config_test.rb
288
+ - test/unit/context_test.rb
271
289
  - test/unit/environment_test.rb
272
290
  - test/unit/histogram_test.rb
273
291
  - test/unit/ignored_uris_test.rb