scout_apm 3.0.0.pre11 → 3.0.0.pre12

Sign up to get free protection for your applications and to get access to all the features.
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 = {