scout_apm 3.0.0.pre25 → 4.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/test.yml +49 -0
- data/.gitignore +1 -1
- data/.rubocop.yml +5 -5
- data/.travis.yml +19 -14
- data/CHANGELOG.markdown +148 -4
- data/Gemfile +1 -7
- data/README.markdown +30 -4
- data/Rakefile +1 -1
- data/ext/allocations/allocations.c +2 -0
- data/gems/README.md +28 -0
- data/gems/octoshark.gemfile +4 -0
- data/gems/rails3.gemfile +5 -0
- data/gems/rails4.gemfile +4 -0
- data/gems/rails5.gemfile +4 -0
- data/gems/rails6.gemfile +4 -0
- data/lib/scout_apm.rb +38 -9
- data/lib/scout_apm/agent.rb +29 -10
- data/lib/scout_apm/agent/exit_handler.rb +0 -1
- data/lib/scout_apm/agent_context.rb +22 -3
- data/lib/scout_apm/app_server_load.rb +7 -2
- data/lib/scout_apm/attribute_arranger.rb +0 -2
- data/lib/scout_apm/auto_instrument.rb +5 -0
- data/lib/scout_apm/auto_instrument/instruction_sequence.rb +31 -0
- data/lib/scout_apm/auto_instrument/layer.rb +23 -0
- data/lib/scout_apm/auto_instrument/parser.rb +27 -0
- data/lib/scout_apm/auto_instrument/rails.rb +175 -0
- data/lib/scout_apm/background_job_integrations/legacy_sneakers.rb +55 -0
- data/lib/scout_apm/background_job_integrations/que.rb +134 -0
- data/lib/scout_apm/background_job_integrations/resque.rb +6 -2
- data/lib/scout_apm/background_job_integrations/shoryuken.rb +124 -0
- data/lib/scout_apm/background_job_integrations/sidekiq.rb +5 -19
- data/lib/scout_apm/background_job_integrations/sneakers.rb +87 -0
- data/lib/scout_apm/config.rb +45 -8
- data/lib/scout_apm/detailed_trace.rb +217 -0
- data/lib/scout_apm/environment.rb +19 -1
- data/lib/scout_apm/error.rb +27 -0
- data/lib/scout_apm/error_service.rb +32 -0
- data/lib/scout_apm/error_service/error_buffer.rb +39 -0
- data/lib/scout_apm/error_service/error_record.rb +211 -0
- data/lib/scout_apm/error_service/ignored_exceptions.rb +66 -0
- data/lib/scout_apm/error_service/middleware.rb +32 -0
- data/lib/scout_apm/error_service/notifier.rb +33 -0
- data/lib/scout_apm/error_service/payload.rb +47 -0
- data/lib/scout_apm/error_service/periodic_work.rb +17 -0
- data/lib/scout_apm/error_service/railtie.rb +11 -0
- data/lib/scout_apm/error_service/sidekiq.rb +80 -0
- data/lib/scout_apm/extensions/transaction_callback_payload.rb +1 -1
- data/lib/scout_apm/fake_store.rb +3 -0
- data/lib/scout_apm/framework_integrations/rails_2.rb +2 -1
- data/lib/scout_apm/framework_integrations/rails_3_or_4.rb +17 -6
- data/lib/scout_apm/git_revision.rb +6 -3
- data/lib/scout_apm/instant/middleware.rb +2 -1
- data/lib/scout_apm/instrument_manager.rb +8 -7
- data/lib/scout_apm/instruments/action_controller_rails_2.rb +3 -1
- data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +56 -55
- data/lib/scout_apm/instruments/action_view.rb +126 -26
- data/lib/scout_apm/instruments/active_record.rb +70 -19
- data/lib/scout_apm/instruments/http.rb +48 -0
- data/lib/scout_apm/instruments/memcached.rb +43 -0
- data/lib/scout_apm/instruments/mongoid.rb +9 -4
- data/lib/scout_apm/instruments/net_http.rb +8 -1
- data/lib/scout_apm/job_record.rb +4 -2
- data/lib/scout_apm/layaway_file.rb +4 -0
- data/lib/scout_apm/layer.rb +6 -57
- data/lib/scout_apm/layer_children_set.rb +15 -6
- data/lib/scout_apm/layer_converters/converter_base.rb +15 -30
- data/lib/scout_apm/layer_converters/database_converter.rb +2 -15
- data/lib/scout_apm/layer_converters/slow_job_converter.rb +12 -2
- data/lib/scout_apm/layer_converters/slow_request_converter.rb +14 -4
- data/lib/scout_apm/layer_converters/trace_converter.rb +184 -0
- data/lib/scout_apm/limited_layer.rb +0 -7
- data/lib/scout_apm/metric_stats.rb +0 -8
- data/lib/scout_apm/middleware.rb +1 -1
- data/lib/scout_apm/periodic_work.rb +19 -0
- data/lib/scout_apm/remote/message.rb +4 -0
- data/lib/scout_apm/remote/server.rb +13 -1
- data/lib/scout_apm/reporter.rb +8 -3
- data/lib/scout_apm/reporting.rb +2 -1
- data/lib/scout_apm/request_histograms.rb +8 -0
- data/lib/scout_apm/serializers/app_server_load_serializer.rb +4 -0
- data/lib/scout_apm/serializers/directive_serializer.rb +4 -0
- data/lib/scout_apm/serializers/payload_serializer.rb +2 -2
- data/lib/scout_apm/serializers/payload_serializer_to_json.rb +30 -15
- data/lib/scout_apm/slow_job_record.rb +5 -1
- 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/slow_transaction.rb +3 -1
- data/lib/scout_apm/store.rb +12 -8
- data/lib/scout_apm/tracked_request.rb +39 -30
- data/lib/scout_apm/utils/active_record_metric_name.rb +16 -3
- data/lib/scout_apm/utils/backtrace_parser.rb +3 -0
- data/lib/scout_apm/utils/marshal_logging.rb +90 -0
- data/lib/scout_apm/utils/sql_sanitizer.rb +10 -1
- data/lib/scout_apm/utils/sql_sanitizer_regex.rb +8 -1
- data/lib/scout_apm/utils/sql_sanitizer_regex_1_8_7.rb +6 -0
- data/lib/scout_apm/utils/unique_id.rb +27 -0
- data/lib/scout_apm/version.rb +1 -1
- data/scout_apm.gemspec +13 -7
- data/test/test_helper.rb +2 -2
- data/test/unit/agent_context_test.rb +29 -0
- data/test/unit/auto_instrument/assignments-instrumented.rb +31 -0
- data/test/unit/auto_instrument/assignments.rb +31 -0
- data/test/unit/auto_instrument/controller-ast.txt +57 -0
- data/test/unit/auto_instrument/controller-instrumented.rb +49 -0
- data/test/unit/auto_instrument/controller.rb +49 -0
- data/test/unit/auto_instrument/rescue_from-instrumented.rb +13 -0
- data/test/unit/auto_instrument/rescue_from.rb +13 -0
- data/test/unit/auto_instrument_test.rb +54 -0
- data/test/unit/environment_test.rb +2 -2
- data/test/unit/error_service/error_buffer_test.rb +25 -0
- data/test/unit/error_service/ignored_exceptions_test.rb +49 -0
- data/test/unit/instruments/active_record_test.rb +40 -0
- data/test/unit/layer_children_set_test.rb +9 -0
- data/test/unit/request_histograms_test.rb +17 -0
- data/test/unit/serializers/payload_serializer_test.rb +39 -5
- data/test/unit/slow_request_policy_test.rb +41 -13
- data/test/unit/sql_sanitizer_test.rb +78 -0
- data/test/unit/utils/active_record_metric_name_test.rb +10 -2
- metadata +100 -63
- data/ext/stacks/extconf.rb +0 -37
- data/ext/stacks/scout_atomics.h +0 -86
- data/ext/stacks/stacks.c +0 -814
- data/lib/scout_apm/slow_job_policy.rb +0 -111
- data/lib/scout_apm/trace_compactor.rb +0 -312
- data/lib/scout_apm/utils/fake_stacks.rb +0 -88
- data/test/unit/instruments/active_record_instruments_test.rb +0 -5
- data/test/unit/slow_job_policy_test.rb +0 -6
- data/tester.rb +0 -53
@@ -17,6 +17,10 @@ module ScoutApm
|
|
17
17
|
|
18
18
|
def encode
|
19
19
|
Marshal.dump(self)
|
20
|
+
rescue
|
21
|
+
ScoutApm::Agent.instance.logger.info("Failed Marshalling Remote::Message")
|
22
|
+
ScoutApm::Agent.instance.logger.info(ScoutApm::Utils::MarshalLogging.new(self).dive) rescue nil
|
23
|
+
raise
|
20
24
|
end
|
21
25
|
end
|
22
26
|
end
|
@@ -16,8 +16,20 @@ module ScoutApm
|
|
16
16
|
@server = nil
|
17
17
|
end
|
18
18
|
|
19
|
-
def
|
19
|
+
def require_webrick
|
20
20
|
require 'webrick'
|
21
|
+
true
|
22
|
+
rescue LoadError
|
23
|
+
@logger.warn(
|
24
|
+
%q|Could not require Webrick. Ruby 3.0 stopped bundling it
|
25
|
+
automatically, but it is required to instrument Resque. Please add
|
26
|
+
Webrick to your Gemfile.|
|
27
|
+
)
|
28
|
+
false
|
29
|
+
end
|
30
|
+
|
31
|
+
def start
|
32
|
+
return false unless require_webrick
|
21
33
|
|
22
34
|
@server = WEBrick::HTTPServer.new(
|
23
35
|
:BindAddress => bind,
|
data/lib/scout_apm/reporter.rb
CHANGED
@@ -2,7 +2,6 @@ require 'openssl'
|
|
2
2
|
|
3
3
|
module ScoutApm
|
4
4
|
class Reporter
|
5
|
-
CA_FILE = File.join( File.dirname(__FILE__), *%w[.. .. data cacert.pem] )
|
6
5
|
VERIFY_MODE = OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
7
6
|
|
8
7
|
attr_reader :type
|
@@ -23,6 +22,7 @@ module ScoutApm
|
|
23
22
|
context.logger
|
24
23
|
end
|
25
24
|
|
25
|
+
# The fully serialized string payload to be sent
|
26
26
|
def report(payload, headers = {})
|
27
27
|
hosts = determine_hosts
|
28
28
|
|
@@ -36,6 +36,7 @@ module ScoutApm
|
|
36
36
|
logger.debug("Original Size: #{original_payload_size} Compressed Size: #{compress_payload_size}")
|
37
37
|
end
|
38
38
|
|
39
|
+
logger.info("Posting payload to #{hosts.inspect}")
|
39
40
|
post_payload(hosts, payload, headers)
|
40
41
|
end
|
41
42
|
|
@@ -52,6 +53,8 @@ module ScoutApm
|
|
52
53
|
URI.parse("#{host}/apps/deploy.scout?key=#{key}&name=#{encoded_app_name}")
|
53
54
|
when :instant_trace
|
54
55
|
URI.parse("#{host}/apps/instant_trace.scout?key=#{key}&name=#{encoded_app_name}&instant_key=#{instant_key}")
|
56
|
+
when :errors
|
57
|
+
URI.parse("#{host}/apps/error.scout?key=#{key}&name=#{encoded_app_name}")
|
55
58
|
end.tap { |u| logger.debug("Posting to #{u}") }
|
56
59
|
end
|
57
60
|
|
@@ -90,7 +93,7 @@ module ScoutApm
|
|
90
93
|
logger.debug "got response: #{response.inspect}"
|
91
94
|
case response
|
92
95
|
when Net::HTTPSuccess, Net::HTTPNotModified
|
93
|
-
logger.debug "
|
96
|
+
logger.debug "#{type} OK"
|
94
97
|
when Net::HTTPBadRequest
|
95
98
|
logger.warn "/#{type} FAILED: The Account Key [#{config.value('key')}] is invalid."
|
96
99
|
when Net::HTTPUnprocessableEntity
|
@@ -123,7 +126,7 @@ module ScoutApm
|
|
123
126
|
proxy_uri.password).new(url.host, url.port)
|
124
127
|
if url.is_a?(URI::HTTPS)
|
125
128
|
http.use_ssl = true
|
126
|
-
http.ca_file =
|
129
|
+
http.ca_file = config.value("ssl_cert_file")
|
127
130
|
http.verify_mode = VERIFY_MODE
|
128
131
|
end
|
129
132
|
http
|
@@ -142,6 +145,8 @@ module ScoutApm
|
|
142
145
|
def determine_hosts
|
143
146
|
if [:deploy_hook, :instant_trace].include?(type)
|
144
147
|
config.value('direct_host')
|
148
|
+
elsif [:errors].include?(type)
|
149
|
+
config.value('errors_host')
|
145
150
|
else
|
146
151
|
config.value('host')
|
147
152
|
end
|
data/lib/scout_apm/reporting.rb
CHANGED
@@ -83,10 +83,11 @@ module ScoutApm
|
|
83
83
|
slow_jobs = reporting_period.slow_jobs_payload
|
84
84
|
histograms = reporting_period.histograms
|
85
85
|
db_query_metrics = reporting_period.db_query_metrics_payload
|
86
|
+
traces = (slow_transactions.map(&:span_trace) + slow_jobs.map(&:span_trace)).compact
|
86
87
|
|
87
88
|
log_deliver(metrics, slow_transactions, metadata, slow_jobs, histograms)
|
88
89
|
|
89
|
-
payload = ScoutApm::Serializers::PayloadSerializer.serialize(metadata, metrics, slow_transactions, jobs, slow_jobs, histograms, db_query_metrics)
|
90
|
+
payload = ScoutApm::Serializers::PayloadSerializer.serialize(metadata, metrics, slow_transactions, jobs, slow_jobs, histograms, db_query_metrics, traces)
|
90
91
|
logger.debug("Sending payload w/ Headers: #{headers.inspect}")
|
91
92
|
|
92
93
|
reporter.report(payload, headers)
|
@@ -5,6 +5,10 @@ module ScoutApm
|
|
5
5
|
class AppServerLoadSerializer
|
6
6
|
def self.serialize(data)
|
7
7
|
Marshal.dump(data)
|
8
|
+
rescue
|
9
|
+
ScoutApm::Agent.instance.logger.info("Failed Marshalling AppServerLoad")
|
10
|
+
ScoutApm::Agent.instance.logger.info(ScoutApm::Utils::MarshalLogging.new(data).dive) rescue nil
|
11
|
+
raise
|
8
12
|
end
|
9
13
|
|
10
14
|
def self.deserialize(data)
|
@@ -5,6 +5,10 @@ module ScoutApm
|
|
5
5
|
class DirectiveSerializer
|
6
6
|
def self.serialize(data)
|
7
7
|
Marshal.dump(data)
|
8
|
+
rescue
|
9
|
+
ScoutApm::Agent.instance.logger.info("Failed Marshalling Directive")
|
10
|
+
ScoutApm::Agent.instance.logger.info(ScoutApm::Utils::MarshalLogging.new(data).dive) rescue nil
|
11
|
+
raise
|
8
12
|
end
|
9
13
|
|
10
14
|
def self.deserialize(data)
|
@@ -2,9 +2,9 @@
|
|
2
2
|
module ScoutApm
|
3
3
|
module Serializers
|
4
4
|
class PayloadSerializer
|
5
|
-
def self.serialize(metadata, metrics, slow_transactions, jobs, slow_jobs, histograms, db_query_metrics)
|
5
|
+
def self.serialize(metadata, metrics, slow_transactions, jobs, slow_jobs, histograms, db_query_metrics, traces)
|
6
6
|
if ScoutApm::Agent.instance.context.config.value("report_format") == 'json'
|
7
|
-
ScoutApm::Serializers::PayloadSerializerToJson.serialize(metadata, metrics, slow_transactions, jobs, slow_jobs, histograms, db_query_metrics)
|
7
|
+
ScoutApm::Serializers::PayloadSerializerToJson.serialize(metadata, metrics, slow_transactions, jobs, slow_jobs, histograms, db_query_metrics, traces)
|
8
8
|
else
|
9
9
|
metadata = metadata.dup
|
10
10
|
metadata.default = nil
|
@@ -2,7 +2,7 @@ module ScoutApm
|
|
2
2
|
module Serializers
|
3
3
|
module PayloadSerializerToJson
|
4
4
|
class << self
|
5
|
-
def serialize(metadata, metrics, slow_transactions, jobs, slow_jobs, histograms, db_query_metrics)
|
5
|
+
def serialize(metadata, metrics, slow_transactions, jobs, slow_jobs, histograms, db_query_metrics, traces)
|
6
6
|
metadata.merge!({:payload_version => 2})
|
7
7
|
|
8
8
|
jsonify_hash({:metadata => metadata,
|
@@ -14,6 +14,7 @@ module ScoutApm
|
|
14
14
|
:db_metrics => {
|
15
15
|
:query => DbQuerySerializerToJson.new(db_query_metrics).as_json,
|
16
16
|
},
|
17
|
+
:span_traces => traces.map{ |t| t.as_json },
|
17
18
|
})
|
18
19
|
end
|
19
20
|
|
@@ -44,18 +45,36 @@ module ScoutApm
|
|
44
45
|
"{#{str_parts.join(",")}}"
|
45
46
|
end
|
46
47
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
48
|
+
# Ruby 1.8.7 seems to be fundamentally different in how gsub or regexes
|
49
|
+
# work. This is a hack and will be removed as soon as we can drop
|
50
|
+
# support
|
51
|
+
if RUBY_VERSION == "1.8.7"
|
52
|
+
ESCAPE_MAPPINGS = {
|
53
|
+
"\b" => '\\b',
|
54
|
+
"\t" => '\\t',
|
55
|
+
"\n" => '\\n',
|
56
|
+
"\f" => '\\f',
|
57
|
+
"\r" => '\\r',
|
58
|
+
'"' => '\\"',
|
59
|
+
'\\' => '\\\\',
|
60
|
+
}
|
61
|
+
else
|
62
|
+
ESCAPE_MAPPINGS = {
|
63
|
+
# Stackoverflow answer on gsub matches and backslashes - https://stackoverflow.com/a/4149087/2705125
|
64
|
+
'\\' => '\\\\\\\\',
|
65
|
+
"\b" => '\\b',
|
66
|
+
"\t" => '\\t',
|
67
|
+
"\n" => '\\n',
|
68
|
+
"\f" => '\\f',
|
69
|
+
"\r" => '\\r',
|
70
|
+
'"' => '\\"',
|
71
|
+
}
|
72
|
+
end
|
56
73
|
|
57
74
|
def escape(string)
|
58
|
-
ESCAPE_MAPPINGS.inject(string.to_s) {|s, (bad, good)|
|
75
|
+
ESCAPE_MAPPINGS.inject(string.to_s) {|s, (bad, good)|
|
76
|
+
s.gsub(bad, good)
|
77
|
+
}
|
59
78
|
end
|
60
79
|
|
61
80
|
def format_by_type(formatee)
|
@@ -71,10 +90,6 @@ module ScoutApm
|
|
71
90
|
%Q["#{formatee.iso8601}"]
|
72
91
|
when nil
|
73
92
|
"null"
|
74
|
-
when TrueClass
|
75
|
-
"true"
|
76
|
-
when FalseClass
|
77
|
-
"false"
|
78
93
|
else # strings and everything
|
79
94
|
%Q["#{escape(formatee)}"]
|
80
95
|
end
|
@@ -23,7 +23,9 @@ module ScoutApm
|
|
23
23
|
attr_reader :git_sha
|
24
24
|
attr_reader :truncated_metrics
|
25
25
|
|
26
|
-
|
26
|
+
attr_reader :span_trace
|
27
|
+
|
28
|
+
def initialize(agent_context, queue_name, job_name, time, total_time, exclusive_time, context, metrics, allocation_metrics, mem_delta, allocations, score, truncated_metrics, span_trace)
|
27
29
|
@queue_name = queue_name
|
28
30
|
@job_name = job_name
|
29
31
|
@time = time
|
@@ -40,6 +42,8 @@ module ScoutApm
|
|
40
42
|
@score = score
|
41
43
|
@truncated_metrics = truncated_metrics
|
42
44
|
|
45
|
+
@span_trace = span_trace
|
46
|
+
|
43
47
|
agent_context.logger.debug { "Slow Job [#{metric_name}] - Call Time: #{total_call_time} Mem Delta: #{mem_delta}"}
|
44
48
|
end
|
45
49
|
|
@@ -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
|