scout_apm 3.0.0.pre11 → 3.0.0.pre12

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.markdown +13 -4
  3. data/Guardfile +1 -0
  4. data/lib/scout_apm.rb +6 -0
  5. data/lib/scout_apm/agent/reporting.rb +2 -1
  6. data/lib/scout_apm/attribute_arranger.rb +14 -1
  7. data/lib/scout_apm/config.rb +12 -0
  8. data/lib/scout_apm/db_query_metric_set.rb +80 -0
  9. data/lib/scout_apm/db_query_metric_stats.rb +102 -0
  10. data/lib/scout_apm/fake_store.rb +6 -0
  11. data/lib/scout_apm/instant/middleware.rb +6 -1
  12. data/lib/scout_apm/instruments/active_record.rb +111 -0
  13. data/lib/scout_apm/layer.rb +4 -0
  14. data/lib/scout_apm/layer_converters/allocation_metric_converter.rb +5 -6
  15. data/lib/scout_apm/layer_converters/converter_base.rb +7 -19
  16. data/lib/scout_apm/layer_converters/database_converter.rb +81 -0
  17. data/lib/scout_apm/layer_converters/depth_first_walker.rb +22 -10
  18. data/lib/scout_apm/layer_converters/error_converter.rb +5 -7
  19. data/lib/scout_apm/layer_converters/find_layer_by_type.rb +34 -0
  20. data/lib/scout_apm/layer_converters/histograms.rb +14 -0
  21. data/lib/scout_apm/layer_converters/job_converter.rb +35 -49
  22. data/lib/scout_apm/layer_converters/metric_converter.rb +16 -18
  23. data/lib/scout_apm/layer_converters/request_queue_time_converter.rb +10 -12
  24. data/lib/scout_apm/layer_converters/slow_job_converter.rb +33 -35
  25. data/lib/scout_apm/layer_converters/slow_request_converter.rb +22 -18
  26. data/lib/scout_apm/limited_layer.rb +4 -0
  27. data/lib/scout_apm/metric_meta.rb +0 -5
  28. data/lib/scout_apm/metric_stats.rb +8 -1
  29. data/lib/scout_apm/serializers/db_query_serializer_to_json.rb +15 -0
  30. data/lib/scout_apm/serializers/payload_serializer.rb +4 -3
  31. data/lib/scout_apm/serializers/payload_serializer_to_json.rb +5 -2
  32. data/lib/scout_apm/slow_job_policy.rb +1 -10
  33. data/lib/scout_apm/slow_request_policy.rb +1 -10
  34. data/lib/scout_apm/store.rb +41 -22
  35. data/lib/scout_apm/tracked_request.rb +28 -40
  36. data/lib/scout_apm/utils/active_record_metric_name.rb +8 -4
  37. data/lib/scout_apm/version.rb +1 -1
  38. data/test/unit/db_query_metric_set_test.rb +56 -0
  39. data/test/unit/db_query_metric_stats_test.rb +113 -0
  40. data/test/unit/fake_store_test.rb +10 -0
  41. data/test/unit/layer_converters/depth_first_walker_test.rb +66 -0
  42. data/test/unit/layer_converters/metric_converter_test.rb +22 -0
  43. data/test/unit/layer_converters/stubs.rb +33 -0
  44. data/test/unit/serializers/payload_serializer_test.rb +3 -12
  45. data/test/unit/store_test.rb +4 -4
  46. data/test/unit/utils/active_record_metric_name_test.rb +8 -0
  47. metadata +20 -2
@@ -290,47 +290,35 @@ module ScoutApm
290
290
  # Bail out early if the user asked us to ignore this uri
291
291
  return if ScoutApm::Agent.instance.ignored_uris.ignore?(annotations[:uri])
292
292
 
293
- # Update immediate and long-term histograms for both job and web requests
294
- if unique_name != :unknown
295
- ScoutApm::Agent.instance.request_histograms.add(unique_name, root_layer.total_call_time)
296
- ScoutApm::Agent.instance.request_histograms_by_time[@store.current_timestamp].
297
- add(unique_name, root_layer.total_call_time)
293
+ converters = [
294
+ LayerConverters::Histograms,
295
+ LayerConverters::MetricConverter,
296
+ LayerConverters::ErrorConverter,
297
+ LayerConverters::AllocationMetricConverter,
298
+ LayerConverters::RequestQueueTimeConverter,
299
+ LayerConverters::JobConverter,
300
+ LayerConverters::DatabaseConverter,
301
+
302
+ LayerConverters::SlowJobConverter,
303
+ LayerConverters::SlowRequestConverter,
304
+ ]
305
+
306
+ layer_finder = LayerConverters::FindLayerByType.new(self)
307
+ walker = LayerConverters::DepthFirstWalker.new(self.root_layer)
308
+ converters = converters.map do |klass|
309
+ instance = klass.new(self, layer_finder, @store)
310
+ instance.register_hooks(walker)
311
+ instance
298
312
  end
299
-
300
- metrics = LayerConverters::MetricConverter.new(self).call
301
- @store.track!(metrics)
302
-
303
- error_metrics = LayerConverters::ErrorConverter.new(self).call
304
- @store.track!(error_metrics)
305
-
306
- allocation_metrics = LayerConverters::AllocationMetricConverter.new(self).call
307
- @store.track!(allocation_metrics)
308
-
309
- if web?
310
- # Don't #call this - that's the job of the ScoredItemSet later.
311
- slow_converter = LayerConverters::SlowRequestConverter.new(self)
312
- @store.track_slow_transaction!(slow_converter)
313
-
314
- queue_time_metrics = LayerConverters::RequestQueueTimeConverter.new(self).call
315
- @store.track!(queue_time_metrics)
316
-
317
- # If there's an instant_key, it means we need to report this right away
318
- if instant?
319
- trace = slow_converter.call
320
- ScoutApm::InstantReporting.new(trace, instant_key).call
321
- end
313
+ walker.walk
314
+ converters.each {|i| i.record! }
315
+
316
+ # If there's an instant_key, it means we need to report this right away
317
+ if web? && instant?
318
+ converter = converters.find{|c| c.class == LayerConverters::SlowRequestConverter}
319
+ trace = converter.call
320
+ ScoutApm::InstantReporting.new(trace, instant_key).call
322
321
  end
323
-
324
- if job?
325
- job_metrics = LayerConverters::JobConverter.new(self).call
326
- @store.track_job!(job_metrics)
327
-
328
- job_converter = LayerConverters::SlowJobConverter.new(self)
329
- @store.track_slow_job!(job_converter)
330
- end
331
-
332
- allocation_metrics = LayerConverters::AllocationMetricConverter.new(self).call
333
- @store.track!(allocation_metrics)
334
322
  end
335
323
 
336
324
  # Only call this after the request is complete
@@ -338,7 +326,7 @@ module ScoutApm
338
326
  return nil if ignoring_request?
339
327
 
340
328
  @unique_name ||= begin
341
- scope_layer = LayerConverters::ConverterBase.new(self).scope_layer
329
+ scope_layer = LayerConverters::FindLayerByType.new(self).scope
342
330
  if scope_layer
343
331
  scope_layer.legacy_metric_name
344
332
  else
@@ -27,6 +27,14 @@ module ScoutApm
27
27
  end
28
28
  end
29
29
 
30
+ def model
31
+ parts.first
32
+ end
33
+
34
+ def normalized_operation
35
+ parse_operation
36
+ end
37
+
30
38
  # For the layer lookup.
31
39
  def hash
32
40
  h = name.downcase.hash
@@ -44,10 +52,6 @@ module ScoutApm
44
52
 
45
53
  private
46
54
 
47
- def model
48
- parts.first
49
- end
50
-
51
55
  def operation
52
56
  if parts.length >= 2
53
57
  parts[1].downcase
@@ -1,3 +1,3 @@
1
1
  module ScoutApm
2
- VERSION = "3.0.0.pre11"
2
+ VERSION = "3.0.0.pre12"
3
3
  end
@@ -0,0 +1,56 @@
1
+ require 'test_helper'
2
+
3
+ require 'scout_apm/db_query_metric_set'
4
+
5
+ module ScoutApm
6
+ class DbQueryMetricSetTest < Minitest::Test
7
+ def test_hard_limit
8
+ config = make_fake_config(
9
+ 'database_metric_limit' => 5, # The hard limit on db metrics
10
+ 'database_metric_report_limit' => 2,)
11
+ set = DbQueryMetricSet.new(config)
12
+ set << fake_stat("a", 10)
13
+ set << fake_stat("b", 20)
14
+ set << fake_stat("c", 30)
15
+ set << fake_stat("d", 40)
16
+ set << fake_stat("e", 50)
17
+ set << fake_stat("f", 60)
18
+
19
+ assert_equal 5, set.metrics.size
20
+ end
21
+
22
+ def test_report_limit
23
+ config = make_fake_config(
24
+ 'database_metric_limit' => 50, # much larger max, uninterested in hitting it.
25
+ 'database_metric_report_limit' => 2,)
26
+ set = DbQueryMetricSet.new(config)
27
+ set << fake_stat("a", 10)
28
+ set << fake_stat("b", 20)
29
+ set << fake_stat("c", 30)
30
+ set << fake_stat("d", 40)
31
+ set << fake_stat("e", 50)
32
+ set << fake_stat("f", 60)
33
+
34
+ assert_equal 2, set.metrics_to_report.size
35
+ assert_equal ["f","e"], set.metrics_to_report.map{|m| m.key}
36
+ end
37
+
38
+ def test_combine
39
+ set1 = DbQueryMetricSet.new
40
+ set1 << fake_stat("a", 10)
41
+ set1 << fake_stat("b", 20)
42
+ set2 = DbQueryMetricSet.new
43
+ set2 << fake_stat("c", 10)
44
+ set2 << fake_stat("d", 20)
45
+
46
+ combined = set1.combine!(set2)
47
+ assert_equal ["a", "b", "c", "d"], combined.metrics.map{|_k, m| m.key}
48
+ end
49
+
50
+ def fake_stat(key, call_time)
51
+ OpenStruct.new(
52
+ key: key,
53
+ call_time: call_time)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,113 @@
1
+ require 'test_helper'
2
+
3
+ require 'scout_apm/db_query_metric_stats'
4
+
5
+ module ScoutApm
6
+ class DbQueryMetricStatsTest < Minitest::Test
7
+ def test_as_json_empty_stats
8
+ stat = build("table", "op", "Controller/public/index", 1, 10, 20)
9
+
10
+ assert_equal({
11
+ model_name: "table",
12
+ operation: "op",
13
+ call_count: 1,
14
+ transaction_count: 0,
15
+ scope: "Controller/public/index",
16
+ histogram: [[10.0, 1]],
17
+
18
+ max_call_time: 10.0,
19
+ min_call_time: 10.0,
20
+ call_time: 10.0,
21
+
22
+ max_rows_returned: 20,
23
+ min_rows_returned: 20,
24
+ rows_returned: 20,
25
+ }, stat.as_json)
26
+ end
27
+
28
+ def test_increment_transaction_count
29
+ stat = build()
30
+ assert_equal 0, stat.transaction_count
31
+
32
+ stat.increment_transaction_count!
33
+
34
+ assert_equal 1, stat.transaction_count
35
+ end
36
+
37
+ def test_key_name
38
+ stat = build("User", "find", "Controller/public/index")
39
+ assert_equal ["User", "find", "Controller/public/index"], stat.key
40
+ end
41
+
42
+ def test_combine_min_call_time_picks_smallest
43
+ stat1, stat2 = build_pair
44
+ assert_equal 5.1, stat1.combine!(stat2).min_call_time
45
+ end
46
+
47
+ def test_combine_max_call_time_picks_largest
48
+ stat1, stat2 = build_pair
49
+ assert_equal 8.2, stat1.combine!(stat2).max_call_time
50
+ end
51
+
52
+ def test_combine_call_counts_adds
53
+ stat1, stat2 = build_pair
54
+ assert_equal 5, stat1.combine!(stat2).call_count
55
+ end
56
+
57
+ def test_combine_transaction_count_adds
58
+ stat1, stat2 = build_pair
59
+ 2.times { stat1.increment_transaction_count! }
60
+ 3.times { stat2.increment_transaction_count! }
61
+
62
+ assert_equal 5, stat1.combine!(stat2).call_count
63
+ end
64
+
65
+ def test_combine_doesnt_merge_with_self
66
+ stat = build
67
+ merged = stat.combine!(stat)
68
+
69
+ assert_equal DEFAULTS[:call_count], merged.call_count
70
+ assert_equal DEFAULTS[:call_time], merged.call_time
71
+ assert_equal DEFAULTS[:rows_returned], merged.rows_returned
72
+ end
73
+
74
+ # A.combine!(B) should be the the same as B.combine!(A)
75
+ # Have to be a bit careful, since combine! is destructive, so make two pairs
76
+ # with same data to do both sides, then check that they result in the same
77
+ # answer
78
+ [:transaction_count, :call_count, :rows_returned, :min_rows_returned, :max_rows_returned, :max_call_time, :min_call_time].each do |attr|
79
+ define_method :"test_combine_#{attr}_is_symmetric" do
80
+ stat1_a, stat2_a = build_pair
81
+ stat1_b, stat2_b = build_pair
82
+ merged_a = stat1_a.combine!(stat2_a)
83
+ merged_b = stat2_b.combine!(stat1_b)
84
+
85
+ assert_equal merged_a.send(attr), merged_b.send(attr)
86
+ end
87
+ end
88
+
89
+ #############
90
+ # Helpers #
91
+ #############
92
+ DEFAULTS = {
93
+ call_count: 1,
94
+ call_time: 10.0,
95
+ rows_returned: 20,
96
+ }
97
+
98
+ def build(model_name="User",
99
+ operation="find",
100
+ scope="Controller/public/index",
101
+ call_count=DEFAULTS[:call_count],
102
+ call_time=DEFAULTS[:call_time],
103
+ rows_returned=DEFAULTS[:rows_returned])
104
+ DbQueryMetricStats.new(model_name, operation, scope, call_count, call_time, rows_returned)
105
+ end
106
+
107
+ def build_pair
108
+ stat1 = build("table", "op", "Controller/public/index", 2, 5.1, 10)
109
+ stat2 = build("table", "op", "Controller/public/index", 3, 8.2, 20)
110
+ [stat1, stat2]
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,10 @@
1
+ require 'test_helper'
2
+
3
+ class FakeStoreTest < Minitest::Test
4
+ def test_responds_to_same_instance_methods_as_store
5
+ fs = ScoutApm::FakeStore.new
6
+ s = ScoutApm::Store.new
7
+
8
+ assert_equal [], s.methods - fs.methods
9
+ end
10
+ end
@@ -0,0 +1,66 @@
1
+ require 'test_helper'
2
+
3
+ module ScoutApm
4
+ class DepthFirstWalkerTest < Minitest::Test
5
+ def test_walk_single_node_calls_callbacks_in_order
6
+ calls = []
7
+ layer = Layer.new("A", "x")
8
+
9
+ walker = LayerConverters::DepthFirstWalker.new(layer)
10
+ walker.before { |l| calls << :before }
11
+ walker.after { |l| calls << :after }
12
+ walker.on { |l| calls << :on }
13
+
14
+ walker.walk
15
+
16
+ assert_equal [:before, :on, :after], calls
17
+ end
18
+
19
+ # Tree looks like:
20
+ # A
21
+ # |
22
+ # B
23
+ # / \ \
24
+ # C D E
25
+ # / \
26
+ # F G
27
+ def test_walk_interesting_tree
28
+ calls = []
29
+ a = Layer.new("A", "x")
30
+ b = Layer.new("B", "x")
31
+ c = Layer.new("C", "x")
32
+ d = Layer.new("D", "x")
33
+ e = Layer.new("E", "x")
34
+ f = Layer.new("F", "x")
35
+ g = Layer.new("G", "x")
36
+ a.add_child(b)
37
+ b.add_child(c)
38
+ b.add_child(d)
39
+ b.add_child(e)
40
+ c.add_child(f)
41
+ c.add_child(g)
42
+
43
+ root_layer = a
44
+
45
+ walker = LayerConverters::DepthFirstWalker.new(root_layer)
46
+ walker.before { |l| calls << "#{l.type} before" }
47
+ walker.after { |l| calls << "#{l.type} after" }
48
+ walker.on { |l| calls << "#{l.type} on" }
49
+
50
+ walker.walk
51
+
52
+ assert_equal [
53
+ "A before", "A on", # before & on always line up next to each other
54
+ "B before", "B on",
55
+ "C before", "C on",
56
+ "F before", "F on", "F after", # leaf nodes run all 3
57
+ "G before", "G on", "G after",
58
+ "C after", # once all children are done, do a node's after
59
+ "D before", "D on", "D after",
60
+ "E before", "E on", "E after",
61
+ "B after",
62
+ "A after"
63
+ ], calls
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,22 @@
1
+ require 'test_helper'
2
+ require_relative 'stubs'
3
+
4
+ module ScoutApm
5
+ module LayerConverters
6
+ class MetricConverterTest < Minitest::Test
7
+ include Stubs
8
+
9
+ def test_register_adds_hooks
10
+ mc = MetricConverter.new(faux_request, faux_layer_finder, faux_store)
11
+ faux_walker.expects(:on)
12
+ mc.register_hooks(faux_walker)
13
+ end
14
+
15
+ def test_record
16
+ mc = MetricConverter.new(faux_request, faux_layer_finder, faux_store)
17
+ faux_store.expects(:track!)
18
+ mc.record!
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,33 @@
1
+ ####################################
2
+ # Stubs for LayerConverter Tests #
3
+ ####################################
4
+ module ScoutApm
5
+ module LayerConverters
6
+ module Stubs
7
+ def faux_walker(subscope_stubs: true)
8
+ @w ||= stub
9
+
10
+ if subscope_stubs && !@w_set_subscope_stubs
11
+ @w_set_subscope_stubs = true
12
+ @w.expects(:before)
13
+ @w.expects(:after)
14
+ end
15
+ @w
16
+ end
17
+
18
+ def faux_request
19
+ @req ||= stub(:root_layer => stub)
20
+ end
21
+
22
+ def faux_layer_finder
23
+ @layer_finder ||= stub
24
+ @layer_finder.stubs(scope: stub)
25
+ @layer_finder
26
+ end
27
+
28
+ def faux_store
29
+ @store ||= stub
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,13 +1,4 @@
1
1
  require 'test_helper'
2
- require 'scout_apm/attribute_arranger'
3
- require 'scout_apm/bucket_name_splitter'
4
- require 'scout_apm/serializers/payload_serializer'
5
- require 'scout_apm/serializers/payload_serializer_to_json'
6
- require 'scout_apm/slow_transaction'
7
- require 'scout_apm/metric_meta'
8
- require 'scout_apm/metric_stats'
9
- require 'scout_apm/context'
10
- require 'ostruct'
11
2
  require 'json' # to deserialize what has been manually serialized by the production code
12
3
 
13
4
  class PayloadSerializerTest < Minitest::Test
@@ -17,7 +8,7 @@ class PayloadSerializerTest < Minitest::Test
17
8
  :unique_id => "unique_idz",
18
9
  :agent_version => 123
19
10
  }
20
- payload = ScoutApm::Serializers::PayloadSerializerToJson.serialize(metadata, {}, {}, [], [], [])
11
+ payload = ScoutApm::Serializers::PayloadSerializerToJson.serialize(metadata, {}, {}, [], [], [], {})
21
12
 
22
13
  # symbol keys turn to strings
23
14
  formatted_metadata = {
@@ -58,7 +49,7 @@ class PayloadSerializerTest < Minitest::Test
58
49
  stats.total_exclusive_time = 0.07813208899999999
59
50
  }
60
51
  }
61
- payload = ScoutApm::Serializers::PayloadSerializerToJson.serialize({}, metrics, {}, [], [], [])
52
+ payload = ScoutApm::Serializers::PayloadSerializerToJson.serialize({}, metrics, {}, [], [], [], {})
62
53
  formatted_metrics = [
63
54
  {
64
55
  "key" => {
@@ -104,7 +95,7 @@ class PayloadSerializerTest < Minitest::Test
104
95
  :quotie => "here are some \"quotes\"",
105
96
  :payload_version => 2,
106
97
  }
107
- payload = ScoutApm::Serializers::PayloadSerializerToJson.serialize(metadata, {}, {}, [], [], [])
98
+ payload = ScoutApm::Serializers::PayloadSerializerToJson.serialize(metadata, {}, {}, [], [], [], {})
108
99
 
109
100
  # symbol keys turn to strings
110
101
  formatted_metadata = {