skylight 4.3.2 → 5.0.1

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 (113) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +35 -3
  3. data/CONTRIBUTING.md +2 -8
  4. data/ext/extconf.rb +6 -5
  5. data/ext/libskylight.yml +7 -6
  6. data/ext/skylight_native.c +22 -99
  7. data/lib/skylight.rb +211 -14
  8. data/lib/skylight/api.rb +10 -3
  9. data/lib/skylight/cli.rb +4 -3
  10. data/lib/skylight/cli/doctor.rb +13 -14
  11. data/lib/skylight/cli/merger.rb +6 -4
  12. data/lib/skylight/config.rb +597 -127
  13. data/lib/skylight/deprecation.rb +17 -0
  14. data/lib/skylight/errors.rb +21 -6
  15. data/lib/skylight/extensions.rb +107 -0
  16. data/lib/skylight/extensions/source_location.rb +291 -0
  17. data/lib/skylight/formatters/http.rb +20 -0
  18. data/lib/skylight/gc.rb +109 -0
  19. data/lib/skylight/helpers.rb +69 -26
  20. data/lib/skylight/instrumenter.rb +326 -15
  21. data/lib/skylight/middleware.rb +138 -1
  22. data/lib/skylight/native.rb +52 -2
  23. data/lib/skylight/native_ext_fetcher.rb +4 -3
  24. data/lib/skylight/normalizers.rb +153 -0
  25. data/lib/skylight/normalizers/action_controller/process_action.rb +69 -0
  26. data/lib/skylight/normalizers/action_controller/send_file.rb +50 -0
  27. data/lib/skylight/normalizers/action_dispatch/process_middleware.rb +22 -0
  28. data/lib/skylight/normalizers/action_dispatch/route_set.rb +27 -0
  29. data/lib/skylight/normalizers/action_view/render_collection.rb +24 -0
  30. data/lib/skylight/normalizers/action_view/render_layout.rb +25 -0
  31. data/lib/skylight/normalizers/action_view/render_partial.rb +23 -0
  32. data/lib/skylight/normalizers/action_view/render_template.rb +23 -0
  33. data/lib/skylight/normalizers/active_job/perform.rb +86 -0
  34. data/lib/skylight/normalizers/active_model_serializers/render.rb +28 -0
  35. data/lib/skylight/normalizers/active_record/instantiation.rb +16 -0
  36. data/lib/skylight/normalizers/active_record/sql.rb +12 -0
  37. data/lib/skylight/normalizers/active_storage.rb +30 -0
  38. data/lib/skylight/normalizers/active_support/cache.rb +22 -0
  39. data/lib/skylight/normalizers/active_support/cache_clear.rb +16 -0
  40. data/lib/skylight/normalizers/active_support/cache_decrement.rb +16 -0
  41. data/lib/skylight/normalizers/active_support/cache_delete.rb +16 -0
  42. data/lib/skylight/normalizers/active_support/cache_exist.rb +16 -0
  43. data/lib/skylight/normalizers/active_support/cache_fetch_hit.rb +16 -0
  44. data/lib/skylight/normalizers/active_support/cache_generate.rb +16 -0
  45. data/lib/skylight/normalizers/active_support/cache_increment.rb +16 -0
  46. data/lib/skylight/normalizers/active_support/cache_read.rb +16 -0
  47. data/lib/skylight/normalizers/active_support/cache_read_multi.rb +16 -0
  48. data/lib/skylight/normalizers/active_support/cache_write.rb +16 -0
  49. data/lib/skylight/normalizers/coach/handler_finish.rb +46 -0
  50. data/lib/skylight/normalizers/coach/middleware_finish.rb +33 -0
  51. data/lib/skylight/normalizers/couch_potato/query.rb +20 -0
  52. data/lib/skylight/normalizers/data_mapper/sql.rb +12 -0
  53. data/lib/skylight/normalizers/default.rb +32 -0
  54. data/lib/skylight/normalizers/elasticsearch/request.rb +20 -0
  55. data/lib/skylight/normalizers/faraday/request.rb +40 -0
  56. data/lib/skylight/normalizers/grape/endpoint.rb +34 -0
  57. data/lib/skylight/normalizers/grape/endpoint_render.rb +25 -0
  58. data/lib/skylight/normalizers/grape/endpoint_run.rb +41 -0
  59. data/lib/skylight/normalizers/grape/endpoint_run_filters.rb +22 -0
  60. data/lib/skylight/normalizers/grape/format_response.rb +20 -0
  61. data/lib/skylight/normalizers/graphiti/render.rb +22 -0
  62. data/lib/skylight/normalizers/graphiti/resolve.rb +31 -0
  63. data/lib/skylight/normalizers/graphql/base.rb +132 -0
  64. data/lib/skylight/normalizers/render.rb +81 -0
  65. data/lib/skylight/normalizers/sequel/sql.rb +12 -0
  66. data/lib/skylight/normalizers/shrine.rb +34 -0
  67. data/lib/skylight/normalizers/sql.rb +45 -0
  68. data/lib/skylight/probes.rb +181 -0
  69. data/lib/skylight/probes/action_controller.rb +48 -0
  70. data/lib/skylight/probes/action_dispatch.rb +2 -0
  71. data/lib/skylight/probes/action_dispatch/request_id.rb +29 -0
  72. data/lib/skylight/probes/action_dispatch/routing/route_set.rb +28 -0
  73. data/lib/skylight/probes/action_view.rb +43 -0
  74. data/lib/skylight/probes/active_job.rb +27 -0
  75. data/lib/skylight/probes/active_job_enqueue.rb +41 -0
  76. data/lib/skylight/probes/active_model_serializers.rb +50 -0
  77. data/lib/skylight/probes/delayed_job.rb +149 -0
  78. data/lib/skylight/probes/elasticsearch.rb +38 -0
  79. data/lib/skylight/probes/excon.rb +25 -0
  80. data/lib/skylight/probes/excon/middleware.rb +66 -0
  81. data/lib/skylight/probes/faraday.rb +23 -0
  82. data/lib/skylight/probes/graphql.rb +43 -0
  83. data/lib/skylight/probes/httpclient.rb +44 -0
  84. data/lib/skylight/probes/middleware.rb +126 -0
  85. data/lib/skylight/probes/mongo.rb +164 -0
  86. data/lib/skylight/probes/mongoid.rb +13 -0
  87. data/lib/skylight/probes/net_http.rb +54 -0
  88. data/lib/skylight/probes/redis.rb +63 -0
  89. data/lib/skylight/probes/sequel.rb +33 -0
  90. data/lib/skylight/probes/sinatra.rb +63 -0
  91. data/lib/skylight/probes/sinatra_add_middleware.rb +10 -10
  92. data/lib/skylight/probes/tilt.rb +27 -0
  93. data/lib/skylight/railtie.rb +162 -18
  94. data/lib/skylight/sidekiq.rb +48 -0
  95. data/lib/skylight/subscriber.rb +110 -0
  96. data/lib/skylight/test.rb +146 -0
  97. data/lib/skylight/trace.rb +307 -10
  98. data/lib/skylight/user_config.rb +61 -0
  99. data/lib/skylight/util.rb +12 -0
  100. data/lib/skylight/util/allocation_free.rb +26 -0
  101. data/lib/skylight/util/clock.rb +56 -0
  102. data/lib/skylight/util/component.rb +5 -2
  103. data/lib/skylight/util/deploy.rb +7 -10
  104. data/lib/skylight/util/gzip.rb +20 -0
  105. data/lib/skylight/util/http.rb +4 -10
  106. data/lib/skylight/util/instrumenter_method.rb +26 -0
  107. data/lib/skylight/util/logging.rb +138 -0
  108. data/lib/skylight/util/lru_cache.rb +40 -0
  109. data/lib/skylight/util/platform.rb +1 -1
  110. data/lib/skylight/vendor/cli/thor/rake_compat.rb +1 -1
  111. data/lib/skylight/version.rb +5 -1
  112. data/lib/skylight/vm/gc.rb +68 -0
  113. metadata +126 -13
@@ -0,0 +1,25 @@
1
+ module Skylight
2
+ module Probes
3
+ module Excon
4
+ # Probe for instrumenting Excon requests. Installs {Excon::Middleware} to achieve this.
5
+ class Probe
6
+ def install
7
+ if defined?(::Excon::Middleware)
8
+ # Don't require until installation since it depends on Excon being loaded
9
+ require "skylight/probes/excon/middleware"
10
+
11
+ idx = ::Excon.defaults[:middlewares].index(::Excon::Middleware::Instrumentor)
12
+
13
+ # TODO: Handle possibility of idx being nil
14
+ ::Excon.defaults[:middlewares].insert(idx, Probes::Excon::Middleware)
15
+ else
16
+ Skylight.error "The installed version of Excon doesn't support Middlewares. " \
17
+ "The Excon probe will be disabled."
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ register(:excon, "Excon", "excon", Excon::Probe.new)
24
+ end
25
+ end
@@ -0,0 +1,66 @@
1
+ require "skylight/formatters/http"
2
+
3
+ module Skylight
4
+ module Probes
5
+ module Excon
6
+ # Middleware for Excon that instruments requests
7
+ class Middleware < ::Excon::Middleware::Base
8
+ def initialize(*)
9
+ @requests = {}.compare_by_identity
10
+ super
11
+ end
12
+
13
+ # TODO: Review the following:
14
+ # - Consider whether a LIFO queue would be sufficient
15
+ # - Check that errors can't be called without a request
16
+
17
+ def request_call(datum)
18
+ begin_instrumentation(datum)
19
+ super
20
+ end
21
+
22
+ def response_call(datum)
23
+ super
24
+ ensure
25
+ end_instrumentation(datum)
26
+ end
27
+
28
+ def error_call(datum)
29
+ super
30
+ ensure
31
+ end_instrumentation(datum)
32
+ end
33
+
34
+ private
35
+
36
+ def begin_instrumentation(datum)
37
+ method = datum[:method].to_s
38
+ scheme = datum[:scheme]
39
+ host = datum[:host]
40
+ # TODO: Maybe don't show other default ports like 443
41
+ port = datum[:port] == 80 ? nil : datum[:port]
42
+ path = datum[:path]
43
+ query = datum[:query]
44
+
45
+ opts = Formatters::HTTP.build_opts(method, scheme, host, port, path, query)
46
+
47
+ @requests[datum] = Skylight.instrument(opts)
48
+ rescue Exception => e
49
+ Skylight.error "failed to begin instrumentation for Excon; msg=%s", e.message
50
+ end
51
+
52
+ def end_instrumentation(datum)
53
+ if (request = @requests.delete(datum))
54
+ meta = {}
55
+ if datum[:error].is_a?(Exception)
56
+ meta[:exception_object] = datum[:error]
57
+ end
58
+ Skylight.done(request, meta)
59
+ end
60
+ rescue Exception => e
61
+ Skylight.error "failed to end instrumentation for Excon; msg=%s", e.message
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,23 @@
1
+ module Skylight
2
+ module Probes
3
+ module Faraday
4
+ module Instrumentation
5
+ def builder
6
+ unless defined?(@__sk__setup)
7
+ @__sk__setup = true
8
+ @builder.insert 0, ::Faraday::Request::Instrumentation
9
+ end
10
+ @builder
11
+ end
12
+ end
13
+
14
+ class Probe
15
+ def install
16
+ ::Faraday::Connection.prepend(Instrumentation)
17
+ end
18
+ end
19
+ end
20
+
21
+ register(:faraday, "Faraday", "faraday", Faraday::Probe.new)
22
+ end
23
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/inflector"
4
+
5
+ module Skylight
6
+ module Probes
7
+ module GraphQL
8
+ module Instrumentation
9
+ def initialize(*, **)
10
+ super
11
+
12
+ return unless defined?(@tracers)
13
+
14
+ unless @tracers.include?(::GraphQL::Tracing::ActiveSupportNotificationsTracing)
15
+ @tracers << ::GraphQL::Tracing::ActiveSupportNotificationsTracing
16
+ end
17
+ end
18
+ end
19
+
20
+ class Probe
21
+ def install
22
+ tracing_klass_name = "::GraphQL::Tracing::ActiveSupportNotificationsTracing"
23
+ klasses_to_probe = %w[
24
+ ::GraphQL::Execution::Multiplex
25
+ ::GraphQL::Query
26
+ ]
27
+
28
+ return unless ([tracing_klass_name] + klasses_to_probe).all?(&method(:safe_constantize))
29
+
30
+ klasses_to_probe.each do |klass_name|
31
+ safe_constantize(klass_name).prepend(Instrumentation)
32
+ end
33
+ end
34
+
35
+ def safe_constantize(klass_name)
36
+ ActiveSupport::Inflector.safe_constantize(klass_name)
37
+ end
38
+ end
39
+ end
40
+
41
+ register(:graphql, "GraphQL", "graphql", GraphQL::Probe.new)
42
+ end
43
+ end
@@ -0,0 +1,44 @@
1
+ require "skylight/formatters/http"
2
+
3
+ module Skylight
4
+ module Probes
5
+ module HTTPClient
6
+ module Instrumentation
7
+ # HTTPClient has request methods on the class object itself,
8
+ # but they internally instantiate a client and perform the method
9
+ # on that, so this instance method override will cover both
10
+ # `HTTPClient.get(...)` and `HTTPClient.new.get(...)`
11
+
12
+ def do_request(method, uri, *)
13
+ return super if Probes::HTTPClient::Probe.disabled?
14
+
15
+ opts = Formatters::HTTP.build_opts(method, uri.scheme, uri.host, uri.port, uri.path, uri.query)
16
+
17
+ Skylight.instrument(opts) { super }
18
+ end
19
+ end
20
+
21
+ class Probe
22
+ DISABLED_KEY = :__skylight_httpclient_disabled
23
+
24
+ def self.disable
25
+ old_value = Thread.current[DISABLED_KEY]
26
+ Thread.current[DISABLED_KEY] = true
27
+ yield
28
+ ensure
29
+ Thread.current[DISABLED_KEY] = old_value
30
+ end
31
+
32
+ def self.disabled?
33
+ !!Thread.current[DISABLED_KEY]
34
+ end
35
+
36
+ def install
37
+ ::HTTPClient.prepend(Instrumentation)
38
+ end
39
+ end
40
+ end
41
+
42
+ register(:httpclient, "HTTPClient", "httpclient", HTTPClient::Probe.new)
43
+ end
44
+ end
@@ -0,0 +1,126 @@
1
+ module Skylight
2
+ module Probes
3
+ module Middleware
4
+ # for Rails >= 6.0, which includes InstrumentationProxy
5
+ module InstrumentationExtensions
6
+ def initialize(middleware, class_name)
7
+ super
8
+
9
+ # NOTE: Caching here leads to better performance, but will not notice if the method is overridden
10
+ # We don't have access to the config here so we can't check whether source locations are enabled.
11
+ # However, this only happens once per middleware so it should be minimal impact.
12
+ @payload[:sk_source_location] =
13
+ begin
14
+ if middleware.is_a?(Proc)
15
+ middleware.source_location
16
+ elsif middleware.respond_to?(:call)
17
+ middleware.method(:call).source_location
18
+ end
19
+ rescue
20
+ nil
21
+ end
22
+ end
23
+ end
24
+
25
+ # for Rails <= 5.2 ActionDispatch::MiddlewareStack::Middleware
26
+ module Instrumentation
27
+ def build(*)
28
+ sk_instrument_middleware(super)
29
+ end
30
+
31
+ def sk_instrument_middleware(middleware)
32
+ return middleware if middleware.is_a?(Skylight::Middleware)
33
+
34
+ # Not sure how this would actually happen
35
+ return middleware if middleware.respond_to?(:__has_sk__)
36
+
37
+ # On Rails 3, ActionDispatch::Session::CookieStore is frozen, for one
38
+ return middleware if middleware.frozen?
39
+
40
+ Skylight::Probes::Middleware::Probe.add_instrumentation(middleware)
41
+
42
+ middleware
43
+ end
44
+ end
45
+
46
+ class Probe
47
+ DISABLED_KEY = :__skylight_middleware_disabled
48
+
49
+ def self.disable!
50
+ @disabled = true
51
+ end
52
+
53
+ def self.enable!
54
+ @disabled = false
55
+ end
56
+
57
+ def self.disabled?
58
+ !!@disabled
59
+ end
60
+
61
+ module InstanceInstrumentation
62
+ def call(*args)
63
+ return super(*args) if Skylight::Probes::Middleware::Probe.disabled?
64
+
65
+ trace = Skylight.instrumenter&.current_trace
66
+ return super(*args) unless trace
67
+
68
+ begin
69
+ name = self.class.name || __sk_default_name
70
+
71
+ trace.endpoint = name
72
+
73
+ source_file, source_line = method(__method__).super_method.source_location
74
+
75
+ spans = Skylight.instrument(title: name, category: __sk_category,
76
+ source_file: source_file, source_line: source_line)
77
+
78
+ proxied_response =
79
+ Skylight::Middleware.with_after_close(super(*args), debug_identifier: "Middleware: #{name}") do
80
+ Skylight.done(spans)
81
+ end
82
+ rescue Exception => e
83
+ Skylight.done(spans, exception_object: e)
84
+ raise
85
+ ensure
86
+ unless e || proxied_response
87
+ # If we've gotten to this point, the most likely scenario is that
88
+ # a throw/catch has bypassed a portion of the callstack. Since these spans would not otherwise
89
+ # be closed, mark them deferred to indicate that they should be implicitly closed.
90
+ # See Trace#deferred_spans or Trace#stop for more information.
91
+ Skylight.done(spans, defer: true)
92
+ end
93
+ end
94
+ end
95
+
96
+ def __sk_default_name
97
+ "Anonymous Middleware"
98
+ end
99
+
100
+ def __sk_category
101
+ "rack.middleware"
102
+ end
103
+
104
+ def __has_sk__
105
+ true
106
+ end
107
+ end
108
+
109
+ def self.add_instrumentation(middleware)
110
+ middleware.singleton_class.prepend(InstanceInstrumentation)
111
+ end
112
+
113
+ def install
114
+ if defined?(::ActionDispatch::MiddlewareStack::InstrumentationProxy)
115
+ ::ActionDispatch::MiddlewareStack::InstrumentationProxy.prepend(InstrumentationExtensions)
116
+ else
117
+ ::ActionDispatch::MiddlewareStack::Middleware.prepend(Instrumentation)
118
+ end
119
+ end
120
+ end
121
+ end
122
+
123
+ register(:middleware, "ActionDispatch::MiddlewareStack::Middleware", "actionpack/action_dispatch",
124
+ Middleware::Probe.new)
125
+ end
126
+ end
@@ -0,0 +1,164 @@
1
+ module Skylight
2
+ module Probes
3
+ module Mongo
4
+ CAT = "db.mongo.command".freeze
5
+
6
+ class Probe
7
+ def install
8
+ ::Mongo::Monitoring::Global.subscribe(::Mongo::Monitoring::COMMAND, Subscriber.new)
9
+ end
10
+ end
11
+
12
+ class Subscriber
13
+ include Skylight::Util::Logging
14
+
15
+ COMMANDS = %i[insert find count distinct update findandmodify findAndModify delete aggregate].freeze
16
+
17
+ COMMAND_NAMES = {
18
+ findandmodify: "findAndModify".freeze
19
+ }.freeze
20
+
21
+ def initialize
22
+ @events = {}
23
+ end
24
+
25
+ def started(event)
26
+ begin_instrumentation(event)
27
+ end
28
+
29
+ def succeeded(event)
30
+ end_instrumentation(event)
31
+ end
32
+
33
+ def failed(event)
34
+ end_instrumentation(event)
35
+ end
36
+
37
+ # For logging
38
+ def config
39
+ Skylight.config
40
+ end
41
+
42
+ private
43
+
44
+ def begin_instrumentation(event)
45
+ return unless COMMANDS.include?(event.command_name.to_sym)
46
+
47
+ command_name = COMMAND_NAMES[event.command_name] || event.command_name.to_s
48
+
49
+ title = "#{event.database_name}.#{command_name}"
50
+
51
+ command = event.command
52
+
53
+ # Not sure if this will always exist
54
+ # Delete so the description will be less redundant
55
+ if (target = command[event.command_name])
56
+ title << " #{target}"
57
+ end
58
+
59
+ payload = {}
60
+
61
+ # Ruby Hashes are ordered based on insertion so do the most important ones first
62
+
63
+ add_value("key".freeze, command, payload)
64
+ add_bound("query".freeze, command, payload)
65
+ add_bound("filter".freeze, command, payload)
66
+ add_value("sort".freeze, command, payload)
67
+
68
+ if command_name == "findAndModify".freeze
69
+ add_bound("update".freeze, command, payload)
70
+ end
71
+
72
+ add_value("remove".freeze, command, payload)
73
+ add_value("new".freeze, command, payload)
74
+
75
+ if (updates = command["updates".freeze])
76
+ # AFAICT the gem generally just sends one item in the updates array
77
+ update = updates[0]
78
+ update_payload = {}
79
+ add_bound("q".freeze, update, update_payload)
80
+ add_bound("u".freeze, update, update_payload)
81
+ add_value("multi".freeze, update, update_payload)
82
+ add_value("upsert".freeze, update, update_payload)
83
+
84
+ payload["updates".freeze] = [update_payload]
85
+
86
+ if updates.length > 1
87
+ payload["updates".freeze] << "..."
88
+ end
89
+ end
90
+
91
+ if (deletes = command["deletes".freeze])
92
+ # AFAICT the gem generally just sends one item in the updates array
93
+ delete = deletes[0]
94
+ delete_payload = {}
95
+ add_bound("q".freeze, delete, delete_payload)
96
+ add_value("limit".freeze, delete, delete_payload)
97
+
98
+ payload["deletes".freeze] = [delete_payload]
99
+
100
+ if deletes.length > 1
101
+ payload["deletes".freeze] << "..."
102
+ end
103
+ end
104
+
105
+ if (pipeline = command["pipeline".freeze])
106
+ payload["pipeline".freeze] = pipeline.map { |segment| extract_binds(segment) }
107
+ end
108
+
109
+ # We're ignoring documents from insert because they could have completely inconsistent
110
+ # format which would make it hard to merge.
111
+
112
+ opts = {
113
+ category: CAT,
114
+ title: title,
115
+ description: payload.empty? ? nil : payload.to_json,
116
+ meta: { database: event.database_name },
117
+ internal: true
118
+ }
119
+
120
+ @events[event.operation_id] = Skylight.instrument(opts)
121
+ rescue Exception => e
122
+ error "failed to begin instrumentation for Mongo; msg=%s", e.message
123
+ end
124
+
125
+ def end_instrumentation(event)
126
+ if (original_event = @events.delete(event.operation_id))
127
+ meta = {}
128
+ if event.is_a?(::Mongo::Monitoring::Event::CommandFailed)
129
+ meta[:exception] = ["CommandFailed", event.message]
130
+ end
131
+ Skylight.done(original_event, meta)
132
+ end
133
+ rescue Exception => e
134
+ error "failed to end instrumentation for Mongo; msg=%s", e.message
135
+ end
136
+
137
+ def add_value(key, command, payload)
138
+ if command.key?(key)
139
+ value = command[key]
140
+ payload[key] = value
141
+ end
142
+ end
143
+
144
+ def add_bound(key, command, payload)
145
+ if (value = command[key])
146
+ payload[key] = extract_binds(value)
147
+ end
148
+ end
149
+
150
+ def extract_binds(hash)
151
+ ret = {}
152
+
153
+ hash.each do |k, v|
154
+ ret[k] = v.is_a?(Hash) ? extract_binds(v) : "?".freeze
155
+ end
156
+
157
+ ret
158
+ end
159
+ end
160
+ end
161
+
162
+ register(:mongo, "Mongo", "mongo", Mongo::Probe.new)
163
+ end
164
+ end