scout_apm 4.0.4 → 5.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/.github/workflows/test.yml +4 -0
- data/CHANGELOG.markdown +33 -0
- data/LICENSE.md +21 -28
- data/gems/typhoeus.gemfile +3 -0
- data/lib/scout_apm/agent/preconditions.rb +3 -3
- data/lib/scout_apm/auto_instrument/rails.rb +0 -1
- data/lib/scout_apm/background_job_integrations/shoryuken.rb +2 -0
- data/lib/scout_apm/background_job_integrations/sidekiq.rb +13 -2
- data/lib/scout_apm/config.rb +10 -0
- data/lib/scout_apm/error_service/middleware.rb +2 -2
- data/lib/scout_apm/error_service/payload.rb +1 -1
- data/lib/scout_apm/error_service.rb +3 -1
- data/lib/scout_apm/external_service_metric_set.rb +97 -0
- data/lib/scout_apm/external_service_metric_stats.rb +85 -0
- data/lib/scout_apm/fake_store.rb +3 -0
- data/lib/scout_apm/framework_integrations/rails_3_or_4.rb +5 -1
- data/lib/scout_apm/git_revision.rb +9 -0
- data/lib/scout_apm/ignored_uris.rb +3 -1
- data/lib/scout_apm/instant/middleware.rb +2 -0
- data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +5 -2
- data/lib/scout_apm/instruments/action_view.rb +13 -5
- data/lib/scout_apm/instruments/active_record.rb +2 -0
- data/lib/scout_apm/instruments/elasticsearch.rb +2 -0
- data/lib/scout_apm/instruments/sinatra.rb +2 -0
- data/lib/scout_apm/instruments/typhoeus.rb +8 -6
- data/lib/scout_apm/layer_converters/external_service_converter.rb +65 -0
- data/lib/scout_apm/layer_converters/find_layer_by_type.rb +4 -0
- data/lib/scout_apm/layer_converters/request_queue_time_converter.rb +2 -0
- data/lib/scout_apm/logger.rb +2 -2
- data/lib/scout_apm/reporting.rb +2 -1
- data/lib/scout_apm/serializers/external_service_serializer_to_json.rb +15 -0
- data/lib/scout_apm/serializers/payload_serializer.rb +4 -3
- data/lib/scout_apm/serializers/payload_serializer_to_json.rb +4 -1
- data/lib/scout_apm/store.rb +21 -1
- data/lib/scout_apm/tracked_request.rb +7 -1
- data/lib/scout_apm/utils/sql_sanitizer.rb +32 -6
- data/lib/scout_apm/version.rb +1 -1
- data/lib/scout_apm.rb +5 -0
- data/scout_apm.gemspec +0 -2
- data/test/unit/auto_instrument/anonymous_block_value.rb +7 -0
- data/test/unit/auto_instrument/hanging_method.rb +6 -0
- data/test/unit/auto_instrument_test.rb +8 -0
- data/test/unit/external_service_metric_set_test.rb +67 -0
- data/test/unit/external_service_metric_stats_test.rb +106 -0
- data/test/unit/ignored_uris_test.rb +6 -0
- data/test/unit/instruments/typhoeus_test.rb +42 -0
- data/test/unit/remote/{test_message.rb → message_test.rb} +0 -0
- data/test/unit/remote/{test_router.rb → route_test.rb} +0 -0
- data/test/unit/remote/{test_server.rb → server_test.rb} +4 -1
- data/test/unit/serializers/payload_serializer_test.rb +3 -3
- data/test/unit/sql_sanitizer_test.rb +19 -2
- metadata +16 -9
- data/.travis.yml +0 -22
- data/lib/scout_apm/utils/sql_sanitizer_regex.rb +0 -32
- data/lib/scout_apm/utils/sql_sanitizer_regex_1_8_7.rb +0 -32
@@ -0,0 +1,65 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
module LayerConverters
|
3
|
+
class ExternalServiceConverter < ConverterBase
|
4
|
+
def initialize(*)
|
5
|
+
super
|
6
|
+
@external_service_metric_set = ExternalServiceMetricSet.new(context)
|
7
|
+
end
|
8
|
+
|
9
|
+
def register_hooks(walker)
|
10
|
+
super
|
11
|
+
|
12
|
+
return unless scope_layer
|
13
|
+
|
14
|
+
walker.on do |layer|
|
15
|
+
next if skip_layer?(layer)
|
16
|
+
stat = ExternalServiceMetricStats.new(
|
17
|
+
domain_name(layer),
|
18
|
+
operation_name(layer), # operation name/verb. GET/POST/PUT etc.
|
19
|
+
scope_layer.legacy_metric_name, # controller_scope
|
20
|
+
1, # count, this is a single call, so 1
|
21
|
+
layer.total_call_time
|
22
|
+
)
|
23
|
+
@external_service_metric_set << stat
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def record!
|
28
|
+
# Everything in the metric set here is from a single transaction, which
|
29
|
+
# we want to keep track of. (One web call did a User#find 10 times, but
|
30
|
+
# only due to 1 http request)
|
31
|
+
@external_service_metric_set.increment_transaction_count!
|
32
|
+
@store.track_external_service_metrics!(@external_service_metric_set)
|
33
|
+
|
34
|
+
nil # not returning anything in the layer results ... not used
|
35
|
+
end
|
36
|
+
|
37
|
+
def skip_layer?(layer)
|
38
|
+
layer.type != 'HTTP' ||
|
39
|
+
layer.limited? ||
|
40
|
+
super
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
# If we can't name the domain name, default to:
|
46
|
+
DEFAULT_DOMAIN = "Unknown"
|
47
|
+
|
48
|
+
def domain_name(layer)
|
49
|
+
domain = ""
|
50
|
+
desc_str = layer.desc.to_s
|
51
|
+
desc_str = 'http://' + desc_str unless desc_str =~ /^http/i
|
52
|
+
domain = URI.parse(desc_str).host
|
53
|
+
rescue
|
54
|
+
# Do nothing
|
55
|
+
ensure
|
56
|
+
domain = DEFAULT_DOMAIN if domain.to_s.blank?
|
57
|
+
domain
|
58
|
+
end
|
59
|
+
|
60
|
+
def operation_name(layer)
|
61
|
+
"all" # Hardcode to "all" until we support breakout by verb
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -5,6 +5,10 @@
|
|
5
5
|
# show
|
6
6
|
# render :update
|
7
7
|
# end
|
8
|
+
|
9
|
+
# This doesn't cache the negative result when searching for a controller / job,
|
10
|
+
# so that we can ask again later after more of the request has occurred and
|
11
|
+
# correctly find it.
|
8
12
|
module ScoutApm
|
9
13
|
module LayerConverters
|
10
14
|
class FindLayerByType
|
data/lib/scout_apm/logger.rb
CHANGED
@@ -69,14 +69,14 @@ module ScoutApm
|
|
69
69
|
private
|
70
70
|
|
71
71
|
def build_logger
|
72
|
-
logger_class.new(@log_destination)
|
72
|
+
logger_class.new(@log_destination) rescue logger_class.new
|
73
73
|
end
|
74
74
|
|
75
75
|
def logger_class
|
76
76
|
klass = @opts.fetch(:logger_class, ::Logger)
|
77
77
|
case klass
|
78
78
|
when String
|
79
|
-
result = KlassHelper.lookup(klass)
|
79
|
+
result = Utils::KlassHelper.lookup(klass)
|
80
80
|
if result == :missing_class
|
81
81
|
::Logger
|
82
82
|
else
|
data/lib/scout_apm/reporting.rb
CHANGED
@@ -83,11 +83,12 @@ 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
|
+
external_service_metrics = reporting_period.external_service_metrics_payload
|
86
87
|
traces = (slow_transactions.map(&:span_trace) + slow_jobs.map(&:span_trace)).compact
|
87
88
|
|
88
89
|
log_deliver(metrics, slow_transactions, metadata, slow_jobs, histograms)
|
89
90
|
|
90
|
-
payload = ScoutApm::Serializers::PayloadSerializer.serialize(metadata, metrics, slow_transactions, jobs, slow_jobs, histograms, db_query_metrics, traces)
|
91
|
+
payload = ScoutApm::Serializers::PayloadSerializer.serialize(metadata, metrics, slow_transactions, jobs, slow_jobs, histograms, db_query_metrics, external_service_metrics, traces)
|
91
92
|
logger.debug("Sending payload w/ Headers: #{headers.inspect}")
|
92
93
|
|
93
94
|
reporter.report(payload, headers)
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
module Serializers
|
3
|
+
class ExternalServiceSerializerToJson
|
4
|
+
attr_reader :external_service_metrics
|
5
|
+
|
6
|
+
def initialize(external_service_metrics)
|
7
|
+
@external_service_metrics = external_service_metrics
|
8
|
+
end
|
9
|
+
|
10
|
+
def as_json
|
11
|
+
external_service_metrics.map{|metric| metric.as_json }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -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, traces)
|
5
|
+
def self.serialize(metadata, metrics, slow_transactions, jobs, slow_jobs, histograms, db_query_metrics, external_service_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, traces)
|
7
|
+
ScoutApm::Serializers::PayloadSerializerToJson.serialize(metadata, metrics, slow_transactions, jobs, slow_jobs, histograms, db_query_metrics, external_service_metrics, traces)
|
8
8
|
else
|
9
9
|
metadata = metadata.dup
|
10
10
|
metadata.default = nil
|
@@ -22,7 +22,8 @@ module ScoutApm
|
|
22
22
|
# payloads. At this point, the marshal code branch is
|
23
23
|
# very rarely used anyway.
|
24
24
|
:histograms => HistogramsSerializerToJson.new(histograms).as_json,
|
25
|
-
:db_query_metrics => db_query_metrics
|
25
|
+
:db_query_metrics => db_query_metrics,
|
26
|
+
:external_service_metrics => external_service_metrics)
|
26
27
|
end
|
27
28
|
end
|
28
29
|
|
@@ -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, traces)
|
5
|
+
def serialize(metadata, metrics, slow_transactions, jobs, slow_jobs, histograms, db_query_metrics, external_service_metrics, traces)
|
6
6
|
metadata.merge!({:payload_version => 2})
|
7
7
|
|
8
8
|
jsonify_hash({:metadata => metadata,
|
@@ -14,6 +14,9 @@ module ScoutApm
|
|
14
14
|
:db_metrics => {
|
15
15
|
:query => DbQuerySerializerToJson.new(db_query_metrics).as_json,
|
16
16
|
},
|
17
|
+
:es_metrics => {
|
18
|
+
:http => ExternalServiceSerializerToJson.new(external_service_metrics).as_json,
|
19
|
+
},
|
17
20
|
:span_traces => traces.map{ |t| t.as_json },
|
18
21
|
})
|
19
22
|
end
|
data/lib/scout_apm/store.rb
CHANGED
@@ -52,6 +52,13 @@ module ScoutApm
|
|
52
52
|
}
|
53
53
|
end
|
54
54
|
|
55
|
+
def track_external_service_metrics!(external_service_metric_set, options={})
|
56
|
+
@mutex.synchronize {
|
57
|
+
period = find_period(options[:timestamp])
|
58
|
+
period.merge_external_service_metrics!(external_service_metric_set)
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
55
62
|
def track_one!(type, name, value, options={})
|
56
63
|
meta = MetricMeta.new("#{type}/#{name}")
|
57
64
|
stat = MetricStats.new(false)
|
@@ -206,6 +213,8 @@ module ScoutApm
|
|
206
213
|
|
207
214
|
attr_reader :db_query_metric_set
|
208
215
|
|
216
|
+
attr_reader :external_service_metric_set
|
217
|
+
|
209
218
|
def initialize(timestamp, context)
|
210
219
|
@timestamp = timestamp
|
211
220
|
|
@@ -216,6 +225,7 @@ module ScoutApm
|
|
216
225
|
|
217
226
|
@metric_set = MetricSet.new
|
218
227
|
@db_query_metric_set = DbQueryMetricSet.new(context)
|
228
|
+
@external_service_metric_set = ExternalServiceMetricSet.new(context)
|
219
229
|
|
220
230
|
@jobs = Hash.new
|
221
231
|
end
|
@@ -228,7 +238,8 @@ module ScoutApm
|
|
228
238
|
merge_jobs!(other.jobs).
|
229
239
|
merge_slow_jobs!(other.slow_jobs_payload).
|
230
240
|
merge_histograms!(other.histograms).
|
231
|
-
merge_db_query_metrics!(other.db_query_metric_set)
|
241
|
+
merge_db_query_metrics!(other.db_query_metric_set).
|
242
|
+
merge_external_service_metrics!(other.external_service_metric_set)
|
232
243
|
self
|
233
244
|
end
|
234
245
|
|
@@ -254,6 +265,11 @@ module ScoutApm
|
|
254
265
|
self
|
255
266
|
end
|
256
267
|
|
268
|
+
def merge_external_service_metrics!(other_metric_set)
|
269
|
+
external_service_metric_set.combine!(other_metric_set)
|
270
|
+
self
|
271
|
+
end
|
272
|
+
|
257
273
|
def merge_slow_transactions!(new_transactions)
|
258
274
|
Array(new_transactions).each do |one_transaction|
|
259
275
|
request_traces << one_transaction
|
@@ -316,6 +332,10 @@ module ScoutApm
|
|
316
332
|
db_query_metric_set.metrics_to_report
|
317
333
|
end
|
318
334
|
|
335
|
+
def external_service_metrics_payload
|
336
|
+
external_service_metric_set.metrics_to_report
|
337
|
+
end
|
338
|
+
|
319
339
|
#################################
|
320
340
|
# Debug Helpers
|
321
341
|
#################################
|
@@ -117,7 +117,12 @@ module ScoutApm
|
|
117
117
|
# Must follow layer.record_stop_time! as the total_call_time is used to determine if the layer is significant.
|
118
118
|
return if layer_insignificant?(layer)
|
119
119
|
|
120
|
-
|
120
|
+
# Check that the parent exists before calling a method on it, since some threading can get us into a weird state.
|
121
|
+
# this doesn't fix that state, but prevents exceptions from leaking out.
|
122
|
+
parent = @layers[-1]
|
123
|
+
if parent
|
124
|
+
parent.add_child(layer)
|
125
|
+
end
|
121
126
|
|
122
127
|
# This must be called before checking if a backtrace should be collected as the call count influences our capture logic.
|
123
128
|
# We call `#update_call_counts in stop layer to ensure the layer has a final desc. Layer#desc is updated during the AR instrumentation flow.
|
@@ -315,6 +320,7 @@ module ScoutApm
|
|
315
320
|
:queue_time => LayerConverters::RequestQueueTimeConverter,
|
316
321
|
:job => LayerConverters::JobConverter,
|
317
322
|
:db => LayerConverters::DatabaseConverter,
|
323
|
+
:external_service => LayerConverters::ExternalServiceConverter,
|
318
324
|
|
319
325
|
:slow_job => LayerConverters::SlowJobConverter,
|
320
326
|
:slow_req => LayerConverters::SlowRequestConverter,
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
|
1
3
|
require 'scout_apm/environment'
|
2
4
|
|
3
5
|
# Removes actual values from SQL. Used to both obfuscate the SQL and group
|
@@ -5,12 +7,34 @@ require 'scout_apm/environment'
|
|
5
7
|
module ScoutApm
|
6
8
|
module Utils
|
7
9
|
class SqlSanitizer
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
MULTIPLE_SPACES = %r|\s+|.freeze
|
11
|
+
MULTIPLE_QUESTIONS = /\?(,\?)+/.freeze
|
12
|
+
|
13
|
+
PSQL_VAR_INTERPOLATION = %r|\[\[.*\]\]\s*\z|.freeze
|
14
|
+
PSQL_REMOVE_STRINGS = /'(?:[^']|'')*'/.freeze
|
15
|
+
PSQL_REMOVE_INTEGERS = /(?<!LIMIT )\b\d+\b/.freeze
|
16
|
+
PSQL_AFTER_SELECT = /(?:SELECT\s+).*?(?:WHERE|FROM\z)/im.freeze # Should be everything between a FROM and a WHERE
|
17
|
+
PSQL_PLACEHOLDER = /\$\d+/.freeze
|
18
|
+
PSQL_IN_CLAUSE = /IN\s+\(\?[^\)]*\)/.freeze
|
19
|
+
PSQL_AFTER_FROM = /(?:FROM\s+).*?(?:WHERE|\z)/im.freeze # Should be everything between a FROM and a WHERE
|
20
|
+
PSQL_AFTER_JOIN = /(?:JOIN\s+).*?\z/im.freeze
|
21
|
+
PSQL_AFTER_WHERE = /(?:WHERE\s+).*?(?:SELECT|\z)/im.freeze
|
22
|
+
PSQL_AFTER_SET = /(?:SET\s+).*?(?:WHERE|\z)/im.freeze
|
23
|
+
|
24
|
+
MYSQL_VAR_INTERPOLATION = %r|\[\[.*\]\]\s*$|.freeze
|
25
|
+
MYSQL_REMOVE_INTEGERS = /(?<!LIMIT )\b\d+\b/.freeze
|
26
|
+
MYSQL_REMOVE_SINGLE_QUOTE_STRINGS = %r{'(?:\\'|[^']|'')*'}.freeze
|
27
|
+
MYSQL_REMOVE_DOUBLE_QUOTE_STRINGS = %r{"(?:\\"|[^"]|"")*"}.freeze
|
28
|
+
MYSQL_IN_CLAUSE = /IN\s+\(\?[^\)]*\)/.freeze
|
29
|
+
|
30
|
+
SQLITE_VAR_INTERPOLATION = %r|\[\[.*\]\]\s*$|.freeze
|
31
|
+
SQLITE_REMOVE_STRINGS = /'(?:[^']|'')*'/.freeze
|
32
|
+
SQLITE_REMOVE_INTEGERS = /(?<!LIMIT )\b\d+\b/.freeze
|
33
|
+
|
34
|
+
# => "EXEC sp_executesql N'SELECT [users].* FROM [users] WHERE (age > 50) ORDER BY [users].[id] ASC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY', N'@0 int', @0 = 10"
|
35
|
+
SQLSERVER_EXECUTESQL = /EXEC sp_executesql N'(.*?)'.*/
|
36
|
+
SQLSERVER_REMOVE_INTEGERS = /(?<!LIMIT )\b(?<!@)\d+\b/.freeze
|
37
|
+
SQLSERVER_IN_CLAUSE = /IN\s+\(\?[^\)]*\)/.freeze
|
14
38
|
|
15
39
|
attr_accessor :database_engine
|
16
40
|
|
@@ -50,7 +74,9 @@ module ScoutApm
|
|
50
74
|
def to_s_postgres
|
51
75
|
sql.gsub!(PSQL_PLACEHOLDER, '?')
|
52
76
|
sql.gsub!(PSQL_VAR_INTERPOLATION, '')
|
77
|
+
# sql.gsub!(PSQL_REMOVE_STRINGS, '?')
|
53
78
|
sql.gsub!(PSQL_AFTER_WHERE) {|c| c.gsub(PSQL_REMOVE_STRINGS, '?')}
|
79
|
+
sql.gsub!(PSQL_AFTER_JOIN) {|c| c.gsub(PSQL_REMOVE_STRINGS, '?')}
|
54
80
|
sql.gsub!(PSQL_AFTER_SET) {|c| c.gsub(PSQL_REMOVE_STRINGS, '?')}
|
55
81
|
sql.gsub!(PSQL_REMOVE_INTEGERS, '?')
|
56
82
|
sql.gsub!(PSQL_IN_CLAUSE, 'IN (?)')
|
data/lib/scout_apm/version.rb
CHANGED
data/lib/scout_apm.rb
CHANGED
@@ -42,6 +42,7 @@ require 'scout_apm/layer_converters/job_converter'
|
|
42
42
|
require 'scout_apm/layer_converters/slow_job_converter'
|
43
43
|
require 'scout_apm/layer_converters/metric_converter'
|
44
44
|
require 'scout_apm/layer_converters/database_converter'
|
45
|
+
require 'scout_apm/layer_converters/external_service_converter'
|
45
46
|
require 'scout_apm/layer_converters/slow_request_converter'
|
46
47
|
require 'scout_apm/layer_converters/request_queue_time_converter'
|
47
48
|
require 'scout_apm/layer_converters/allocation_metric_converter'
|
@@ -130,6 +131,7 @@ require 'scout_apm/bucket_name_splitter'
|
|
130
131
|
require 'scout_apm/stack_item'
|
131
132
|
require 'scout_apm/metric_set'
|
132
133
|
require 'scout_apm/db_query_metric_set'
|
134
|
+
require 'scout_apm/external_service_metric_set'
|
133
135
|
require 'scout_apm/store'
|
134
136
|
require 'scout_apm/fake_store'
|
135
137
|
require 'scout_apm/tracer'
|
@@ -142,6 +144,7 @@ require 'scout_apm/synchronous_recorder'
|
|
142
144
|
require 'scout_apm/metric_meta'
|
143
145
|
require 'scout_apm/metric_stats'
|
144
146
|
require 'scout_apm/db_query_metric_stats'
|
147
|
+
require 'scout_apm/external_service_metric_stats'
|
145
148
|
require 'scout_apm/slow_transaction'
|
146
149
|
require 'scout_apm/slow_job_record'
|
147
150
|
require 'scout_apm/detailed_trace'
|
@@ -167,6 +170,7 @@ require 'scout_apm/serializers/slow_jobs_serializer_to_json'
|
|
167
170
|
require 'scout_apm/serializers/metrics_to_json_serializer'
|
168
171
|
require 'scout_apm/serializers/histograms_serializer_to_json'
|
169
172
|
require 'scout_apm/serializers/db_query_serializer_to_json'
|
173
|
+
require 'scout_apm/serializers/external_service_serializer_to_json'
|
170
174
|
require 'scout_apm/serializers/directive_serializer'
|
171
175
|
require 'scout_apm/serializers/app_server_load_serializer'
|
172
176
|
|
@@ -193,6 +197,7 @@ require 'scout_apm/tasks/support'
|
|
193
197
|
require 'scout_apm/extensions/config'
|
194
198
|
require 'scout_apm/extensions/transaction_callback_payload'
|
195
199
|
|
200
|
+
require 'scout_apm/error'
|
196
201
|
require 'scout_apm/error_service'
|
197
202
|
require 'scout_apm/error_service/middleware'
|
198
203
|
require 'scout_apm/error_service/notifier'
|
data/scout_apm.gemspec
CHANGED
@@ -12,8 +12,6 @@ Gem::Specification.new do |s|
|
|
12
12
|
s.description = "Monitors Ruby apps and reports detailed metrics on performance to Scout."
|
13
13
|
s.license = "Proprietary (See LICENSE.md)"
|
14
14
|
|
15
|
-
s.rubyforge_project = "scout_apm"
|
16
|
-
|
17
15
|
s.files = `git ls-files`.split("\n")
|
18
16
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
17
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
@@ -51,4 +51,12 @@ class AutoInstrumentTest < Minitest::Test
|
|
51
51
|
assert_equal instrumented_source("assignments"),
|
52
52
|
normalize_backtrace(::ScoutApm::AutoInstrument::Rails.rewrite(source_path("assignments")))
|
53
53
|
end
|
54
|
+
|
55
|
+
def test_hanging_method_rewrite
|
56
|
+
::ScoutApm::AutoInstrument::Rails.rewrite(source_path("hanging_method"))
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_anonymous_block_value
|
60
|
+
::ScoutApm::AutoInstrument::Rails.rewrite(source_path("anonymous_block_value"))
|
61
|
+
end
|
54
62
|
end if defined? ScoutApm::AutoInstrument
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
require 'scout_apm/external_service_metric_set'
|
4
|
+
|
5
|
+
module ScoutApm
|
6
|
+
class ExternalServiceMetricSetTest < Minitest::Test
|
7
|
+
def test_hard_limit
|
8
|
+
config = make_fake_config(
|
9
|
+
'external_service_metric_limit' => 5, # The hard limit on db metrics
|
10
|
+
'external_service_metric_report_limit' => 2
|
11
|
+
)
|
12
|
+
context = ScoutApm::AgentContext.new().tap{|c| c.config = config }
|
13
|
+
set = ExternalServiceMetricSet.new(context)
|
14
|
+
|
15
|
+
set << fake_stat("a", 10)
|
16
|
+
set << fake_stat("b", 20)
|
17
|
+
set << fake_stat("c", 30)
|
18
|
+
set << fake_stat("d", 40)
|
19
|
+
set << fake_stat("e", 50)
|
20
|
+
set << fake_stat("f", 60)
|
21
|
+
|
22
|
+
assert_equal 5, set.metrics.size
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_report_limit
|
26
|
+
config = make_fake_config(
|
27
|
+
'external_service_metric_limit' => 50, # much larger max, uninterested in hitting it.
|
28
|
+
'external_service_metric_report_limit' => 2
|
29
|
+
)
|
30
|
+
context = ScoutApm::AgentContext.new().tap{|c| c.config = config }
|
31
|
+
set = ExternalServiceMetricSet.new(context)
|
32
|
+
set << fake_stat("a", 10)
|
33
|
+
set << fake_stat("b", 20)
|
34
|
+
set << fake_stat("c", 30)
|
35
|
+
set << fake_stat("d", 40)
|
36
|
+
set << fake_stat("e", 50)
|
37
|
+
set << fake_stat("f", 60)
|
38
|
+
|
39
|
+
assert_equal 2, set.metrics_to_report.size
|
40
|
+
assert_equal ["f","e"], set.metrics_to_report.map{|m| m.key}
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_combine
|
44
|
+
config = make_fake_config(
|
45
|
+
'external_service_metric_limit' => 5, # The hard limit on db metrics
|
46
|
+
'external_service_metric_report_limit' => 2
|
47
|
+
)
|
48
|
+
context = ScoutApm::AgentContext.new().tap{|c| c.config = config }
|
49
|
+
set1 = ExternalServiceMetricSet.new(context)
|
50
|
+
set1 << fake_stat("a", 10)
|
51
|
+
set1 << fake_stat("b", 20)
|
52
|
+
set2 = ExternalServiceMetricSet.new(context)
|
53
|
+
set2 << fake_stat("c", 10)
|
54
|
+
set2 << fake_stat("d", 20)
|
55
|
+
|
56
|
+
combined = set1.combine!(set2)
|
57
|
+
assert_equal ["a", "b", "c", "d"], combined.metrics.map{|_k, m| m.key}.sort
|
58
|
+
end
|
59
|
+
|
60
|
+
def fake_stat(key, call_time)
|
61
|
+
OpenStruct.new(
|
62
|
+
:key => key,
|
63
|
+
:call_time => call_time
|
64
|
+
)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
require 'scout_apm/external_service_metric_stats'
|
4
|
+
|
5
|
+
module ScoutApm
|
6
|
+
class ExternalServiceMetricStatsTest < Minitest::Test
|
7
|
+
def test_as_json_empty_stats
|
8
|
+
stat = build("example.com", "GET", "Controller/public/index", 1, 10)
|
9
|
+
|
10
|
+
assert_equal({
|
11
|
+
:domain_name => "example.com",
|
12
|
+
:operation => "GET",
|
13
|
+
:call_count => 1,
|
14
|
+
:transaction_count => 0,
|
15
|
+
:scope => "Controller/public/index",
|
16
|
+
:histogram => [[10.0, 1]],
|
17
|
+
|
18
|
+
:max_call_time => 10.0,
|
19
|
+
:min_call_time => 10.0,
|
20
|
+
:call_time => 10.0
|
21
|
+
}, stat.as_json)
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_increment_transaction_count
|
25
|
+
stat = build()
|
26
|
+
assert_equal 0, stat.transaction_count
|
27
|
+
|
28
|
+
stat.increment_transaction_count!
|
29
|
+
|
30
|
+
assert_equal 1, stat.transaction_count
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_key_name
|
34
|
+
stat = build("example.com", "GET", "Controller/public/index")
|
35
|
+
assert_equal ["example.com", "GET", "Controller/public/index"], stat.key
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_combine_min_call_time_picks_smallest
|
39
|
+
stat1, stat2 = build_pair
|
40
|
+
assert_equal 5.1, stat1.combine!(stat2).min_call_time
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_combine_max_call_time_picks_largest
|
44
|
+
stat1, stat2 = build_pair
|
45
|
+
assert_equal 8.2, stat1.combine!(stat2).max_call_time
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_combine_call_counts_adds
|
49
|
+
stat1, stat2 = build_pair
|
50
|
+
assert_equal 5, stat1.combine!(stat2).call_count
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_combine_transaction_count_adds
|
54
|
+
stat1, stat2 = build_pair
|
55
|
+
2.times { stat1.increment_transaction_count! }
|
56
|
+
3.times { stat2.increment_transaction_count! }
|
57
|
+
|
58
|
+
assert_equal 5, stat1.combine!(stat2).call_count
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_combine_doesnt_merge_with_self
|
62
|
+
stat = build
|
63
|
+
merged = stat.combine!(stat)
|
64
|
+
|
65
|
+
assert_equal DEFAULTS[:call_count], merged.call_count
|
66
|
+
assert_equal DEFAULTS[:call_time], merged.call_time
|
67
|
+
end
|
68
|
+
|
69
|
+
# A.combine!(B) should be the the same as B.combine!(A)
|
70
|
+
# Have to be a bit careful, since combine! is destructive, so make two pairs
|
71
|
+
# with same data to do both sides, then check that they result in the same
|
72
|
+
# answer
|
73
|
+
[:transaction_count, :call_count, :max_call_time, :min_call_time].each do |attr|
|
74
|
+
define_method :"test_combine_#{attr}_is_symmetric" do
|
75
|
+
stat1_a, stat2_a = build_pair
|
76
|
+
stat1_b, stat2_b = build_pair
|
77
|
+
merged_a = stat1_a.combine!(stat2_a)
|
78
|
+
merged_b = stat2_b.combine!(stat1_b)
|
79
|
+
|
80
|
+
assert_equal merged_a.send(attr), merged_b.send(attr)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
#############
|
85
|
+
# Helpers #
|
86
|
+
#############
|
87
|
+
DEFAULTS = {
|
88
|
+
:call_count => 1,
|
89
|
+
:call_time => 10.0,
|
90
|
+
}
|
91
|
+
|
92
|
+
def build(domain_name="example.com",
|
93
|
+
operation="GET",
|
94
|
+
scope="Controller/public/index",
|
95
|
+
call_count=DEFAULTS[:call_count],
|
96
|
+
call_time=DEFAULTS[:call_time])
|
97
|
+
ExternalServiceMetricStats.new(domain_name, operation, scope, call_count, call_time)
|
98
|
+
end
|
99
|
+
|
100
|
+
def build_pair
|
101
|
+
stat1 = build("example.com", "GET", "Controller/public/index", 2, 5.1)
|
102
|
+
stat2 = build("example.com", "GET", "Controller/public/index", 3, 8.2)
|
103
|
+
[stat1, stat2]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -13,4 +13,10 @@ class IgnoredUrlsTest < Minitest::Test
|
|
13
13
|
i = ScoutApm::IgnoredUris.new(["/slow", "/health"])
|
14
14
|
assert_equal false, i.ignore?("/users/2/health")
|
15
15
|
end
|
16
|
+
|
17
|
+
def test_does_not_ignore_empty_string
|
18
|
+
i = ScoutApm::IgnoredUris.new(["", "/admin"])
|
19
|
+
assert_equal false, i.ignore?("/users/2/health")
|
20
|
+
assert_equal true, i.ignore?("/admin/dashboard")
|
21
|
+
end
|
16
22
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
if (ENV["SCOUT_TEST_FEATURES"] || "").include?("typhoeus")
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
require 'scout_apm/instruments/typhoeus'
|
5
|
+
|
6
|
+
require 'typhoeus'
|
7
|
+
|
8
|
+
class TyphoeusTest < Minitest::Test
|
9
|
+
def setup
|
10
|
+
@context = ScoutApm::AgentContext.new
|
11
|
+
@recorder = FakeRecorder.new
|
12
|
+
ScoutApm::Agent.instance.context.recorder = @recorder
|
13
|
+
ScoutApm::Instruments::Typhoeus.new(@context).install
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_instruments_typhoeus_hydra
|
17
|
+
hydra = Typhoeus::Hydra.new
|
18
|
+
2.times.map{ hydra.queue(Typhoeus::Request.new("example.com", followlocation: true)) }
|
19
|
+
|
20
|
+
assert_equal "2 requests", hydra.scout_desc
|
21
|
+
|
22
|
+
hydra.run
|
23
|
+
assert_equal "0 requests", hydra.scout_desc
|
24
|
+
assert_recorded(@recorder, "HTTP", "Hydra", "2 requests")
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_instruments_typhoeus
|
28
|
+
Typhoeus.get("example.com", followlocation: true)
|
29
|
+
assert_recorded(@recorder, "HTTP", "get", "example.com")
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def assert_recorded(recorder, type, name, desc = nil)
|
35
|
+
req = recorder.requests.first
|
36
|
+
assert req, "recorder recorded no layers"
|
37
|
+
assert_equal type, req.root_layer.type
|
38
|
+
assert_equal name, req.root_layer.name
|
39
|
+
assert_equal desc, req.root_layer.desc if !desc.nil?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
File without changes
|
File without changes
|
@@ -8,8 +8,11 @@ class TestRemoteServer < Minitest::Test
|
|
8
8
|
logger_io = StringIO.new
|
9
9
|
server = ScoutApm::Remote::Server.new(bind, port, router, Logger.new(logger_io))
|
10
10
|
|
11
|
+
# Cannot test this if we can't require webrick. Ruby 3 stopped including by default
|
12
|
+
skip unless server.require_webrick
|
13
|
+
|
11
14
|
server.start
|
12
|
-
sleep 0.
|
15
|
+
sleep 0.05 # Let the server finish starting. The assert should instead allow a time
|
13
16
|
assert server.running?
|
14
17
|
end
|
15
18
|
end
|