scout_apm 2.2.0.pre3 → 2.3.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/CHANGELOG.markdown +147 -2
- data/Guardfile +43 -0
- data/Rakefile +2 -2
- data/ext/allocations/allocations.c +6 -0
- data/ext/allocations/extconf.rb +1 -0
- data/ext/rusage/README.md +26 -0
- data/ext/rusage/extconf.rb +5 -0
- data/ext/rusage/rusage.c +52 -0
- data/lib/scout_apm.rb +28 -15
- data/lib/scout_apm/agent.rb +89 -37
- data/lib/scout_apm/agent/logging.rb +6 -1
- data/lib/scout_apm/agent/reporting.rb +9 -6
- data/lib/scout_apm/app_server_load.rb +21 -10
- data/lib/scout_apm/attribute_arranger.rb +6 -3
- data/lib/scout_apm/background_job_integrations/delayed_job.rb +71 -1
- data/lib/scout_apm/background_job_integrations/resque.rb +85 -0
- data/lib/scout_apm/background_job_integrations/sidekiq.rb +22 -20
- data/lib/scout_apm/background_recorder.rb +43 -0
- data/lib/scout_apm/background_worker.rb +19 -15
- data/lib/scout_apm/config.rb +138 -28
- data/lib/scout_apm/db_query_metric_set.rb +80 -0
- data/lib/scout_apm/db_query_metric_stats.rb +102 -0
- data/lib/scout_apm/debug.rb +37 -0
- data/lib/scout_apm/environment.rb +22 -15
- data/lib/scout_apm/git_revision.rb +51 -0
- data/lib/scout_apm/histogram.rb +11 -2
- data/lib/scout_apm/instant/assets/xmlhttp_instrumentation.html +2 -2
- data/lib/scout_apm/instant/middleware.rb +196 -54
- data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +89 -68
- data/lib/scout_apm/instruments/action_view.rb +49 -0
- data/lib/scout_apm/instruments/active_record.rb +127 -3
- data/lib/scout_apm/instruments/grape.rb +4 -3
- data/lib/scout_apm/instruments/middleware_detailed.rb +4 -6
- data/lib/scout_apm/instruments/mongoid.rb +24 -3
- data/lib/scout_apm/instruments/net_http.rb +7 -2
- data/lib/scout_apm/instruments/percentile_sampler.rb +36 -19
- data/lib/scout_apm/instruments/process/process_cpu.rb +3 -2
- data/lib/scout_apm/instruments/process/process_memory.rb +3 -3
- data/lib/scout_apm/instruments/resque.rb +40 -0
- data/lib/scout_apm/layaway.rb +67 -28
- data/lib/scout_apm/layer.rb +19 -59
- data/lib/scout_apm/layer_children_set.rb +77 -0
- data/lib/scout_apm/layer_converters/allocation_metric_converter.rb +5 -6
- data/lib/scout_apm/layer_converters/converter_base.rb +201 -14
- data/lib/scout_apm/layer_converters/database_converter.rb +55 -0
- data/lib/scout_apm/layer_converters/depth_first_walker.rb +22 -10
- data/lib/scout_apm/layer_converters/error_converter.rb +5 -7
- data/lib/scout_apm/layer_converters/find_layer_by_type.rb +34 -0
- data/lib/scout_apm/layer_converters/histograms.rb +14 -0
- data/lib/scout_apm/layer_converters/job_converter.rb +36 -50
- data/lib/scout_apm/layer_converters/metric_converter.rb +17 -19
- data/lib/scout_apm/layer_converters/request_queue_time_converter.rb +10 -12
- data/lib/scout_apm/layer_converters/slow_job_converter.rb +41 -115
- data/lib/scout_apm/layer_converters/slow_request_converter.rb +33 -117
- data/lib/scout_apm/limited_layer.rb +126 -0
- data/lib/scout_apm/metric_meta.rb +0 -5
- data/lib/scout_apm/metric_set.rb +9 -1
- data/lib/scout_apm/metric_stats.rb +7 -8
- data/lib/scout_apm/rack.rb +26 -0
- data/lib/scout_apm/remote/message.rb +23 -0
- data/lib/scout_apm/remote/recorder.rb +57 -0
- data/lib/scout_apm/remote/router.rb +49 -0
- data/lib/scout_apm/remote/server.rb +58 -0
- data/lib/scout_apm/reporter.rb +51 -15
- data/lib/scout_apm/request_histograms.rb +4 -0
- data/lib/scout_apm/request_manager.rb +2 -1
- data/lib/scout_apm/scored_item_set.rb +7 -0
- data/lib/scout_apm/serializers/db_query_serializer_to_json.rb +15 -0
- data/lib/scout_apm/serializers/histograms_serializer_to_json.rb +21 -0
- data/lib/scout_apm/serializers/payload_serializer.rb +10 -3
- data/lib/scout_apm/serializers/payload_serializer_to_json.rb +6 -6
- data/lib/scout_apm/serializers/slow_jobs_serializer_to_json.rb +2 -1
- data/lib/scout_apm/server_integrations/puma.rb +5 -2
- data/lib/scout_apm/slow_job_policy.rb +1 -10
- data/lib/scout_apm/slow_job_record.rb +6 -1
- data/lib/scout_apm/slow_request_policy.rb +1 -10
- data/lib/scout_apm/slow_transaction.rb +20 -2
- data/lib/scout_apm/store.rb +66 -12
- data/lib/scout_apm/synchronous_recorder.rb +26 -0
- data/lib/scout_apm/tracked_request.rb +136 -71
- data/lib/scout_apm/utils/active_record_metric_name.rb +8 -4
- data/lib/scout_apm/utils/backtrace_parser.rb +3 -3
- data/lib/scout_apm/utils/gzip_helper.rb +24 -0
- data/lib/scout_apm/utils/numbers.rb +14 -0
- data/lib/scout_apm/utils/scm.rb +14 -0
- data/lib/scout_apm/version.rb +1 -1
- data/scout_apm.gemspec +5 -4
- data/test/test_helper.rb +18 -0
- data/test/unit/config_test.rb +59 -8
- data/test/unit/db_query_metric_set_test.rb +56 -0
- data/test/unit/db_query_metric_stats_test.rb +113 -0
- data/test/unit/git_revision_test.rb +15 -0
- data/test/unit/histogram_test.rb +14 -0
- data/test/unit/instruments/net_http_test.rb +21 -0
- data/test/unit/instruments/percentile_sampler_test.rb +137 -0
- data/test/unit/layaway_test.rb +20 -0
- data/test/unit/layer_children_set_test.rb +88 -0
- data/test/unit/layer_converters/depth_first_walker_test.rb +66 -0
- data/test/unit/layer_converters/metric_converter_test.rb +22 -0
- data/test/unit/layer_converters/stubs.rb +33 -0
- data/test/unit/limited_layer_test.rb +53 -0
- data/test/unit/remote/test_message.rb +13 -0
- data/test/unit/remote/test_router.rb +33 -0
- data/test/unit/remote/test_server.rb +15 -0
- data/test/unit/serializers/payload_serializer_test.rb +3 -12
- data/test/unit/store_test.rb +66 -0
- data/test/unit/test_tracked_request.rb +87 -0
- data/test/unit/utils/active_record_metric_name_test.rb +8 -0
- data/test/unit/utils/backtrace_parser_test.rb +5 -0
- data/test/unit/utils/numbers_test.rb +15 -0
- data/test/unit/utils/scm.rb +17 -0
- metadata +125 -30
- data/ext/stacks/extconf.rb +0 -37
- data/ext/stacks/scout_atomics.h +0 -86
- data/ext/stacks/stacks.c +0 -811
- data/lib/scout_apm/capacity.rb +0 -57
- data/lib/scout_apm/deploy_integrations/capistrano_2.cap +0 -12
- data/lib/scout_apm/deploy_integrations/capistrano_2.rb +0 -83
- data/lib/scout_apm/deploy_integrations/capistrano_3.cap +0 -12
- data/lib/scout_apm/deploy_integrations/capistrano_3.rb +0 -88
- data/lib/scout_apm/instruments/delayed_job.rb +0 -57
- data/lib/scout_apm/serializers/deploy_serializer.rb +0 -16
- data/lib/scout_apm/trace_compactor.rb +0 -312
- data/lib/scout_apm/utils/fake_stacks.rb +0 -87
- data/tester.rb +0 -53
@@ -0,0 +1,80 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
class DbQueryMetricSet
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
attr_reader :metrics # the raw metrics. You probably want #metrics_to_report
|
6
|
+
attr_reader :config # A ScoutApm::Config instance
|
7
|
+
|
8
|
+
def initialize(config=ScoutApm::Agent.instance.config)
|
9
|
+
# A hash of DbQueryMetricStats values, keyed by DbQueryMetricStats.key
|
10
|
+
@metrics = Hash.new
|
11
|
+
@config = config
|
12
|
+
end
|
13
|
+
|
14
|
+
def each
|
15
|
+
metrics.each do |_key, db_query_metric_stat|
|
16
|
+
yield db_query_metric_stat
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Looks up a DbQueryMetricStats instance in the +@metrics+ hash. Sets the value to +other+ if no key
|
21
|
+
# Returns a DbQueryMetricStats instance
|
22
|
+
def lookup(other)
|
23
|
+
metrics[other.key] ||= other
|
24
|
+
end
|
25
|
+
|
26
|
+
# Take another set, and merge it with this one
|
27
|
+
def combine!(other)
|
28
|
+
other.each do |metric|
|
29
|
+
self << metric
|
30
|
+
end
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
# Add a single DbQueryMetricStats object to this set.
|
35
|
+
#
|
36
|
+
# Looks up an existing one under this key and merges, or just saves a new
|
37
|
+
# one under the key
|
38
|
+
def <<(stat)
|
39
|
+
existing_stat = metrics[stat.key]
|
40
|
+
if existing_stat
|
41
|
+
existing_stat.combine!(stat)
|
42
|
+
elsif at_limit?
|
43
|
+
# We're full up, can't add any more.
|
44
|
+
# Should I log this? It may get super noisy?
|
45
|
+
else
|
46
|
+
metrics[stat.key] = stat
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def increment_transaction_count!
|
51
|
+
metrics.each do |_key, db_query_metric_stat|
|
52
|
+
db_query_metric_stat.increment_transaction_count!
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def metrics_to_report
|
57
|
+
report_limit = config.value('database_metric_report_limit')
|
58
|
+
if metrics.size > report_limit
|
59
|
+
metrics.
|
60
|
+
values.
|
61
|
+
sort_by {|stat| stat.call_time }.
|
62
|
+
reverse.
|
63
|
+
take(report_limit)
|
64
|
+
else
|
65
|
+
metrics.values
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def inspect
|
70
|
+
metrics.map {|key, metric|
|
71
|
+
"#{key.inspect} - Count: #{metric.call_count}, Total Time: #{"%.2f" % metric.call_time}"
|
72
|
+
}.join("\n")
|
73
|
+
end
|
74
|
+
|
75
|
+
def at_limit?
|
76
|
+
@limit ||= config.value('database_metric_limit')
|
77
|
+
metrics.size >= @limit
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
class DbQueryMetricStats
|
3
|
+
|
4
|
+
DEFAULT_HISTOGRAM_SIZE = 50
|
5
|
+
|
6
|
+
attr_reader :model_name
|
7
|
+
attr_reader :operation
|
8
|
+
attr_reader :scope
|
9
|
+
|
10
|
+
attr_reader :transaction_count
|
11
|
+
|
12
|
+
attr_reader :call_count
|
13
|
+
attr_reader :call_time
|
14
|
+
attr_reader :rows_returned
|
15
|
+
|
16
|
+
attr_reader :min_call_time
|
17
|
+
attr_reader :max_call_time
|
18
|
+
|
19
|
+
attr_reader :min_rows_returned
|
20
|
+
attr_reader :max_rows_returned
|
21
|
+
|
22
|
+
attr_reader :histogram
|
23
|
+
|
24
|
+
def initialize(model_name, operation, scope, call_count, call_time, rows_returned)
|
25
|
+
@model_name = model_name
|
26
|
+
@operation = operation
|
27
|
+
|
28
|
+
@call_count = call_count
|
29
|
+
|
30
|
+
@call_time = call_time
|
31
|
+
@min_call_time = call_time
|
32
|
+
@max_call_time = call_time
|
33
|
+
|
34
|
+
@rows_returned = rows_returned
|
35
|
+
@min_rows_returned = rows_returned
|
36
|
+
@max_rows_returned = rows_returned
|
37
|
+
|
38
|
+
# Should we have a histogram for timing, and one for rows_returned?
|
39
|
+
# This histogram is for call_time
|
40
|
+
@histogram = NumericHistogram.new(DEFAULT_HISTOGRAM_SIZE)
|
41
|
+
@histogram.add(call_time)
|
42
|
+
|
43
|
+
@transaction_count = 0
|
44
|
+
|
45
|
+
@scope = scope
|
46
|
+
end
|
47
|
+
|
48
|
+
# Merge data in this scope. Used in DbQueryMetricSet
|
49
|
+
def key
|
50
|
+
@key ||= [model_name, operation, scope]
|
51
|
+
end
|
52
|
+
|
53
|
+
# Combine data from another DbQueryMetricStats into +self+. Modifies and returns +self+
|
54
|
+
def combine!(other)
|
55
|
+
return self if other == self
|
56
|
+
|
57
|
+
@transaction_count += other.transaction_count
|
58
|
+
@call_count += other.call_count
|
59
|
+
@rows_returned += other.rows_returned
|
60
|
+
@call_time += other.call_time
|
61
|
+
|
62
|
+
@min_call_time = other.min_call_time if @min_call_time.zero? or other.min_call_time < @min_call_time
|
63
|
+
@max_call_time = other.max_call_time if other.max_call_time > @max_call_time
|
64
|
+
|
65
|
+
@min_rows_returned = other.min_rows_returned if @min_rows_returned.zero? or other.min_rows_returned < @min_rows_returned
|
66
|
+
@max_rows_returned = other.max_rows_returned if other.max_rows_returned > @max_rows_returned
|
67
|
+
|
68
|
+
@histogram.combine!(other.histogram)
|
69
|
+
self
|
70
|
+
end
|
71
|
+
|
72
|
+
def as_json
|
73
|
+
json_attributes = [
|
74
|
+
:model_name,
|
75
|
+
:operation,
|
76
|
+
:scope,
|
77
|
+
|
78
|
+
:transaction_count,
|
79
|
+
:call_count,
|
80
|
+
|
81
|
+
:histogram,
|
82
|
+
:call_time,
|
83
|
+
:max_call_time,
|
84
|
+
:min_call_time,
|
85
|
+
|
86
|
+
:max_rows_returned,
|
87
|
+
:min_rows_returned,
|
88
|
+
:rows_returned,
|
89
|
+
]
|
90
|
+
|
91
|
+
ScoutApm::AttributeArranger.call(self, json_attributes)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Called by the Set on each DbQueryMetricStats object that it holds, only
|
95
|
+
# once during the recording of a transaction.
|
96
|
+
#
|
97
|
+
# Don't call elsewhere, and don't set to 1 in the initializer.
|
98
|
+
def increment_transaction_count!
|
99
|
+
@transaction_count += 1
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
class Debug
|
3
|
+
# see self.instance
|
4
|
+
@@instance = nil
|
5
|
+
|
6
|
+
def self.instance
|
7
|
+
@@instance ||= new
|
8
|
+
end
|
9
|
+
|
10
|
+
def register_periodic_hook(&hook)
|
11
|
+
@periodic_hooks << hook
|
12
|
+
end
|
13
|
+
|
14
|
+
def call_periodic_hooks
|
15
|
+
@periodic_hooks.each do |hook|
|
16
|
+
begin
|
17
|
+
hook.call
|
18
|
+
rescue => e
|
19
|
+
logger.info("Periodic debug hook failed to run: #{e}\n\t#{e.backtrace.join("\n\t")}")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
rescue
|
23
|
+
# Something went super wrong for the inner rescue to not catch this. Just
|
24
|
+
# swallow the error. The debug tool should never crash the app.
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def initialize
|
30
|
+
@periodic_hooks = []
|
31
|
+
end
|
32
|
+
|
33
|
+
def logger
|
34
|
+
ScoutApm::Agent.instance.logger
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -24,8 +24,9 @@ module ScoutApm
|
|
24
24
|
]
|
25
25
|
|
26
26
|
BACKGROUND_JOB_INTEGRATIONS = [
|
27
|
+
ScoutApm::BackgroundJobIntegrations::Resque.new,
|
27
28
|
ScoutApm::BackgroundJobIntegrations::Sidekiq.new,
|
28
|
-
|
29
|
+
ScoutApm::BackgroundJobIntegrations::DelayedJob.new,
|
29
30
|
]
|
30
31
|
|
31
32
|
FRAMEWORK_INTEGRATIONS = [
|
@@ -41,13 +42,8 @@ module ScoutApm
|
|
41
42
|
ScoutApm::PlatformIntegrations::Server.new,
|
42
43
|
]
|
43
44
|
|
44
|
-
DEPLOY_INTEGRATIONS = [
|
45
|
-
ScoutApm::DeployIntegrations::Capistrano3.new(STDOUT_LOGGER),
|
46
|
-
# ScoutApm::DeployIntegrations::Capistrano2.new(STDOUT_LOGGER),
|
47
|
-
]
|
48
|
-
|
49
45
|
def env
|
50
|
-
@env ||=
|
46
|
+
@env ||= framework_integration.env
|
51
47
|
end
|
52
48
|
|
53
49
|
def framework
|
@@ -63,7 +59,9 @@ module ScoutApm
|
|
63
59
|
end
|
64
60
|
|
65
61
|
def application_name
|
66
|
-
Agent.instance.config.value("name") ||
|
62
|
+
Agent.instance.config.value("name") ||
|
63
|
+
framework_integration.application_name ||
|
64
|
+
"App"
|
67
65
|
end
|
68
66
|
|
69
67
|
def database_engine
|
@@ -87,8 +85,16 @@ module ScoutApm
|
|
87
85
|
end
|
88
86
|
end
|
89
87
|
|
88
|
+
def scm_subdirectory
|
89
|
+
@scm_subdirectory ||= if Agent.instance.config.value('scm_subdirectory').empty?
|
90
|
+
''
|
91
|
+
else
|
92
|
+
Agent.instance.config.value('scm_subdirectory').sub(/^\//, '') # Trim any leading slash
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
90
96
|
def root
|
91
|
-
@root ||=
|
97
|
+
@root ||= framework_root
|
92
98
|
end
|
93
99
|
|
94
100
|
def framework_root
|
@@ -110,6 +116,10 @@ module ScoutApm
|
|
110
116
|
@hostname ||= Agent.instance.config.value("hostname") || platform_integration.hostname
|
111
117
|
end
|
112
118
|
|
119
|
+
def git_revision
|
120
|
+
@git_revision ||= ScoutApm::GitRevision.new
|
121
|
+
end
|
122
|
+
|
113
123
|
# Returns the whole integration object
|
114
124
|
# This needs to be improved. Frequently, multiple app servers gem are present and which
|
115
125
|
# ever is checked first becomes the designated app server.
|
@@ -143,12 +153,9 @@ module ScoutApm
|
|
143
153
|
background_job_integration && background_job_integration.name
|
144
154
|
end
|
145
155
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
def deploy_integration?
|
151
|
-
!@deploy_integration.nil?
|
156
|
+
# If both stdin & stdout are interactive and the Rails::Console constant is defined
|
157
|
+
def interactive?
|
158
|
+
defined?(::Rails::Console) && $stdout.isatty && $stdin.isatty
|
152
159
|
end
|
153
160
|
|
154
161
|
### ruby checks
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
class GitRevision
|
3
|
+
|
4
|
+
attr_accessor :sha
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@sha = detect
|
8
|
+
ScoutApm::Agent.instance.logger.debug "Detected Git Revision [#{@sha}]"
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def detect
|
14
|
+
detect_from_env_var ||
|
15
|
+
detect_from_heroku ||
|
16
|
+
detect_from_capistrano ||
|
17
|
+
detect_from_git
|
18
|
+
end
|
19
|
+
|
20
|
+
def detect_from_heroku
|
21
|
+
ENV['HEROKU_SLUG_COMMIT']
|
22
|
+
end
|
23
|
+
|
24
|
+
def detect_from_env_var
|
25
|
+
ENV['SCOUT_REVISION_SHA']
|
26
|
+
end
|
27
|
+
|
28
|
+
def detect_from_capistrano
|
29
|
+
version = File.read(File.join(app_root, 'REVISION')).strip
|
30
|
+
# Capistrano 3.0 - 3.1.x
|
31
|
+
version || File.open(File.join(app_root, '..', 'revisions.log')).to_a.last.strip.sub(/.*as release ([0-9]+).*/, '\1')
|
32
|
+
rescue
|
33
|
+
ScoutApm::Agent.instance.logger.debug "Unable to detect Git Revision from Capistrano: #{$!.message}"
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def detect_from_git
|
38
|
+
if File.directory?(".git")
|
39
|
+
`git rev-parse --short HEAD`.strip
|
40
|
+
end
|
41
|
+
rescue
|
42
|
+
ScoutApm::Agent.instance.logger.debug "Unable to detect Git Revision from Git: #{$!.message}"
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
|
46
|
+
def app_root
|
47
|
+
ScoutApm::Environment.instance.root
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
data/lib/scout_apm/histogram.rb
CHANGED
@@ -90,7 +90,11 @@ module ScoutApm
|
|
90
90
|
def combine!(other)
|
91
91
|
mutex.synchronize do
|
92
92
|
other.mutex.synchronize do
|
93
|
-
@bins = (other.bins + @bins).
|
93
|
+
@bins = (other.bins + @bins).
|
94
|
+
group_by {|b| b.value }.
|
95
|
+
map {|val, bs| [val, bs.inject(0) {|sum, b| sum + b.count }] }.
|
96
|
+
map {|val, sum| HistogramBin.new(val,sum) }.
|
97
|
+
sort_by { |b| b.value }
|
94
98
|
@total += other.total
|
95
99
|
trim
|
96
100
|
self
|
@@ -100,7 +104,12 @@ module ScoutApm
|
|
100
104
|
|
101
105
|
def as_json
|
102
106
|
mutex.synchronize do
|
103
|
-
bins.map{|b|
|
107
|
+
bins.map{ |b|
|
108
|
+
[
|
109
|
+
ScoutApm::Utils::Numbers.round(b.value, 4),
|
110
|
+
b.count
|
111
|
+
]
|
112
|
+
}
|
104
113
|
end
|
105
114
|
end
|
106
115
|
|
@@ -3,7 +3,7 @@
|
|
3
3
|
(function(){var open=window.XMLHttpRequest.prototype.open;var send=window.XMLHttpRequest.prototype.send;function openReplacement(method,url,async,user,password){this._url=url;return open.apply(this,arguments);}
|
4
4
|
function sendReplacement(data){if(this.onload){this._onload=this.onload;}
|
5
5
|
this.onload=onLoadReplacement;return send.apply(this,arguments);}
|
6
|
-
function onLoadReplacement(){if(this._url.startsWith(window.location.protocol+"//"+window.location.host)||!this._url.startsWith("http")){try{traceText=this.getResponseHeader("X-scoutapminstant");if(traceText){setTimeout(function(){window.scoutInstant("addTrace",traceText)},0);}}catch(e){console.debug("Problem getting X-scoutapminstant header");}}
|
6
|
+
function onLoadReplacement(){if(this._url.startsWith(window.location.protocol+"//"+window.location.host)||!this._url.startsWith("http")){try{var traceText=this.getResponseHeader("X-scoutapminstant");if(traceText){setTimeout(function(){window.scoutInstant("addTrace",traceText)},0);}}catch(e){console.debug("Problem getting X-scoutapminstant header");}}
|
7
7
|
if(this._onload){return this._onload.apply(this,arguments);}}
|
8
8
|
window.XMLHttpRequest.prototype.open=openReplacement;window.XMLHttpRequest.prototype.send=sendReplacement;})();
|
9
|
-
</script>
|
9
|
+
</script>
|
@@ -5,6 +5,11 @@ module ScoutApm
|
|
5
5
|
class Page
|
6
6
|
def initialize(html)
|
7
7
|
@html = html
|
8
|
+
|
9
|
+
if html.is_a?(Array)
|
10
|
+
@html = html.inject("") { |memo, str| memo + str }
|
11
|
+
end
|
12
|
+
|
8
13
|
@to_add_to_head = []
|
9
14
|
@to_add_to_body = []
|
10
15
|
end
|
@@ -45,63 +50,200 @@ module ScoutApm
|
|
45
50
|
end
|
46
51
|
|
47
52
|
def call(env)
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
53
|
+
rack_response = @app.call(env)
|
54
|
+
begin
|
55
|
+
DevTraceResponseManipulator.new(env, rack_response).call
|
56
|
+
rescue Exception => e
|
57
|
+
# If anything went wrong at all, just bail out and return the unmodified response.
|
58
|
+
ScoutApm::Agent.instance.logger.debug("DevTrace: Raised an exception: #{e.message}, #{e.backtrace}")
|
59
|
+
rack_response
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class DevTraceResponseManipulator
|
65
|
+
attr_reader :rack_response
|
66
|
+
attr_reader :rack_status, :rack_headers, :rack_body
|
67
|
+
attr_reader :env
|
68
|
+
|
69
|
+
def initialize(env, rack_response)
|
70
|
+
@env = env
|
71
|
+
@rack_response = rack_response
|
72
|
+
|
73
|
+
@rack_status = rack_response[0]
|
74
|
+
@rack_headers = rack_response[1]
|
75
|
+
@rack_body = rack_response[2]
|
76
|
+
end
|
77
|
+
|
78
|
+
def call
|
79
|
+
return rack_response unless preconditions_met?
|
80
|
+
|
81
|
+
if ajax_request?
|
82
|
+
ScoutApm::Agent.instance.logger.debug("DevTrace: in middleware, dev_trace is active, and response has a body. This is either AJAX or JSON. Path=#{path}; ContentType=#{content_type}")
|
83
|
+
adjust_ajax_header
|
84
|
+
else
|
85
|
+
adjust_html_response
|
86
|
+
end
|
87
|
+
|
88
|
+
rebuild_rack_response
|
89
|
+
end
|
90
|
+
|
91
|
+
###########################
|
92
|
+
# Precondition checking #
|
93
|
+
###########################
|
94
|
+
|
95
|
+
def preconditions_met?
|
96
|
+
if dev_trace_disabled?
|
97
|
+
logger.debug("DevTrace: isn't activated via config. Try: SCOUT_DEV_TRACE=true rails server")
|
98
|
+
return false
|
99
|
+
end
|
100
|
+
|
101
|
+
# Don't attempt to instrument assets.
|
102
|
+
# Don't log this case, since it would be very noisy
|
103
|
+
logger.debug("DevTrace: dev asset ignored") and return false if development_asset?
|
104
|
+
|
105
|
+
# If we didn't have a tracked_request object, or we explicitly ignored
|
106
|
+
# this request, don't do any work.
|
107
|
+
logger.debug("DevTrace: no tracked request") and return false if tracked_request.nil? || tracked_request.ignoring_request?
|
108
|
+
|
109
|
+
# If we didn't get a trace, we can't show a trace...
|
110
|
+
if trace.nil?
|
111
|
+
logger.debug("DevTrace: in middleware, dev_trace is active, and response has a body, but no trace was found. Path=#{path}; ContentType=#{content_type}")
|
112
|
+
return false
|
113
|
+
end
|
114
|
+
|
115
|
+
true
|
116
|
+
end
|
117
|
+
|
118
|
+
def dev_trace_disabled?
|
119
|
+
! ScoutApm::Agent.instance.config.value('dev_trace')
|
120
|
+
end
|
121
|
+
|
122
|
+
########################
|
123
|
+
# Response Injection #
|
124
|
+
########################
|
125
|
+
|
126
|
+
def rebuild_rack_response
|
127
|
+
[rack_status, rack_headers, rack_body]
|
128
|
+
end
|
129
|
+
|
130
|
+
def adjust_ajax_header
|
131
|
+
rack_headers['X-scoutapminstant'] = payload
|
132
|
+
end
|
133
|
+
|
134
|
+
def adjust_html_response
|
135
|
+
case true
|
136
|
+
when older_rails_response? then adjust_older_rails_response
|
137
|
+
when newer_rails_response? then adjust_newer_rails_response
|
138
|
+
when rack_proxy_response? then adjust_rack_proxy_response
|
100
139
|
else
|
101
|
-
|
102
|
-
|
140
|
+
# No action taken, we only adjust if we know exactly what we have.
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def older_rails_response?
|
145
|
+
if defined?(ActionDispatch::Response)
|
146
|
+
return true if rack_body.is_a?(ActionDispatch::Response)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def newer_rails_response?
|
151
|
+
if defined?(ActionDispatch::Response::RackBody)
|
152
|
+
return true if rack_body.is_a?(ActionDispatch::Response::RackBody)
|
103
153
|
end
|
104
154
|
end
|
155
|
+
|
156
|
+
def rack_proxy_response?
|
157
|
+
rack_body.is_a?(Rack::BodyProxy)
|
158
|
+
end
|
159
|
+
|
160
|
+
def adjust_older_rails_response
|
161
|
+
logger.debug("DevTrace: in middleware, dev_trace is active, and response has a (older) body. This appears to be an HTML page and an ActionDispatch::Response. Path=#{path}; ContentType=#{content_type}")
|
162
|
+
rack_body.body = [ html_manipulator.res ]
|
163
|
+
end
|
164
|
+
|
165
|
+
# Preserve the ActionDispatch::Response object we're working with
|
166
|
+
def adjust_newer_rails_response
|
167
|
+
logger.debug("DevTrace: in middleware, dev_trace is active, and response has a (newer) body. This appears to be an HTML page and an ActionDispatch::Response. Path=#{path}; ContentType=#{content_type}")
|
168
|
+
@rack_body = [ html_manipulator.res ]
|
169
|
+
end
|
170
|
+
|
171
|
+
def adjust_rack_proxy_response
|
172
|
+
logger.debug("DevTrace: in middleware, dev_trace is active, and response has a body. This appears to be an HTML page and an Rack::BodyProxy. Path=#{path}; ContentType=#{content_type}")
|
173
|
+
@rack_body = [ html_manipulator.res ]
|
174
|
+
@rack_headers.delete("Content-Length")
|
175
|
+
end
|
176
|
+
|
177
|
+
def html_manipulator
|
178
|
+
@html_manipulator ||=
|
179
|
+
begin
|
180
|
+
page = ScoutApm::Instant::Page.new(rack_body.body)
|
181
|
+
|
182
|
+
# This monkey-patches XMLHttpRequest. It could possibly be part of the main scout_instant.js too. Putting it here so it runs as soon as possible.
|
183
|
+
page.add_to_head(ScoutApm::Instant::Util.read_asset("xmlhttp_instrumentation.html"))
|
184
|
+
|
185
|
+
# Add a link to CSS, then JS
|
186
|
+
page.add_to_head("<link href='#{apm_host}/instant/scout_instant.css?cachebust=#{Time.now.to_i}' media='all' rel='stylesheet' />")
|
187
|
+
page.add_to_body("<script src='#{apm_host}/instant/scout_instant.js?cachebust=#{Time.now.to_i}'></script>")
|
188
|
+
page.add_to_body("<script>var scoutInstantPageTrace=#{payload};window.scoutInstant=window.scoutInstant('#{apm_host}', scoutInstantPageTrace)</script>")
|
189
|
+
|
190
|
+
page
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def ajax_request?
|
195
|
+
env['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' || content_type.include?("application/json")
|
196
|
+
end
|
197
|
+
|
198
|
+
def development_asset?
|
199
|
+
!rack_body.respond_to?(:body)
|
200
|
+
end
|
201
|
+
|
202
|
+
def path
|
203
|
+
env['PATH_INFO']
|
204
|
+
end
|
205
|
+
|
206
|
+
def content_type
|
207
|
+
rack_headers['Content-Type']
|
208
|
+
end
|
209
|
+
|
210
|
+
##############################
|
211
|
+
# APM Helpers & Shorthands #
|
212
|
+
##############################
|
213
|
+
|
214
|
+
def logger
|
215
|
+
ScoutApm::Agent.instance.logger
|
216
|
+
end
|
217
|
+
|
218
|
+
def tracked_request
|
219
|
+
@tracked_request ||= ScoutApm::RequestManager.lookup
|
220
|
+
end
|
221
|
+
|
222
|
+
def apm_host
|
223
|
+
ScoutApm::Agent.instance.config.value("direct_host")
|
224
|
+
end
|
225
|
+
|
226
|
+
def trace
|
227
|
+
@trace ||= LayerConverters::SlowRequestConverter.new(tracked_request).call
|
228
|
+
end
|
229
|
+
|
230
|
+
def payload
|
231
|
+
@payload ||=
|
232
|
+
begin
|
233
|
+
metadata = {
|
234
|
+
:app_root => ScoutApm::Environment.instance.root.to_s,
|
235
|
+
:unique_id => env['action_dispatch.request_id'], # note, this is a different unique_id than what "normal" payloads use
|
236
|
+
:agent_version => ScoutApm::VERSION,
|
237
|
+
:platform => "ruby",
|
238
|
+
}
|
239
|
+
|
240
|
+
hash = ScoutApm::Serializers::PayloadSerializerToJson.
|
241
|
+
rearrange_slow_transaction(trace).
|
242
|
+
merge!(:metadata => metadata)
|
243
|
+
ScoutApm::Serializers::PayloadSerializerToJson.jsonify_hash(hash)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
105
247
|
end
|
106
248
|
end
|
107
249
|
end
|