scout_apm 2.6.10 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|