scout_apm 2.5.1 → 5.3.2
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 +68 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +5 -5
- data/CHANGELOG.markdown +176 -3
- data/Gemfile +1 -7
- data/LICENSE.md +21 -28
- data/gems/README.md +28 -0
- data/gems/instruments.gemfile +6 -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/gems/sidekiq.gemfile +4 -0
- data/gems/typhoeus.gemfile +3 -0
- data/lib/scout_apm/agent/preconditions.rb +3 -3
- data/lib/scout_apm/agent.rb +22 -0
- data/lib/scout_apm/agent_context.rb +21 -2
- data/lib/scout_apm/app_server_load.rb +7 -2
- 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 +174 -0
- data/lib/scout_apm/auto_instrument.rb +5 -0
- data/lib/scout_apm/background_job_integrations/delayed_job.rb +1 -1
- data/lib/scout_apm/background_job_integrations/faktory.rb +103 -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/shoryuken.rb +2 -0
- data/lib/scout_apm/background_job_integrations/sidekiq.rb +15 -10
- data/lib/scout_apm/background_job_integrations/sneakers.rb +11 -11
- data/lib/scout_apm/config.rb +54 -6
- data/lib/scout_apm/detailed_trace.rb +3 -2
- data/lib/scout_apm/environment.rb +18 -1
- data/lib/scout_apm/error.rb +27 -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/error_service.rb +34 -0
- data/lib/scout_apm/exceptions.rb +12 -0
- data/lib/scout_apm/extensions/transaction_callback_payload.rb +1 -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 +7 -2
- 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 +4 -1
- data/lib/scout_apm/instrument_manager.rb +22 -1
- data/lib/scout_apm/instruments/action_controller_rails_2.rb +1 -1
- data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +53 -29
- data/lib/scout_apm/instruments/action_view.rb +30 -9
- data/lib/scout_apm/instruments/active_record.rb +69 -19
- data/lib/scout_apm/instruments/elasticsearch.rb +93 -42
- data/lib/scout_apm/instruments/grape.rb +1 -1
- data/lib/scout_apm/instruments/http.rb +68 -0
- data/lib/scout_apm/instruments/http_client.rb +33 -14
- data/lib/scout_apm/instruments/influxdb.rb +2 -2
- data/lib/scout_apm/instruments/memcached.rb +58 -0
- data/lib/scout_apm/instruments/middleware_detailed.rb +1 -1
- data/lib/scout_apm/instruments/middleware_summary.rb +1 -1
- data/lib/scout_apm/instruments/mongoid.rb +10 -5
- data/lib/scout_apm/instruments/moped.rb +44 -19
- data/lib/scout_apm/instruments/net_http.rb +51 -16
- data/lib/scout_apm/instruments/rails_router.rb +1 -1
- data/lib/scout_apm/instruments/redis.rb +27 -12
- data/lib/scout_apm/instruments/redis5.rb +59 -0
- data/lib/scout_apm/instruments/sinatra.rb +3 -1
- data/lib/scout_apm/instruments/typhoeus.rb +90 -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 +5 -2
- data/lib/scout_apm/layer_children_set.rb +9 -8
- 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/layer_converters/trace_converter.rb +7 -4
- data/lib/scout_apm/logger.rb +5 -1
- 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/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 +10 -3
- 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/store.rb +31 -1
- data/lib/scout_apm/tracer.rb +2 -2
- data/lib/scout_apm/tracked_request.rb +35 -4
- 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 +47 -7
- data/lib/scout_apm/version.rb +1 -1
- data/lib/scout_apm.rb +46 -1
- data/scout_apm.gemspec +14 -9
- data/test/test_helper.rb +2 -2
- data/test/tmp/README.md +17 -0
- data/test/unit/agent_context_test.rb +29 -0
- data/test/unit/auto_instrument/anonymous_block_value.rb +7 -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/hanging_method.rb +6 -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 +62 -0
- data/test/unit/background_job_integrations/sidekiq_test.rb +17 -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/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/active_record_test.rb +40 -0
- data/test/unit/instruments/http_client_test.rb +24 -0
- data/test/unit/instruments/http_test.rb +24 -0
- data/test/unit/instruments/moped_test.rb +24 -0
- data/test/unit/instruments/net_http_test.rb +11 -1
- data/test/unit/instruments/redis_test.rb +24 -0
- data/test/unit/instruments/typhoeus_test.rb +42 -0
- data/test/unit/layer_children_set_test.rb +9 -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/request_histograms_test.rb +17 -0
- data/test/unit/serializers/payload_serializer_test.rb +39 -3
- data/test/unit/slow_request_policy_test.rb +41 -13
- data/test/unit/sql_sanitizer_test.rb +106 -0
- data/test/unit/tracer_test.rb +25 -0
- metadata +118 -60
- data/.travis.yml +0 -25
- data/lib/scout_apm/instruments/.DS_Store +0 -0
- data/lib/scout_apm/slow_job_policy.rb +0 -111
- data/lib/scout_apm/utils/sql_sanitizer_regex.rb +0 -25
- data/lib/scout_apm/utils/sql_sanitizer_regex_1_8_7.rb +0 -26
- data/test/unit/instruments/active_record_instruments_test.rb +0 -5
- data/test/unit/slow_job_policy_test.rb +0 -6
@@ -0,0 +1,174 @@
|
|
1
|
+
|
2
|
+
require 'scout_apm/auto_instrument/layer'
|
3
|
+
require 'scout_apm/auto_instrument/parser'
|
4
|
+
|
5
|
+
module ScoutApm
|
6
|
+
module AutoInstrument
|
7
|
+
module Rails
|
8
|
+
# A general pattern to match Rails controller files:
|
9
|
+
CONTROLLER_FILE = /\/app\/controllers\/*\/.*_controller.rb$/.freeze
|
10
|
+
|
11
|
+
# Some gems (Devise) provide controllers that match CONTROLLER_FILE pattern.
|
12
|
+
# Try a simple match to see if it's a Gemfile
|
13
|
+
GEM_FILE = /\/gems?\//.freeze
|
14
|
+
|
15
|
+
# Whether the given path is likely to be a Rails controller and not provided by a Gem.
|
16
|
+
def self.controller_path? path
|
17
|
+
CONTROLLER_FILE.match(path) && !GEM_FILE.match(path)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Autoinstruments increases overhead when applied to many code expressions that perform little work.
|
21
|
+
# You can exclude files from autoinstruments via the `auto_instruments_ignore` option.
|
22
|
+
def self.ignore?(path)
|
23
|
+
res = false
|
24
|
+
ScoutApm::Agent.instance.context.config.value('auto_instruments_ignore').each do |ignored_file_name|
|
25
|
+
if path.include?(ignored_file_name)
|
26
|
+
res = true
|
27
|
+
break
|
28
|
+
end
|
29
|
+
end
|
30
|
+
res
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.rewrite(path, code = nil)
|
34
|
+
code ||= File.read(path)
|
35
|
+
|
36
|
+
ast = ::Parser::CurrentRuby.parse(code)
|
37
|
+
|
38
|
+
# pp ast
|
39
|
+
|
40
|
+
buffer = ::Parser::Source::Buffer.new(path)
|
41
|
+
buffer.source = code
|
42
|
+
|
43
|
+
rewriter = Rewriter.new
|
44
|
+
|
45
|
+
# Rewrite the AST, returns a String with the new form.
|
46
|
+
rewriter.rewrite(buffer, ast)
|
47
|
+
end
|
48
|
+
|
49
|
+
class Rewriter < ::Parser::TreeRewriter
|
50
|
+
def initialize
|
51
|
+
super
|
52
|
+
|
53
|
+
# Keeps track of the parent - child relationship between nodes:
|
54
|
+
@nesting = []
|
55
|
+
|
56
|
+
# The stack of method nodes (type :def):
|
57
|
+
@method = []
|
58
|
+
|
59
|
+
# The stack of class nodes:
|
60
|
+
@scope = []
|
61
|
+
|
62
|
+
@cache = Cache.new
|
63
|
+
end
|
64
|
+
|
65
|
+
def instrument(source, file_name, line)
|
66
|
+
# Don't log huge chunks of code... just the first line:
|
67
|
+
if lines = source.lines and lines.count > 1
|
68
|
+
source = lines.first.chomp + "..."
|
69
|
+
end
|
70
|
+
|
71
|
+
method_name = @method.last.children[0]
|
72
|
+
bt = ["#{file_name}:#{line}:in `#{method_name}'"]
|
73
|
+
|
74
|
+
return [
|
75
|
+
"::ScoutApm::AutoInstrument("+ source.dump + ",#{bt}){",
|
76
|
+
"}"
|
77
|
+
]
|
78
|
+
end
|
79
|
+
|
80
|
+
# Look up 1 or more nodes to check if the parent exists and matches the given type.
|
81
|
+
# @param type [Symbol] the symbol type to match.
|
82
|
+
# @param up [Integer] how far up to look.
|
83
|
+
def parent_type?(type, up = 1)
|
84
|
+
parent = @nesting[@nesting.size - up - 1] and parent.type == type
|
85
|
+
end
|
86
|
+
|
87
|
+
def on_block(node)
|
88
|
+
# If we are not in a method, don't do any instrumentation:
|
89
|
+
return if @method.empty?
|
90
|
+
|
91
|
+
line = node.location.line || 'line?'
|
92
|
+
column = node.location.column || 'column?' # not used
|
93
|
+
method_name = node.children[0].children[1] || '*unknown*' # not used
|
94
|
+
file_name = @source_rewriter.source_buffer.name
|
95
|
+
|
96
|
+
wrap(node.location.expression, *instrument(node.location.expression.source, file_name, line))
|
97
|
+
end
|
98
|
+
|
99
|
+
def on_mlhs(node)
|
100
|
+
# Ignore / don't instrument multiple assignment (LHS).
|
101
|
+
return
|
102
|
+
end
|
103
|
+
|
104
|
+
def on_op_asgn(node)
|
105
|
+
process(node.children[2])
|
106
|
+
end
|
107
|
+
|
108
|
+
def on_or_asgn(node)
|
109
|
+
process(node.children[1])
|
110
|
+
end
|
111
|
+
|
112
|
+
def on_and_asgn(node)
|
113
|
+
process(node.children[1])
|
114
|
+
end
|
115
|
+
|
116
|
+
# Handle the method call AST node. If this method doesn't call `super`, no futher rewriting is applied to children.
|
117
|
+
def on_send(node)
|
118
|
+
# We aren't interested in top level function calls:
|
119
|
+
return if @method.empty?
|
120
|
+
|
121
|
+
if @cache.local_assignments?(node)
|
122
|
+
return super
|
123
|
+
end
|
124
|
+
|
125
|
+
# This ignores both initial block method invocation `*x*{}`, and subsequent nested invocations `x{*y*}`:
|
126
|
+
return if parent_type?(:block)
|
127
|
+
|
128
|
+
# Extract useful metadata for instrumentation:
|
129
|
+
line = node.location.line || 'line?'
|
130
|
+
column = node.location.column || 'column?' # not used
|
131
|
+
method_name = node.children[1] || '*unknown*' # not used
|
132
|
+
file_name = @source_rewriter.source_buffer.name
|
133
|
+
|
134
|
+
# Wrap the expression with instrumentation:
|
135
|
+
wrap(node.location.expression, *instrument(node.location.expression.source, file_name, line))
|
136
|
+
end
|
137
|
+
|
138
|
+
# def on_class(node)
|
139
|
+
# class_name = node.children[1]
|
140
|
+
#
|
141
|
+
# Kernel.const_get(class_name).ancestors.include? ActionController::Controller
|
142
|
+
#
|
143
|
+
# if class_name =~ /.../
|
144
|
+
# super # continue processing
|
145
|
+
# end
|
146
|
+
# end
|
147
|
+
|
148
|
+
# Invoked for every AST node as it is processed top to bottom.
|
149
|
+
def process(node)
|
150
|
+
# We are nesting inside this node:
|
151
|
+
@nesting.push(node)
|
152
|
+
|
153
|
+
if node and node.type == :def
|
154
|
+
# If the node is a method, push it on the method stack as well:
|
155
|
+
@method.push(node)
|
156
|
+
super
|
157
|
+
@method.pop
|
158
|
+
elsif node and node.type == :class
|
159
|
+
@scope.push(node.children[0])
|
160
|
+
super
|
161
|
+
@scope.pop
|
162
|
+
else
|
163
|
+
super
|
164
|
+
end
|
165
|
+
|
166
|
+
@nesting.pop
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# Force any lazy loading to occur here, before we patch iseq_load. Otherwise you might end up in an infinite loop when rewriting code.
|
174
|
+
ScoutApm::AutoInstrument::Rails.rewrite('(preload)', '')
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
module BackgroundJobIntegrations
|
3
|
+
class Faktory
|
4
|
+
attr_reader :logger
|
5
|
+
|
6
|
+
def name
|
7
|
+
:faktory
|
8
|
+
end
|
9
|
+
|
10
|
+
def present?
|
11
|
+
defined?(::Faktory)
|
12
|
+
end
|
13
|
+
|
14
|
+
def forking?
|
15
|
+
false
|
16
|
+
end
|
17
|
+
|
18
|
+
def install
|
19
|
+
add_middleware
|
20
|
+
install_processor
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_middleware
|
24
|
+
::Faktory.configure_worker do |config|
|
25
|
+
config.worker_middleware do |chain|
|
26
|
+
chain.add FaktoryMiddleware
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def install_processor
|
32
|
+
require 'faktory/processor' # sidekiq v4 has not loaded this file by this point
|
33
|
+
|
34
|
+
::Faktory::Processor.class_eval do
|
35
|
+
def initialize_with_scout(*args)
|
36
|
+
agent = ::ScoutApm::Agent.instance
|
37
|
+
agent.start
|
38
|
+
initialize_without_scout(*args)
|
39
|
+
end
|
40
|
+
|
41
|
+
alias_method :initialize_without_scout, :initialize
|
42
|
+
alias_method :initialize, :initialize_with_scout
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# We insert this middleware into the Sidekiq stack, to capture each job,
|
48
|
+
# and time them.
|
49
|
+
class FaktoryMiddleware
|
50
|
+
def call(worker_instance, job)
|
51
|
+
queue = job["queue"]
|
52
|
+
|
53
|
+
req = ScoutApm::RequestManager.lookup
|
54
|
+
req.annotate_request(:queue_latency => latency(job))
|
55
|
+
|
56
|
+
begin
|
57
|
+
req.start_layer(ScoutApm::Layer.new('Queue', queue))
|
58
|
+
started_queue = true
|
59
|
+
req.start_layer(ScoutApm::Layer.new('Job', job_class(job)))
|
60
|
+
started_job = true
|
61
|
+
|
62
|
+
yield
|
63
|
+
rescue
|
64
|
+
req.error!
|
65
|
+
raise
|
66
|
+
ensure
|
67
|
+
req.stop_layer if started_job
|
68
|
+
req.stop_layer if started_queue
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
UNKNOWN_CLASS_PLACEHOLDER = 'UnknownJob'.freeze
|
73
|
+
ACTIVE_JOB_KLASS = 'ActiveJob::QueueAdapters::FaktoryAdapter::JobWrapper'.freeze
|
74
|
+
|
75
|
+
def job_class(job)
|
76
|
+
job_class = job.fetch('jobtype', UNKNOWN_CLASS_PLACEHOLDER)
|
77
|
+
|
78
|
+
if job_class == ACTIVE_JOB_KLASS && job.key?('custom') && job['custom'].key?('wrapped')
|
79
|
+
begin
|
80
|
+
job_class = job['custom']['wrapped']
|
81
|
+
rescue
|
82
|
+
ACTIVE_JOB_KLASS
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
job_class
|
87
|
+
rescue
|
88
|
+
UNKNOWN_CLASS_PLACEHOLDER
|
89
|
+
end
|
90
|
+
|
91
|
+
def latency(job, time = Time.now)
|
92
|
+
created_at = Time.parse(job['enqueued_at'] || job['created_at'])
|
93
|
+
if created_at
|
94
|
+
(time - created_at)
|
95
|
+
else
|
96
|
+
0
|
97
|
+
end
|
98
|
+
rescue
|
99
|
+
0
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# This is different than other BackgroundJobIntegrations and must be prepended
|
2
|
+
# manually in each job.
|
3
|
+
#
|
4
|
+
# class MyWorker
|
5
|
+
# prepend ScoutApm::BackgroundJobIntegrations::LegacySneakers
|
6
|
+
#
|
7
|
+
# def work(msg)
|
8
|
+
# ...
|
9
|
+
# end
|
10
|
+
# end
|
11
|
+
module ScoutApm
|
12
|
+
module BackgroundJobIntegrations
|
13
|
+
module LegacySneakers
|
14
|
+
UNKNOWN_QUEUE_PLACEHOLDER = 'default'.freeze
|
15
|
+
|
16
|
+
def self.prepended(base)
|
17
|
+
ScoutApm::Agent.instance.logger.info("Prepended LegacySneakers in #{base}")
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(*args)
|
21
|
+
super
|
22
|
+
|
23
|
+
# Save off the existing value to call the correct existing work
|
24
|
+
# function in the instrumentation. But then override Sneakers to always
|
25
|
+
# use the extra-argument version, which has data Scout needs
|
26
|
+
@call_work = respond_to?(:work)
|
27
|
+
end
|
28
|
+
|
29
|
+
def work_with_params(msg, delivery_info, metadata)
|
30
|
+
queue = delivery_info[:routing_key] || UNKNOWN_QUEUE_PLACEHOLDER
|
31
|
+
job_class = self.class.name
|
32
|
+
req = ScoutApm::RequestManager.lookup
|
33
|
+
|
34
|
+
begin
|
35
|
+
req.start_layer(ScoutApm::Layer.new('Queue', queue))
|
36
|
+
started_queue = true
|
37
|
+
req.start_layer(ScoutApm::Layer.new('Job', job_class))
|
38
|
+
started_job = true
|
39
|
+
|
40
|
+
if @call_work
|
41
|
+
work(msg)
|
42
|
+
else
|
43
|
+
super
|
44
|
+
end
|
45
|
+
rescue Exception
|
46
|
+
req.error!
|
47
|
+
raise
|
48
|
+
ensure
|
49
|
+
req.stop_layer if started_job
|
50
|
+
req.stop_layer if started_queue
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
module BackgroundJobIntegrations
|
3
|
+
class Que
|
4
|
+
|
5
|
+
UNKNOWN_CLASS_PLACEHOLDER = 'UnknownJob'.freeze
|
6
|
+
ACTIVE_JOB_KLASS = 'ActiveJob::QueueAdapters::QueAdapter::JobWrapper'.freeze
|
7
|
+
UNKNOWN_QUEUE_PLACEHOLDER = 'UnknownQueue'.freeze
|
8
|
+
|
9
|
+
attr_reader :logger
|
10
|
+
|
11
|
+
def name
|
12
|
+
:que
|
13
|
+
end
|
14
|
+
|
15
|
+
def present?
|
16
|
+
if defined?(::Que) && File.basename($PROGRAM_NAME).start_with?('que')
|
17
|
+
# 0.x releases used "Version" constant.
|
18
|
+
# 1.x releases used "VERSION" constant.
|
19
|
+
return defined?(::Que::Version)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def forking?
|
24
|
+
false
|
25
|
+
end
|
26
|
+
|
27
|
+
def install
|
28
|
+
install_tracer
|
29
|
+
install_worker
|
30
|
+
install_job
|
31
|
+
end
|
32
|
+
|
33
|
+
def install_tracer
|
34
|
+
::Que::Job.class_eval do
|
35
|
+
include ScoutApm::Tracer
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def install_worker
|
40
|
+
::Que::Worker.class_eval do
|
41
|
+
def initialize_with_scout(*args)
|
42
|
+
agent = ::ScoutApm::Agent.instance
|
43
|
+
agent.start
|
44
|
+
initialize_without_scout(*args)
|
45
|
+
end
|
46
|
+
|
47
|
+
alias_method :initialize_without_scout, :initialize
|
48
|
+
alias_method :initialize, :initialize_with_scout
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def install_job
|
53
|
+
::Que::Job.class_eval do
|
54
|
+
# attrs = {
|
55
|
+
# "queue"=>"",
|
56
|
+
# "priority"=>100,
|
57
|
+
# "run_at"=>2018-12-19 15:12:32 -0700,
|
58
|
+
# "job_id"=>4,
|
59
|
+
# "job_class"=>"ExampleJob",
|
60
|
+
# "args"=>[{"key"=>"value"}],
|
61
|
+
# "error_count"=>0
|
62
|
+
# }
|
63
|
+
#
|
64
|
+
# With ActiveJob, v0.14.3:
|
65
|
+
# attrs = {
|
66
|
+
# "queue"=>"",
|
67
|
+
# "priority"=>100,
|
68
|
+
# "run_at"=>2018-12-19 15:29:18 -0700,
|
69
|
+
# "job_id"=>6,
|
70
|
+
# "job_class"=>"ActiveJob::QueueAdapters::QueAdapter::JobWrapper",
|
71
|
+
# "args"=> [{
|
72
|
+
# "job_class"=>"ExampleJob",
|
73
|
+
# "job_id"=>"60798b45-4b55-436e-806d-693939266c97",
|
74
|
+
# "provider_job_id"=>nil,
|
75
|
+
# "queue_name"=>"default",
|
76
|
+
# "priority"=>nil,
|
77
|
+
# "arguments"=>[],
|
78
|
+
# "executions"=>0,
|
79
|
+
# "locale"=>"en"
|
80
|
+
# }],
|
81
|
+
# "error_count"=>0
|
82
|
+
# }
|
83
|
+
#
|
84
|
+
# With ActiveJob, v1.0:
|
85
|
+
# There are changes here to make Que more compatible with ActiveJob
|
86
|
+
# and this probably needs to be revisited.
|
87
|
+
|
88
|
+
def _run_with_scout(*args)
|
89
|
+
# default queue unless specifed is a blank string
|
90
|
+
queue = attrs['queue'] || UNKNOWN_QUEUE_PLACEHOLDER
|
91
|
+
|
92
|
+
job_class = begin
|
93
|
+
if self.class == ActiveJob::QueueAdapters::QueAdapter::JobWrapper
|
94
|
+
Array(attrs['args']).first['job_class'] || UNKNOWN_CLASS_PLACEHOLDER
|
95
|
+
else
|
96
|
+
self.class.name
|
97
|
+
end
|
98
|
+
rescue => e
|
99
|
+
UNKNOWN_CLASS_PLACEHOLDER
|
100
|
+
end
|
101
|
+
|
102
|
+
latency = begin
|
103
|
+
Time.now - attrs['run_at']
|
104
|
+
rescue
|
105
|
+
0
|
106
|
+
end
|
107
|
+
|
108
|
+
req = ScoutApm::RequestManager.lookup
|
109
|
+
req.annotate_request(:queue_latency => latency)
|
110
|
+
|
111
|
+
begin
|
112
|
+
req.start_layer(ScoutApm::Layer.new('Queue', queue))
|
113
|
+
started_queue = true
|
114
|
+
req.start_layer(ScoutApm::Layer.new('Job', job_class))
|
115
|
+
started_job = true
|
116
|
+
|
117
|
+
_run_without_scout(*args)
|
118
|
+
rescue Exception => e
|
119
|
+
req.error!
|
120
|
+
raise
|
121
|
+
ensure
|
122
|
+
req.stop_layer if started_job
|
123
|
+
req.stop_layer if started_queue
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
alias_method :_run_without_scout, :_run
|
128
|
+
alias_method :_run, :_run_with_scout
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|
@@ -37,6 +37,8 @@ module ScoutApm
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def install_processor
|
40
|
+
# celluloid has not loaded by this point and older versions of `shorykuen/processor` assume that it did
|
41
|
+
require 'celluloid' if defined?(::Shoryuken::VERSION) && ::Shoryuken::VERSION < '3'
|
40
42
|
require 'shoryuken/processor' # sidekiq v4 has not loaded this file by this point
|
41
43
|
|
42
44
|
::Shoryuken::Processor.class_eval do
|
@@ -37,17 +37,11 @@ module ScoutApm
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def install_processor
|
40
|
-
|
41
|
-
|
42
|
-
::Sidekiq::Processor.class_eval do
|
43
|
-
def initialize_with_scout(boss)
|
40
|
+
::Sidekiq.configure_server do |config|
|
41
|
+
config.on(:startup) do
|
44
42
|
agent = ::ScoutApm::Agent.instance
|
45
43
|
agent.start
|
46
|
-
initialize_without_scout(boss)
|
47
44
|
end
|
48
|
-
|
49
|
-
alias_method :initialize_without_scout, :initialize
|
50
|
-
alias_method :initialize, :initialize_with_scout
|
51
45
|
end
|
52
46
|
end
|
53
47
|
end
|
@@ -80,12 +74,23 @@ module ScoutApm
|
|
80
74
|
DELAYED_WRAPPER_KLASS = 'Sidekiq::Extensions::DelayedClass'.freeze
|
81
75
|
|
82
76
|
|
77
|
+
# Capturing the class name is a little tricky, since we need to handle several cases:
|
78
|
+
# 1. ActiveJob, with the class in the key 'wrapped'
|
79
|
+
# 2. ActiveJob, but the 'wrapped' key is wrong (due to YAJL serializing weirdly), find it in args.job_class
|
80
|
+
# 3. DelayedJob wrapper, deserializing using YAML into the real object, which can be introspected
|
81
|
+
# 4. No wrapper, just sidekiq's class
|
83
82
|
def job_class(msg)
|
84
83
|
job_class = msg.fetch('class', UNKNOWN_CLASS_PLACEHOLDER)
|
85
84
|
|
86
|
-
if job_class == ACTIVE_JOB_KLASS && msg.key?('wrapped')
|
85
|
+
if job_class == ACTIVE_JOB_KLASS && msg.key?('wrapped') && msg['wrapped'].is_a?(String)
|
86
|
+
begin
|
87
|
+
job_class = msg['wrapped'].to_s
|
88
|
+
rescue
|
89
|
+
ACTIVE_JOB_KLASS
|
90
|
+
end
|
91
|
+
elsif job_class == ACTIVE_JOB_KLASS && msg.try(:[], 'args').try(:[], 'job_class')
|
87
92
|
begin
|
88
|
-
job_class = msg['
|
93
|
+
job_class = msg['args']['job_class'].to_s
|
89
94
|
rescue
|
90
95
|
ACTIVE_JOB_KLASS
|
91
96
|
end
|
@@ -1,14 +1,22 @@
|
|
1
1
|
module ScoutApm
|
2
2
|
module BackgroundJobIntegrations
|
3
3
|
class Sneakers
|
4
|
-
attr_reader :logger
|
5
|
-
|
6
4
|
def name
|
7
5
|
:sneakers
|
8
6
|
end
|
9
7
|
|
10
8
|
def present?
|
11
|
-
defined?(::Sneakers)
|
9
|
+
defined?(::Sneakers) && supported_version?
|
10
|
+
end
|
11
|
+
|
12
|
+
# Only support Sneakers 2.7 and up
|
13
|
+
def supported_version?
|
14
|
+
result = Gem::Version.new(::Sneakers::VERSION) > Gem::Version.new("2.7.0")
|
15
|
+
ScoutApm::Agent.instance.logger.info("Skipping Sneakers instrumentation. Only versions 2.7+ are supported. See docs or contact support@scoutapm.com for instrumentation of older versions.")
|
16
|
+
result
|
17
|
+
rescue
|
18
|
+
ScoutApm::Agent.instance.logger.info("Failed comparing Sneakers Version. Skipping")
|
19
|
+
false
|
12
20
|
end
|
13
21
|
|
14
22
|
def forking?
|
@@ -69,14 +77,6 @@ module ScoutApm
|
|
69
77
|
alias_method :process_work_without_scout, :process_work
|
70
78
|
alias_method :process_work, :process_work_with_scout
|
71
79
|
end
|
72
|
-
|
73
|
-
# msg = {
|
74
|
-
# "job_class":"DummyWorker",
|
75
|
-
# "job_id":"ea23ba1c-3022-4e05-870b-c3bcb1c4f328",
|
76
|
-
# "queue_name":"default",
|
77
|
-
# "arguments":["fjdkl"],
|
78
|
-
# "locale":"en"
|
79
|
-
# }
|
80
80
|
end
|
81
81
|
|
82
82
|
ACTIVE_JOB_KLASS = 'ActiveJob::QueueAdapters::SneakersAdapter::JobWrapper'.freeze
|