scout_apm 2.6.10 → 4.0.0
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/.rubocop.yml +0 -4
- data/.travis.yml +0 -6
- data/CHANGELOG.markdown +7 -0
- data/Gemfile +1 -12
- data/lib/scout_apm.rb +6 -1
- data/lib/scout_apm/agent_context.rb +2 -2
- data/lib/scout_apm/instruments/active_record.rb +13 -28
- data/lib/scout_apm/slow_policy/age_policy.rb +33 -0
- data/lib/scout_apm/slow_policy/percent_policy.rb +22 -0
- data/lib/scout_apm/slow_policy/percentile_policy.rb +24 -0
- data/lib/scout_apm/slow_policy/policy.rb +21 -0
- data/lib/scout_apm/slow_policy/speed_policy.rb +16 -0
- data/lib/scout_apm/slow_request_policy.rb +18 -77
- data/lib/scout_apm/version.rb +1 -1
- data/scout_apm.gemspec +6 -6
- data/test/unit/agent_context_test.rb +14 -0
- data/test/unit/slow_request_policy_test.rb +41 -13
- metadata +10 -7
- data/lib/scout_apm/slow_job_policy.rb +0 -111
- data/test/unit/slow_job_policy_test.rb +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ffcf571075c7f443ebe029e8f07c726cc5cb6c12e156d47e2565c576ac671316
|
4
|
+
data.tar.gz: 684f8ed4ba52d2e5819ea319288333a83f8b5481491ae879be2e8031cdd5d6ee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a4d90917dc02469213092848211fe779f44ec09e7db4829aaaa737ca15cf1811137dc2a4ae00adaa76cc2be92916b0467f164f79a76fac501c7a4827ba5d6f7d
|
7
|
+
data.tar.gz: 776d064902d998cc69330e470fbc9bfb368ae401d8e8a341310e4dc2a9070a256a2e57370505ee3475761d26145e4efced633963243527e39b95ab584bf48b2a
|
data/.rubocop.yml
CHANGED
data/.travis.yml
CHANGED
data/CHANGELOG.markdown
CHANGED
data/Gemfile
CHANGED
@@ -3,15 +3,4 @@ source "https://rubygems.org"
|
|
3
3
|
# Specify your gem's dependencies in scout_apm.gemspec
|
4
4
|
gemspec
|
5
5
|
|
6
|
-
|
7
|
-
if RUBY_VERSION <= "1.8.7"
|
8
|
-
gem "activesupport", "~> 3.2"
|
9
|
-
gem "i18n", "~> 0.6.11"
|
10
|
-
gem "pry", "~> 0.9.12"
|
11
|
-
gem "rake", "~> 10.5"
|
12
|
-
gem "minitest", "~> 5.11.3"
|
13
|
-
elsif RUBY_VERSION <= "1.9.3"
|
14
|
-
gem "rake", "~> 10.5"
|
15
|
-
else
|
16
|
-
gem "rake", ">= 12.3.3"
|
17
|
-
end
|
6
|
+
gem "rake", ">= 12.3.3"
|
data/lib/scout_apm.rb
CHANGED
@@ -144,8 +144,13 @@ require 'scout_apm/slow_transaction'
|
|
144
144
|
require 'scout_apm/slow_job_record'
|
145
145
|
require 'scout_apm/detailed_trace'
|
146
146
|
require 'scout_apm/scored_item_set'
|
147
|
+
|
147
148
|
require 'scout_apm/slow_request_policy'
|
148
|
-
require 'scout_apm/
|
149
|
+
require 'scout_apm/slow_policy/age_policy'
|
150
|
+
require 'scout_apm/slow_policy/speed_policy'
|
151
|
+
require 'scout_apm/slow_policy/percent_policy'
|
152
|
+
require 'scout_apm/slow_policy/percentile_policy'
|
153
|
+
|
149
154
|
require 'scout_apm/job_record'
|
150
155
|
require 'scout_apm/request_histograms'
|
151
156
|
require 'scout_apm/transaction_time_consumed'
|
@@ -96,11 +96,11 @@ module ScoutApm
|
|
96
96
|
end
|
97
97
|
|
98
98
|
def slow_request_policy
|
99
|
-
@slow_request_policy ||= ScoutApm::SlowRequestPolicy.new(self)
|
99
|
+
@slow_request_policy ||= ScoutApm::SlowRequestPolicy.new(self).tap{|p| p.add_default_policies }
|
100
100
|
end
|
101
101
|
|
102
102
|
def slow_job_policy
|
103
|
-
@slow_job_policy ||= ScoutApm::
|
103
|
+
@slow_job_policy ||= ScoutApm::SlowRequestPolicy.new(self).tap{|p| p.add_default_policies }
|
104
104
|
end
|
105
105
|
|
106
106
|
# Maintains a Histogram of insignificant/significant autoinstrument layers.
|
@@ -82,15 +82,8 @@ module ScoutApm
|
|
82
82
|
|
83
83
|
# Install #log tracing
|
84
84
|
if Utils::KlassHelper.defined?("ActiveRecord::ConnectionAdapters::AbstractAdapter")
|
85
|
-
|
86
|
-
|
87
|
-
::ActiveRecord::ConnectionAdapters::AbstractAdapter.include(Tracer)
|
88
|
-
else
|
89
|
-
::ActiveRecord::ConnectionAdapters::AbstractAdapter.module_eval do
|
90
|
-
include ::ScoutApm::Instruments::ActiveRecordAliasMethodInstruments
|
91
|
-
include ::ScoutApm::Tracer
|
92
|
-
end
|
93
|
-
end
|
85
|
+
::ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend(ActiveRecordInstruments)
|
86
|
+
::ActiveRecord::ConnectionAdapters::AbstractAdapter.include(Tracer)
|
94
87
|
end
|
95
88
|
|
96
89
|
if Utils::KlassHelper.defined?("ActiveRecord::Base")
|
@@ -172,20 +165,12 @@ module ScoutApm
|
|
172
165
|
# to the real SQL, and an AR generated "name" for the Query
|
173
166
|
#
|
174
167
|
################################################################################
|
175
|
-
|
176
|
-
|
177
|
-
module ActiveRecordAliasMethodInstruments
|
178
|
-
def self.included(instrumented_class)
|
168
|
+
module ActiveRecordInstruments
|
169
|
+
def self.prepended(instrumented_class)
|
179
170
|
ScoutApm::Agent.instance.context.logger.info "Instrumenting #{instrumented_class.inspect}"
|
180
|
-
instrumented_class.class_eval do
|
181
|
-
unless instrumented_class.method_defined?(:log_without_scout_instruments)
|
182
|
-
alias_method :log_without_scout_instruments, :log
|
183
|
-
alias_method :log, :log_with_scout_instruments
|
184
|
-
end
|
185
|
-
end
|
186
171
|
end
|
187
172
|
|
188
|
-
def
|
173
|
+
def log(*args, &block)
|
189
174
|
# Extract data from the arguments
|
190
175
|
sql, name = args
|
191
176
|
metric_name = Utils::ActiveRecordMetricName.new(sql, name)
|
@@ -216,7 +201,7 @@ module ScoutApm
|
|
216
201
|
end
|
217
202
|
current_layer.desc.merge(desc)
|
218
203
|
|
219
|
-
|
204
|
+
super(*args, &block)
|
220
205
|
|
221
206
|
# OR: Start a new layer, we didn't pick up instrumentation earlier in the stack.
|
222
207
|
else
|
@@ -224,7 +209,7 @@ module ScoutApm
|
|
224
209
|
layer.desc = desc
|
225
210
|
req.start_layer(layer)
|
226
211
|
begin
|
227
|
-
|
212
|
+
super(*args, &block)
|
228
213
|
ensure
|
229
214
|
req.stop_layer
|
230
215
|
end
|
@@ -323,14 +308,14 @@ module ScoutApm
|
|
323
308
|
end
|
324
309
|
end
|
325
310
|
|
326
|
-
def find_by_sql_with_scout_instruments(*args, &block)
|
311
|
+
def find_by_sql_with_scout_instruments(*args, **kwargs, &block)
|
327
312
|
req = ScoutApm::RequestManager.lookup
|
328
313
|
layer = ScoutApm::Layer.new("ActiveRecord", Utils::ActiveRecordMetricName::DEFAULT_METRIC)
|
329
314
|
layer.annotate_layer(:ignorable => true)
|
330
315
|
req.start_layer(layer)
|
331
316
|
req.ignore_children!
|
332
317
|
begin
|
333
|
-
find_by_sql_without_scout_instruments(*args, &block)
|
318
|
+
find_by_sql_without_scout_instruments(*args, **kwargs, &block)
|
334
319
|
ensure
|
335
320
|
req.acknowledge_children!
|
336
321
|
req.stop_layer
|
@@ -408,7 +393,7 @@ module ScoutApm
|
|
408
393
|
end
|
409
394
|
|
410
395
|
module ActiveRecordUpdateInstruments
|
411
|
-
def save(*args, &block)
|
396
|
+
def save(*args, **options, &block)
|
412
397
|
model = self.class.name
|
413
398
|
operation = self.persisted? ? "Update" : "Create"
|
414
399
|
|
@@ -418,14 +403,14 @@ module ScoutApm
|
|
418
403
|
req.start_layer(layer)
|
419
404
|
req.ignore_children!
|
420
405
|
begin
|
421
|
-
super(*args, &block)
|
406
|
+
super(*args, **options, &block)
|
422
407
|
ensure
|
423
408
|
req.acknowledge_children!
|
424
409
|
req.stop_layer
|
425
410
|
end
|
426
411
|
end
|
427
412
|
|
428
|
-
def save!(*args, &block)
|
413
|
+
def save!(*args, **options, &block)
|
429
414
|
model = self.class.name
|
430
415
|
operation = self.persisted? ? "Update" : "Create"
|
431
416
|
|
@@ -434,7 +419,7 @@ module ScoutApm
|
|
434
419
|
req.start_layer(layer)
|
435
420
|
req.ignore_children!
|
436
421
|
begin
|
437
|
-
super(*args, &block)
|
422
|
+
super(*args, **options, &block)
|
438
423
|
ensure
|
439
424
|
req.acknowledge_children!
|
440
425
|
req.stop_layer
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'scout_apm/slow_policy/policy'
|
2
|
+
|
3
|
+
module ScoutApm::SlowPolicy
|
4
|
+
class AgePolicy < Policy
|
5
|
+
# For each minute we haven't seen an endpoint
|
6
|
+
POINT_MULTIPLIER_AGE = 0.25
|
7
|
+
|
8
|
+
# A hash of Endpoint Name to the last time we stored a slow transaction for it.
|
9
|
+
#
|
10
|
+
# Defaults to a start time that is pretty close to application boot time.
|
11
|
+
# So the "age" of an endpoint we've never seen is the time the application
|
12
|
+
# has been running.
|
13
|
+
attr_reader :last_seen
|
14
|
+
|
15
|
+
def initialize(context)
|
16
|
+
super
|
17
|
+
|
18
|
+
zero_time = Time.now
|
19
|
+
@last_seen = Hash.new { |h, k| h[k] = zero_time }
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(request)
|
23
|
+
# How long has it been since we've seen this?
|
24
|
+
age = Time.now - last_seen[request.unique_name]
|
25
|
+
|
26
|
+
age / 60.0 * POINT_MULTIPLIER_AGE
|
27
|
+
end
|
28
|
+
|
29
|
+
def stored!(request)
|
30
|
+
last_seen[request.unique_name] = Time.now
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'scout_apm/slow_policy/policy'
|
2
|
+
|
3
|
+
module ScoutApm::SlowPolicy
|
4
|
+
class PercentPolicy < Policy
|
5
|
+
# Points for an endpoint's who's throughput * response time is a large % of
|
6
|
+
# overall time spent processing requests
|
7
|
+
POINT_MULTIPLIER_PERCENT_TIME = 2.5
|
8
|
+
|
9
|
+
# Of the total time spent handling endpoints in this app, if this endpoint
|
10
|
+
# is a higher percent, it should get more points.
|
11
|
+
#
|
12
|
+
# A: 20 calls @ 100ms each => 2 seconds of total time
|
13
|
+
# B: 10 calls @ 100ms each => 1 second of total time
|
14
|
+
#
|
15
|
+
# Then A is 66% of the total call time
|
16
|
+
def call(request) # Scale 0.0 - 1.0
|
17
|
+
percent = context.transaction_time_consumed.percent_of_total(request.unique_name)
|
18
|
+
|
19
|
+
percent * POINT_MULTIPLIER_PERCENT_TIME
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'scout_apm/slow_policy/policy'
|
2
|
+
|
3
|
+
module ScoutApm::SlowPolicy
|
4
|
+
class PercentilePolicy < Policy
|
5
|
+
def call(request)
|
6
|
+
# What approximate percentile was this request?
|
7
|
+
total_time = request.root_layer.total_call_time
|
8
|
+
percentile = context.request_histograms.approximate_quantile_of_value(request.unique_name, total_time)
|
9
|
+
|
10
|
+
if percentile < 40
|
11
|
+
0.4 # Don't put much emphasis on capturing low percentiles.
|
12
|
+
elsif percentile < 60
|
13
|
+
1.4 # Highest here to get mean traces
|
14
|
+
elsif percentile < 90
|
15
|
+
0.7 # Between 60 & 90% is fine.
|
16
|
+
elsif percentile >= 90
|
17
|
+
1.4 # Highest here to get 90+%ile traces
|
18
|
+
else
|
19
|
+
# impossible.
|
20
|
+
percentile
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Note that this is semi-internal API. You should not need this, and if you do
|
2
|
+
# we're here to help at support@scoutapm.com. TrackedRequest doesn't change
|
3
|
+
# often, but we can't promise a perfectly stable API for it either.
|
4
|
+
module ScoutApm::SlowPolicy
|
5
|
+
class Policy
|
6
|
+
attr_reader :context
|
7
|
+
|
8
|
+
def initialize(context)
|
9
|
+
@context = context
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(request)
|
13
|
+
raise NotImplementedError
|
14
|
+
end
|
15
|
+
|
16
|
+
# Override in subclasses to execute some behavior if the request gets a
|
17
|
+
# slot in the ScoredItemSet. Defaults to no-op
|
18
|
+
def stored!(request)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'scout_apm/slow_policy/policy'
|
2
|
+
|
3
|
+
module ScoutApm::SlowPolicy
|
4
|
+
class SpeedPolicy < Policy
|
5
|
+
# Adjust speed points. See the function
|
6
|
+
POINT_MULTIPLIER_SPEED = 0.25
|
7
|
+
|
8
|
+
# Time in seconds
|
9
|
+
# Logarithm keeps huge times from swamping the other metrics.
|
10
|
+
# 1+ is necessary to keep the log function in positive territory.
|
11
|
+
def call(request)
|
12
|
+
total_time = request.root_layer.total_call_time
|
13
|
+
Math.log(1 + total_time) * POINT_MULTIPLIER_SPEED
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -3,43 +3,29 @@
|
|
3
3
|
|
4
4
|
module ScoutApm
|
5
5
|
class SlowRequestPolicy
|
6
|
-
CAPTURE_TYPES = [
|
7
|
-
CAPTURE_DETAIL = "capture_detail",
|
8
|
-
CAPTURE_NONE = "capture_none",
|
9
|
-
]
|
10
|
-
|
11
|
-
# Adjust speed points. See the function
|
12
|
-
POINT_MULTIPLIER_SPEED = 0.25
|
13
|
-
|
14
|
-
# For each minute we haven't seen an endpoint
|
15
|
-
POINT_MULTIPLIER_AGE = 0.25
|
16
|
-
|
17
|
-
# Outliers are worth up to "1000ms" of weight
|
18
|
-
POINT_MULTIPLIER_PERCENTILE = 1.0
|
19
|
-
|
20
|
-
# Points for an endpoint's who's throughput * response time is a large % of
|
21
|
-
# overall time spent processing requests
|
22
|
-
POINT_MULTIPLIER_PERCENT_TIME = 2.5
|
23
|
-
|
24
|
-
# A hash of Endpoint Name to the last time we stored a slow transaction for it.
|
25
|
-
#
|
26
|
-
# Defaults to a start time that is pretty close to application boot time.
|
27
|
-
# So the "age" of an endpoint we've never seen is the time the application
|
28
|
-
# has been running.
|
29
|
-
attr_reader :last_seen
|
30
|
-
|
31
6
|
# The AgentContext we're running in
|
32
7
|
attr_reader :context
|
8
|
+
attr_reader :policies
|
33
9
|
|
34
10
|
def initialize(context)
|
35
11
|
@context = context
|
12
|
+
@policies = []
|
13
|
+
end
|
36
14
|
|
37
|
-
|
38
|
-
|
15
|
+
def add_default_policies
|
16
|
+
add(SlowPolicy::SpeedPolicy.new(context))
|
17
|
+
add(SlowPolicy::PercentilePolicy.new(context))
|
18
|
+
add(SlowPolicy::AgePolicy.new(context))
|
19
|
+
add(SlowPolicy::PercentilePolicy.new(context))
|
39
20
|
end
|
40
21
|
|
41
|
-
|
42
|
-
|
22
|
+
# policy is an object that behaves like a policy (responds to .call(req) for the score, and .store!(req))
|
23
|
+
def add(policy)
|
24
|
+
unless policy.respond_to?(:call) && policy.respond_to?(:stored!)
|
25
|
+
raise "SlowRequestPolicy must implement policy api call(req) and stored!(req)"
|
26
|
+
end
|
27
|
+
|
28
|
+
@policies << policy
|
43
29
|
end
|
44
30
|
|
45
31
|
# Determine if this request trace should be fully analyzed by scoring it
|
@@ -56,56 +42,11 @@ module ScoutApm
|
|
56
42
|
return -1 # A negative score, should never be good enough to store.
|
57
43
|
end
|
58
44
|
|
59
|
-
|
60
|
-
|
61
|
-
# How long has it been since we've seen this?
|
62
|
-
age = Time.now - last_seen[unique_name]
|
63
|
-
|
64
|
-
# What approximate percentile was this request?
|
65
|
-
percentile = context.request_histograms.approximate_quantile_of_value(unique_name, total_time)
|
66
|
-
|
67
|
-
percent_of_total_time = context.transaction_time_consumed.percent_of_total(unique_name)
|
68
|
-
|
69
|
-
return speed_points(total_time) + percentile_points(percentile) + age_points(age) + percent_time_points(percent_of_total_time)
|
70
|
-
end
|
71
|
-
|
72
|
-
private
|
73
|
-
|
74
|
-
# Time in seconds
|
75
|
-
# Logarithm keeps huge times from swamping the other metrics.
|
76
|
-
# 1+ is necessary to keep the log function in positive territory.
|
77
|
-
def speed_points(time)
|
78
|
-
Math.log(1 + time) * POINT_MULTIPLIER_SPEED
|
79
|
-
end
|
80
|
-
|
81
|
-
def percentile_points(percentile)
|
82
|
-
if percentile < 40
|
83
|
-
0.4 # Don't put much emphasis on capturing low percentiles.
|
84
|
-
elsif percentile < 60
|
85
|
-
1.4 # Highest here to get mean traces
|
86
|
-
elsif percentile < 90
|
87
|
-
0.7 # Between 60 & 90% is fine.
|
88
|
-
elsif percentile >= 90
|
89
|
-
1.4 # Highest here to get 90+%ile traces
|
90
|
-
else
|
91
|
-
# impossible.
|
92
|
-
percentile
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
def age_points(age)
|
97
|
-
age / 60.0 * POINT_MULTIPLIER_AGE
|
45
|
+
policies.map{ |p| p.call(request) }.sum
|
98
46
|
end
|
99
47
|
|
100
|
-
|
101
|
-
|
102
|
-
#
|
103
|
-
# A: 20 calls @ 100ms each => 2 seconds of total time
|
104
|
-
# B: 10 calls @ 100ms each => 1 second of total time
|
105
|
-
#
|
106
|
-
# Then A is 66% of the total call time
|
107
|
-
def percent_time_points(percent) # Scale 0.0 - 1.0
|
108
|
-
percent * POINT_MULTIPLIER_PERCENT_TIME
|
48
|
+
def stored!(request)
|
49
|
+
policies.each{ |p| p.stored!(request) }
|
109
50
|
end
|
110
51
|
end
|
111
52
|
end
|
data/lib/scout_apm/version.rb
CHANGED
data/scout_apm.gemspec
CHANGED
@@ -21,6 +21,8 @@ Gem::Specification.new do |s|
|
|
21
21
|
s.extensions << 'ext/allocations/extconf.rb'
|
22
22
|
s.extensions << 'ext/rusage/extconf.rb'
|
23
23
|
|
24
|
+
s.required_ruby_version = '~> 2.1'
|
25
|
+
|
24
26
|
s.add_development_dependency "minitest"
|
25
27
|
s.add_development_dependency "mocha"
|
26
28
|
s.add_development_dependency "pry"
|
@@ -36,10 +38,8 @@ Gem::Specification.new do |s|
|
|
36
38
|
s.add_development_dependency "activerecord"
|
37
39
|
s.add_development_dependency "sqlite3"
|
38
40
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
s.add_development_dependency "m"
|
44
|
-
end
|
41
|
+
s.add_development_dependency "rubocop"
|
42
|
+
s.add_development_dependency "guard"
|
43
|
+
s.add_development_dependency "guard-minitest"
|
44
|
+
s.add_development_dependency "m"
|
45
45
|
end
|
@@ -12,4 +12,18 @@ class AgentContextTest < Minitest::Test
|
|
12
12
|
context = ScoutApm::AgentContext.new
|
13
13
|
assert ScoutApm::ErrorService::ErrorBuffer, context.error_buffer.class
|
14
14
|
end
|
15
|
+
|
16
|
+
|
17
|
+
class TestPolicy
|
18
|
+
def call(req); 1; end
|
19
|
+
def stored!(req); end
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_customize_slow_request_policy
|
23
|
+
context = ScoutApm::AgentContext.new
|
24
|
+
assert 4, context.slow_request_policy.policies
|
25
|
+
|
26
|
+
context.slow_request_policy.add(TestPolicy.new)
|
27
|
+
assert 5, context.slow_request_policy.policies
|
28
|
+
end
|
15
29
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
3
|
require 'scout_apm/slow_request_policy'
|
4
|
+
require 'scout_apm/slow_policy/policy'
|
4
5
|
require 'scout_apm/layer'
|
5
6
|
|
6
7
|
class FakeRequest
|
@@ -16,35 +17,62 @@ class FakeRequest
|
|
16
17
|
end
|
17
18
|
end
|
18
19
|
|
20
|
+
class FixedPolicy < ScoutApm::SlowPolicy::Policy
|
21
|
+
attr_reader :stored
|
22
|
+
|
23
|
+
def initialize(x)
|
24
|
+
@x = x
|
25
|
+
end
|
26
|
+
|
27
|
+
def call(req)
|
28
|
+
@x
|
29
|
+
end
|
30
|
+
|
31
|
+
def stored!(req)
|
32
|
+
@stored = true
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
19
36
|
class SlowRequestPolicyTest < Minitest::Test
|
20
37
|
def setup
|
21
38
|
@context = ScoutApm::AgentContext.new
|
22
39
|
end
|
23
40
|
|
24
|
-
def
|
41
|
+
def test_age_policy_stored_records_current_time
|
25
42
|
test_start = Time.now
|
26
|
-
policy = ScoutApm::
|
43
|
+
policy = ScoutApm::SlowPolicy::AgePolicy.new(@context)
|
27
44
|
request = FakeRequest.new("users/index")
|
28
45
|
|
29
46
|
policy.stored!(request)
|
30
47
|
assert policy.last_seen[request.unique_name] > test_start
|
31
48
|
end
|
32
49
|
|
33
|
-
def
|
50
|
+
def test_sums_up_score
|
34
51
|
policy = ScoutApm::SlowRequestPolicy.new(@context)
|
35
52
|
request = FakeRequest.new("users/index")
|
36
53
|
|
37
|
-
|
38
|
-
policy.
|
39
|
-
@context.request_histograms.add(request.unique_name, 1)
|
40
|
-
@context.transaction_time_consumed.add(request.unique_name, 1)
|
54
|
+
policy.add(FixedPolicy.new(1))
|
55
|
+
policy.add(FixedPolicy.new(2))
|
41
56
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
57
|
+
assert_equal 3, policy.score(request)
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_calls_store_on_policies
|
61
|
+
policy = ScoutApm::SlowRequestPolicy.new(@context)
|
62
|
+
request = FakeRequest.new("users/index")
|
63
|
+
|
64
|
+
policy.add(fp1 = FixedPolicy.new(1))
|
65
|
+
policy.add(fp2 = FixedPolicy.new(2))
|
66
|
+
policy.stored!(request)
|
67
|
+
|
68
|
+
assert_equal true, fp1.stored
|
69
|
+
assert_equal true, fp2.stored
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_checks_new_policy_api
|
73
|
+
policy = ScoutApm::SlowRequestPolicy.new(@context)
|
46
74
|
|
47
|
-
|
48
|
-
|
75
|
+
assert_raises { policy.add(Object.new) }
|
76
|
+
assert_raises { policy.add(->(req){1}) } # only implements call
|
49
77
|
end
|
50
78
|
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:
|
4
|
+
version: 4.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Derek Haynes
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2020-
|
12
|
+
date: 2020-11-25 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: minitest
|
@@ -369,8 +369,12 @@ files:
|
|
369
369
|
- lib/scout_apm/server_integrations/thin.rb
|
370
370
|
- lib/scout_apm/server_integrations/unicorn.rb
|
371
371
|
- lib/scout_apm/server_integrations/webrick.rb
|
372
|
-
- lib/scout_apm/slow_job_policy.rb
|
373
372
|
- lib/scout_apm/slow_job_record.rb
|
373
|
+
- lib/scout_apm/slow_policy/age_policy.rb
|
374
|
+
- lib/scout_apm/slow_policy/percent_policy.rb
|
375
|
+
- lib/scout_apm/slow_policy/percentile_policy.rb
|
376
|
+
- lib/scout_apm/slow_policy/policy.rb
|
377
|
+
- lib/scout_apm/slow_policy/speed_policy.rb
|
374
378
|
- lib/scout_apm/slow_request_policy.rb
|
375
379
|
- lib/scout_apm/slow_transaction.rb
|
376
380
|
- lib/scout_apm/stack_item.rb
|
@@ -442,7 +446,6 @@ files:
|
|
442
446
|
- test/unit/request_histograms_test.rb
|
443
447
|
- test/unit/scored_item_set_test.rb
|
444
448
|
- test/unit/serializers/payload_serializer_test.rb
|
445
|
-
- test/unit/slow_job_policy_test.rb
|
446
449
|
- test/unit/slow_request_policy_test.rb
|
447
450
|
- test/unit/sql_sanitizer_test.rb
|
448
451
|
- test/unit/store_test.rb
|
@@ -465,16 +468,16 @@ require_paths:
|
|
465
468
|
- data
|
466
469
|
required_ruby_version: !ruby/object:Gem::Requirement
|
467
470
|
requirements:
|
468
|
-
- - "
|
471
|
+
- - "~>"
|
469
472
|
- !ruby/object:Gem::Version
|
470
|
-
version: '
|
473
|
+
version: '2.1'
|
471
474
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
472
475
|
requirements:
|
473
476
|
- - ">="
|
474
477
|
- !ruby/object:Gem::Version
|
475
478
|
version: '0'
|
476
479
|
requirements: []
|
477
|
-
rubygems_version: 3.0.
|
480
|
+
rubygems_version: 3.0.3
|
478
481
|
signing_key:
|
479
482
|
specification_version: 4
|
480
483
|
summary: Ruby application performance monitoring
|
@@ -1,111 +0,0 @@
|
|
1
|
-
# Long running class that determines if, and in how much detail a potentially
|
2
|
-
# slow job should be recorded in
|
3
|
-
|
4
|
-
module ScoutApm
|
5
|
-
class SlowJobPolicy
|
6
|
-
CAPTURE_TYPES = [
|
7
|
-
CAPTURE_DETAIL = "capture_detail",
|
8
|
-
CAPTURE_NONE = "capture_none",
|
9
|
-
]
|
10
|
-
|
11
|
-
# Adjust speed points. See the function
|
12
|
-
POINT_MULTIPLIER_SPEED = 0.25
|
13
|
-
|
14
|
-
# For each minute we haven't seen an endpoint
|
15
|
-
POINT_MULTIPLIER_AGE = 0.25
|
16
|
-
|
17
|
-
# Outliers are worth up to "1000ms" of weight
|
18
|
-
POINT_MULTIPLIER_PERCENTILE = 1.0
|
19
|
-
|
20
|
-
# Points for an endpoint's who's throughput * response time is a large % of
|
21
|
-
# overall time spent processing requests
|
22
|
-
POINT_MULTIPLIER_PERCENT_TIME = 2.5
|
23
|
-
|
24
|
-
# A hash of Job Names to the last time we stored a slow trace for it.
|
25
|
-
#
|
26
|
-
# Defaults to a start time that is pretty close to application boot time.
|
27
|
-
# So the "age" of an endpoint we've never seen is the time the application
|
28
|
-
# has been running.
|
29
|
-
attr_reader :last_seen
|
30
|
-
|
31
|
-
# The AgentContext we're running in
|
32
|
-
attr_reader :context
|
33
|
-
|
34
|
-
def initialize(context)
|
35
|
-
@context = context
|
36
|
-
|
37
|
-
zero_time = Time.now
|
38
|
-
@last_seen = Hash.new { |h, k| h[k] = zero_time }
|
39
|
-
end
|
40
|
-
|
41
|
-
def stored!(request)
|
42
|
-
last_seen[request.unique_name] = Time.now
|
43
|
-
end
|
44
|
-
|
45
|
-
# Determine if this job trace should be fully analyzed by scoring it
|
46
|
-
# across several metrics, and then determining if that's good enough to
|
47
|
-
# make it into this minute's payload.
|
48
|
-
#
|
49
|
-
# Due to the combining nature of the agent & layaway file, there's no
|
50
|
-
# guarantee that a high scoring local champion will still be a winner when
|
51
|
-
# they go up to "regionals" and are compared against the other processes
|
52
|
-
# running on a node.
|
53
|
-
def score(request)
|
54
|
-
unique_name = request.unique_name
|
55
|
-
if unique_name == :unknown
|
56
|
-
return -1 # A negative score, should never be good enough to store.
|
57
|
-
end
|
58
|
-
|
59
|
-
total_time = request.root_layer.total_call_time
|
60
|
-
|
61
|
-
# How long has it been since we've seen this?
|
62
|
-
age = Time.now - last_seen[unique_name]
|
63
|
-
|
64
|
-
# What approximate percentile was this request?
|
65
|
-
percentile = context.request_histograms.approximate_quantile_of_value(unique_name, total_time)
|
66
|
-
|
67
|
-
percent_of_total_time = context.transaction_time_consumed.percent_of_total(unique_name)
|
68
|
-
|
69
|
-
return speed_points(total_time) + percentile_points(percentile) + age_points(age) + percent_time_points(percent_of_total_time)
|
70
|
-
end
|
71
|
-
|
72
|
-
private
|
73
|
-
|
74
|
-
# Time in seconds
|
75
|
-
# Logarithm keeps huge times from swamping the other metrics.
|
76
|
-
# 1+ is necessary to keep the log function in positive territory.
|
77
|
-
def speed_points(time)
|
78
|
-
Math.log(1 + time) * POINT_MULTIPLIER_SPEED
|
79
|
-
end
|
80
|
-
|
81
|
-
def percentile_points(percentile)
|
82
|
-
if percentile < 40
|
83
|
-
0.4 # Don't put much emphasis on capturing low percentiles.
|
84
|
-
elsif percentile < 60
|
85
|
-
1.4 # Highest here to get mean traces
|
86
|
-
elsif percentile < 90
|
87
|
-
0.7 # Between 60 & 90% is fine.
|
88
|
-
elsif percentile >= 90
|
89
|
-
1.4 # Highest here to get 90+%ile traces
|
90
|
-
else
|
91
|
-
# impossible.
|
92
|
-
percentile
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
def age_points(age)
|
97
|
-
age / 60.0 * POINT_MULTIPLIER_AGE
|
98
|
-
end
|
99
|
-
|
100
|
-
# Of the total time spent handling endpoints in this app, if this endpoint
|
101
|
-
# is a higher percent, it should get more points.
|
102
|
-
#
|
103
|
-
# A: 20 calls @ 100ms each => 2 seconds of total time
|
104
|
-
# B: 10 calls @ 100ms each => 1 second of total time
|
105
|
-
#
|
106
|
-
# Then A is 66% of the total call time
|
107
|
-
def percent_time_points(percent) # Scale 0.0 - 1.0
|
108
|
-
percent * POINT_MULTIPLIER_PERCENT_TIME
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|