scout_apm 1.2.4.pre → 1.2.4.pre1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.markdown +1 -0
- data/Rakefile +2 -0
- data/lib/scout_apm.rb +1 -0
- data/lib/scout_apm/agent/reporting.rb +9 -1
- data/lib/scout_apm/layer_converter.rb +27 -27
- data/lib/scout_apm/slow_request_policy.rb +3 -57
- data/lib/scout_apm/slow_transaction_set.rb +67 -0
- data/lib/scout_apm/stackprof_tree_collapser.rb +2 -1
- data/lib/scout_apm/store.rb +9 -6
- data/lib/scout_apm/tracked_request.rb +2 -1
- data/lib/scout_apm/version.rb +1 -1
- data/test/unit/layaway_test.rb +1 -1
- data/test/unit/slow_transaction_set_test.rb +93 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f7457a6c1ecec53bd454c6e9ccc2d21fc21d6c61
|
4
|
+
data.tar.gz: 46579e9b5c9e9b948c9828d5552df54275d92d07
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 915274645119dc9afabe6ce236774a19c41db1464f82f2d393541b29dfe8f27ce3d12bcb0087aca6bfdd5079420772341b7c1a7679b05062982d4ef7df78e462
|
7
|
+
data.tar.gz: b760b06a1cf2b6c9a29d9543af9784888016b324d328dd19ec6d36585d6f0066976018d34939d4966ed2ffbfc77fd9f964c9579dfa12e1a7fefe9397e8dec514
|
data/CHANGELOG.markdown
CHANGED
data/Rakefile
CHANGED
data/lib/scout_apm.rb
CHANGED
@@ -98,6 +98,7 @@ require 'scout_apm/context'
|
|
98
98
|
require 'scout_apm/stackprof_tree_collapser'
|
99
99
|
require 'scout_apm/slow_transaction'
|
100
100
|
require 'scout_apm/slow_request_policy'
|
101
|
+
require 'scout_apm/slow_transaction_set'
|
101
102
|
require 'scout_apm/capacity'
|
102
103
|
require 'scout_apm/attribute_arranger'
|
103
104
|
|
@@ -65,7 +65,15 @@ module ScoutApm
|
|
65
65
|
select { |meta,stats| meta.metric_name =~ /\AController/ }.
|
66
66
|
inject(0) {|sum, (_, stat)| sum + stat.call_count }
|
67
67
|
|
68
|
-
|
68
|
+
memory = metrics.
|
69
|
+
find {|meta,stats| meta.metric_name =~ /\AMemory/ }
|
70
|
+
process_log_str = if memory
|
71
|
+
"Recorded from #{memory.last.call_count} processes"
|
72
|
+
else
|
73
|
+
"Recorded across (unknown) processes"
|
74
|
+
end
|
75
|
+
|
76
|
+
logger.info "[#{Time.parse(metadata[:agent_time]).strftime("%H:%M")}] Delivering #{metrics.length} Metrics for #{total_request_count} requests and #{slow_transactions.length} Slow Transaction Traces, #{process_log_str}."
|
69
77
|
logger.debug("Metrics: #{metrics.pretty_inspect}\nSlowTrans: #{slow_transactions.pretty_inspect}\nMetadata: #{metadata.inspect.pretty_inspect}")
|
70
78
|
end
|
71
79
|
|
@@ -86,42 +86,42 @@ module ScoutApm
|
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
89
|
-
# Take a TrackedRequest and turn it into
|
89
|
+
# Take a TrackedRequest and turn it into a slow transaction if needed
|
90
|
+
# return a 2 element array, [ Slow Transaction or Nil , Hash of metrics to store ]
|
90
91
|
class LayerSlowTransactionConverter < LayerConverterBase
|
91
92
|
def call
|
92
93
|
policy = ScoutApm::Agent.instance.slow_request_policy.capture_type(root_layer.total_call_time)
|
93
94
|
|
94
|
-
|
95
|
-
|
95
|
+
case policy
|
96
|
+
when ScoutApm::SlowRequestPolicy::CAPTURE_SUMMARY
|
97
|
+
return [nil, {}]
|
98
|
+
when ScoutApm::SlowRequestPolicy::CAPTURE_NONE
|
99
|
+
return [nil, {}]
|
96
100
|
end
|
97
101
|
|
98
|
-
|
99
102
|
scope = scope_layer
|
100
|
-
return nil unless scope
|
103
|
+
return [nil, {}] unless scope
|
104
|
+
|
101
105
|
uri = request.annotations[:uri] || ""
|
102
106
|
|
103
|
-
metrics =
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
metrics,
|
122
|
-
request.context,
|
123
|
-
root_layer.stop_time,
|
124
|
-
stackprof)
|
107
|
+
metrics = create_metrics
|
108
|
+
# Disable stackprof output for now
|
109
|
+
stackprof = [] # request.stackprof
|
110
|
+
|
111
|
+
meta = MetricMeta.new("SlowTransaction/#{scope_layer.legacy_metric_name}")
|
112
|
+
stat = MetricStats.new
|
113
|
+
stat.update!(1)
|
114
|
+
|
115
|
+
[
|
116
|
+
SlowTransaction.new(uri,
|
117
|
+
scope.legacy_metric_name,
|
118
|
+
root_layer.total_call_time,
|
119
|
+
metrics,
|
120
|
+
request.context,
|
121
|
+
root_layer.stop_time,
|
122
|
+
stackprof),
|
123
|
+
{ meta => stat }
|
124
|
+
]
|
125
125
|
|
126
126
|
end
|
127
127
|
|
@@ -1,9 +1,8 @@
|
|
1
|
-
# Long running class that
|
1
|
+
# Long running class that determines if, and in how much detail a potentially
|
2
|
+
# slow transaction should be recorded in
|
2
3
|
#
|
3
4
|
# Rules:
|
4
5
|
# - Runtime must be slower than a threshold
|
5
|
-
# - Log detailed metrics only for the first X
|
6
|
-
# - Stop logging anything after a maximum
|
7
6
|
|
8
7
|
module ScoutApm
|
9
8
|
class SlowRequestPolicy
|
@@ -16,68 +15,15 @@ module ScoutApm
|
|
16
15
|
# It's not slow unless it's at least this slow
|
17
16
|
SLOW_REQUEST_TIME_THRESHOLD = 2.0 # seconds
|
18
17
|
|
19
|
-
# Stop recording detailed metrics after this count. Still record the fact
|
20
|
-
# a slow request happened though
|
21
|
-
MAX_DETAIL_PER_MINUTE = 10
|
22
|
-
|
23
|
-
# Stop recording anything after this number of slow transactions in a
|
24
|
-
# minute. Will also log a message once per minute that it is stopping
|
25
|
-
# recording.
|
26
|
-
MAX_PER_MINUTE = 500
|
27
|
-
|
28
|
-
def initialize
|
29
|
-
@minute_count = 0
|
30
|
-
@detailed_count = 0 # How many detailed slow transactions have we captured this minute?
|
31
|
-
@minute = Time.now.min
|
32
|
-
@clipped_recording = false
|
33
|
-
end
|
34
|
-
|
35
18
|
def capture_type(time)
|
36
|
-
reset_counters
|
37
|
-
|
38
19
|
return CAPTURE_NONE unless slow_enough?(time)
|
39
|
-
return
|
40
|
-
|
41
|
-
@minute_count += 1
|
42
|
-
|
43
|
-
if @detailed_count < MAX_DETAIL_PER_MINUTE
|
44
|
-
@detailed_count += 1
|
45
|
-
return CAPTURE_DETAIL
|
46
|
-
else
|
47
|
-
return CAPTURE_SUMMARY
|
48
|
-
end
|
49
|
-
|
20
|
+
return CAPTURE_DETAIL
|
50
21
|
end
|
51
22
|
|
52
23
|
private
|
53
24
|
|
54
|
-
def reset_counters
|
55
|
-
t = Time.now.min
|
56
|
-
return if t == @minute
|
57
|
-
|
58
|
-
@minute_count = 0
|
59
|
-
@detailed_count = 0
|
60
|
-
@minute = t
|
61
|
-
@clipped_recording = false
|
62
|
-
end
|
63
|
-
|
64
25
|
def slow_enough?(time)
|
65
26
|
time > SLOW_REQUEST_TIME_THRESHOLD
|
66
27
|
end
|
67
|
-
|
68
|
-
# Breaker for rapid-fire excessive slow requests.
|
69
|
-
# If we trip this breaker continually, it will log once per minute that it is broken
|
70
|
-
def clip_recording?
|
71
|
-
if @minute_count > MAX_PER_MINUTE
|
72
|
-
if !@clipped_recording
|
73
|
-
ScoutApm::Agent.instance.logger.info("Skipping future slow requests this minute, reached limit of #{MAX_PER_MINUTE}")
|
74
|
-
end
|
75
|
-
|
76
|
-
@clipped_recording = true
|
77
|
-
true
|
78
|
-
else
|
79
|
-
false
|
80
|
-
end
|
81
|
-
end
|
82
28
|
end
|
83
29
|
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# In order to keep load down, only record a sample of Slow Transactions. In
|
2
|
+
# order to make that sampling as fair as possible, follow a basic algorithm:
|
3
|
+
#
|
4
|
+
# When adding a new SlowTransaction:
|
5
|
+
# * Just add it if there is an open spot
|
6
|
+
# * If there isn't an open spot, attempt to remove an over-represented
|
7
|
+
# endpoint instead ("attempt_to_evict"). Overrepresented is simply "has more
|
8
|
+
# than @fair number of SlowTransactions for that end point"
|
9
|
+
# * If there isn't an open spot, and nobody is valid to evict, drop the
|
10
|
+
# incoming SlowTransaction without adding.
|
11
|
+
#
|
12
|
+
# There is no way to remove SlowTransactions from this set, create a new object
|
13
|
+
# for each reporting period.
|
14
|
+
module ScoutApm
|
15
|
+
class SlowTransactionSet
|
16
|
+
include Enumerable
|
17
|
+
|
18
|
+
DEFAULT_TOTAL = 10
|
19
|
+
DEFAULT_FAIR = 1
|
20
|
+
|
21
|
+
attr_reader :total, :fair
|
22
|
+
|
23
|
+
def initialize(total=DEFAULT_TOTAL, fair=DEFAULT_FAIR)
|
24
|
+
@total = total
|
25
|
+
@fair = fair
|
26
|
+
@slow_transactions = []
|
27
|
+
end
|
28
|
+
|
29
|
+
def each
|
30
|
+
@slow_transactions.each { |s| yield s }
|
31
|
+
end
|
32
|
+
|
33
|
+
def <<(slow_transaction)
|
34
|
+
return if attempt_append(slow_transaction)
|
35
|
+
attempt_to_evict
|
36
|
+
attempt_append(slow_transaction)
|
37
|
+
end
|
38
|
+
|
39
|
+
def empty_slot?
|
40
|
+
@slow_transactions.length < total
|
41
|
+
end
|
42
|
+
|
43
|
+
def attempt_append(slow_transaction)
|
44
|
+
if empty_slot?
|
45
|
+
@slow_transactions.push(slow_transaction)
|
46
|
+
true
|
47
|
+
else
|
48
|
+
false
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def attempt_to_evict
|
53
|
+
return if @slow_transactions.length == 0
|
54
|
+
|
55
|
+
overrepresented = @slow_transactions.
|
56
|
+
group_by { |st| st.metric_name }.
|
57
|
+
to_a.
|
58
|
+
sort_by { |(_, sts)| sts.length }.
|
59
|
+
last
|
60
|
+
|
61
|
+
if overrepresented[1].length > fair
|
62
|
+
fastest = overrepresented[1].sort_by { |st| st.total_call_time }.first
|
63
|
+
@slow_transactions.delete(fastest)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -11,7 +11,8 @@ module ScoutApm
|
|
11
11
|
begin
|
12
12
|
ScoutApm::Agent.instance.logger.debug("StackProf - Samples: #{raw_stackprof[:samples]}, GC: #{raw_stackprof[:gc_samples]}, missed: #{raw_stackprof[:missed_samples]}, Interval: #{raw_stackprof[:interval]}")
|
13
13
|
rescue
|
14
|
-
|
14
|
+
# Not a useful log message, currently stackprof is disabled
|
15
|
+
# ScoutApm::Agent.instance.logger.debug("StackProf Raw - #{raw_stackprof.inspect}")
|
15
16
|
end
|
16
17
|
end
|
17
18
|
end
|
data/lib/scout_apm/store.rb
CHANGED
@@ -31,6 +31,7 @@ module ScoutApm
|
|
31
31
|
|
32
32
|
# Save a new slow transaction
|
33
33
|
def track_slow_transaction!(slow_transaction)
|
34
|
+
return unless slow_transaction
|
34
35
|
@mutex.synchronize {
|
35
36
|
reporting_periods[current_timestamp].merge_slow_transactions!(slow_transaction)
|
36
37
|
}
|
@@ -91,7 +92,7 @@ module ScoutApm
|
|
91
92
|
def initialize(timestamp)
|
92
93
|
@timestamp = timestamp
|
93
94
|
|
94
|
-
@slow_transactions =
|
95
|
+
@slow_transactions = SlowTransactionSet.new
|
95
96
|
@aggregate_metrics = Hash.new
|
96
97
|
end
|
97
98
|
|
@@ -103,9 +104,11 @@ module ScoutApm
|
|
103
104
|
self
|
104
105
|
end
|
105
106
|
|
106
|
-
def merge_slow_transactions!(
|
107
|
-
|
108
|
-
|
107
|
+
def merge_slow_transactions!(new_transactions)
|
108
|
+
Array(new_transactions).each do |one_transaction|
|
109
|
+
@slow_transactions << one_transaction
|
110
|
+
end
|
111
|
+
|
109
112
|
self
|
110
113
|
end
|
111
114
|
|
@@ -117,7 +120,7 @@ module ScoutApm
|
|
117
120
|
end
|
118
121
|
|
119
122
|
def slow_transactions_payload
|
120
|
-
@slow_transactions
|
123
|
+
@slow_transactions.to_a
|
121
124
|
end
|
122
125
|
|
123
126
|
private
|
@@ -139,7 +142,7 @@ module ScoutApm
|
|
139
142
|
|
140
143
|
# We can't aggregate CPU, Memory, Capacity, or Controller, so pass through these metrics directly
|
141
144
|
# TODO: Figure out a way to not have this duplicate what's in Samplers, and also on server's ingest
|
142
|
-
PASSTHROUGH_METRICS = ["CPU", "Memory", "Instance", "Controller"]
|
145
|
+
PASSTHROUGH_METRICS = ["CPU", "Memory", "Instance", "Controller", "SlowTransaction"]
|
143
146
|
|
144
147
|
# Absorbs a single new metric into the aggregates
|
145
148
|
def absorb(metric)
|
@@ -130,8 +130,9 @@ module ScoutApm
|
|
130
130
|
metrics = LayerMetricConverter.new(self).call
|
131
131
|
ScoutApm::Agent.instance.store.track!(metrics)
|
132
132
|
|
133
|
-
slow = LayerSlowTransactionConverter.new(self).call
|
133
|
+
slow, slow_metrics = LayerSlowTransactionConverter.new(self).call
|
134
134
|
ScoutApm::Agent.instance.store.track_slow_transaction!(slow)
|
135
|
+
ScoutApm::Agent.instance.store.track!(slow_metrics)
|
135
136
|
|
136
137
|
error_metrics = LayerErrorConverter.new(self).call
|
137
138
|
ScoutApm::Agent.instance.store.track!(error_metrics)
|
data/lib/scout_apm/version.rb
CHANGED
data/test/unit/layaway_test.rb
CHANGED
@@ -13,7 +13,7 @@ class LayawayTest < Minitest::Test
|
|
13
13
|
data = ScoutApm::Layaway.new
|
14
14
|
t = ScoutApm::StoreReportingPeriodTimestamp.new
|
15
15
|
data.add_reporting_period(t,ScoutApm::StoreReportingPeriod.new(t))
|
16
|
-
assert_equal [TIMESTAMP,t], Marshal.load(File.read(DATA_FILE_PATH)).keys
|
16
|
+
assert_equal [TIMESTAMP,t].sort_by(&:timestamp), Marshal.load(File.read(DATA_FILE_PATH)).keys.sort_by(&:timestamp)
|
17
17
|
end
|
18
18
|
|
19
19
|
def test_merge_reporting_period
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
require 'scout_apm/slow_transaction_set'
|
4
|
+
require 'scout_apm/slow_transaction'
|
5
|
+
|
6
|
+
class SlowTransactionSetTest < Minitest::Test
|
7
|
+
def test_adding_to_empty_set
|
8
|
+
set = ScoutApm::SlowTransactionSet.new(3, 1)
|
9
|
+
set << make_slow("Controller/Foo")
|
10
|
+
assert_equal 1, set.count
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_adding_to_partially_full_set
|
14
|
+
set = ScoutApm::SlowTransactionSet.new(3, 1)
|
15
|
+
set << make_slow("Controller/Foo")
|
16
|
+
set << make_slow("Controller/Foo")
|
17
|
+
assert_equal 2, set.count
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_overflow_of_one_type
|
21
|
+
max_size = 3
|
22
|
+
set = ScoutApm::SlowTransactionSet.new(max_size, 1)
|
23
|
+
set << make_slow("Controller/Foo")
|
24
|
+
set << make_slow("Controller/Foo")
|
25
|
+
set << make_slow("Controller/Foo")
|
26
|
+
set << make_slow("Controller/Foo")
|
27
|
+
set << make_slow("Controller/Foo")
|
28
|
+
set << make_slow("Controller/Foo")
|
29
|
+
assert_equal max_size, set.count
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_eviction_of_overrepresented
|
33
|
+
max_size = 3
|
34
|
+
set = ScoutApm::SlowTransactionSet.new(max_size, 1)
|
35
|
+
set << make_slow("Controller/Foo")
|
36
|
+
set << make_slow("Controller/Foo")
|
37
|
+
set << make_slow("Controller/Foo")
|
38
|
+
set << make_slow("Controller/Foo")
|
39
|
+
set << make_slow("Controller/Foo")
|
40
|
+
set << make_slow("Controller/Bar")
|
41
|
+
|
42
|
+
# 3 total
|
43
|
+
assert_equal max_size, set.count
|
44
|
+
assert_equal 1, set.select{|sl| sl.metric_name == "Controller/Bar"}.length
|
45
|
+
assert_equal 2, set.select{|sl| sl.metric_name == "Controller/Foo"}.length
|
46
|
+
end
|
47
|
+
|
48
|
+
# Fill the set with /Foo records, then add a /Bar to evict. Check that the
|
49
|
+
# evicted one was the fastest of the Foos
|
50
|
+
def test_eviction_of_fastest
|
51
|
+
max_size = 3
|
52
|
+
set = ScoutApm::SlowTransactionSet.new(max_size, 1)
|
53
|
+
|
54
|
+
[1,2,3].shuffle.each do |seconds| # Shuffle to remove any assumptions on order
|
55
|
+
set << make_slow("Controller/Foo", seconds)
|
56
|
+
end
|
57
|
+
set << make_slow("Controller/Bar", 8)
|
58
|
+
|
59
|
+
# The foo taking 1 second should be evicted
|
60
|
+
assert_equal 2, set.select{|sl| sl.metric_name == "Controller/Foo"}.map{ |sl| sl.total_call_time}.min
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_eviction_when_no_overrepresented
|
64
|
+
max_size = 4
|
65
|
+
fair = 2
|
66
|
+
set = ScoutApm::SlowTransactionSet.new(max_size, fair)
|
67
|
+
|
68
|
+
# Full, but each is at fair level
|
69
|
+
set << make_slow("Controller/Bar")
|
70
|
+
set << make_slow("Controller/Bar")
|
71
|
+
set << make_slow("Controller/Foo")
|
72
|
+
set << make_slow("Controller/Foo")
|
73
|
+
|
74
|
+
set << make_slow("Controller/Quux")
|
75
|
+
assert_equal max_size, set.count
|
76
|
+
assert_equal 0, set.select{|sl| sl.metric_name == "Controller/Quux" }.length
|
77
|
+
end
|
78
|
+
|
79
|
+
##############
|
80
|
+
#### Helpers
|
81
|
+
##############
|
82
|
+
|
83
|
+
def make_slow(metric, time=5)
|
84
|
+
ScoutApm::SlowTransaction.new(
|
85
|
+
"http://foo.app/#{metric}",
|
86
|
+
metric,
|
87
|
+
time,
|
88
|
+
{}, # metrics
|
89
|
+
{}, # context
|
90
|
+
Time.now, # end time
|
91
|
+
[]) # stackprof
|
92
|
+
end
|
93
|
+
end
|
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: 1.2.4.
|
4
|
+
version: 1.2.4.pre1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Derek Haynes
|
@@ -127,6 +127,7 @@ files:
|
|
127
127
|
- lib/scout_apm/server_integrations/webrick.rb
|
128
128
|
- lib/scout_apm/slow_request_policy.rb
|
129
129
|
- lib/scout_apm/slow_transaction.rb
|
130
|
+
- lib/scout_apm/slow_transaction_set.rb
|
130
131
|
- lib/scout_apm/stack_item.rb
|
131
132
|
- lib/scout_apm/stackprof_tree_collapser.rb
|
132
133
|
- lib/scout_apm/store.rb
|
@@ -150,6 +151,7 @@ files:
|
|
150
151
|
- test/unit/instruments/active_record_instruments_test.rb
|
151
152
|
- test/unit/layaway_test.rb
|
152
153
|
- test/unit/serializers/payload_serializer_test.rb
|
154
|
+
- test/unit/slow_transaction_set_test.rb
|
153
155
|
- test/unit/sql_sanitizer_test.rb
|
154
156
|
homepage: https://github.com/scoutapp/scout_apm_ruby
|
155
157
|
licenses: []
|
@@ -171,7 +173,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
171
173
|
version: 1.3.1
|
172
174
|
requirements: []
|
173
175
|
rubyforge_project: scout_apm
|
174
|
-
rubygems_version: 2.
|
176
|
+
rubygems_version: 2.2.2
|
175
177
|
signing_key:
|
176
178
|
specification_version: 4
|
177
179
|
summary: Ruby application performance monitoring
|
@@ -184,4 +186,5 @@ test_files:
|
|
184
186
|
- test/unit/instruments/active_record_instruments_test.rb
|
185
187
|
- test/unit/layaway_test.rb
|
186
188
|
- test/unit/serializers/payload_serializer_test.rb
|
189
|
+
- test/unit/slow_transaction_set_test.rb
|
187
190
|
- test/unit/sql_sanitizer_test.rb
|