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.
- 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 = {
|