scout_apm 2.5.3 → 2.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|