skylight 4.2.3 → 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.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +420 -331
  3. data/CLA.md +1 -1
  4. data/CONTRIBUTING.md +2 -8
  5. data/ERRORS.md +3 -0
  6. data/LICENSE.md +7 -17
  7. data/README.md +1 -1
  8. data/ext/extconf.rb +61 -56
  9. data/ext/libskylight.yml +8 -6
  10. data/ext/skylight_native.c +26 -100
  11. data/lib/skylight/api.rb +32 -21
  12. data/lib/skylight/cli/doctor.rb +64 -65
  13. data/lib/skylight/cli/helpers.rb +19 -19
  14. data/lib/skylight/cli/merger.rb +142 -138
  15. data/lib/skylight/cli.rb +48 -46
  16. data/lib/skylight/config.rb +640 -201
  17. data/lib/skylight/data/cacert.pem +730 -1023
  18. data/lib/skylight/deprecation.rb +17 -0
  19. data/lib/skylight/errors.rb +26 -9
  20. data/lib/skylight/extensions/source_location.rb +291 -0
  21. data/lib/skylight/extensions.rb +95 -0
  22. data/lib/skylight/formatters/http.rb +18 -0
  23. data/lib/skylight/gc.rb +99 -0
  24. data/lib/skylight/helpers.rb +81 -36
  25. data/lib/skylight/instrumenter.rb +336 -18
  26. data/lib/skylight/middleware.rb +147 -1
  27. data/lib/skylight/native.rb +60 -12
  28. data/lib/skylight/native_ext_fetcher.rb +13 -14
  29. data/lib/skylight/normalizers/action_controller/process_action.rb +68 -0
  30. data/lib/skylight/normalizers/action_controller/send_file.rb +51 -0
  31. data/lib/skylight/normalizers/action_dispatch/process_middleware.rb +22 -0
  32. data/lib/skylight/normalizers/action_dispatch/route_set.rb +27 -0
  33. data/lib/skylight/normalizers/action_view/render_collection.rb +24 -0
  34. data/lib/skylight/normalizers/action_view/render_layout.rb +25 -0
  35. data/lib/skylight/normalizers/action_view/render_partial.rb +23 -0
  36. data/lib/skylight/normalizers/action_view/render_template.rb +23 -0
  37. data/lib/skylight/normalizers/active_job/perform.rb +87 -0
  38. data/lib/skylight/normalizers/active_model_serializers/render.rb +32 -0
  39. data/lib/skylight/normalizers/active_record/instantiation.rb +16 -0
  40. data/lib/skylight/normalizers/active_record/sql.rb +20 -0
  41. data/lib/skylight/normalizers/active_storage.rb +28 -0
  42. data/lib/skylight/normalizers/active_support/cache.rb +11 -0
  43. data/lib/skylight/normalizers/active_support/cache_clear.rb +16 -0
  44. data/lib/skylight/normalizers/active_support/cache_decrement.rb +16 -0
  45. data/lib/skylight/normalizers/active_support/cache_delete.rb +16 -0
  46. data/lib/skylight/normalizers/active_support/cache_exist.rb +16 -0
  47. data/lib/skylight/normalizers/active_support/cache_fetch_hit.rb +16 -0
  48. data/lib/skylight/normalizers/active_support/cache_generate.rb +16 -0
  49. data/lib/skylight/normalizers/active_support/cache_increment.rb +16 -0
  50. data/lib/skylight/normalizers/active_support/cache_read.rb +16 -0
  51. data/lib/skylight/normalizers/active_support/cache_read_multi.rb +16 -0
  52. data/lib/skylight/normalizers/active_support/cache_write.rb +16 -0
  53. data/lib/skylight/normalizers/coach/handler_finish.rb +44 -0
  54. data/lib/skylight/normalizers/coach/middleware_finish.rb +33 -0
  55. data/lib/skylight/normalizers/couch_potato/query.rb +20 -0
  56. data/lib/skylight/normalizers/data_mapper/sql.rb +12 -0
  57. data/lib/skylight/normalizers/default.rb +24 -0
  58. data/lib/skylight/normalizers/elasticsearch/request.rb +20 -0
  59. data/lib/skylight/normalizers/faraday/request.rb +38 -0
  60. data/lib/skylight/normalizers/grape/endpoint.rb +28 -0
  61. data/lib/skylight/normalizers/grape/endpoint_render.rb +25 -0
  62. data/lib/skylight/normalizers/grape/endpoint_run.rb +39 -0
  63. data/lib/skylight/normalizers/grape/endpoint_run_filters.rb +20 -0
  64. data/lib/skylight/normalizers/grape/format_response.rb +20 -0
  65. data/lib/skylight/normalizers/graphiti/render.rb +22 -0
  66. data/lib/skylight/normalizers/graphiti/resolve.rb +31 -0
  67. data/lib/skylight/normalizers/graphql/base.rb +127 -0
  68. data/lib/skylight/normalizers/render.rb +79 -0
  69. data/lib/skylight/normalizers/sequel/sql.rb +12 -0
  70. data/lib/skylight/normalizers/shrine.rb +32 -0
  71. data/lib/skylight/normalizers/sql.rb +41 -0
  72. data/lib/skylight/normalizers.rb +157 -0
  73. data/lib/skylight/probes/action_controller.rb +52 -0
  74. data/lib/skylight/probes/action_dispatch/request_id.rb +33 -0
  75. data/lib/skylight/probes/action_dispatch/routing/route_set.rb +30 -0
  76. data/lib/skylight/probes/action_dispatch.rb +2 -0
  77. data/lib/skylight/probes/action_view.rb +42 -0
  78. data/lib/skylight/probes/active_job.rb +27 -0
  79. data/lib/skylight/probes/active_job_enqueue.rb +35 -0
  80. data/lib/skylight/probes/active_model_serializers.rb +50 -0
  81. data/lib/skylight/probes/active_record_async.rb +96 -0
  82. data/lib/skylight/probes/delayed_job.rb +144 -0
  83. data/lib/skylight/probes/elasticsearch.rb +36 -0
  84. data/lib/skylight/probes/excon/middleware.rb +65 -0
  85. data/lib/skylight/probes/excon.rb +25 -0
  86. data/lib/skylight/probes/faraday.rb +23 -0
  87. data/lib/skylight/probes/graphql.rb +38 -0
  88. data/lib/skylight/probes/httpclient.rb +44 -0
  89. data/lib/skylight/probes/middleware.rb +135 -0
  90. data/lib/skylight/probes/mongo.rb +156 -0
  91. data/lib/skylight/probes/mongoid.rb +13 -0
  92. data/lib/skylight/probes/net_http.rb +54 -0
  93. data/lib/skylight/probes/rack_builder.rb +37 -0
  94. data/lib/skylight/probes/redis.rb +51 -0
  95. data/lib/skylight/probes/sequel.rb +29 -0
  96. data/lib/skylight/probes/sinatra.rb +66 -0
  97. data/lib/skylight/probes/sinatra_add_middleware.rb +10 -10
  98. data/lib/skylight/probes/tilt.rb +25 -0
  99. data/lib/skylight/probes.rb +173 -0
  100. data/lib/skylight/railtie.rb +166 -28
  101. data/lib/skylight/sidekiq.rb +47 -0
  102. data/lib/skylight/sinatra.rb +1 -1
  103. data/lib/skylight/subscriber.rb +130 -0
  104. data/lib/skylight/test.rb +147 -0
  105. data/lib/skylight/trace.rb +325 -22
  106. data/lib/skylight/user_config.rb +58 -0
  107. data/lib/skylight/util/allocation_free.rb +26 -0
  108. data/lib/skylight/util/clock.rb +57 -0
  109. data/lib/skylight/util/component.rb +22 -22
  110. data/lib/skylight/util/deploy.rb +19 -24
  111. data/lib/skylight/util/gzip.rb +20 -0
  112. data/lib/skylight/util/http.rb +106 -113
  113. data/lib/skylight/util/instrumenter_method.rb +26 -0
  114. data/lib/skylight/util/logging.rb +136 -0
  115. data/lib/skylight/util/lru_cache.rb +36 -0
  116. data/lib/skylight/util/platform.rb +3 -7
  117. data/lib/skylight/util/ssl.rb +1 -25
  118. data/lib/skylight/util.rb +12 -0
  119. data/lib/skylight/vendor/cli/thor/rake_compat.rb +1 -1
  120. data/lib/skylight/version.rb +5 -1
  121. data/lib/skylight/vm/gc.rb +60 -0
  122. data/lib/skylight.rb +201 -14
  123. metadata +134 -18
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/deprecation"
4
+
5
+ module Skylight
6
+ SKYLIGHT_GEM_ROOT = "#{File.expand_path("../..", __dir__)}/"
7
+
8
+ class Deprecation < ActiveSupport::Deprecation
9
+ private
10
+
11
+ def ignored_callstack(path)
12
+ path.start_with?(SKYLIGHT_GEM_ROOT)
13
+ end
14
+ end
15
+
16
+ DEPRECATOR = Deprecation.new("6.0", "skylight")
17
+ end
@@ -1,17 +1,21 @@
1
+ require "json"
2
+
1
3
  module Skylight
4
+ # @api private
5
+ class ConfigError < RuntimeError
6
+ end
7
+
2
8
  class NativeError < StandardError
3
9
  @classes = {}
4
10
 
5
11
  def self.register(code, name, message)
6
- if @classes.key?(code)
7
- raise "Duplicate error class code: #{code}; name=#{name}"
8
- end
12
+ raise "Duplicate error class code: #{code}; name=#{name}" if @classes.key?(code)
9
13
 
10
14
  Skylight.module_eval <<-RUBY, __FILE__, __LINE__ + 1
11
- class #{name}Error < NativeError
12
- def self.code; #{code}; end
13
- def self.message; #{message.to_json}; end
14
- end
15
+ class #{name}Error < NativeError # class SqlLexError < NativeError
16
+ def self.code; #{code}; end # def self.code; 4; end
17
+ def self.message; #{message.to_json}; end # def self.message; "Failed to lex SQL query."; end
18
+ end # end
15
19
  RUBY
16
20
 
17
21
  klass = Skylight.const_get("#{name}Error")
@@ -29,13 +33,17 @@ module Skylight
29
33
  9999
30
34
  end
31
35
 
36
+ def self.formatted_code
37
+ format("%<code>04d", code: code)
38
+ end
39
+
32
40
  def self.message
33
41
  "Encountered an unknown internal error"
34
42
  end
35
43
 
36
44
  def initialize(method_name)
37
45
  @method_name = method_name
38
- super(format("[E%<code>04d] %<message>s [%<meth>s]", code: code, message: message, meth: method_name))
46
+ super(format("[E%<code>04d] %<message>s [%<meth>s]", code: code, message: self.class.message, meth: method_name))
39
47
  end
40
48
 
41
49
  def code
@@ -43,9 +51,12 @@ module Skylight
43
51
  end
44
52
 
45
53
  def formatted_code
46
- format("%04d", code)
54
+ self.class.formatted_code
47
55
  end
48
56
 
57
+ # E0002
58
+ # Too many unique descriptions - daemon only
59
+
49
60
  # E0003
50
61
  register(3, "MaximumTraceSpans", "Exceeded maximum number of spans in a trace.")
51
62
 
@@ -54,5 +65,11 @@ module Skylight
54
65
 
55
66
  # E0005
56
67
  register(5, "InstrumenterUnrecoverable", "Instrumenter is not running.")
68
+
69
+ # E0006
70
+ register(6, "InvalidUtf8", "Invalid UTF-8")
71
+
72
+ # E0007
73
+ register(7, "GrpcConnect", "Failed to connect to gRPC server.")
57
74
  end
58
75
  end
@@ -0,0 +1,291 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "skylight/util/lru_cache"
4
+ require "active_support/inflector"
5
+
6
+ module Skylight
7
+ module Extensions
8
+ class SourceLocation < Extension
9
+ attr_reader :config
10
+
11
+ include Util::Logging
12
+
13
+ META_KEYS = %i[source_location source_file source_line].freeze
14
+ MAX_CALLER_DEPTH = 75
15
+
16
+ def initialize(*)
17
+ super
18
+ cache_size = (config[:source_location_cache_size] || 1000).to_i
19
+ @caller_cache = Util::LruCache.new(cache_size)
20
+ @instance_method_source_location_cache = Util::LruCache.new(cache_size)
21
+ gem_require_trie # memoize this at startup
22
+ end
23
+
24
+ def process_trace_meta(meta)
25
+ unless meta[:source_location] || meta[:source_file]
26
+ warn "Ignoring source_line without source_file" if meta[:source_line]
27
+ if (location = find_caller)
28
+ meta[:source_file] = location.absolute_path
29
+ meta[:source_line] = location.lineno
30
+ end
31
+ end
32
+ end
33
+
34
+ def process_instrument_options(opts, meta)
35
+ source_location = opts[:source_location] || opts[:meta]&.[](:source_location)
36
+ source_file = opts[:source_file] || opts[:meta]&.[](:source_file)
37
+ source_line = opts[:source_line] || opts[:meta]&.[](:source_line)
38
+ source_name_hint, const_name, method_name =
39
+ opts[:source_location_hint] || opts[:meta]&.[](:source_location_hint)
40
+ instrument_location = opts[:sk_instrument_location]
41
+
42
+ if source_location
43
+ meta[:source_location] = source_location
44
+ elsif source_name_hint
45
+ source_location = dispatch_hinted_source_location(source_name_hint, const_name, method_name)
46
+ meta[:source_file], meta[:source_line] = source_location
47
+ meta.delete(:source_location_hint) if source_location
48
+ elsif source_file
49
+ meta[:source_file] = source_file
50
+ meta[:source_line] = source_line
51
+ elsif instrument_location && project_path?(instrument_location.absolute_path)
52
+ meta[:source_file] = instrument_location.absolute_path
53
+ meta[:source_line] = instrument_location.lineno
54
+ else
55
+ warn "Ignoring source_line without source_file" if source_line
56
+ if (location = find_caller(cache_key: opts.hash))
57
+ meta[:source_file] = location.absolute_path
58
+ meta[:source_line] = location.lineno
59
+ end
60
+ end
61
+
62
+ meta
63
+ end
64
+
65
+ def process_normalizer_meta(payload, meta, **opts)
66
+ if opts[:source_location] && (opts[:source_file] || opts[:source_line])
67
+ warn "Found both source_location and source_file or source_line in normalizer\n" \
68
+ " location=#{opts[:source_location]}; file=#{opts[:source_file]}; line=#{opts[:source_line]}"
69
+ end
70
+
71
+ sl =
72
+ if (source_name, constant_name, method_name = opts[:source_location_hint])
73
+ dispatch_hinted_source_location(source_name, constant_name, method_name)
74
+ elsif opts[:source_file]
75
+ [opts[:source_file], opts[:source_line]]
76
+ end
77
+
78
+ sl ||= source_location(payload, meta, cache_key: opts[:cache_key])
79
+
80
+ if sl
81
+ trace("normalizer source_location=#{sl}")
82
+ meta[:source_file], meta[:source_line] = sl
83
+ end
84
+
85
+ meta
86
+ end
87
+
88
+ def trace_preprocess_meta(meta)
89
+ source_line = meta.delete(:source_line)
90
+ source_file = meta.delete(:source_file)
91
+
92
+ if meta[:source_location]
93
+ if source_file || source_line
94
+ warn "Found both source_location and source_file or source_line, using source_location\n" \
95
+ " location=#{meta[:source_location]}; file=#{source_file}; line=#{source_line}"
96
+ end
97
+
98
+ unless meta[:source_location].is_a?(String)
99
+ warn "Found non-string value for source_location; skipping"
100
+ meta.delete(:source_location)
101
+ end
102
+ elsif source_file
103
+ meta[:source_location] = sanitize_source_location(source_file, source_line)
104
+ elsif source_line
105
+ warn "Ignoring source_line without source_file; source_line=#{source_line}"
106
+ end
107
+
108
+ trace("source_location=#{meta[:source_location]}") if meta[:source_location]
109
+ end
110
+
111
+ def allowed_meta_keys
112
+ META_KEYS
113
+ end
114
+
115
+ protected
116
+
117
+ def dispatch_hinted_source_location(source_name, const_name, method_name)
118
+ return unless const_name && method_name
119
+
120
+ instance_method_source_location(const_name, method_name, source_name: source_name)
121
+ end
122
+
123
+ # from normalizers.rb
124
+ # Returns an array of file and line
125
+ def source_location(payload, meta, cache_key: nil)
126
+ # FIXME: what should precedence be?
127
+ if meta.is_a?(Hash) && meta[:source_location]
128
+ meta.delete(:source_location)
129
+ elsif payload.is_a?(Hash) && payload[:sk_source_location]
130
+ payload[:sk_source_location]
131
+ elsif (location = find_caller(cache_key: cache_key))
132
+ [location.absolute_path, location.lineno]
133
+ end
134
+ end
135
+
136
+ def find_caller(cache_key: nil)
137
+ # starting at 4 to skip Skylight extension processing logic
138
+ locations = ::Kernel.caller_locations(4..MAX_CALLER_DEPTH)
139
+
140
+ if cache_key
141
+ localized_cache_key = [cache_key, locations.map(&:lineno)].hash
142
+ @caller_cache.fetch(localized_cache_key) { find_caller_inner(locations) }
143
+ else
144
+ find_caller_inner(locations)
145
+ end
146
+ end
147
+
148
+ def project_path?(path)
149
+ return false unless path
150
+
151
+ # Must be in the project root
152
+ return false unless path.start_with?(config.root.to_s)
153
+
154
+ # Must not be Bundler's vendor location
155
+ return false if defined?(Bundler) && path.start_with?(Bundler.bundle_path.to_s)
156
+
157
+ # Must not be Ruby files
158
+ return false if path.include?("/ruby-#{RUBY_VERSION}/lib/ruby/")
159
+
160
+ # So it must be a project file
161
+ true
162
+ end
163
+
164
+ def instance_method_source_location(constant_name, method_name, source_name: :instance_method)
165
+ @instance_method_source_location_cache.fetch([constant_name, method_name, source_name]) do
166
+ if (constant = ::ActiveSupport::Inflector.safe_constantize(constant_name))
167
+ if constant.instance_methods.include?(:"before_instrument_#{method_name}")
168
+ method_name = :"before_instrument_#{method_name}"
169
+ end
170
+ begin
171
+ unbound_method =
172
+ case source_name
173
+ when :instance_method
174
+ find_instance_method(constant, method_name)
175
+ when :own_instance_method
176
+ find_own_instance_method(constant, method_name)
177
+ when :instance_method_super
178
+ find_instance_method_super(constant, method_name)
179
+ when :class_method
180
+ find_class_method(constant, method_name)
181
+ end
182
+
183
+ unbound_method&.source_location
184
+ rescue NameError
185
+ nil
186
+ end
187
+ end
188
+ end
189
+ end
190
+
191
+ def sanitize_source_location(path, line)
192
+ # Do this first since gems may be vendored in the app repo. However, it might be slower.
193
+ # Should we cache matches?
194
+ if (gem_name = find_source_gem(path))
195
+ path = gem_name
196
+ line = nil
197
+ elsif project_path?(path)
198
+ # Get relative path to root
199
+ path = Pathname.new(path).relative_path_from(config.root).to_s
200
+ else
201
+ return
202
+ end
203
+
204
+ line ? "#{path}:#{line}" : path
205
+ end
206
+
207
+ private
208
+
209
+ def gem_require_trie
210
+ @gem_require_trie ||=
211
+ begin
212
+ trie = {}
213
+
214
+ Gem.loaded_specs.each do |name, spec|
215
+ next if config.source_location_ignored_gems&.include?(name)
216
+
217
+ spec.full_require_paths.each do |path|
218
+ t1 = trie
219
+
220
+ path
221
+ .split(File::SEPARATOR)
222
+ .each do |segment|
223
+ t1[segment] ||= {}
224
+ t1 = t1[segment]
225
+ end
226
+
227
+ t1[:name] = name
228
+ end
229
+ end
230
+
231
+ trie
232
+ end
233
+ end
234
+
235
+ def find_source_gem(path)
236
+ return nil unless path
237
+
238
+ trie = gem_require_trie
239
+
240
+ path
241
+ .split(File::SEPARATOR)
242
+ .each do |segment|
243
+ trie = trie[segment]
244
+ break unless trie
245
+ return trie[:name] if trie[:name]
246
+ end
247
+
248
+ nil
249
+ end
250
+
251
+ def find_caller_inner(locations)
252
+ # Start at file before this one
253
+ # NOTE: We could start farther back now to avoid more Skylight files
254
+ locations.find do |l|
255
+ absolute_path = l.absolute_path
256
+ find_source_gem(absolute_path) || project_path?(absolute_path)
257
+ end
258
+ end
259
+
260
+ # walks up the inheritance tree until it finds the last method
261
+ # without a super_method definition.
262
+ def find_instance_method_super(constant, method_name)
263
+ return unless (unbound_method = find_instance_method(constant, method_name))
264
+
265
+ unbound_method = unbound_method.super_method while unbound_method.super_method
266
+
267
+ unbound_method
268
+ end
269
+
270
+ # walks up the inheritance tree until it finds the instance method
271
+ # belonging to the constant given (skip prepended modules)
272
+ def find_own_instance_method(constant, method_name)
273
+ return unless (unbound_method = find_instance_method(constant, method_name))
274
+
275
+ while unbound_method.owner != constant && unbound_method.super_method
276
+ unbound_method = unbound_method.super_method
277
+ end
278
+
279
+ unbound_method if unbound_method.owner == constant
280
+ end
281
+
282
+ def find_instance_method(constant, method_name)
283
+ constant.instance_method(method_name)
284
+ end
285
+
286
+ def find_class_method(constant, method_name)
287
+ constant.method(method_name)
288
+ end
289
+ end
290
+ end
291
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/inflector"
4
+
5
+ module Skylight
6
+ module Extensions
7
+ class Collection
8
+ def initialize(config, extensions = [])
9
+ @config = config
10
+ @extensions = extensions
11
+ end
12
+
13
+ def enable!(ext_name)
14
+ return if enabled?(ext_name)
15
+
16
+ find_by_name(ext_name) do |ext_class|
17
+ extensions << ext_class.new(config)
18
+ rememoize!
19
+ end
20
+ end
21
+
22
+ def disable!(ext_name)
23
+ find_by_name(ext_name) do |ext_class|
24
+ extensions.reject! { |x| x.is_a?(ext_class) }
25
+ rememoize!
26
+ end
27
+ end
28
+
29
+ def enabled?(ext_name)
30
+ return unless (ext_class = find_by_name(ext_name))
31
+
32
+ !!extensions.detect { |x| x.is_a?(ext_class) }
33
+ end
34
+
35
+ def process_trace_meta(meta)
36
+ extensions.each { |ext| ext.process_trace_meta(meta) }
37
+ end
38
+
39
+ # meta is a mutable hash that will be passed to the instrumenter.
40
+ # This method bridges Skylight.instrument and instrumenter.instrument.
41
+ def process_instrument_options(opts, meta)
42
+ extensions.each { |ext| ext.process_instrument_options(opts, meta) }
43
+ end
44
+
45
+ def process_normalizer_meta(payload, meta, **opts)
46
+ extensions.each { |ext| ext.process_normalizer_meta(payload, meta, **opts) }
47
+ end
48
+
49
+ def trace_preprocess_meta(meta)
50
+ extensions.each { |ext| ext.trace_preprocess_meta(meta) }
51
+ end
52
+
53
+ def allowed_meta_keys
54
+ @allowed_meta_keys ||= extensions.flat_map(&:allowed_meta_keys).uniq
55
+ end
56
+
57
+ private
58
+
59
+ attr_reader :extensions, :config
60
+
61
+ def find_by_name(ext_name)
62
+ begin
63
+ Skylight::Extensions.const_get(ActiveSupport::Inflector.classify(ext_name))
64
+ rescue NameError
65
+ return nil
66
+ end.tap { |const| yield const if block_given? }
67
+ end
68
+
69
+ def rememoize!
70
+ @allowed_meta_keys = nil
71
+ allowed_meta_keys
72
+ end
73
+ end
74
+
75
+ class Extension
76
+ def initialize(config)
77
+ @config = config
78
+ end
79
+
80
+ def process_trace_meta(_meta); end
81
+
82
+ def process_instrument_options(_opts, _meta); end
83
+
84
+ def process_normalizer_meta(_payload, _meta, **opts); end
85
+
86
+ def trace_preprocess_meta(_meta); end
87
+
88
+ def allowed_meta_keys
89
+ []
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ require "skylight/extensions/source_location"
@@ -0,0 +1,18 @@
1
+ module Skylight
2
+ module Formatters
3
+ module HTTP
4
+ # Build instrumentation options for HTTP queries
5
+ #
6
+ # @param [String] method HTTP method, e.g. get, post
7
+ # @param [String] scheme HTTP scheme, e.g. http, https
8
+ # @param [String] host Request host, e.g. example.com
9
+ # @param [String, Integer] port Request port
10
+ # @param [String] path Request path
11
+ # @param [String] query Request query string
12
+ # @return [Hash] a hash containing `:category`, `:title`, and `:annotations`
13
+ def self.build_opts(method, _scheme, host, _port, _path, _query)
14
+ { category: "api.http.#{method.downcase}", title: "#{method.upcase} #{host}", internal: true }
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,99 @@
1
+ require "skylight/util/logging"
2
+
3
+ module Skylight
4
+ # @api private
5
+ class GC
6
+ METHODS = %i[enable total_time].freeze
7
+ TH_KEY = :SK_GC_CURR_WINDOW
8
+ MAX_COUNT = 1000
9
+ MAX_TIME = 30_000_000
10
+
11
+ include Util::Logging
12
+
13
+ attr_reader :config
14
+
15
+ def initialize(config, profiler)
16
+ @listeners = []
17
+ @config = config
18
+ @lock = Mutex.new
19
+ @time = 0
20
+
21
+ if METHODS.all? { |m| profiler.respond_to?(m) }
22
+ @profiler = profiler
23
+ @time = @profiler.total_time
24
+ else
25
+ debug "disabling GC profiling"
26
+ end
27
+ end
28
+
29
+ def enable
30
+ @profiler&.enable
31
+ end
32
+
33
+ # Total time in microseconds for GC over entire process lifetime
34
+ def total_time
35
+ @profiler ? @profiler.total_time : nil
36
+ end
37
+
38
+ def track
39
+ if @profiler
40
+ win = Window.new(self)
41
+
42
+ @lock.synchronize do
43
+ __update
44
+ @listeners << win
45
+
46
+ # Cleanup any listeners that might have leaked
47
+ @listeners.shift until @listeners[0].time < MAX_TIME
48
+
49
+ @listeners.shift if @listeners.length > MAX_COUNT
50
+ end
51
+
52
+ win
53
+ else
54
+ Window.new(nil)
55
+ end
56
+ end
57
+
58
+ def release(win)
59
+ @lock.synchronize { @listeners.delete(win) }
60
+ end
61
+
62
+ def update
63
+ @lock.synchronize { __update }
64
+
65
+ nil
66
+ end
67
+
68
+ private
69
+
70
+ def __update
71
+ time = @profiler.total_time
72
+ diff = time - @time
73
+ @time = time
74
+
75
+ @listeners.each { |l| l.add(diff) } if diff > 0
76
+ end
77
+
78
+ class Window
79
+ attr_reader :time
80
+
81
+ def initialize(global)
82
+ @global = global
83
+ @time = 0
84
+ end
85
+
86
+ def update
87
+ @global&.update
88
+ end
89
+
90
+ def add(time)
91
+ @time += time
92
+ end
93
+
94
+ def release
95
+ @global&.release(self)
96
+ end
97
+ end
98
+ end
99
+ end