scout_apm 3.0.0.pre27 → 4.0.3
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 +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 +139 -4
- data/Gemfile +1 -7
- data/README.markdown +13 -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 +3 -1
- 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 +9 -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 +122 -26
- data/lib/scout_apm/instruments/active_record.rb +66 -18
- 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/instruments/typhoeus.rb +88 -0
- 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 +9 -8
- data/lib/scout_apm/layer_converters/converter_base.rb +15 -30
- 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 +0 -1
- data/lib/scout_apm/tracked_request.rb +39 -30
- 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
- metadata +101 -62
- 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
|
@@ -85,13 +85,6 @@ module ScoutApm
|
|
|
85
85
|
nil
|
|
86
86
|
end
|
|
87
87
|
|
|
88
|
-
def add_traces(*)
|
|
89
|
-
# noop
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
def traces
|
|
93
|
-
ScoutApm::TraceSet.new
|
|
94
|
-
end
|
|
95
88
|
|
|
96
89
|
#######################################################################
|
|
97
90
|
# Many methods don't make any sense on a limited layer. Raise errors #
|
|
@@ -9,7 +9,6 @@ class MetricStats
|
|
|
9
9
|
attr_accessor :sum_of_squares
|
|
10
10
|
attr_accessor :queue
|
|
11
11
|
attr_accessor :latency
|
|
12
|
-
attr_accessor :traces
|
|
13
12
|
|
|
14
13
|
def initialize(scoped = false)
|
|
15
14
|
@scoped = scoped
|
|
@@ -19,7 +18,6 @@ class MetricStats
|
|
|
19
18
|
self.min_call_time = 0.0
|
|
20
19
|
self.max_call_time = 0.0
|
|
21
20
|
self.sum_of_squares = 0.0
|
|
22
|
-
self.traces = []
|
|
23
21
|
end
|
|
24
22
|
|
|
25
23
|
# Note, that you must include exclusive_time if you wish to set
|
|
@@ -41,10 +39,6 @@ class MetricStats
|
|
|
41
39
|
self
|
|
42
40
|
end
|
|
43
41
|
|
|
44
|
-
def add_traces(traces)
|
|
45
|
-
self.traces += Array(traces)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
42
|
# combines data from another MetricStats object
|
|
49
43
|
def combine!(other)
|
|
50
44
|
self.call_count += other.call_count
|
|
@@ -53,7 +47,6 @@ class MetricStats
|
|
|
53
47
|
self.min_call_time = other.min_call_time if self.min_call_time.zero? or other.min_call_time < self.min_call_time
|
|
54
48
|
self.max_call_time = other.max_call_time if other.max_call_time > self.max_call_time
|
|
55
49
|
self.sum_of_squares += other.sum_of_squares
|
|
56
|
-
self.traces = Array(self.traces) + Array(other.traces)
|
|
57
50
|
self
|
|
58
51
|
end
|
|
59
52
|
|
|
@@ -64,7 +57,6 @@ class MetricStats
|
|
|
64
57
|
:min_call_time,
|
|
65
58
|
:total_call_time,
|
|
66
59
|
:total_exclusive_time,
|
|
67
|
-
:traces,
|
|
68
60
|
]
|
|
69
61
|
ScoutApm::AttributeArranger.call(self, json_attributes)
|
|
70
62
|
end
|
data/lib/scout_apm/middleware.rb
CHANGED
|
@@ -26,7 +26,7 @@ module ScoutApm
|
|
|
26
26
|
ScoutApm::Agent.instance.start
|
|
27
27
|
@started = ScoutApm::Agent.instance.context.started? && ScoutApm::Agent.instance.background_worker_running?
|
|
28
28
|
rescue => e
|
|
29
|
-
ScoutApm::Agent.instance.context.logger("Failed to start via Middleware: #{e.message}\n\t#{e.backtrace.join("\n\t")}")
|
|
29
|
+
ScoutApm::Agent.instance.context.logger.info("Failed to start via Middleware: #{e.message}\n\t#{e.backtrace.join("\n\t")}")
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
end
|
|
@@ -12,10 +12,29 @@ module ScoutApm
|
|
|
12
12
|
ScoutApm::Debug.instance.call_periodic_hooks
|
|
13
13
|
@reporting.process_metrics
|
|
14
14
|
clean_old_percentiles
|
|
15
|
+
|
|
16
|
+
if context.config.value('auto_instruments')
|
|
17
|
+
log_autoinstrument_significant_counts rescue nil
|
|
18
|
+
end
|
|
15
19
|
end
|
|
16
20
|
|
|
17
21
|
private
|
|
18
22
|
|
|
23
|
+
def log_autoinstrument_significant_counts
|
|
24
|
+
# Ex key/value -
|
|
25
|
+
# "/Users/dlite/projects/scout/apm/app/controllers/application_controller.rb"=>[[0.0, 689], [1.0, 16]]
|
|
26
|
+
hists = context.auto_instruments_layer_histograms.as_json
|
|
27
|
+
hists_summary = hists.map { |file, buckets|
|
|
28
|
+
total = buckets.map(&:last).inject(0) { |sum, count| sum + count }
|
|
29
|
+
significant = (buckets.last.last / total.to_f).round(2)
|
|
30
|
+
[
|
|
31
|
+
file,
|
|
32
|
+
{:total => total, :significant => significant}
|
|
33
|
+
]
|
|
34
|
+
}.to_h
|
|
35
|
+
context.logger.debug("AutoInstrument Significant Layer Histograms: #{hists_summary.pretty_inspect}")
|
|
36
|
+
end
|
|
37
|
+
|
|
19
38
|
# XXX: Move logic into a RequestHistogramsByTime class that can keep the timeout logic in it
|
|
20
39
|
def clean_old_percentiles
|
|
21
40
|
context.
|
|
@@ -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
|