scout_apm 5.3.3 → 5.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release.yml +30 -0
  3. data/.github/workflows/test.yml +13 -12
  4. data/CHANGELOG.markdown +24 -0
  5. data/README.markdown +5 -1
  6. data/gems/instruments.gemfile +2 -0
  7. data/gems/sqlite3-1.3.gemfile +3 -0
  8. data/lib/scout_apm/app_server_load.rb +1 -1
  9. data/lib/scout_apm/auto_instrument/rails.rb +21 -0
  10. data/lib/scout_apm/background_job_integrations/good_job.rb +49 -0
  11. data/lib/scout_apm/background_job_integrations/solid_queue.rb +47 -0
  12. data/lib/scout_apm/environment.rb +2 -0
  13. data/lib/scout_apm/instruments/action_view.rb +62 -3
  14. data/lib/scout_apm/instruments/active_record.rb +1 -1
  15. data/lib/scout_apm/instruments/net_http.rb +1 -1
  16. data/lib/scout_apm/layer_converters/external_service_converter.rb +1 -1
  17. data/lib/scout_apm/layer_converters/request_queue_time_converter.rb +7 -0
  18. data/lib/scout_apm/slow_request_policy.rb +1 -1
  19. data/lib/scout_apm/version.rb +1 -1
  20. data/lib/scout_apm.rb +2 -0
  21. data/scout_apm.gemspec +2 -2
  22. data/test/test_helper.rb +32 -0
  23. data/test/unit/auto_instrument/hash_shorthand_controller-instrumented.rb +41 -0
  24. data/test/unit/auto_instrument/hash_shorthand_controller.rb +41 -0
  25. data/test/unit/auto_instrument_test.rb +7 -1
  26. data/test/unit/environment_test.rb +0 -28
  27. data/test/unit/instruments/action_view_test.rb +102 -0
  28. data/test/unit/instruments/active_record_test.rb +30 -0
  29. data/test/unit/instruments/fixtures/test/_test_partial.html.erb +3 -0
  30. data/test/unit/instruments/fixtures/test/_test_partial_collection.html.erb +3 -0
  31. data/test/unit/instruments/fixtures/test_view.html.erb +10 -0
  32. metadata +17 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e1e83c44e949efe75ec1ee2c429b0596816c2b9f8e7c5416e7f3dd7243ef69c4
4
- data.tar.gz: 9b8a870504f4b32729ddb85f999cf7af30f6aac4215d356caefda6db2349dfb8
3
+ metadata.gz: 6e125a347fd3bbfa0b52f6dde597daf1dc95d97ba95cb6c44553bc36fae71b82
4
+ data.tar.gz: 9f0c708e386be527f255582195a791938c1663b418999816b79f25e4f7caaf6f
5
5
  SHA512:
6
- metadata.gz: ac045625ade6b059933ce82b1eaa014be50ce2eea7db34fb1e8a1f94145810c814bb95ba4cb8b70858547a3aaddebc812c8b3986e753f2f2304a7ae477a70974
7
- data.tar.gz: 19c81bacc7df78bbae4b6c9a03007a4ce84ce6c2de230e0e0e6b937e7b8ef4885788f70b36bebcd9cc192ae3aba3b0a7899830cf5ce20182fb95d19a4d2d1843
6
+ metadata.gz: d7e079bde40861cf673834ec606f8a09fc84bd4416eddccd22abd29d5cd64dbf071347d832549f840dd3b07026c8a3d4e6788f458587128482a1b44682479ec4
7
+ data.tar.gz: e20c7d5fadbfe008f1febb62e3e31e5aa23cc4410f02634329493b10159603fb467e4ae66d504709f247ff457b0bf5d386f62baa0835010e7e5a0954160f7b97
@@ -0,0 +1,30 @@
1
+ name: Release Gem
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+
8
+ # Directly from https://github.com/rubygems/release-gem/tree/v1
9
+ # Predicated on Tag Ruleset preventing arbitrary tag creation and
10
+ # requiring checks to pass for commit before tag creation
11
+ jobs:
12
+ push:
13
+ name: Push gem to RubyGems.org
14
+ runs-on: ubuntu-latest
15
+
16
+ permissions:
17
+ id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
18
+ contents: write # IMPORTANT: this permission is required for `rake release` to push the release tag
19
+
20
+ steps:
21
+ # Set up
22
+ - uses: actions/checkout@v4
23
+ - name: Set up Ruby
24
+ uses: ruby/setup-ruby@v1
25
+ with:
26
+ bundler-cache: true
27
+ ruby-version: ruby
28
+
29
+ # Release
30
+ - uses: rubygems/release-gem@v1
@@ -7,7 +7,7 @@ jobs:
7
7
  runs-on: ubuntu-latest
8
8
 
9
9
  steps:
10
- - uses: actions/checkout@v2
10
+ - uses: actions/checkout@v4
11
11
  - uses: ruby/setup-ruby@v1
12
12
  with:
13
13
  bundler-cache: true
@@ -23,7 +23,9 @@ jobs:
23
23
  gemfile: gems/rails3.gemfile
24
24
  - ruby: 2.2
25
25
  - ruby: 2.3
26
+ gemfile: gems/sqlite3-1.3.gemfile
26
27
  - ruby: 2.4
28
+ gemfile: gems/sqlite3-1.3.gemfile
27
29
  - ruby: 2.5
28
30
  - ruby: 2.6
29
31
  - ruby: 2.6
@@ -31,35 +33,34 @@ jobs:
31
33
  test_features: "typhoeus"
32
34
  - ruby: 2.6
33
35
  gemfile: gems/octoshark.gemfile
34
- - ruby: 2.6
35
- gemfile: gems/rails3.gemfile
36
- bundler: 1.17.3
37
36
  - ruby: 2.7
38
37
  - ruby: 2.7
39
38
  prepend: true
40
- - ruby: 3.0
41
- - ruby: 3.0
39
+ - ruby: "3.0"
40
+ - ruby: "3.0"
42
41
  prepend: true
43
- - ruby: 3.0
42
+ - ruby: "3.0"
44
43
  gemfile: gems/instruments.gemfile
45
44
  test_features: "instruments"
46
- - ruby: 3.0
45
+ - ruby: "3.0"
47
46
  gemfile: gems/instruments.gemfile
48
47
  prepend: true
49
48
  test_features: "instruments"
50
- - ruby: 3.0
49
+ - ruby: "3.0"
51
50
  gemfile: gems/sidekiq.gemfile
52
51
  test_features: "sidekiq_install"
53
-
52
+ - ruby: 3.1
53
+ - ruby: 3.2
54
54
  env:
55
55
  BUNDLE_GEMFILE: ${{ matrix.gemfile }}
56
56
  SCOUT_TEST_FEATURES: ${{ matrix.test_features }}
57
57
  SCOUT_USE_PREPEND: ${{ matrix.prepend }}
58
58
 
59
- runs-on: ubuntu-latest
59
+ # https://github.com/ruby/setup-ruby/issues/496
60
+ runs-on: ${{ matrix.ruby == '2.2' && 'ubuntu-20.04' || 'ubuntu-latest' }}
60
61
 
61
62
  steps:
62
- - uses: actions/checkout@v2
63
+ - uses: actions/checkout@v4
63
64
  - uses: ruby/setup-ruby@v1
64
65
  with:
65
66
  bundler-cache: true
data/CHANGELOG.markdown CHANGED
@@ -1,5 +1,29 @@
1
1
  # Unreleased
2
2
 
3
+ # 5.4.0
4
+ * Add support for GoodJob (#506)
5
+ * Add support for Solid Queue (#508)
6
+
7
+ # 5.3.8
8
+ * Avoid inaccurate websocket queue time capturing (#494)
9
+
10
+ # 5.3.7
11
+ * Fix parser dependency issue
12
+
13
+ # 5.3.6
14
+ * Fix AutoInstruments when instrumenting hash with shorthand (#486)
15
+ * Fix Connection Handling deprecation in ActiveRecord for Rails 7.1 (#483)
16
+ * Update ActionView partial instrumentation for Rails 7 (#487)
17
+
18
+ # 5.3.5
19
+ * Fix adding instrumentation of ActiveRecord after configuration has initialized for Rails versions greater than 3. (#465)
20
+ * Fix typo with double use of PercentilePolicy, instead of PercentPolicy, for scoring. (#468)
21
+ * Fix span annotations/desc for external service requests with the use of prepend. (#471)
22
+ * Fix ActiveSupport methods and replace them with non ActiveSupport methods. (#474)
23
+
24
+ # 5.3.4
25
+ Unused.
26
+
3
27
  # 5.3.3
4
28
 
5
29
  * Fix double firing of Puma `on_worker_boot` when preloading. (#463)
data/README.markdown CHANGED
@@ -1,6 +1,6 @@
1
1
  # ScoutApm Ruby Agent
2
2
 
3
- [![Build Status](https://travis-ci.org/scoutapp/scout_apm_ruby.svg?branch=master)](https://travis-ci.org/scoutapp/scout_apm_ruby)
3
+ [![Build Status](https://github.com/scoutapp/scout_apm_ruby/actions/workflows/test.yml/badge.svg)](https://github.com/scoutapp/scout_apm_ruby/actions)
4
4
 
5
5
  A Ruby gem for detailed Rails application performance monitoring 📈. Metrics and transaction traces are
6
6
  reported to [Scout](https://scoutapp.com), a hosted application monitoring
@@ -21,6 +21,10 @@ Add the gem to your Gemfile
21
21
 
22
22
  gem 'scout_apm'
23
23
 
24
+ Add [a version of the `parser` gem that supports your version of Ruby](https://github.com/whitequark/parser?tab=readme-ov-file#backwards-compatibility). For example, if you're on Ruby 3.3.0:
25
+
26
+ gem 'parser', '~> 3.3.0.0'
27
+
24
28
  Update your Gemfile
25
29
 
26
30
  bundle install
@@ -4,3 +4,5 @@ gem 'httpclient'
4
4
  gem 'http'
5
5
  gem 'redis'
6
6
  gem 'moped'
7
+ gem 'actionpack'
8
+ gem 'actionview'
@@ -0,0 +1,3 @@
1
+ eval_gemfile("../Gemfile")
2
+
3
+ gem "sqlite3", "~> 1.3.5"
@@ -51,7 +51,7 @@ module ScoutApm
51
51
  ensure
52
52
  # Sometimes :database_engine and :database_adapter can cause a reference to an AR connection.
53
53
  # Make sure we release all AR connections held by this thread.
54
- ActiveRecord::Base.clear_active_connections! if Utils::KlassHelper.defined?("ActiveRecord::Base")
54
+ ActiveRecord::Base.connection_handler.clear_active_connections! if Utils::KlassHelper.defined?("ActiveRecord::Base")
55
55
  end
56
56
 
57
57
  # Calls `.to_s` on the object passed in.
@@ -135,6 +135,27 @@ module ScoutApm
135
135
  wrap(node.location.expression, *instrument(node.location.expression.source, file_name, line))
136
136
  end
137
137
 
138
+ def on_hash(node)
139
+ node.children.each do |pair|
140
+ # Skip `pair` if we're sure it's not using the hash shorthand syntax
141
+ next if pair.type != :pair
142
+ key_node, value_node = pair.children
143
+ next unless key_node.type == :sym && value_node.type == :send
144
+ key = key_node.children[0]
145
+ next unless value_node.children.size == 2 && value_node.children[0].nil? && key == value_node.children[1]
146
+
147
+ # Extract useful metadata for instrumentation:
148
+ line = pair.location.line || 'line?'
149
+ # column = pair.location.column || 'column?' # not used
150
+ # method_name = key || '*unknown*' # not used
151
+ file_name = @source_rewriter.source_buffer.name
152
+
153
+ instrument_before, instrument_after = instrument(pair.location.expression.source, file_name, line)
154
+ replace(pair.loc.expression, "#{key}: #{instrument_before}#{key}#{instrument_after}")
155
+ end
156
+ super
157
+ end
158
+
138
159
  # def on_class(node)
139
160
  # class_name = node.children[1]
140
161
  #
@@ -0,0 +1,49 @@
1
+ module ScoutApm
2
+ module BackgroundJobIntegrations
3
+ class GoodJob
4
+ UNKNOWN_QUEUE_PLACEHOLDER = 'default'.freeze
5
+ attr_reader :logger
6
+
7
+ def name
8
+ :good_job
9
+ end
10
+
11
+ def present?
12
+ defined?(::GoodJob::VERSION)
13
+ end
14
+
15
+ def forking?
16
+ false
17
+ end
18
+
19
+ def install
20
+ ActiveSupport.on_load(:active_job) do
21
+ include ScoutApm::Tracer
22
+
23
+ around_perform do |job, block|
24
+ # I have a sneaking suspicion there is a better way to handle Agent starting
25
+ # Maybe hook into GoodJob lifecycle events?
26
+ req = ScoutApm::RequestManager.lookup
27
+ latency = Time.now - (job.scheduled_at || job.enqueued_at) rescue 0
28
+ req.annotate_request(queue_latency: latency)
29
+
30
+ begin
31
+ req.start_layer ScoutApm::Layer.new("Queue", job.queue_name.presence || UNKNOWN_QUEUE_PLACEHOLDER)
32
+ started_queue = true # Following Convention
33
+ req.start_layer ScoutApm::Layer.new("Job", job.class.name)
34
+ started_job = true # Following Convention
35
+
36
+ block.call
37
+ rescue
38
+ req.error!
39
+ raise
40
+ ensure
41
+ req.stop_layer if started_job
42
+ req.stop_layer if started_queue
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,47 @@
1
+ module ScoutApm
2
+ module BackgroundJobIntegrations
3
+ class SolidQueue
4
+ UNKNOWN_QUEUE_PLACEHOLDER = 'default'.freeze
5
+ attr_reader :logger
6
+
7
+ def name
8
+ :solid_queue
9
+ end
10
+
11
+ def present?
12
+ defined?(::SolidQueue::VERSION)
13
+ end
14
+
15
+ def forking?
16
+ false
17
+ end
18
+
19
+ def install
20
+ ActiveSupport.on_load(:active_job) do
21
+ include ScoutApm::Tracer
22
+
23
+ around_perform do |job, block|
24
+ req = ScoutApm::RequestManager.lookup
25
+ latency = Time.now - (job.scheduled_at || job.enqueued_at) rescue 0
26
+ req.annotate_request(queue_latency: latency)
27
+
28
+ begin
29
+ req.start_layer ScoutApm::Layer.new("Queue", job.queue_name.presence || UNKNOWN_QUEUE_PLACEHOLDER)
30
+ started_queue = true # Following Convention
31
+ req.start_layer ScoutApm::Layer.new("Job", job.class.name)
32
+ started_job = true # Following Convention
33
+
34
+ block.call
35
+ rescue
36
+ req.error!
37
+ raise
38
+ ensure
39
+ req.stop_layer if started_job
40
+ req.stop_layer if started_queue
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -31,6 +31,8 @@ module ScoutApm
31
31
  ScoutApm::BackgroundJobIntegrations::DelayedJob.new,
32
32
  ScoutApm::BackgroundJobIntegrations::Que.new,
33
33
  ScoutApm::BackgroundJobIntegrations::Faktory.new,
34
+ ScoutApm::BackgroundJobIntegrations::GoodJob.new,
35
+ ScoutApm::BackgroundJobIntegrations::SolidQueue.new,
34
36
  ]
35
37
 
36
38
  FRAMEWORK_INTEGRATIONS = [
@@ -72,9 +72,67 @@ module ScoutApm
72
72
 
73
73
  logger.info "Instrumenting ActionView::TemplateRenderer"
74
74
  ::ActionView::TemplateRenderer.prepend(ActionViewTemplateRendererInstruments)
75
+
76
+ if defined?(::ActionView::CollectionRenderer)
77
+ logger.info "Instrumenting ActionView::CollectionRenderer"
78
+ ::ActionView::CollectionRenderer.prepend(ActionViewCollectionRendererInstruments)
79
+ end
80
+ end
81
+
82
+ # In Rails 6.1 collection was moved to CollectionRenderer.
83
+ module ActionViewCollectionRendererInstruments
84
+ def render_collection(*args, **kwargs)
85
+ req = ScoutApm::RequestManager.lookup
86
+
87
+ maybe_template = args[3]
88
+
89
+ template_name ||= maybe_template.virtual_path rescue nil
90
+ template_name ||= "Unknown Collection"
91
+ layer_name = template_name + "/Rendering"
92
+
93
+ layer = ScoutApm::Layer.new("View", layer_name)
94
+ layer.subscopable!
95
+
96
+ begin
97
+ req.start_layer(layer)
98
+ if ScoutApm::Agent.instance.context.environment.supports_kwarg_delegation?
99
+ super(*args, **kwargs)
100
+ else
101
+ super(*args)
102
+ end
103
+ ensure
104
+ req.stop_layer
105
+ end
106
+ end
75
107
  end
76
108
 
77
109
  module ActionViewPartialRendererInstruments
110
+ # In Rails 6.1, render_partial was renamed to render_partial_template
111
+ def render_partial_template(*args, **kwargs)
112
+ req = ScoutApm::RequestManager.lookup
113
+
114
+ # Template was moved to the third argument in Rails 6.1.
115
+ maybe_template = args[2]
116
+
117
+ template_name ||= maybe_template.virtual_path rescue nil
118
+ template_name ||= "Unknown Partial"
119
+
120
+ layer_name = template_name + "/Rendering"
121
+ layer = ScoutApm::Layer.new("View", layer_name)
122
+ layer.subscopable!
123
+
124
+ begin
125
+ req.start_layer(layer)
126
+ if ScoutApm::Agent.instance.context.environment.supports_kwarg_delegation?
127
+ super(*args, **kwargs)
128
+ else
129
+ super(*args)
130
+ end
131
+ ensure
132
+ req.stop_layer
133
+ end
134
+ end
135
+
78
136
  # In Rails 6, the signature changed to pass the view & template args directly, as opposed to through the instance var
79
137
  # New signature is: def render_partial(view, template)
80
138
  def render_partial(*args, **kwargs)
@@ -83,7 +141,7 @@ module ScoutApm
83
141
  maybe_template = args[1]
84
142
 
85
143
  template_name = @template.virtual_path rescue nil # Works on Rails 3.2 -> end of Rails 5 series
86
- template_name ||= maybe_template.virtual_path rescue nil # Works on Rails 6 -> 6.0.3.5
144
+ template_name ||= maybe_template.virtual_path rescue nil # Works on Rails 6 -> 6.0.6
87
145
  template_name ||= "Unknown Partial"
88
146
 
89
147
  layer_name = template_name + "/Rendering"
@@ -102,13 +160,14 @@ module ScoutApm
102
160
  end
103
161
  end
104
162
 
163
+ # This method was moved in Rails 6.1 to CollectionRender.
105
164
  def collection_with_template(*args, **kwargs)
106
165
  req = ScoutApm::RequestManager.lookup
107
166
 
108
167
  maybe_template = args[1]
109
168
 
110
169
  template_name = @template.virtual_path rescue nil # Works on Rails 3.2 -> end of Rails 5 series
111
- template_name ||= maybe_template.virtual_path rescue nil # Works on Rails 6 -> 6.0.3.5
170
+ template_name ||= maybe_template.virtual_path rescue nil # Works on Rails 6 -> 6.0.6
112
171
  template_name ||= "Unknown Collection"
113
172
  layer_name = template_name + "/Rendering"
114
173
 
@@ -137,7 +196,7 @@ module ScoutApm
137
196
  maybe_template = args[1]
138
197
 
139
198
  template_name = args[0].virtual_path rescue nil # Works on Rails 3.2 -> end of Rails 5 series
140
- template_name ||= maybe_template.virtual_path rescue nil # Works on Rails 6 -> 6.1.3
199
+ template_name ||= maybe_template.virtual_path rescue nil # Works on Rails 6 -> 7.1.3
141
200
  template_name ||= "Unknown"
142
201
  layer_name = template_name + "/Rendering"
143
202
 
@@ -66,7 +66,7 @@ module ScoutApm
66
66
  defined?(::Rails) &&
67
67
  defined?(::Rails::VERSION) &&
68
68
  defined?(::Rails::VERSION::MAJOR) &&
69
- ::Rails::VERSION::MAJOR.to_i == 3 &&
69
+ ::Rails::VERSION::MAJOR.to_i >= 3 &&
70
70
  ::Rails.respond_to?(:configuration)
71
71
  end
72
72
 
@@ -60,7 +60,7 @@ module ScoutApm
60
60
 
61
61
  module NetHttpInstrumentationPrepend
62
62
  def request(request, *args, &block)
63
- self.class.instrument("HTTP", "request", :ignore_children => true, :desc => request_scout_description(args.first)) do
63
+ self.class.instrument("HTTP", "request", :ignore_children => true, :desc => request_scout_description(request)) do
64
64
  super(request, *args, &block)
65
65
  end
66
66
  end
@@ -53,7 +53,7 @@ module ScoutApm
53
53
  rescue
54
54
  # Do nothing
55
55
  ensure
56
- domain = DEFAULT_DOMAIN if domain.to_s.blank?
56
+ domain = DEFAULT_DOMAIN if (domain.nil? || domain.empty?)
57
57
  domain
58
58
  end
59
59
 
@@ -15,6 +15,9 @@ module ScoutApm
15
15
 
16
16
  return unless headers
17
17
 
18
+ # When an application uses Turbo Streams, we capture very innaccurate queue times.
19
+ return if request_over_websocket?
20
+
18
21
  raw_start = locate_timestamp
19
22
  return unless raw_start
20
23
 
@@ -38,6 +41,10 @@ module ScoutApm
38
41
 
39
42
  private
40
43
 
44
+ def request_over_websocket?
45
+ headers["Upgrade"] == "websocket"
46
+ end
47
+
41
48
  # Looks through the possible headers with this data, and extracts the raw
42
49
  # value of the header
43
50
  # Returns nil if not found
@@ -14,7 +14,7 @@ module ScoutApm
14
14
 
15
15
  def add_default_policies
16
16
  add(SlowPolicy::SpeedPolicy.new(context))
17
- add(SlowPolicy::PercentilePolicy.new(context))
17
+ add(SlowPolicy::PercentPolicy.new(context))
18
18
  add(SlowPolicy::AgePolicy.new(context))
19
19
  add(SlowPolicy::PercentilePolicy.new(context))
20
20
  end
@@ -1,3 +1,3 @@
1
1
  module ScoutApm
2
- VERSION = "5.3.3"
2
+ VERSION = "5.4.0"
3
3
  end
data/lib/scout_apm.rb CHANGED
@@ -67,6 +67,8 @@ require 'scout_apm/background_job_integrations/shoryuken'
67
67
  require 'scout_apm/background_job_integrations/sneakers'
68
68
  require 'scout_apm/background_job_integrations/que'
69
69
  require 'scout_apm/background_job_integrations/legacy_sneakers'
70
+ require 'scout_apm/background_job_integrations/good_job'
71
+ require 'scout_apm/background_job_integrations/solid_queue'
70
72
 
71
73
  require 'scout_apm/framework_integrations/rails_2'
72
74
  require 'scout_apm/framework_integrations/rails_3_or_4'
data/scout_apm.gemspec CHANGED
@@ -31,10 +31,10 @@ Gem::Specification.new do |s|
31
31
  s.add_runtime_dependency "parser"
32
32
 
33
33
  # These are general development dependencies which are used in instrumentation
34
- # tests. Specific versions are pulled in using specific gemfiles, e.g.
34
+ # tests. Specific versions are pulled in using specific gemfiles, e.g.
35
35
  # `gems/rails3.gemfile`.
36
36
  s.add_development_dependency "activerecord"
37
- s.add_development_dependency "sqlite3"
37
+ s.add_development_dependency "sqlite3", "~> 1.4"
38
38
 
39
39
  s.add_development_dependency "rubocop"
40
40
  s.add_development_dependency "guard"
data/test/test_helper.rb CHANGED
@@ -65,6 +65,38 @@ class FakeEnvironment
65
65
  end
66
66
  end
67
67
 
68
+ def remove_rails_namespace
69
+ Object.send(:remove_const, "Rails") if defined?(Rails)
70
+ end
71
+
72
+ def fake_rails(version)
73
+ remove_rails_namespace if (ENV["SCOUT_TEST_FEATURES"] || "").include?("instruments")
74
+
75
+ Kernel.const_set("Rails", Module.new)
76
+ Kernel.const_set("ActionController", Module.new)
77
+ r = Kernel.const_get("Rails")
78
+ r.const_set("VERSION", Module.new)
79
+ v = r.const_get("VERSION")
80
+ v.const_set("MAJOR", version)
81
+
82
+ assert_equal version, Rails::VERSION::MAJOR
83
+ end
84
+
85
+ def clean_fake_rails
86
+ Kernel.send(:remove_const, "Rails") if defined?(Kernel::Rails)
87
+ Kernel.send(:remove_const, "ActionController") if defined?(Kernel::ActionController)
88
+ end
89
+
90
+ def fake_sinatra
91
+ Kernel.const_set("Sinatra", Module.new)
92
+ s = Kernel.const_get("Sinatra")
93
+ s.const_set("Base", Module.new)
94
+ end
95
+
96
+ def clean_fake_sinatra
97
+ Kernel.const_unset("Sinatra") if defined?(Kernel::Sinatra)
98
+ end
99
+
68
100
  # Helpers available to all tests
69
101
  class Minitest::Test
70
102
  def setup
@@ -0,0 +1,41 @@
1
+
2
+ class HashShorthandController < ApplicationController
3
+ def hash
4
+ json = {
5
+ static: "static",
6
+ shorthand: ::ScoutApm::AutoInstrument("shorthand:",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:6:in `hash'"]){shorthand},
7
+ longhand: ::ScoutApm::AutoInstrument("longhand: longhand",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:7:in `hash'"]){longhand},
8
+ longhand_different_key: ::ScoutApm::AutoInstrument("longhand",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:8:in `hash'"]){longhand},
9
+ hash_rocket: ::ScoutApm::AutoInstrument(":hash_rocket => hash_rocket",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:9:in `hash'"]){hash_rocket},
10
+ :hash_rocket_different_key => ::ScoutApm::AutoInstrument("hash_rocket",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:10:in `hash'"]){hash_rocket},
11
+ non_nil_receiver: ::ScoutApm::AutoInstrument("non_nil_receiver.value",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:11:in `hash'"]){non_nil_receiver.value},
12
+ nested: {
13
+ shorthand: ::ScoutApm::AutoInstrument("shorthand:",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:13:in `hash'"]){shorthand},
14
+ },
15
+ nested_call: ::ScoutApm::AutoInstrument("nested_call(params[\"timestamp\"])",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:15:in `hash'"]){nested_call(params["timestamp"])}
16
+ }
17
+ ::ScoutApm::AutoInstrument("render json:",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:17:in `hash'"]){render json:}
18
+ end
19
+
20
+ private
21
+
22
+ def nested_call(noop)
23
+ noop
24
+ end
25
+
26
+ def shorthand
27
+ "shorthand"
28
+ end
29
+
30
+ def longhand
31
+ "longhand"
32
+ end
33
+
34
+ def hash_rocket
35
+ "hash_rocket"
36
+ end
37
+
38
+ def non_nil_receiver
39
+ ::ScoutApm::AutoInstrument("OpenStruct.new(value: \"value\")",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:39:in `non_nil_receiver'"]){OpenStruct.new(value: "value")}
40
+ end
41
+ end
@@ -0,0 +1,41 @@
1
+
2
+ class HashShorthandController < ApplicationController
3
+ def hash
4
+ json = {
5
+ static: "static",
6
+ shorthand:,
7
+ longhand: longhand,
8
+ longhand_different_key: longhand,
9
+ :hash_rocket => hash_rocket,
10
+ :hash_rocket_different_key => hash_rocket,
11
+ non_nil_receiver: non_nil_receiver.value,
12
+ nested: {
13
+ shorthand:,
14
+ },
15
+ nested_call: nested_call(params["timestamp"])
16
+ }
17
+ render json:
18
+ end
19
+
20
+ private
21
+
22
+ def nested_call(noop)
23
+ noop
24
+ end
25
+
26
+ def shorthand
27
+ "shorthand"
28
+ end
29
+
30
+ def longhand
31
+ "longhand"
32
+ end
33
+
34
+ def hash_rocket
35
+ "hash_rocket"
36
+ end
37
+
38
+ def non_nil_receiver
39
+ OpenStruct.new(value: "value")
40
+ end
41
+ end
@@ -4,7 +4,7 @@ require 'scout_apm/auto_instrument'
4
4
 
5
5
  class AutoInstrumentTest < Minitest::Test
6
6
  ROOT = File.expand_path("../../", __dir__)
7
-
7
+
8
8
  def source_path(name)
9
9
  File.expand_path("auto_instrument/#{name}.rb", __dir__)
10
10
  end
@@ -38,6 +38,12 @@ class AutoInstrumentTest < Minitest::Test
38
38
  normalize_backtrace(::ScoutApm::AutoInstrument::Rails.rewrite(source_path("controller")))
39
39
  end
40
40
 
41
+ def test_controller_rewrite_hash_shorthand
42
+ skip if RUBY_VERSION < "3.1"
43
+ assert_equal instrumented_source("hash_shorthand_controller"),
44
+ normalize_backtrace(::ScoutApm::AutoInstrument::Rails.rewrite(source_path("hash_shorthand_controller")))
45
+ end
46
+
41
47
  def test_rescue_from_rewrite
42
48
  # update_instrumented_source("rescue_from")
43
49
 
@@ -29,32 +29,4 @@ class EnvironmentTest < Minitest::Test
29
29
  def test_framework_ruby
30
30
  assert_equal :ruby, ScoutApm::Environment.send(:new).framework
31
31
  end
32
-
33
- ############################################################
34
-
35
- def fake_rails(version)
36
- Kernel.const_set("Rails", Module.new)
37
- Kernel.const_set("ActionController", Module.new)
38
- r = Kernel.const_get("Rails")
39
- r.const_set("VERSION", Module.new)
40
- v = r.const_get("VERSION")
41
- v.const_set("MAJOR", version)
42
-
43
- assert_equal version, Rails::VERSION::MAJOR
44
- end
45
-
46
- def clean_fake_rails
47
- Kernel.send(:remove_const, "Rails") if defined?(Kernel::Rails)
48
- Kernel.send(:remove_const, "ActionController") if defined?(Kernel::ActionController)
49
- end
50
-
51
- def fake_sinatra
52
- Kernel.const_set("Sinatra", Module.new)
53
- s = Kernel.const_get("Sinatra")
54
- s.const_set("Base", Module.new)
55
- end
56
-
57
- def clean_fake_sinatra
58
- Kernel.const_unset("Sinatra") if defined?(Kernel::Sinatra)
59
- end
60
32
  end
@@ -0,0 +1,102 @@
1
+ # Most of this was taken from Rails:
2
+ # https://github.com/rails/rails/blob/v7.1.3/actionview/test/actionpack/controller/render_test.rb
3
+ # https://github.com/rails/rails/blob/v7.1.3/actionview/test/abstract_unit.rb
4
+
5
+ if (ENV["SCOUT_TEST_FEATURES"] || "").include?("instruments")
6
+ require 'test_helper'
7
+ require 'action_view'
8
+ require 'action_pack'
9
+ require 'action_controller'
10
+
11
+ FIXTURE_LOAD_PATH = File.expand_path("fixtures", __dir__)
12
+
13
+ include ActionView::Context
14
+ include ActionView::Helpers::TagHelper
15
+ include ActionView::Helpers::TextHelper
16
+
17
+ module ActionController
18
+
19
+ class Base
20
+ self.view_paths = FIXTURE_LOAD_PATH
21
+
22
+ def self.test_routes(&block)
23
+ routes = ActionDispatch::Routing::RouteSet.new
24
+ routes.draw(&block)
25
+ include routes.url_helpers
26
+ routes
27
+ end
28
+ end
29
+
30
+ class TestCase
31
+ include ActionDispatch::TestProcess
32
+
33
+ def self.with_routes(&block)
34
+ routes = ActionDispatch::Routing::RouteSet.new
35
+ routes.draw(&block)
36
+ include Module.new {
37
+ define_method(:setup) do
38
+ super()
39
+ @routes = routes
40
+ @controller.singleton_class.include @routes.url_helpers if @controller
41
+ end
42
+ }
43
+ routes
44
+ end
45
+ end
46
+ end
47
+
48
+ class TestController < ActionController::Base
49
+
50
+ def render_test_view
51
+ render template: "test_view"
52
+ end
53
+ end
54
+
55
+ class RenderTest < ActionController::TestCase
56
+
57
+ tests TestController
58
+
59
+ with_routes do
60
+ get :render_test_view, to: "test#render_test_view"
61
+ end
62
+
63
+ def setup
64
+ super
65
+ @controller.logger = ActiveSupport::Logger.new(nil)
66
+ ActionView::Base.logger = ActiveSupport::Logger.new(nil)
67
+
68
+ @request.host = "www.scoutapm.com"
69
+
70
+ @old_view_paths = ActionController::Base.view_paths
71
+ ActionController::Base.view_paths = FIXTURE_LOAD_PATH
72
+ end
73
+
74
+ def teardown
75
+ ActionView::Base.logger = nil
76
+
77
+ ActionController::Base.view_paths = @old_view_paths
78
+ end
79
+
80
+ def test_partial_instrumentation
81
+ recorder = FakeRecorder.new
82
+ agent_context.recorder = recorder
83
+
84
+ instrument = ScoutApm::Instruments::ActionView.new(agent_context)
85
+ instrument.install(prepend: true)
86
+
87
+ get :render_test_view
88
+ assert_response :success
89
+
90
+ root_layer = recorder.requests.first.root_layer
91
+ children = root_layer.children.to_a
92
+ assert_equal 2, children.size
93
+
94
+ partial_layer = children[0]
95
+ collection_layer = children[1]
96
+
97
+ assert_equal "test_view/Rendering", root_layer.name
98
+ assert_equal "test/_test_partial/Rendering", partial_layer.name
99
+ assert_equal "test/_test_partial_collection/Rendering", collection_layer.name
100
+ end
101
+ end
102
+ end
@@ -24,6 +24,36 @@ class ActiveRecordTest < Minitest::Test
24
24
  class User < ActiveRecord::Base
25
25
  end
26
26
 
27
+ class DumbRailsConfig
28
+ def self.after_initialize; end
29
+ end
30
+
31
+ def test_old_rails_initialization
32
+ recorder = FakeRecorder.new
33
+ agent_context.recorder = recorder
34
+ old_rails_version = (1..2).to_a.sample
35
+ fake_rails(old_rails_version)
36
+
37
+ ::Rails.expects(:configuration).never
38
+
39
+ instrument = ScoutApm::Instruments::ActiveRecord.new(agent_context)
40
+ instrument.install(prepend: false)
41
+ clean_fake_rails
42
+ end
43
+
44
+ def test_modern_rails_initialization
45
+ recorder = FakeRecorder.new
46
+ agent_context.recorder = recorder
47
+ modern_rails_version = (3..7).to_a.sample
48
+ fake_rails(modern_rails_version)
49
+
50
+ ::Rails.expects(:configuration).returns(DumbRailsConfig).once
51
+
52
+ instrument = ScoutApm::Instruments::ActiveRecord.new(agent_context)
53
+ instrument.install(prepend: false)
54
+ clean_fake_rails
55
+ end
56
+
27
57
  def test_instrumentation
28
58
  recorder = FakeRecorder.new
29
59
  agent_context.recorder = recorder
@@ -0,0 +1,3 @@
1
+ <div>
2
+ <p><%= message %></p>
3
+ </div>
@@ -0,0 +1,3 @@
1
+ <div>
2
+ <p><%= index %></p>
3
+ </div>
@@ -0,0 +1,10 @@
1
+ <html>
2
+ <head>
3
+ <title>View</title>
4
+ </head>
5
+ <body>
6
+ <h1>View</h1>
7
+ <%= render partial: "test_partial", locals: { message: 'Partial' } %>
8
+ <%= render partial: "test_partial_collection", collection: [1, 2, 3], as: :index %>
9
+ </body>
10
+ </html>
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: 5.3.3
4
+ version: 5.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Derek Haynes
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2022-12-09 00:00:00.000000000 Z
12
+ date: 2024-09-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: minitest
@@ -141,16 +141,16 @@ dependencies:
141
141
  name: sqlite3
142
142
  requirement: !ruby/object:Gem::Requirement
143
143
  requirements:
144
- - - ">="
144
+ - - "~>"
145
145
  - !ruby/object:Gem::Version
146
- version: '0'
146
+ version: '1.4'
147
147
  type: :development
148
148
  prerelease: false
149
149
  version_requirements: !ruby/object:Gem::Requirement
150
150
  requirements:
151
- - - ">="
151
+ - - "~>"
152
152
  - !ruby/object:Gem::Version
153
- version: '0'
153
+ version: '1.4'
154
154
  - !ruby/object:Gem::Dependency
155
155
  name: rubocop
156
156
  requirement: !ruby/object:Gem::Requirement
@@ -216,6 +216,7 @@ extensions:
216
216
  - ext/rusage/extconf.rb
217
217
  extra_rdoc_files: []
218
218
  files:
219
+ - ".github/workflows/release.yml"
219
220
  - ".github/workflows/test.yml"
220
221
  - ".gitignore"
221
222
  - ".rubocop.yml"
@@ -239,6 +240,7 @@ files:
239
240
  - gems/rails5.gemfile
240
241
  - gems/rails6.gemfile
241
242
  - gems/sidekiq.gemfile
243
+ - gems/sqlite3-1.3.gemfile
242
244
  - gems/typhoeus.gemfile
243
245
  - lib/scout_apm.rb
244
246
  - lib/scout_apm/agent.rb
@@ -254,12 +256,14 @@ files:
254
256
  - lib/scout_apm/auto_instrument/rails.rb
255
257
  - lib/scout_apm/background_job_integrations/delayed_job.rb
256
258
  - lib/scout_apm/background_job_integrations/faktory.rb
259
+ - lib/scout_apm/background_job_integrations/good_job.rb
257
260
  - lib/scout_apm/background_job_integrations/legacy_sneakers.rb
258
261
  - lib/scout_apm/background_job_integrations/que.rb
259
262
  - lib/scout_apm/background_job_integrations/resque.rb
260
263
  - lib/scout_apm/background_job_integrations/shoryuken.rb
261
264
  - lib/scout_apm/background_job_integrations/sidekiq.rb
262
265
  - lib/scout_apm/background_job_integrations/sneakers.rb
266
+ - lib/scout_apm/background_job_integrations/solid_queue.rb
263
267
  - lib/scout_apm/background_recorder.rb
264
268
  - lib/scout_apm/background_worker.rb
265
269
  - lib/scout_apm/bucket_name_splitter.rb
@@ -423,6 +427,8 @@ files:
423
427
  - test/unit/auto_instrument/controller-instrumented.rb
424
428
  - test/unit/auto_instrument/controller.rb
425
429
  - test/unit/auto_instrument/hanging_method.rb
430
+ - test/unit/auto_instrument/hash_shorthand_controller-instrumented.rb
431
+ - test/unit/auto_instrument/hash_shorthand_controller.rb
426
432
  - test/unit/auto_instrument/rescue_from-instrumented.rb
427
433
  - test/unit/auto_instrument/rescue_from.rb
428
434
  - test/unit/auto_instrument_test.rb
@@ -442,7 +448,11 @@ files:
442
448
  - test/unit/git_revision_test.rb
443
449
  - test/unit/histogram_test.rb
444
450
  - test/unit/ignored_uris_test.rb
451
+ - test/unit/instruments/action_view_test.rb
445
452
  - test/unit/instruments/active_record_test.rb
453
+ - test/unit/instruments/fixtures/test/_test_partial.html.erb
454
+ - test/unit/instruments/fixtures/test/_test_partial_collection.html.erb
455
+ - test/unit/instruments/fixtures/test_view.html.erb
446
456
  - test/unit/instruments/http_client_test.rb
447
457
  - test/unit/instruments/http_test.rb
448
458
  - test/unit/instruments/moped_test.rb
@@ -495,7 +505,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
495
505
  - !ruby/object:Gem::Version
496
506
  version: '0'
497
507
  requirements: []
498
- rubygems_version: 3.0.3
508
+ rubygems_version: 3.5.16
499
509
  signing_key:
500
510
  specification_version: 4
501
511
  summary: Ruby application performance monitoring