scout_apm 2.5.3 → 2.6.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/.rubocop.yml +3 -0
- data/CHANGELOG.markdown +6 -2
- data/lib/scout_apm.rb +7 -0
- data/lib/scout_apm/auto_instrument.rb +5 -0
- data/lib/scout_apm/auto_instrument/instruction_sequence.rb +29 -0
- data/lib/scout_apm/auto_instrument/layer.rb +20 -0
- data/lib/scout_apm/auto_instrument/parser.rb +27 -0
- data/lib/scout_apm/auto_instrument/rails.rb +158 -0
- data/lib/scout_apm/config.rb +7 -3
- data/lib/scout_apm/layer.rb +1 -1
- data/lib/scout_apm/tracked_request.rb +21 -0
- data/lib/scout_apm/utils/backtrace_parser.rb +3 -0
- data/lib/scout_apm/version.rb +1 -1
- data/scout_apm.gemspec +1 -0
- data/test/unit/auto_instrument/assignments-instrumented.rb +26 -0
- data/test/unit/auto_instrument/assignments.rb +26 -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 +53 -0
- metadata +37 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a0d5d02cc644b23e02868a9705fba1a15f0250bba5bff1c92eb97efaf04f1d87
|
4
|
+
data.tar.gz: e0f5acef9895941f7d2f327596d1f9eef9f6a3ef374ddced1ee658b2882bc525
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 976b31dc166e501368825e07b3377138be135790a23a92fc52681f81d780034641d86496ac0d03dda0639127673d6b1908f5191f966a9193488094a24a7720a7
|
7
|
+
data.tar.gz: 835d1e4f4db51c71a41521f5892e91d1d67c7cd96effc91f684bacbe6fa9e9303e9abdb9b697c75d79d656bab45d9b241b3200ac8203236fb75a07c101f3c5fb
|
data/.rubocop.yml
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# Disable all cops by default
|
2
2
|
AllCops:
|
3
3
|
DisabledByDefault: true
|
4
|
+
Exclude:
|
5
|
+
- 'test/unit/auto_instrument/*'
|
6
|
+
- vendor/bundle/**/*
|
4
7
|
|
5
8
|
# 80 is stifling, especially with a few levels of nesting before we even start.
|
6
9
|
# So bump it to 100 to keep really long lines from creeping in.
|
data/CHANGELOG.markdown
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
# 2.6.0
|
2
|
+
|
3
|
+
* Autoinstruments (#247). Disabled by default. Set `auto_instruments: true` to enable.
|
4
|
+
|
1
5
|
# 2.5.3
|
2
6
|
|
3
7
|
* Add Que support (#265)
|
@@ -27,7 +31,7 @@
|
|
27
31
|
|
28
32
|
# 2.4.22
|
29
33
|
|
30
|
-
* Support Rails 6.0 View Instruments (#251)
|
34
|
+
* Support Rails 6.0 View Instruments (#251)
|
31
35
|
* Update documentation URLs (#236)
|
32
36
|
|
33
37
|
# 2.4.21
|
@@ -675,7 +679,7 @@ Big set of features getting merged in for this release.
|
|
675
679
|
|
676
680
|
# 0.1.3
|
677
681
|
|
678
|
-
* Adds capacity calculation via "Instance/Capacity" metric.
|
682
|
+
* Adds capacity calculation via "Instance/Capacity" metric.
|
679
683
|
* Tweaks tracing to still count a transaction if it results in a 500 error and includes it in accumulated time.
|
680
684
|
* Adds per-transaction error tracking (ex: Errors/Controller/widgets/index)
|
681
685
|
|
data/lib/scout_apm.rb
CHANGED
@@ -196,6 +196,13 @@ if defined?(Rails) && defined?(Rails::VERSION) && defined?(Rails::VERSION::MAJOR
|
|
196
196
|
# Attempt to start right away, this will work best for preloading apps, Unicorn & Puma & similar
|
197
197
|
ScoutApm::Agent.instance.install
|
198
198
|
|
199
|
+
if ScoutApm::Agent.instance.context.config.value("auto_instruments")
|
200
|
+
ScoutApm::Agent.instance.context.logger.debug("AutoInstruments is enabled.")
|
201
|
+
require 'scout_apm/auto_instrument'
|
202
|
+
else
|
203
|
+
ScoutApm::Agent.instance.context.logger.debug("AutoInstruments is disabled.")
|
204
|
+
end
|
205
|
+
|
199
206
|
# Install the middleware every time in development mode.
|
200
207
|
# The middleware is a noop if dev_trace is not enabled in config
|
201
208
|
if Rails.env.development?
|
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
require 'scout_apm/auto_instrument/rails'
|
3
|
+
|
4
|
+
module ScoutApm
|
5
|
+
module AutoInstrument
|
6
|
+
module InstructionSequence
|
7
|
+
def load_iseq(path)
|
8
|
+
if Rails.controller_path?(path)
|
9
|
+
begin
|
10
|
+
new_code = Rails.rewrite(path)
|
11
|
+
return self.compile(new_code, File.basename(path), path)
|
12
|
+
rescue
|
13
|
+
warn "Failed to apply auto-instrumentation to #{path}: #{$!}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
return self.compile_file(path)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# This should work (https://bugs.ruby-lang.org/issues/15572), but it doesn't.
|
22
|
+
# RubyVM::InstructionSequence.extend(InstructionSequence)
|
23
|
+
|
24
|
+
# So we do this instead:
|
25
|
+
class << ::RubyVM::InstructionSequence
|
26
|
+
prepend InstructionSequence
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
module ScoutApm
|
3
|
+
def self.AutoInstrument(name, backtrace = nil)
|
4
|
+
request = ScoutApm::RequestManager.lookup
|
5
|
+
|
6
|
+
begin
|
7
|
+
layer = ScoutApm::Layer.new('AutoInstrument', name)
|
8
|
+
layer.backtrace = backtrace
|
9
|
+
|
10
|
+
request.start_layer(layer)
|
11
|
+
started_layer = true
|
12
|
+
|
13
|
+
result = yield
|
14
|
+
ensure
|
15
|
+
request.stop_layer if started_layer
|
16
|
+
end
|
17
|
+
|
18
|
+
return result
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
require 'parser/current'
|
3
|
+
raise LoadError, "Parser::TreeRewriter was not defined" unless defined?(Parser::TreeRewriter)
|
4
|
+
|
5
|
+
module ScoutApm
|
6
|
+
module AutoInstrument
|
7
|
+
class Cache
|
8
|
+
def initialize
|
9
|
+
@local_assignments = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def local_assignments?(node)
|
13
|
+
unless @local_assignments.key?(node)
|
14
|
+
if node.type == :lvasgn
|
15
|
+
@local_assignments[node] = true
|
16
|
+
elsif node.children.find{|child| child.is_a?(Parser::AST::Node) && self.local_assignments?(child)}
|
17
|
+
@local_assignments[node] = true
|
18
|
+
else
|
19
|
+
@local_assignments[node] = false
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
return @local_assignments[node]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,158 @@
|
|
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
|
+
def self.rewrite(path, code = nil)
|
21
|
+
code ||= File.read(path)
|
22
|
+
|
23
|
+
ast = ::Parser::CurrentRuby.parse(code)
|
24
|
+
|
25
|
+
# pp ast
|
26
|
+
|
27
|
+
buffer = ::Parser::Source::Buffer.new(path)
|
28
|
+
buffer.source = code
|
29
|
+
|
30
|
+
rewriter = Rewriter.new
|
31
|
+
|
32
|
+
# Rewrite the AST, returns a String with the new form.
|
33
|
+
rewriter.rewrite(buffer, ast)
|
34
|
+
end
|
35
|
+
|
36
|
+
class Rewriter < ::Parser::TreeRewriter
|
37
|
+
def initialize
|
38
|
+
super
|
39
|
+
|
40
|
+
# Keeps track of the parent - child relationship between nodes:
|
41
|
+
@nesting = []
|
42
|
+
|
43
|
+
# The stack of method nodes (type :def):
|
44
|
+
@method = []
|
45
|
+
|
46
|
+
# The stack of class nodes:
|
47
|
+
@scope = []
|
48
|
+
|
49
|
+
@cache = Cache.new
|
50
|
+
end
|
51
|
+
|
52
|
+
def instrument(source, file_name, line)
|
53
|
+
# Don't log huge chunks of code... just the first line:
|
54
|
+
if lines = source.lines and lines.count > 1
|
55
|
+
source = lines.first.chomp + "..."
|
56
|
+
end
|
57
|
+
|
58
|
+
method_name = @method.last.children[0]
|
59
|
+
class_name = @scope.last.children[1]
|
60
|
+
bt = ["#{file_name}:#{line}:in `#{method_name}'"]
|
61
|
+
|
62
|
+
return [
|
63
|
+
"::ScoutApm::AutoInstrument("+ source.dump + ",#{bt}" + "){",
|
64
|
+
"}"
|
65
|
+
]
|
66
|
+
end
|
67
|
+
|
68
|
+
# Look up 1 or more nodes to check if the parent exists and matches the given type.
|
69
|
+
# @param type [Symbol] the symbol type to match.
|
70
|
+
# @param up [Integer] how far up to look.
|
71
|
+
def parent_type?(type, up = 1)
|
72
|
+
parent = @nesting[@nesting.size - up - 1] and parent.type == type
|
73
|
+
end
|
74
|
+
|
75
|
+
def on_block(node)
|
76
|
+
# If we are not in a method, don't do any instrumentation:
|
77
|
+
return if @method.empty?
|
78
|
+
|
79
|
+
line = node.location.line || 'line?'
|
80
|
+
column = node.location.column || 'column?' # not used
|
81
|
+
method_name = node.children[0].children[1] || '*unknown*' # not used
|
82
|
+
file_name = @source_rewriter.source_buffer.name
|
83
|
+
|
84
|
+
wrap(node.location.expression, *instrument(node.location.expression.source, file_name, line))
|
85
|
+
end
|
86
|
+
|
87
|
+
def on_mlhs(node)
|
88
|
+
# Ignore / don't instrument multiple assignment (LHS).
|
89
|
+
return
|
90
|
+
end
|
91
|
+
|
92
|
+
def on_or_asgn(node)
|
93
|
+
process(node.children[1])
|
94
|
+
end
|
95
|
+
|
96
|
+
def on_and_asgn(node)
|
97
|
+
process(node.children[1])
|
98
|
+
end
|
99
|
+
|
100
|
+
# Handle the method call AST node. If this method doesn't call `super`, no futher rewriting is applied to children.
|
101
|
+
def on_send(node)
|
102
|
+
# We aren't interested in top level function calls:
|
103
|
+
return if @method.empty?
|
104
|
+
|
105
|
+
if @cache.local_assignments?(node)
|
106
|
+
return super
|
107
|
+
end
|
108
|
+
|
109
|
+
# This ignores both initial block method invocation `*x*{}`, and subsequent nested invocations `x{*y*}`:
|
110
|
+
return if parent_type?(:block)
|
111
|
+
|
112
|
+
# Extract useful metadata for instrumentation:
|
113
|
+
line = node.location.line || 'line?'
|
114
|
+
column = node.location.column || 'column?' # not used
|
115
|
+
method_name = node.children[1] || '*unknown*' # not used
|
116
|
+
file_name = @source_rewriter.source_buffer.name
|
117
|
+
|
118
|
+
# Wrap the expression with instrumentation:
|
119
|
+
wrap(node.location.expression, *instrument(node.location.expression.source, file_name, line))
|
120
|
+
end
|
121
|
+
|
122
|
+
# def on_class(node)
|
123
|
+
# class_name = node.children[1]
|
124
|
+
#
|
125
|
+
# Kernel.const_get(class_name).ancestors.include? ActionController::Controller
|
126
|
+
#
|
127
|
+
# if class_name =~ /.../
|
128
|
+
# super # continue processing
|
129
|
+
# end
|
130
|
+
# end
|
131
|
+
|
132
|
+
# Invoked for every AST node as it is processed top to bottom.
|
133
|
+
def process(node)
|
134
|
+
# We are nesting inside this node:
|
135
|
+
@nesting.push(node)
|
136
|
+
|
137
|
+
if node and node.type == :def
|
138
|
+
# If the node is a method, push it on the method stack as well:
|
139
|
+
@method.push(node)
|
140
|
+
super
|
141
|
+
@method.pop
|
142
|
+
elsif node and node.type == :class
|
143
|
+
@scope.push(node.children[0])
|
144
|
+
super
|
145
|
+
@scope.pop
|
146
|
+
else
|
147
|
+
super
|
148
|
+
end
|
149
|
+
|
150
|
+
@nesting.pop
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Force any lazy loading to occur here, before we patch iseq_load. Otherwise you might end up in an infinite loop when rewriting code.
|
158
|
+
ScoutApm::AutoInstrument::Rails.rewrite('(preload)', '')
|
data/lib/scout_apm/config.rb
CHANGED
@@ -33,6 +33,7 @@ require 'scout_apm/environment'
|
|
33
33
|
# remote_agent_port - What port to bind the remote webserver to
|
34
34
|
# start_resque_server_instrument - Used in special situations with certain Resque installs
|
35
35
|
# timeline_traces - true/false to enable sending of of the timeline trace format.
|
36
|
+
# auto_instruments - true/false whether to install autoinstruments. Only installed if on a supported Ruby version.
|
36
37
|
#
|
37
38
|
# Any of these config settings can be set with an environment variable prefixed
|
38
39
|
# by SCOUT_ and uppercasing the key: SCOUT_LOG_LEVEL for instance.
|
@@ -75,7 +76,8 @@ module ScoutApm
|
|
75
76
|
'start_resque_server_instrument',
|
76
77
|
'uri_reporting',
|
77
78
|
'instrument_http_url_length',
|
78
|
-
'timeline_traces'
|
79
|
+
'timeline_traces',
|
80
|
+
'auto_instruments'
|
79
81
|
]
|
80
82
|
|
81
83
|
################################################################################
|
@@ -168,7 +170,8 @@ module ScoutApm
|
|
168
170
|
'database_metric_report_limit' => IntegerCoercion.new,
|
169
171
|
'instrument_http_url_length' => IntegerCoercion.new,
|
170
172
|
'start_resque_server_instrument' => BooleanCoercion.new,
|
171
|
-
'timeline_traces' => BooleanCoercion.new
|
173
|
+
'timeline_traces' => BooleanCoercion.new,
|
174
|
+
'auto_instruments' => BooleanCoercion.new
|
172
175
|
}
|
173
176
|
|
174
177
|
|
@@ -276,7 +279,8 @@ module ScoutApm
|
|
276
279
|
'instrument_http_url_length' => 300,
|
277
280
|
'start_resque_server_instrument' => true, # still only starts if Resque is detected
|
278
281
|
'collect_remote_ip' => true,
|
279
|
-
'timeline_traces' => true
|
282
|
+
'timeline_traces' => true,
|
283
|
+
'auto_instruments' => false
|
280
284
|
}.freeze
|
281
285
|
|
282
286
|
def value(key)
|
data/lib/scout_apm/layer.rb
CHANGED
@@ -36,7 +36,7 @@ module ScoutApm
|
|
36
36
|
|
37
37
|
# If this layer took longer than a fixed amount of time, store the
|
38
38
|
# backtrace of where it occurred.
|
39
|
-
|
39
|
+
attr_accessor :backtrace
|
40
40
|
|
41
41
|
# As we go through a part of a request, instrumentation can store additional data
|
42
42
|
# Known Keys:
|
@@ -52,6 +52,10 @@ module ScoutApm
|
|
52
52
|
# see that on Sidekiq.
|
53
53
|
REQUEST_TYPES = ["Controller", "Job"]
|
54
54
|
|
55
|
+
# Layers of type 'AutoInstrument' are not recorded if their total_call_time doesn't exceed this threshold.
|
56
|
+
# AutoInstrument layers are frequently of short duration. This throws out this deadweight that is unlikely to be optimized.
|
57
|
+
AUTO_INSTRUMENT_TIMING_THRESHOLD = 5/1_000.0 # units = seconds
|
58
|
+
|
55
59
|
def initialize(agent_context, store)
|
56
60
|
@agent_context = agent_context
|
57
61
|
@store = store #this is passed in so we can use a real store (normal operation) or fake store (instant mode only)
|
@@ -110,6 +114,9 @@ module ScoutApm
|
|
110
114
|
layer.record_stop_time!
|
111
115
|
layer.record_allocations!
|
112
116
|
|
117
|
+
# Must follow layer.record_stop_time! as the total_call_time is used to determine if the layer is significant.
|
118
|
+
return if layer_insignificant?(layer)
|
119
|
+
|
113
120
|
@layers[-1].add_child(layer) if @layers.any?
|
114
121
|
|
115
122
|
# This must be called before checking if a backtrace should be collected as the call count influences our capture logic.
|
@@ -150,6 +157,10 @@ module ScoutApm
|
|
150
157
|
def capture_backtrace?(layer)
|
151
158
|
return if ignoring_request?
|
152
159
|
|
160
|
+
# A backtrace has already been recorded. This happens with autoinstruments as
|
161
|
+
# the partial backtrace is set when creating the layer.
|
162
|
+
return false if layer.backtrace
|
163
|
+
|
153
164
|
# Never capture backtraces for this kind of layer. The backtrace will
|
154
165
|
# always be 100% framework code.
|
155
166
|
return false if BACKTRACE_BLACKLIST.include?(layer.type)
|
@@ -169,6 +180,16 @@ module ScoutApm
|
|
169
180
|
false
|
170
181
|
end
|
171
182
|
|
183
|
+
def layer_insignificant?(layer)
|
184
|
+
if layer.type == 'AutoInstrument'
|
185
|
+
if layer.total_call_time < AUTO_INSTRUMENT_TIMING_THRESHOLD
|
186
|
+
context.logger.debug("IGNORE LAYER name=#{layer.name} total_call_time=#{layer.total_call_time}")
|
187
|
+
return true
|
188
|
+
end
|
189
|
+
end
|
190
|
+
return false
|
191
|
+
end
|
192
|
+
|
172
193
|
# Maintains a lookup Hash of call counts by layer name. Used to determine if we should capture a backtrace.
|
173
194
|
def update_call_counts!(layer)
|
174
195
|
@call_set[layer.name].update!(layer.desc)
|
@@ -11,6 +11,9 @@ module ScoutApm
|
|
11
11
|
|
12
12
|
attr_reader :call_stack
|
13
13
|
|
14
|
+
# call_stack - an +Array+ of calls, typically generated via the +caller+ method.
|
15
|
+
# Example single line:
|
16
|
+
# "/Users/dlite/.rvm/rubies/ruby-2.4.5/lib/ruby/2.4.0/irb/workspace.rb:87:in `eval'"
|
14
17
|
def initialize(call_stack, root=ScoutApm::Agent.instance.context.environment.root)
|
15
18
|
@call_stack = call_stack
|
16
19
|
# We can't use a constant as it'd be too early to fetch environment info
|
data/lib/scout_apm/version.rb
CHANGED
data/scout_apm.gemspec
CHANGED
@@ -28,6 +28,7 @@ Gem::Specification.new do |s|
|
|
28
28
|
s.add_development_dependency "rake-compiler"
|
29
29
|
s.add_development_dependency "addressable"
|
30
30
|
s.add_development_dependency "activesupport"
|
31
|
+
s.add_runtime_dependency "parser"
|
31
32
|
|
32
33
|
# These are general development dependencies which are used in instrumentation
|
33
34
|
# tests. Specific versions are pulled in using specific gemfiles, e.g.
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
class Assignments
|
3
|
+
def nested_assignment
|
4
|
+
@email ||= if (email = ::ScoutApm::AutoInstrument("session[\"email\"]",["BACKTRACE"]){session["email"]}).present?
|
5
|
+
::ScoutApm::AutoInstrument("User.where(email: email).first",["BACKTRACE"]){User.where(email: email).first}
|
6
|
+
else
|
7
|
+
nil
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def paginate_collection(coll)
|
12
|
+
page = (::ScoutApm::AutoInstrument("params[:page].present?",["BACKTRACE"]){params[:page].to_i} : 1)
|
13
|
+
per_page = (::ScoutApm::AutoInstrument("params[:per_page].present?",["BACKTRACE"]){params[:per_page].to_i} : 20)
|
14
|
+
pagination, self.collection = ::ScoutApm::AutoInstrument("pagy(...",["BACKTRACE"]){pagy(
|
15
|
+
coll,
|
16
|
+
items: per_page,
|
17
|
+
page: page
|
18
|
+
)}
|
19
|
+
::ScoutApm::AutoInstrument("headers[PAGINATION_TOTAL_HEADER] = pagination.count.to_s",["BACKTRACE"]){headers[PAGINATION_TOTAL_HEADER] = pagination.count.to_s}
|
20
|
+
::ScoutApm::AutoInstrument("headers[PAGINATION_TOTAL_PAGES_HEADER] = pagination.pages.to_s",["BACKTRACE"]){headers[PAGINATION_TOTAL_PAGES_HEADER] = pagination.pages.to_s}
|
21
|
+
::ScoutApm::AutoInstrument("headers[PAGINATION_PER_PAGE_HEADER] = per_page.to_s",["BACKTRACE"]){headers[PAGINATION_PER_PAGE_HEADER] = per_page.to_s}
|
22
|
+
::ScoutApm::AutoInstrument("headers[PAGINATION_PAGE_HEADER] = pagination.page.to_s",["BACKTRACE"]){headers[PAGINATION_PAGE_HEADER] = pagination.page.to_s}
|
23
|
+
::ScoutApm::AutoInstrument("headers[PAGINATION_NEXT_PAGE_HEADER] = pagination.next.to_s",["BACKTRACE"]){headers[PAGINATION_NEXT_PAGE_HEADER] = pagination.next.to_s}
|
24
|
+
::ScoutApm::AutoInstrument("collection",["BACKTRACE"]){collection}
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
class Assignments
|
3
|
+
def nested_assignment
|
4
|
+
@email ||= if (email = session["email"]).present?
|
5
|
+
User.where(email: email).first
|
6
|
+
else
|
7
|
+
nil
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def paginate_collection(coll)
|
12
|
+
page = (params[:page].present? ? params[:page].to_i : 1)
|
13
|
+
per_page = (params[:per_page].present? ? params[:per_page].to_i : 20)
|
14
|
+
pagination, self.collection = pagy(
|
15
|
+
coll,
|
16
|
+
items: per_page,
|
17
|
+
page: page
|
18
|
+
)
|
19
|
+
headers[PAGINATION_TOTAL_HEADER] = pagination.count.to_s
|
20
|
+
headers[PAGINATION_TOTAL_PAGES_HEADER] = pagination.pages.to_s
|
21
|
+
headers[PAGINATION_PER_PAGE_HEADER] = per_page.to_s
|
22
|
+
headers[PAGINATION_PAGE_HEADER] = pagination.page.to_s
|
23
|
+
headers[PAGINATION_NEXT_PAGE_HEADER] = pagination.next.to_s
|
24
|
+
collection
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
s(:class,
|
2
|
+
s(:const, nil, :ClientsController),
|
3
|
+
s(:const, nil, :ApplicationController),
|
4
|
+
s(:begin,
|
5
|
+
s(:send, nil, :before_action,
|
6
|
+
s(:sym, :check_authorization)),
|
7
|
+
s(:def, :index,
|
8
|
+
s(:args),
|
9
|
+
s(:if,
|
10
|
+
s(:send,
|
11
|
+
s(:send,
|
12
|
+
s(:send, nil, :params), :[],
|
13
|
+
s(:sym, :status)), :==,
|
14
|
+
s(:str, "activated")),
|
15
|
+
s(:ivasgn, :@clients,
|
16
|
+
s(:send,
|
17
|
+
s(:const, nil, :Client), :activated)),
|
18
|
+
s(:ivasgn, :@clients,
|
19
|
+
s(:send,
|
20
|
+
s(:const, nil, :Client), :inactivated)))),
|
21
|
+
s(:def, :create,
|
22
|
+
s(:args),
|
23
|
+
s(:begin,
|
24
|
+
s(:ivasgn, :@client,
|
25
|
+
s(:send,
|
26
|
+
s(:const, nil, :Client), :new,
|
27
|
+
s(:send,
|
28
|
+
s(:send, nil, :params), :[],
|
29
|
+
s(:sym, :client)))),
|
30
|
+
s(:if,
|
31
|
+
s(:send,
|
32
|
+
s(:ivar, :@client), :save),
|
33
|
+
s(:send, nil, :redirect_to,
|
34
|
+
s(:ivar, :@client)),
|
35
|
+
s(:send, nil, :render,
|
36
|
+
s(:str, "new"))))),
|
37
|
+
s(:def, :edit,
|
38
|
+
s(:args),
|
39
|
+
s(:begin,
|
40
|
+
s(:ivasgn, :@client,
|
41
|
+
s(:send,
|
42
|
+
s(:const, nil, :Client), :new,
|
43
|
+
s(:send,
|
44
|
+
s(:send, nil, :params), :[],
|
45
|
+
s(:sym, :client)))),
|
46
|
+
s(:if,
|
47
|
+
s(:send,
|
48
|
+
s(:send, nil, :request), :post?),
|
49
|
+
s(:block,
|
50
|
+
s(:send,
|
51
|
+
s(:ivar, :@client), :transaction),
|
52
|
+
s(:args),
|
53
|
+
s(:send,
|
54
|
+
s(:ivar, :@client), :update_attributes,
|
55
|
+
s(:send,
|
56
|
+
s(:send, nil, :params), :[],
|
57
|
+
s(:sym, :client)))), nil)))))
|
@@ -0,0 +1,49 @@
|
|
1
|
+
|
2
|
+
class ClientsController < ApplicationController
|
3
|
+
before_action :check_authorization
|
4
|
+
|
5
|
+
def index
|
6
|
+
if ::ScoutApm::AutoInstrument("params[:status] == \"activated\"",["BACKTRACE"]){params[:status] == "activated"}
|
7
|
+
@clients = ::ScoutApm::AutoInstrument("Client.activated",["BACKTRACE"]){Client.activated}
|
8
|
+
else
|
9
|
+
@clients = ::ScoutApm::AutoInstrument("Client.inactivated",["BACKTRACE"]){Client.inactivated}
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def create
|
14
|
+
@client = ::ScoutApm::AutoInstrument("Client.new(params[:client])",["BACKTRACE"]){Client.new(params[:client])}
|
15
|
+
if ::ScoutApm::AutoInstrument("@client.save",["BACKTRACE"]){@client.save}
|
16
|
+
::ScoutApm::AutoInstrument("redirect_to @client",["BACKTRACE"]){redirect_to @client}
|
17
|
+
else
|
18
|
+
# This line overrides the default rendering behavior, which
|
19
|
+
# would have been to render the "create" view.
|
20
|
+
::ScoutApm::AutoInstrument("render \"new\"",["BACKTRACE"]){render "new"}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def edit
|
25
|
+
@client = ::ScoutApm::AutoInstrument("Client.new(params[:client])",["BACKTRACE"]){Client.new(params[:client])}
|
26
|
+
|
27
|
+
if ::ScoutApm::AutoInstrument("request.post?",["BACKTRACE"]){request.post?}
|
28
|
+
::ScoutApm::AutoInstrument("@client.transaction do...",["BACKTRACE"]){@client.transaction do
|
29
|
+
@client.update_attributes(params[:client])
|
30
|
+
end}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def data
|
35
|
+
@clients = ::ScoutApm::AutoInstrument("Client.all",["BACKTRACE"]){Client.all}
|
36
|
+
|
37
|
+
formatter = ::ScoutApm::AutoInstrument("proc do |row|...",["BACKTRACE"]){proc do |row|
|
38
|
+
row.to_json
|
39
|
+
end}
|
40
|
+
|
41
|
+
::ScoutApm::AutoInstrument("respond_with @clients.each(&formatter).join(\"\\n\"), :content_type => 'application/json; boundary=NL'",["BACKTRACE"]){respond_with @clients.each(&formatter).join("\n"), :content_type => 'application/json; boundary=NL'}
|
42
|
+
end
|
43
|
+
|
44
|
+
def things
|
45
|
+
x = {}
|
46
|
+
x[:this] ||= 'foo'
|
47
|
+
x[:that] &&= ::ScoutApm::AutoInstrument("'foo'.size",["BACKTRACE"]){'foo'.size}
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
|
2
|
+
class ClientsController < ApplicationController
|
3
|
+
before_action :check_authorization
|
4
|
+
|
5
|
+
def index
|
6
|
+
if params[:status] == "activated"
|
7
|
+
@clients = Client.activated
|
8
|
+
else
|
9
|
+
@clients = Client.inactivated
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def create
|
14
|
+
@client = Client.new(params[:client])
|
15
|
+
if @client.save
|
16
|
+
redirect_to @client
|
17
|
+
else
|
18
|
+
# This line overrides the default rendering behavior, which
|
19
|
+
# would have been to render the "create" view.
|
20
|
+
render "new"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def edit
|
25
|
+
@client = Client.new(params[:client])
|
26
|
+
|
27
|
+
if request.post?
|
28
|
+
@client.transaction do
|
29
|
+
@client.update_attributes(params[:client])
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def data
|
35
|
+
@clients = Client.all
|
36
|
+
|
37
|
+
formatter = proc do |row|
|
38
|
+
row.to_json
|
39
|
+
end
|
40
|
+
|
41
|
+
respond_with @clients.each(&formatter).join("\n"), :content_type => 'application/json; boundary=NL'
|
42
|
+
end
|
43
|
+
|
44
|
+
def things
|
45
|
+
x = {}
|
46
|
+
x[:this] ||= 'foo'
|
47
|
+
x[:that] &&= 'foo'.size
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
|
2
|
+
class BrokenController < ApplicationController
|
3
|
+
rescue_from Exception do |e|
|
4
|
+
if e.is_a? Pundit::NotAuthorizedError
|
5
|
+
unauthorized_error
|
6
|
+
elsif e.is_a? ActionController::ParameterMissing
|
7
|
+
error(status: 422, message: e.message)
|
8
|
+
else
|
9
|
+
log_error(e)
|
10
|
+
error message: 'Internal error', exception: e
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
|
2
|
+
class BrokenController < ApplicationController
|
3
|
+
rescue_from Exception do |e|
|
4
|
+
if e.is_a? Pundit::NotAuthorizedError
|
5
|
+
unauthorized_error
|
6
|
+
elsif e.is_a? ActionController::ParameterMissing
|
7
|
+
error(status: 422, message: e.message)
|
8
|
+
else
|
9
|
+
log_error(e)
|
10
|
+
error message: 'Internal error', exception: e
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
require 'scout_apm/auto_instrument'
|
4
|
+
|
5
|
+
class AutoInstrumentTest < Minitest::Test
|
6
|
+
def source_path(name)
|
7
|
+
File.expand_path("auto_instrument/#{name}.rb", __dir__)
|
8
|
+
end
|
9
|
+
|
10
|
+
def instrumented_path(name)
|
11
|
+
File.expand_path("auto_instrument/#{name}-instrumented.rb", __dir__)
|
12
|
+
end
|
13
|
+
|
14
|
+
def instrumented_source(name)
|
15
|
+
File.read(instrumented_path(name))
|
16
|
+
end
|
17
|
+
|
18
|
+
# Autoinstruments adds a backtrace to each created layer. This is the full path to the
|
19
|
+
# test controller.rb file, which will be different on different environments.
|
20
|
+
# This normalizes backtraces across environments.
|
21
|
+
def normalize_backtrace(string)
|
22
|
+
string.gsub(/\[".+auto_instrument\/.+?:.+?"\]/,'["BACKTRACE"]')
|
23
|
+
end
|
24
|
+
|
25
|
+
# Use this to automatically update the test fixtures.
|
26
|
+
def update_instrumented_source(name)
|
27
|
+
File.write(
|
28
|
+
instrumented_path(name),
|
29
|
+
normalize_backtrace(::ScoutApm::AutoInstrument::Rails.rewrite(source_path(name)))
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_controller_rewrite
|
34
|
+
assert_equal instrumented_source("controller"),
|
35
|
+
normalize_backtrace(::ScoutApm::AutoInstrument::Rails.rewrite(source_path("controller")))
|
36
|
+
|
37
|
+
# update_instrumented_source("controller")
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_rescue_from_rewrite
|
41
|
+
assert_equal instrumented_source("rescue_from"),
|
42
|
+
normalize_backtrace(::ScoutApm::AutoInstrument::Rails.rewrite(source_path("rescue_from")))
|
43
|
+
|
44
|
+
# update_instrumented_source("rescue_from")
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_assignments_rewrite
|
48
|
+
assert_equal instrumented_source("assignments"),
|
49
|
+
normalize_backtrace(::ScoutApm::AutoInstrument::Rails.rewrite(source_path("assignments")))
|
50
|
+
|
51
|
+
# update_instrumented_source("assignments")
|
52
|
+
end
|
53
|
+
end if defined? ScoutApm::AutoInstrument
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: scout_apm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Derek Haynes
|
@@ -109,6 +109,20 @@ dependencies:
|
|
109
109
|
- - ">="
|
110
110
|
- !ruby/object:Gem::Version
|
111
111
|
version: '0'
|
112
|
+
- !ruby/object:Gem::Dependency
|
113
|
+
name: parser
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
type: :runtime
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
112
126
|
- !ruby/object:Gem::Dependency
|
113
127
|
name: activerecord
|
114
128
|
requirement: !ruby/object:Gem::Requirement
|
@@ -230,6 +244,11 @@ files:
|
|
230
244
|
- lib/scout_apm/agent_context.rb
|
231
245
|
- lib/scout_apm/app_server_load.rb
|
232
246
|
- lib/scout_apm/attribute_arranger.rb
|
247
|
+
- lib/scout_apm/auto_instrument.rb
|
248
|
+
- lib/scout_apm/auto_instrument/instruction_sequence.rb
|
249
|
+
- lib/scout_apm/auto_instrument/layer.rb
|
250
|
+
- lib/scout_apm/auto_instrument/parser.rb
|
251
|
+
- lib/scout_apm/auto_instrument/rails.rb
|
233
252
|
- lib/scout_apm/background_job_integrations/delayed_job.rb
|
234
253
|
- lib/scout_apm/background_job_integrations/que.rb
|
235
254
|
- lib/scout_apm/background_job_integrations/resque.rb
|
@@ -370,6 +389,14 @@ files:
|
|
370
389
|
- test/test_helper.rb
|
371
390
|
- test/tmp/README.md
|
372
391
|
- test/unit/agent_test.rb
|
392
|
+
- test/unit/auto_instrument/assignments-instrumented.rb
|
393
|
+
- test/unit/auto_instrument/assignments.rb
|
394
|
+
- test/unit/auto_instrument/controller-ast.txt
|
395
|
+
- test/unit/auto_instrument/controller-instrumented.rb
|
396
|
+
- test/unit/auto_instrument/controller.rb
|
397
|
+
- test/unit/auto_instrument/rescue_from-instrumented.rb
|
398
|
+
- test/unit/auto_instrument/rescue_from.rb
|
399
|
+
- test/unit/auto_instrument_test.rb
|
373
400
|
- test/unit/background_job_integrations/sidekiq_test.rb
|
374
401
|
- test/unit/config_test.rb
|
375
402
|
- test/unit/context_test.rb
|
@@ -430,7 +457,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
430
457
|
- !ruby/object:Gem::Version
|
431
458
|
version: '0'
|
432
459
|
requirements: []
|
433
|
-
rubygems_version: 3.0.
|
460
|
+
rubygems_version: 3.0.6
|
434
461
|
signing_key:
|
435
462
|
specification_version: 4
|
436
463
|
summary: Ruby application performance monitoring
|
@@ -439,6 +466,14 @@ test_files:
|
|
439
466
|
- test/test_helper.rb
|
440
467
|
- test/tmp/README.md
|
441
468
|
- test/unit/agent_test.rb
|
469
|
+
- test/unit/auto_instrument/assignments-instrumented.rb
|
470
|
+
- test/unit/auto_instrument/assignments.rb
|
471
|
+
- test/unit/auto_instrument/controller-ast.txt
|
472
|
+
- test/unit/auto_instrument/controller-instrumented.rb
|
473
|
+
- test/unit/auto_instrument/controller.rb
|
474
|
+
- test/unit/auto_instrument/rescue_from-instrumented.rb
|
475
|
+
- test/unit/auto_instrument/rescue_from.rb
|
476
|
+
- test/unit/auto_instrument_test.rb
|
442
477
|
- test/unit/background_job_integrations/sidekiq_test.rb
|
443
478
|
- test/unit/config_test.rb
|
444
479
|
- test/unit/context_test.rb
|