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.
- checksums.yaml +4 -4
- data/CHANGELOG.markdown +13 -4
- data/Guardfile +1 -0
- data/lib/scout_apm.rb +6 -0
- data/lib/scout_apm/agent/reporting.rb +2 -1
- data/lib/scout_apm/attribute_arranger.rb +14 -1
- data/lib/scout_apm/config.rb +12 -0
- data/lib/scout_apm/db_query_metric_set.rb +80 -0
- data/lib/scout_apm/db_query_metric_stats.rb +102 -0
- data/lib/scout_apm/fake_store.rb +6 -0
- data/lib/scout_apm/instant/middleware.rb +6 -1
- data/lib/scout_apm/instruments/active_record.rb +111 -0
- data/lib/scout_apm/layer.rb +4 -0
- data/lib/scout_apm/layer_converters/allocation_metric_converter.rb +5 -6
- data/lib/scout_apm/layer_converters/converter_base.rb +7 -19
- data/lib/scout_apm/layer_converters/database_converter.rb +81 -0
- data/lib/scout_apm/layer_converters/depth_first_walker.rb +22 -10
- data/lib/scout_apm/layer_converters/error_converter.rb +5 -7
- data/lib/scout_apm/layer_converters/find_layer_by_type.rb +34 -0
- data/lib/scout_apm/layer_converters/histograms.rb +14 -0
- data/lib/scout_apm/layer_converters/job_converter.rb +35 -49
- data/lib/scout_apm/layer_converters/metric_converter.rb +16 -18
- data/lib/scout_apm/layer_converters/request_queue_time_converter.rb +10 -12
- data/lib/scout_apm/layer_converters/slow_job_converter.rb +33 -35
- data/lib/scout_apm/layer_converters/slow_request_converter.rb +22 -18
- data/lib/scout_apm/limited_layer.rb +4 -0
- data/lib/scout_apm/metric_meta.rb +0 -5
- data/lib/scout_apm/metric_stats.rb +8 -1
- data/lib/scout_apm/serializers/db_query_serializer_to_json.rb +15 -0
- data/lib/scout_apm/serializers/payload_serializer.rb +4 -3
- data/lib/scout_apm/serializers/payload_serializer_to_json.rb +5 -2
- data/lib/scout_apm/slow_job_policy.rb +1 -10
- data/lib/scout_apm/slow_request_policy.rb +1 -10
- data/lib/scout_apm/store.rb +41 -22
- data/lib/scout_apm/tracked_request.rb +28 -40
- data/lib/scout_apm/utils/active_record_metric_name.rb +8 -4
- data/lib/scout_apm/version.rb +1 -1
- data/test/unit/db_query_metric_set_test.rb +56 -0
- data/test/unit/db_query_metric_stats_test.rb +113 -0
- data/test/unit/fake_store_test.rb +10 -0
- data/test/unit/layer_converters/depth_first_walker_test.rb +66 -0
- data/test/unit/layer_converters/metric_converter_test.rb +22 -0
- data/test/unit/layer_converters/stubs.rb +33 -0
- data/test/unit/serializers/payload_serializer_test.rb +3 -12
- data/test/unit/store_test.rb +4 -4
- data/test/unit/utils/active_record_metric_name_test.rb +8 -0
- 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
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
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
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
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::
|
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
|
data/lib/scout_apm/version.rb
CHANGED
@@ -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,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 = {
|