scout_apm 5.3.3 → 5.6.4

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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release.yml +30 -0
  3. data/.github/workflows/test.yml +18 -16
  4. data/CHANGELOG.markdown +51 -1
  5. data/README.markdown +5 -1
  6. data/gems/instruments.gemfile +2 -0
  7. data/gems/rails7.gemfile +4 -0
  8. data/gems/sidekiq7.gemfile +3 -0
  9. data/gems/sidekiq8.gemfile +4 -0
  10. data/gems/sqlite3-v2.gemfile +3 -0
  11. data/lib/scout_apm/agent.rb +1 -1
  12. data/lib/scout_apm/agent_context.rb +4 -0
  13. data/lib/scout_apm/app_server_load.rb +1 -1
  14. data/lib/scout_apm/auto_instrument/layer.rb +1 -0
  15. data/lib/scout_apm/auto_instrument/rails.rb +21 -0
  16. data/lib/scout_apm/background_job_integrations/good_job.rb +49 -0
  17. data/lib/scout_apm/background_job_integrations/resque.rb +13 -27
  18. data/lib/scout_apm/background_job_integrations/sidekiq.rb +22 -2
  19. data/lib/scout_apm/background_job_integrations/solid_queue.rb +47 -0
  20. data/lib/scout_apm/config.rb +69 -34
  21. data/lib/scout_apm/environment.rb +2 -0
  22. data/lib/scout_apm/error_service/sidekiq.rb +1 -1
  23. data/lib/scout_apm/git_revision.rb +7 -1
  24. data/lib/scout_apm/instruments/action_view.rb +62 -3
  25. data/lib/scout_apm/instruments/active_record.rb +1 -1
  26. data/lib/scout_apm/instruments/grape.rb +1 -1
  27. data/lib/scout_apm/instruments/mongoid.rb +7 -35
  28. data/lib/scout_apm/instruments/net_http.rb +1 -1
  29. data/lib/scout_apm/instruments/resque.rb +31 -2
  30. data/lib/scout_apm/layer_converters/external_service_converter.rb +1 -1
  31. data/lib/scout_apm/layer_converters/request_queue_time_converter.rb +7 -0
  32. data/lib/scout_apm/sampling.rb +104 -0
  33. data/lib/scout_apm/slow_request_policy.rb +1 -1
  34. data/lib/scout_apm/tracked_request.rb +7 -1
  35. data/lib/scout_apm/utils/installed_gems.rb +2 -1
  36. data/lib/scout_apm/version.rb +1 -1
  37. data/lib/scout_apm.rb +3 -0
  38. data/scout_apm.gemspec +2 -2
  39. data/test/test_helper.rb +61 -0
  40. data/test/unit/auto_instrument/hash_shorthand_controller-instrumented.rb +41 -0
  41. data/test/unit/auto_instrument/hash_shorthand_controller.rb +41 -0
  42. data/test/unit/auto_instrument_test.rb +7 -1
  43. data/test/unit/background_job_integrations/sidekiq_test.rb +13 -3
  44. data/test/unit/config_test.rb +20 -0
  45. data/test/unit/environment_test.rb +0 -28
  46. data/test/unit/git_revision_test.rb +65 -3
  47. data/test/unit/instruments/action_view_test.rb +102 -0
  48. data/test/unit/instruments/active_record_test.rb +30 -0
  49. data/test/unit/instruments/fixtures/test/_test_partial.html.erb +3 -0
  50. data/test/unit/instruments/fixtures/test/_test_partial_collection.html.erb +3 -0
  51. data/test/unit/instruments/fixtures/test_view.html.erb +10 -0
  52. data/test/unit/sampling_test.rb +215 -0
  53. metadata +22 -10
@@ -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 = [
@@ -28,7 +28,7 @@ module ScoutApm
28
28
 
29
29
  def install_sidekiq_with_error_handler
30
30
  ::Sidekiq.configure_server do |config|
31
- config.error_handlers << proc { |exception, job_info|
31
+ config.error_handlers << proc { |exception, job_info, sidekiq_config|
32
32
  context = ScoutApm::Agent.instance.context
33
33
 
34
34
  # Bail out early, and reraise if the error is not interesting.
@@ -20,6 +20,7 @@ module ScoutApm
20
20
  detect_from_config ||
21
21
  detect_from_heroku ||
22
22
  detect_from_capistrano ||
23
+ detect_from_kamal ||
23
24
  detect_from_mina ||
24
25
  detect_from_git
25
26
  end
@@ -44,8 +45,13 @@ module ScoutApm
44
45
  nil
45
46
  end
46
47
 
48
+ # https://github.com/basecamp/kamal
49
+ def detect_from_kamal
50
+ ENV['KAMAL_VERSION']
51
+ end
52
+
47
53
  # https://github.com/mina-deploy/mina
48
- def detect_from_mina
54
+ def detect_from_mina
49
55
  File.read(File.join(app_root, '.mina_git_revision')).strip
50
56
  rescue
51
57
  logger.debug "Unable to detect Git Revision from Mina: #{$!.message}"
@@ -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
 
@@ -53,7 +53,7 @@ module ScoutApm
53
53
  self.options[:path].first,
54
54
  ].compact.map{ |n| n.to_s }.join("/")
55
55
  rescue => e
56
- logger.info("Error getting Grape Endpoint Name. Error: #{e.message}. Options: #{self.options.inspect}")
56
+ ScoutApm::Agent.instance.context.logger.info("Error getting Grape Endpoint Name. Error: #{e.message}. Options: #{self.options.inspect}")
57
57
  name = "Grape/Unknown"
58
58
  end
59
59
 
@@ -20,25 +20,12 @@ module ScoutApm
20
20
  @installed = true
21
21
 
22
22
  # Mongoid versions that use Moped should instrument Moped.
23
+ ### See moped instrument for Moped driven deploys
23
24
  if defined?(::Mongoid) and !defined?(::Moped)
24
- logger.info "Instrumenting Mongoid 2.x"
25
25
  @installed = true
26
26
 
27
- ### OLD (2.x) mongoids
28
- if defined?(::Mongoid::Collection)
29
- ::Mongoid::Collection.class_eval do
30
- include ScoutApm::Tracer
31
- (::Mongoid::Collections::Operations::ALL - [:<<, :[]]).each do |method|
32
- instrument_method method, :type => "MongoDB", :name => '#{@klass}/' + method.to_s
33
- end
34
- end
35
- end
36
-
37
- ### See moped instrument for Moped driven deploys
38
-
39
- ### 5.x Mongoid
40
- if (mongoid_v5? || mongoid_v6? || mongoid_v7?) && defined?(::Mongoid::Contextual::Mongo)
41
- logger.info "Instrumenting Mongoid 5.x/6.x/7.x"
27
+ if (mongoid_at_least_5?) && defined?(::Mongoid::Contextual::Mongo)
28
+ logger.info "Instrumenting Mongoid"
42
29
  # All the public methods from Mongoid::Contextual::Mongo.
43
30
  # TODO: Geo and MapReduce support (?). They are in other Contextual::* classes
44
31
  methods = [
@@ -88,31 +75,17 @@ module ScoutApm
88
75
  ]
89
76
 
90
77
  ::Mongoid::Contextual::Mongo.class_eval(with_scout_instruments)
78
+ else
79
+ logger.warn "Expected method #{method} not defined in Mongoid::Contextual::Mongo."
91
80
  end
92
81
  end
93
82
  end
94
83
  end
95
84
  end
96
85
 
97
- def mongoid_v5?
98
- if defined?(::Mongoid::VERSION)
99
- ::Mongoid::VERSION =~ /\A5/
100
- else
101
- false
102
- end
103
- end
104
-
105
- def mongoid_v6?
86
+ def mongoid_at_least_5?
106
87
  if defined?(::Mongoid::VERSION)
107
- ::Mongoid::VERSION =~ /\A6/
108
- else
109
- false
110
- end
111
- end
112
-
113
- def mongoid_v7?
114
- if defined?(::Mongoid::VERSION)
115
- ::Mongoid::VERSION =~ /\A7/
88
+ ::Mongoid::VERSION =~ /\A[56789]/
116
89
  else
117
90
  false
118
91
  end
@@ -134,4 +107,3 @@ module ScoutApm
134
107
  end
135
108
  end
136
109
  end
137
-
@@ -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
@@ -1,6 +1,15 @@
1
1
  module ScoutApm
2
2
  module Instruments
3
3
  module Resque
4
+ def before_perform_become_client(*args)
5
+ # Don't become remote client if explicitly disabled or if forking is disabled to force synchronous recording.
6
+ if config.value('start_resque_server_instrument') && forking?
7
+ ScoutApm::Agent.instance.context.become_remote_client!(bind, port)
8
+ else
9
+ logger.debug("Not becoming remote client due to 'start_resque_server_instrument' setting or 'fork_per_job' setting")
10
+ end
11
+ end
12
+
4
13
  def around_perform_with_scout_instruments(*args)
5
14
  job_name = self.to_s
6
15
  queue = find_queue
@@ -17,7 +26,6 @@ module ScoutApm
17
26
  started_queue = true
18
27
  req.start_layer(ScoutApm::Layer.new('Job', job_name))
19
28
  started_job = true
20
-
21
29
  yield
22
30
  rescue => e
23
31
  req.error!
@@ -33,7 +41,28 @@ module ScoutApm
33
41
  return queue if self.respond_to?(:queue)
34
42
  return "unknown"
35
43
  end
44
+
45
+ private
46
+
47
+ def bind
48
+ config.value("remote_agent_host")
49
+ end
50
+
51
+ def port
52
+ config.value("remote_agent_port")
53
+ end
54
+
55
+ def config
56
+ @config ||= ScoutApm::Agent.instance.context.config
57
+ end
58
+
59
+ def logger
60
+ @logger ||= ScoutApm::Agent.instance.context.logger
61
+ end
62
+
63
+ def forking?
64
+ @forking ||= ENV["FORK_PER_JOB"] != "false"
65
+ end
36
66
  end
37
67
  end
38
68
  end
39
-
@@ -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
@@ -0,0 +1,104 @@
1
+ module ScoutApm
2
+ class Sampling
3
+ attr_reader :global_sample_rate, :sample_endpoints, :sample_uri_regex, :sample_jobs, :ignore_uri_regex, :ignore_jobs
4
+
5
+ def initialize(config)
6
+ @global_sample_rate = config.value('sample_rate')
7
+ # web endpoints matched prefix by regex
8
+ # jobs matched explicitly by name
9
+
10
+ # for now still support old config key ('ignore') for backwards compatibility
11
+ @ignore_endpoints = config.value('ignore').present? ? config.value('ignore') : config.value('ignore_endpoints')
12
+ @sample_endpoints = individual_sample_to_hash(config.value('sample_endpoints'))
13
+ @endpoint_sample_rate = config.value('endpoint_sample_rate')
14
+
15
+ @ignore_jobs = config.value('ignore_jobs')
16
+ @sample_jobs = individual_sample_to_hash(config.value('sample_jobs'))
17
+ @job_sample_rate = config.value('job_sample_rate')
18
+
19
+ log_string = "Sampling initialized with config: "
20
+ log_string += "global_sample_rate: #{@global_sample_rate.inspect}, "
21
+ log_string += "endpoint_sample_rate: #{@endpoint_sample_rate.inspect}, "
22
+ log_string += "sample_endpoints: #{@sample_endpoints.inspect}, "
23
+ log_string += "ignore_endpoints: #{@ignore_endpoints.inspect}, "
24
+ log_string += "job_sample_rate: #{@job_sample_rate.inspect}, "
25
+ log_string += "sample_jobs: #{@sample_jobs.inspect}, "
26
+ log_string += "ignore_jobs: #{@ignore_jobs.inspect}"
27
+ logger.info(log_string)
28
+ end
29
+
30
+ def drop_request?(transaction)
31
+ # Individual endpoint/job sampling takes precedence over ignoring.
32
+ # Individual endpoint/job sample rate always takes precedence over general endpoint/job rate.
33
+ # General endpoint/job rate always takes precedence over global sample rate
34
+ if transaction.job?
35
+ job_name = transaction.layer_finder.job.name
36
+ rate = job_sample_rate(job_name)
37
+ return sample?(rate) unless rate.nil?
38
+ return true if ignore_job?(job_name)
39
+ return sample?(@job_sample_rate) unless @job_sample_rate.nil?
40
+ elsif transaction.web?
41
+ uri = transaction.annotations[:uri]
42
+ rate = web_sample_rate(uri)
43
+ return sample?(rate) unless rate.nil?
44
+ return true if ignore_uri?(uri)
45
+ return sample?(@endpoint_sample_rate) unless @endpoint_sample_rate.nil?
46
+ end
47
+
48
+ # global sample check
49
+ if @global_sample_rate
50
+ return sample?(@global_sample_rate)
51
+ end
52
+
53
+ false # don't drop the request
54
+ end
55
+
56
+ def individual_sample_to_hash(sampling_config)
57
+ return nil if sampling_config.blank?
58
+ # config looks like ['/foo:50','/bar:100']. parse it into hash of string: integer
59
+ sample_hash = {}
60
+ sampling_config.each do |sample|
61
+ path, rate = sample.split(':')
62
+ sample_hash[path] = rate.to_i
63
+ end
64
+ sample_hash
65
+ end
66
+
67
+ def ignore_uri?(uri)
68
+ return false if @ignore_endpoints.blank?
69
+ @ignore_endpoints.each do |prefix|
70
+ return true if uri.start_with?(prefix)
71
+ end
72
+ false
73
+ end
74
+
75
+ def web_sample_rate(uri)
76
+ return nil if @sample_endpoints.blank?
77
+ @sample_endpoints.each do |prefix, rate|
78
+ return rate if uri.start_with?(prefix)
79
+ end
80
+ nil
81
+ end
82
+
83
+ def ignore_job?(job_name)
84
+ return false if @ignore_jobs.blank?
85
+ @ignore_jobs.include?(job_name)
86
+ end
87
+
88
+ def job_sample_rate(job_name)
89
+ return nil if @sample_jobs.blank?
90
+ @sample_jobs.fetch(job_name, nil)
91
+ end
92
+
93
+ def sample?(rate)
94
+ rand * 100 > rate
95
+ end
96
+
97
+ private
98
+
99
+ def logger
100
+ ScoutApm::Agent.instance.logger
101
+ end
102
+
103
+ end
104
+ end
@@ -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,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # A TrackedRequest is a stack of layers, where completed layers (go into, then
2
4
  # come out of a layer) are forgotten as they finish. Layers are attached to
3
5
  # their children as the process goes, building a tree structure within the
@@ -303,7 +305,11 @@ module ScoutApm
303
305
  restore_from_dump! if @agent_context.nil?
304
306
 
305
307
  # Bail out early if the user asked us to ignore this uri
306
- return if @agent_context.ignored_uris.ignore?(annotations[:uri])
308
+ # return if @agent_context.ignored_uris.ignore?(annotations[:uri])
309
+ if @agent_context.sampling.drop_request?(self)
310
+ logger.debug("Dropping request due to sampling")
311
+ return
312
+ end
307
313
 
308
314
  apply_name_override
309
315
 
@@ -12,7 +12,8 @@ module ScoutApm
12
12
  end
13
13
 
14
14
  def run
15
- Bundler.rubygems.all_specs.map {|spec| [spec.name, spec.version.to_s] }
15
+ specs = Bundler.rubygems.public_send(Bundler.rubygems.respond_to?(:installed_specs) ? :installed_specs : :all_specs)
16
+ specs.map { |spec| [spec.name, spec.version.to_s] }
16
17
  rescue => e
17
18
  logger.warn("Couldn't fetch Gem information: #{e.message}")
18
19
  []
@@ -1,3 +1,3 @@
1
1
  module ScoutApm
2
- VERSION = "5.3.3"
2
+ VERSION = "5.6.4"
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'
@@ -109,6 +111,7 @@ require 'scout_apm/instruments/samplers'
109
111
  require 'scout_apm/app_server_load'
110
112
 
111
113
  require 'scout_apm/ignored_uris.rb'
114
+ require 'scout_apm/sampling.rb'
112
115
  require 'scout_apm/utils/active_record_metric_name'
113
116
  require 'scout_apm/utils/backtrace_parser'
114
117
  require 'scout_apm/utils/installed_gems'
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
@@ -38,6 +38,10 @@ class FakeConfigOverlay
38
38
  @values[key]
39
39
  end
40
40
 
41
+ def values
42
+ @values
43
+ end
44
+
41
45
  def has_key?(key)
42
46
  @values.has_key?(key)
43
47
  end
@@ -65,6 +69,38 @@ class FakeEnvironment
65
69
  end
66
70
  end
67
71
 
72
+ def remove_rails_namespace
73
+ Object.send(:remove_const, "Rails") if defined?(Rails)
74
+ end
75
+
76
+ def fake_rails(version)
77
+ remove_rails_namespace if (ENV["SCOUT_TEST_FEATURES"] || "").include?("instruments")
78
+
79
+ Kernel.const_set("Rails", Module.new)
80
+ Kernel.const_set("ActionController", Module.new)
81
+ r = Kernel.const_get("Rails")
82
+ r.const_set("VERSION", Module.new)
83
+ v = r.const_get("VERSION")
84
+ v.const_set("MAJOR", version)
85
+
86
+ assert_equal version, Rails::VERSION::MAJOR
87
+ end
88
+
89
+ def clean_fake_rails
90
+ Kernel.send(:remove_const, "Rails") if defined?(Kernel::Rails)
91
+ Kernel.send(:remove_const, "ActionController") if defined?(Kernel::ActionController)
92
+ end
93
+
94
+ def fake_sinatra
95
+ Kernel.const_set("Sinatra", Module.new)
96
+ s = Kernel.const_get("Sinatra")
97
+ s.const_set("Base", Module.new)
98
+ end
99
+
100
+ def clean_fake_sinatra
101
+ Kernel.const_unset("Sinatra") if defined?(Kernel::Sinatra)
102
+ end
103
+
68
104
  # Helpers available to all tests
69
105
  class Minitest::Test
70
106
  def setup
@@ -137,3 +173,28 @@ end
137
173
  class Minitest::Test
138
174
  include CustomAsserts
139
175
  end
176
+
177
+ class FakeTrackedRequest
178
+ def self.new_web_request(uri)
179
+ context = ScoutApm::Agent.instance.context
180
+ fake_store = ScoutApm::FakeStore.new
181
+ req = ScoutApm::TrackedRequest.new(context, fake_store)
182
+
183
+ first_layer = ScoutApm::Layer.new("Controller", "index")
184
+ req.start_layer(first_layer)
185
+ req.annotate_request(:uri => uri)
186
+
187
+ req
188
+ end
189
+
190
+ def self.new_job_request(job_name)
191
+ context = ScoutApm::Agent.instance.context
192
+ fake_store = ScoutApm::FakeStore.new
193
+ req = ScoutApm::TrackedRequest.new(context, fake_store)
194
+
195
+ first_layer = ScoutApm::Layer.new("Job", job_name)
196
+ req.start_layer(first_layer)
197
+
198
+ req
199
+ end
200
+ end
@@ -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