skylight 5.1.1 → 5.3.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.
@@ -68,5 +68,8 @@ module Skylight
68
68
 
69
69
  # E0006
70
70
  register(6, "InvalidUtf8", "Invalid UTF-8")
71
+
72
+ # E0007
73
+ register(7, "GrpcConnect", "Failed to connect to gRPC server.")
71
74
  end
72
75
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "skylight/util/lru_cache"
4
- require "active_support/dependencies"
4
+ require "active_support/inflector"
5
5
 
6
6
  module Skylight
7
7
  module Extensions
@@ -163,7 +163,7 @@ module Skylight
163
163
 
164
164
  def instance_method_source_location(constant_name, method_name, source_name: :instance_method)
165
165
  @instance_method_source_location_cache.fetch([constant_name, method_name, source_name]) do
166
- if (constant = ::ActiveSupport::Dependencies.safe_constantize(constant_name))
166
+ if (constant = ::ActiveSupport::Inflector.safe_constantize(constant_name))
167
167
  if constant.instance_methods.include?(:"before_instrument_#{method_name}")
168
168
  method_name = :"before_instrument_#{method_name}"
169
169
  end
@@ -36,7 +36,7 @@ module Skylight
36
36
  end
37
37
  end
38
38
 
39
- attr_reader :uuid, :config, :gc, :extensions
39
+ attr_reader :uuid, :config, :gc, :extensions, :subscriber
40
40
 
41
41
  def self.native_new(_uuid, _config_env)
42
42
  raise "not implemented"
@@ -3,6 +3,8 @@ require "securerandom"
3
3
  module Skylight
4
4
  # @api private
5
5
  class Middleware
6
+ SKYLIGHT_REQUEST_ID = "skylight.request_id".freeze
7
+
6
8
  class BodyProxy
7
9
  def initialize(body, &block)
8
10
  @body = body
@@ -81,7 +83,8 @@ module Skylight
81
83
  set_request_id(env)
82
84
 
83
85
  if Skylight.tracing?
84
- error "Already instrumenting. Make sure the Skylight Rack Middleware hasn't been added more than once."
86
+ debug "Already instrumenting. Make sure the Skylight Rack Middleware hasn't been added more than once."
87
+ return @app.call(env)
85
88
  end
86
89
 
87
90
  if env["REQUEST_METHOD"] == "HEAD"
@@ -108,7 +111,7 @@ module Skylight
108
111
 
109
112
  def log_context
110
113
  # Don't cache this, it will change
111
- { request_id: @current_request_id, inst: Skylight.instrumenter&.uuid }
114
+ { request_id: current_request_id, inst: Skylight.instrumenter&.uuid }
112
115
  end
113
116
 
114
117
  # Allow for overwriting
@@ -122,8 +125,10 @@ module Skylight
122
125
 
123
126
  # Request ID code based on ActionDispatch::RequestId
124
127
  def set_request_id(env)
128
+ return if env[SKYLIGHT_REQUEST_ID]
129
+
125
130
  existing_request_id = env["action_dispatch.request_id"] || env["HTTP_X_REQUEST_ID"]
126
- @current_request_id = env["skylight.request_id"] = make_request_id(existing_request_id)
131
+ self.current_request_id = env[SKYLIGHT_REQUEST_ID] = make_request_id(existing_request_id)
127
132
  end
128
133
 
129
134
  def make_request_id(request_id)
@@ -133,5 +138,13 @@ module Skylight
133
138
  def internal_request_id
134
139
  SecureRandom.uuid
135
140
  end
141
+
142
+ def current_request_id
143
+ Thread.current[SKYLIGHT_REQUEST_ID]
144
+ end
145
+
146
+ def current_request_id=(request_id)
147
+ Thread.current[SKYLIGHT_REQUEST_ID] = request_id
148
+ end
136
149
  end
137
150
  end
@@ -15,7 +15,7 @@ module Skylight
15
15
  .tap do |str|
16
16
  if str.match(DELIVERY_JOB)
17
17
  mailer_class, mailer_method, * = job_instance.arguments
18
- return "#{mailer_class}##{mailer_method}", str
18
+ return "#{mailer_class}##{mailer_method}", str if mailer_class && mailer_method
19
19
  end
20
20
  end
21
21
  end
@@ -33,9 +33,7 @@ module Skylight
33
33
  end
34
34
 
35
35
  def normalize_after(trace, _span, _name, payload)
36
- return unless config.enable_segments? && assign_endpoint?(trace, payload)
37
-
38
- trace.segment = payload[:job].queue_name
36
+ maybe_set_endpoint(trace, payload)
39
37
  end
40
38
 
41
39
  private
@@ -53,31 +51,30 @@ module Skylight
53
51
  end
54
52
 
55
53
  def maybe_set_endpoint(trace, payload)
56
- trace.endpoint = normalize_title(payload[:job]) if assign_endpoint?(trace, payload)
57
- end
54
+ endpoint = normalize_title(payload[:job])
58
55
 
59
- def assign_endpoint?(trace, payload)
60
56
  # Always assign the endpoint if it has not yet been assigned by the ActiveJob probe.
61
- return true unless trace.endpoint
62
- if defined?(Skylight::Probes::ActiveJob::TITLE) && trace.endpoint == Skylight::Probes::ActiveJob::TITLE
63
- return true
64
- end
65
- if defined?(SKylight::Probes::DelayedJob::Probe::UNKNOWN) &&
66
- trace.endpoint == Skylight::Probes::DelayedJob::Probe::UNKNOWN
67
- return true
57
+ if !trace.endpoint ||
58
+ (defined?(Skylight::Probes::ActiveJob::TITLE) && trace.endpoint == Skylight::Probes::ActiveJob::TITLE) ||
59
+ (
60
+ defined?(Skylight::Probes::DelayedJob::Probe::UNKNOWN) &&
61
+ trace.endpoint == Skylight::Probes::DelayedJob::Probe::UNKNOWN
62
+ ) ||
63
+ # If a job is called using #perform_now inside a controller action
64
+ # or within another job's #perform method, we do not want this to
65
+ # overwrite the existing endpoint name (unless it is the default from ActiveJob).
66
+ #
67
+ # If the current endpoint name matches this payload, return true to allow the
68
+ # segment to be assigned by normalize_after.
69
+ trace.endpoint =~ DELIVERY_JOB ||
70
+ # This adapter wrapper needs to be handled specifically due to interactions with the
71
+ # standalone Delayed::Job probe, as there is no consistent way to get the wrapped
72
+ # job name among all Delayed::Job backends.
73
+ trace.endpoint == DELAYED_JOB_WRAPPER
74
+ trace.endpoint = endpoint
68
75
  end
69
76
 
70
- # If a job is called using #perform_now inside a controller action
71
- # or within another job's #perform method, we do not want this to
72
- # overwrite the existing endpoint name (unless it is the default from ActiveJob).
73
- #
74
- # If the current endpoint name matches this payload, return true to allow the
75
- # segment to be assigned by normalize_after.
76
- trace.endpoint == DELIVERY_JOB || trace.endpoint == normalize_title(payload[:job]) ||
77
- # This adapter wrapper needs to be handled specifically due to interactions with the
78
- # standalone Delayed::Job probe, as there is no consistent way to get the wrapped
79
- # job name among all Delayed::Job backends.
80
- trace.endpoint == DELAYED_JOB_WRAPPER
77
+ trace.segment = payload[:job].queue_name if trace.endpoint == endpoint && config.enable_segments?
81
78
  end
82
79
 
83
80
  def normalize_title(job_instance)
@@ -7,6 +7,14 @@ module Skylight
7
7
  class SQL < Skylight::Normalizers::SQL
8
8
  register "sql.active_record"
9
9
  end
10
+
11
+ class FutureResult < Normalizer
12
+ register "future_result.active_record"
13
+
14
+ def normalize(_trace, _name, payload)
15
+ ["db.future_result", "Async #{payload[:args][1] || "Query"}"]
16
+ end
17
+ end
10
18
  end
11
19
  end
12
20
  end
@@ -20,7 +20,7 @@ module Skylight
20
20
 
21
21
  def get_namespace(endpoint)
22
22
  # slice off preceding slash for data continuity
23
- ::Grape::Namespace.joined_space_path(endpoint.namespace_stackable(:namespace)).to_s[1..-1]
23
+ ::Grape::Namespace.joined_space_path(endpoint.namespace_stackable(:namespace)).to_s[1..]
24
24
  end
25
25
  end
26
26
  end
@@ -14,14 +14,10 @@ module Skylight
14
14
  # @option payload [String] [:name] The SQL operation
15
15
  # @option payload [Hash] [:binds] The bound parameters
16
16
  # @return [Array]
17
- def normalize(_trace, name, payload)
18
- case payload[:name]
19
- when "SCHEMA", "CACHE"
20
- return :skip
21
- else
22
- name = CAT
23
- title = payload[:name] || "SQL"
24
- end
17
+ def normalize(_trace, _name, payload)
18
+ return :skip if payload[:name] == "SCHEMA" || payload[:name] == "CACHE"
19
+
20
+ title = payload[:name] || "SQL"
25
21
 
26
22
  # We can only handle UTF-8 encoded strings.
27
23
  # (Construction method here avoids extra allocations)
@@ -38,7 +34,7 @@ module Skylight
38
34
  sql = nil
39
35
  end
40
36
 
41
- [name, title, sql]
37
+ [CAT, title, sql]
42
38
  end
43
39
  end
44
40
  end
@@ -0,0 +1,96 @@
1
+ module Skylight
2
+ module Probes
3
+ module ActiveRecord
4
+ module FutureResult
5
+ # Applied to ActiveSupport::Notifications::Event
6
+ module AsyncEventExtensions
7
+ # Notify Skylight that the event has started
8
+ def __sk_start!
9
+ subscriber = Skylight.instrumenter.subscriber
10
+ subscriber.start(name, nil, payload)
11
+ trace = Skylight.instrumenter.current_trace
12
+
13
+ # Set a finisher to end the event
14
+ @__sk_finisher = ->(name, payload) do
15
+ subscriber.with_trace(trace) { subscriber.finish(name, nil, payload) }
16
+ end
17
+
18
+ # End it immediately if we've actually already ended
19
+ __sk_finish! if @end
20
+ rescue StandardError => e
21
+ Skylight.error("Unable to start event for FutureResult: #{e}")
22
+ end
23
+
24
+ # Notify Skylight that the event has finished
25
+ def __sk_finish!
26
+ return unless @__sk_finisher
27
+
28
+ @__sk_finisher.call(name, payload)
29
+ @__sk_finisher = nil
30
+ rescue StandardError => e
31
+ Skylight.error("Unable to finish event for FutureResult: #{e}")
32
+ end
33
+
34
+ # When the event is marked as finish make sure we notify Skylight
35
+ def finish!
36
+ super
37
+ __sk_finish!
38
+ end
39
+ end
40
+
41
+ # Applied to FutureResult
42
+ module Instrumentation
43
+ def result(*, **)
44
+ # This instruments the whole FutureResult so that we know when we were executing (potenially) async.
45
+ ActiveSupport::Notifications.instrument("future_result.active_record", { args: @args, kwargs: @kwargs }) do
46
+ super
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def execute_or_wait(*, **)
53
+ # At this point we're actually waiting for the query to have finished executing.
54
+
55
+ begin
56
+ # If the query has already started async, the @event_buffer will be defined.
57
+ # We grab the events (currently only the SQL queries), extend them with our
58
+ # special methods and notify Skylight.
59
+ # We act as if the event has just stared, though the query may already have been
60
+ # running. This means we're essentially just logging blocking time right now.
61
+
62
+ # Dup here just in case more get added somehow during the super call
63
+ events = @event_buffer&.instance_variable_get(:@events).dup
64
+
65
+ events&.each do |event|
66
+ event.singleton_class.prepend(AsyncEventExtensions)
67
+ event.__sk_start!
68
+ end
69
+ rescue StandardError => e
70
+ Skylight.error("Unable to start events for FutureResult: #{e}")
71
+ end
72
+
73
+ super
74
+ ensure
75
+ # Once we've actually got a result, we mark each one as finished.
76
+ # Note that it may have already finished, but if it didn't we need to say so now.
77
+ events&.reverse_each(&:__sk_finish!)
78
+ end
79
+ end
80
+
81
+ class Probe
82
+ def install
83
+ ::ActiveRecord::FutureResult.prepend(Instrumentation)
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ register(
90
+ :active_record_async,
91
+ "ActiveRecord::FutureResult",
92
+ "active_record/future_result",
93
+ ActiveRecord::FutureResult::Probe.new
94
+ )
95
+ end
96
+ end
@@ -25,10 +25,10 @@ module Skylight
25
25
  # for Rails <= 5.2 ActionDispatch::MiddlewareStack::Middleware
26
26
  module Instrumentation
27
27
  def build(*)
28
- sk_instrument_middleware(super)
28
+ Instrumentation.sk_instrument_middleware(super)
29
29
  end
30
30
 
31
- def sk_instrument_middleware(middleware)
31
+ def self.sk_instrument_middleware(middleware)
32
32
  return middleware if middleware.is_a?(Skylight::Middleware)
33
33
 
34
34
  # Not sure how this would actually happen
@@ -0,0 +1,37 @@
1
+ module Skylight
2
+ module Probes
3
+ module Rack
4
+ module Builder
5
+ module Instrumentation
6
+ def use(middleware, *args, &block)
7
+ if @map
8
+ mapping = @map
9
+ @map = nil
10
+ @use << proc { |app| generate_map(app, mapping) }
11
+ end
12
+ @use << proc do |app|
13
+ middleware
14
+ .new(app, *args, &block)
15
+ .tap do |middleware_instance|
16
+ Skylight::Probes::Middleware::Instrumentation.sk_instrument_middleware(middleware_instance)
17
+ end
18
+ end
19
+ end
20
+ ruby2_keywords(:use) if respond_to?(:ruby2_keywords, true)
21
+ end
22
+
23
+ class Probe
24
+ def install
25
+ if defined?(::Rack.release) && Gem::Version.new(::Rack.release) >= ::Gem::Version.new("1.4") &&
26
+ defined?(::Rack::Builder)
27
+ require "skylight/probes/middleware"
28
+ ::Rack::Builder.prepend(Instrumentation)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ register(:rack_builder, "Rack::Builder", "rack/builder", Skylight::Probes::Rack::Builder::Probe.new)
36
+ end
37
+ end
@@ -2,8 +2,8 @@ module Skylight
2
2
  module Probes
3
3
  module Sinatra
4
4
  module Instrumentation
5
- def build(*)
6
- use Skylight::Middleware
5
+ def setup_default_middleware(builder)
6
+ builder.use Skylight::Middleware
7
7
  super
8
8
  end
9
9
  end
@@ -16,7 +16,15 @@ module Skylight
16
16
  # net_http, action_controller, action_dispatch, action_view, and middleware are on by default
17
17
  # See https://www.skylight.io/support/getting-more-from-skylight#available-instrumentation-options
18
18
  # for a full list.
19
- config.skylight.probes = %w[net_http action_controller action_dispatch action_view middleware active_job_enqueue]
19
+ config.skylight.probes = %w[
20
+ net_http
21
+ action_controller
22
+ action_dispatch
23
+ action_view
24
+ middleware
25
+ active_job_enqueue
26
+ active_record_async
27
+ ]
20
28
 
21
29
  # The position in the middleware stack to place Skylight
22
30
  # Default is first, but can be `{ after: Middleware::Name }` or `{ before: Middleware::Name }`
@@ -99,8 +107,8 @@ module Skylight
99
107
 
100
108
  configure_logging(config, app)
101
109
 
102
- config[:'daemon.sockdir_path'] ||= tmp
103
- config[:'normalizers.render.view_paths'] = existent_paths(app.config.paths["app/views"]) + [Rails.root.to_s]
110
+ config[:"daemon.sockdir_path"] ||= tmp
111
+ config[:"normalizers.render.view_paths"] = existent_paths(app.config.paths["app/views"]) + [Rails.root.to_s]
104
112
 
105
113
  config[:env] ||= Rails.env.to_s if config[:report_rails_env]
106
114
 
@@ -1,2 +1,2 @@
1
1
  require "skylight"
2
- Skylight.probe(:sinatra_add_middleware, :sinatra, :tilt, :sequel)
2
+ Skylight.probe(:sinatra_add_middleware, :sinatra, :tilt, :sequel, :rack_builder)
@@ -36,16 +36,30 @@ module Skylight
36
36
  end
37
37
  end
38
38
 
39
+ # cargo-culted from Rails's ConnectionAdapter
40
+ EXCEPTION_NEVER = { Exception => :never }.freeze # :nodoc:
41
+ EXCEPTION_IMMEDIATE = { Exception => :immediate }.freeze # :nodoc:
42
+ private_constant :EXCEPTION_NEVER, :EXCEPTION_IMMEDIATE
43
+ def with_trace(trace, &block) # :nodoc:
44
+ Thread.handle_interrupt(EXCEPTION_NEVER) do
45
+ previous_trace = @trace
46
+ @trace = trace
47
+ Thread.handle_interrupt(EXCEPTION_IMMEDIATE, &block)
48
+ ensure
49
+ @trace = previous_trace
50
+ end
51
+ end
52
+
39
53
  def start(name, _id, payload)
40
54
  return if @instrumenter.disabled?
41
- return unless (trace = @instrumenter.current_trace)
55
+ return unless (trace = current_trace)
42
56
 
43
57
  _start(trace, name, payload)
44
58
  end
45
59
 
46
60
  def finish(name, _id, payload)
47
61
  return if @instrumenter.disabled?
48
- return unless (trace = @instrumenter.current_trace)
62
+ return unless (trace = current_trace)
49
63
 
50
64
  while (curr = trace.notifications.pop)
51
65
  next unless curr.name == name
@@ -70,8 +84,16 @@ module Skylight
70
84
  # Ignored for now because nothing in rails uses it
71
85
  end
72
86
 
87
+ def publish_event(event)
88
+ # Ignored for now because only ActiveRecord::FutureResult uses it and we handle that with probes
89
+ end
90
+
73
91
  private
74
92
 
93
+ def current_trace
94
+ @trace || @instrumenter.current_trace
95
+ end
96
+
75
97
  def normalize(*args)
76
98
  @normalizers.normalize(*args)
77
99
  end
data/lib/skylight/test.rb CHANGED
@@ -25,10 +25,6 @@ module Skylight
25
25
  "Mocked Instrumenter"
26
26
  end
27
27
 
28
- def self.native_new(*)
29
- allocate
30
- end
31
-
32
28
  def native_start
33
29
  true
34
30
  end
@@ -47,7 +43,7 @@ module Skylight
47
43
  :Trace,
48
44
  Class.new(OriginalTrace) do
49
45
  def self.native_new(start, _uuid, endpoint, meta)
50
- inst = allocate
46
+ inst = super
51
47
  inst.instance_variable_set(:@start, start)
52
48
  inst.instance_variable_set(:@endpoint, endpoint)
53
49
  inst.instance_variable_set(:@starting_endpoint, endpoint)
@@ -13,7 +13,7 @@ module Skylight
13
13
  # rubocop:disable Lint/DuplicateMethods
14
14
  def tick
15
15
  now = Time.now
16
- now.to_i * 1_000_000_000 + now.usec * 1_000
16
+ (now.to_i * 1_000_000_000) + (now.usec * 1_000)
17
17
  end
18
18
 
19
19
  # rubocop:enable Lint/DuplicateMethods
@@ -52,15 +52,15 @@ module Skylight
52
52
  end
53
53
 
54
54
  def id
55
- config.get(:'deploy.id') || git_sha
55
+ config.get(:"deploy.id") || git_sha
56
56
  end
57
57
 
58
58
  def git_sha
59
- config.get(:'deploy.git_sha')
59
+ config.get(:"deploy.git_sha")
60
60
  end
61
61
 
62
62
  def description
63
- config.get(:'deploy.description')
63
+ config.get(:"deploy.description")
64
64
  end
65
65
  end
66
66
 
@@ -85,7 +85,7 @@ module Skylight
85
85
  private
86
86
 
87
87
  def get_info
88
- info_path = config[:'heroku.dyno_info_path']
88
+ info_path = config[:"heroku.dyno_info_path"]
89
89
 
90
90
  if File.exist?(info_path) && (info = JSON.parse(File.read(info_path)))
91
91
  info["release"]
@@ -38,8 +38,8 @@ module Skylight
38
38
  "x86"
39
39
  when /ppc|powerpc/
40
40
  "powerpc"
41
- when /^arm/
42
- "arm"
41
+ when /arm64|aarch64/
42
+ "aarch64"
43
43
  else
44
44
  cpu
45
45
  end
@@ -3,5 +3,5 @@ module Skylight
3
3
  # for compatibility with semver when it is parsed by the rust agent.
4
4
  # This string will be transformed in the gemspec to "5.0.0.alpha"
5
5
  # to conform with rubygems.
6
- VERSION = "5.1.1".freeze
6
+ VERSION = "5.3.0".freeze
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: skylight
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.1.1
4
+ version: 5.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tilde, Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-28 00:00:00.000000000 Z
11
+ date: 2022-02-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -142,14 +142,14 @@ dependencies:
142
142
  requirements:
143
143
  - - "~>"
144
144
  - !ruby/object:Gem::Version
145
- version: 1.15.0
145
+ version: 1.25.0
146
146
  type: :development
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
150
  - - "~>"
151
151
  - !ruby/object:Gem::Version
152
- version: 1.15.0
152
+ version: 1.25.0
153
153
  - !ruby/object:Gem::Dependency
154
154
  name: simplecov
155
155
  requirement: !ruby/object:Gem::Requirement
@@ -285,6 +285,7 @@ files:
285
285
  - lib/skylight/probes/active_job.rb
286
286
  - lib/skylight/probes/active_job_enqueue.rb
287
287
  - lib/skylight/probes/active_model_serializers.rb
288
+ - lib/skylight/probes/active_record_async.rb
288
289
  - lib/skylight/probes/delayed_job.rb
289
290
  - lib/skylight/probes/elasticsearch.rb
290
291
  - lib/skylight/probes/excon.rb
@@ -296,6 +297,7 @@ files:
296
297
  - lib/skylight/probes/mongo.rb
297
298
  - lib/skylight/probes/mongoid.rb
298
299
  - lib/skylight/probes/net_http.rb
300
+ - lib/skylight/probes/rack_builder.rb
299
301
  - lib/skylight/probes/redis.rb
300
302
  - lib/skylight/probes/sequel.rb
301
303
  - lib/skylight/probes/sinatra.rb
@@ -366,7 +368,8 @@ files:
366
368
  homepage: https://www.skylight.io
367
369
  licenses:
368
370
  - Nonstandard
369
- metadata: {}
371
+ metadata:
372
+ rubygems_mfa_required: 'true'
370
373
  post_install_message:
371
374
  rdoc_options: []
372
375
  require_paths:
@@ -382,7 +385,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
382
385
  - !ruby/object:Gem::Version
383
386
  version: '0'
384
387
  requirements: []
385
- rubygems_version: 3.2.15
388
+ rubygems_version: 3.3.3
386
389
  signing_key:
387
390
  specification_version: 4
388
391
  summary: Skylight is a smart profiler for Rails, Sinatra, and other Ruby apps.