skylight 3.1.4 → 5.3.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 (126) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +465 -294
  3. data/CLA.md +1 -1
  4. data/CONTRIBUTING.md +11 -3
  5. data/ERRORS.md +3 -0
  6. data/LICENSE.md +8 -18
  7. data/README.md +1 -2
  8. data/bin/skylight +1 -1
  9. data/ext/extconf.rb +118 -122
  10. data/ext/libskylight.yml +8 -6
  11. data/ext/skylight_native.c +56 -100
  12. data/lib/skylight/api.rb +41 -27
  13. data/lib/skylight/cli/doctor.rb +68 -70
  14. data/lib/skylight/cli/helpers.rb +3 -5
  15. data/lib/skylight/cli/merger.rb +99 -92
  16. data/lib/skylight/cli.rb +40 -43
  17. data/lib/skylight/config.rb +656 -201
  18. data/lib/skylight/data/cacert.pem +730 -1023
  19. data/lib/skylight/deprecation.rb +17 -0
  20. data/lib/skylight/errors.rb +34 -16
  21. data/lib/skylight/extensions/source_location.rb +291 -0
  22. data/lib/skylight/extensions.rb +95 -0
  23. data/lib/skylight/formatters/http.rb +18 -0
  24. data/lib/skylight/gc.rb +99 -0
  25. data/lib/skylight/helpers.rb +82 -39
  26. data/lib/skylight/instrumenter.rb +339 -9
  27. data/lib/skylight/middleware.rb +147 -1
  28. data/lib/skylight/native.rb +71 -23
  29. data/lib/skylight/native_ext_fetcher.rb +39 -47
  30. data/lib/skylight/normalizers/action_controller/process_action.rb +68 -0
  31. data/lib/skylight/normalizers/action_controller/send_file.rb +51 -0
  32. data/lib/skylight/normalizers/action_dispatch/process_middleware.rb +22 -0
  33. data/lib/skylight/normalizers/action_dispatch/route_set.rb +27 -0
  34. data/lib/skylight/normalizers/action_view/render_collection.rb +24 -0
  35. data/lib/skylight/normalizers/action_view/render_layout.rb +25 -0
  36. data/lib/skylight/normalizers/action_view/render_partial.rb +23 -0
  37. data/lib/skylight/normalizers/action_view/render_template.rb +23 -0
  38. data/lib/skylight/normalizers/active_job/perform.rb +87 -0
  39. data/lib/skylight/normalizers/active_model_serializers/render.rb +32 -0
  40. data/lib/skylight/normalizers/active_record/instantiation.rb +16 -0
  41. data/lib/skylight/normalizers/active_record/sql.rb +20 -0
  42. data/lib/skylight/normalizers/active_storage.rb +28 -0
  43. data/lib/skylight/normalizers/active_support/cache.rb +11 -0
  44. data/lib/skylight/normalizers/active_support/cache_clear.rb +16 -0
  45. data/lib/skylight/normalizers/active_support/cache_decrement.rb +16 -0
  46. data/lib/skylight/normalizers/active_support/cache_delete.rb +16 -0
  47. data/lib/skylight/normalizers/active_support/cache_exist.rb +16 -0
  48. data/lib/skylight/normalizers/active_support/cache_fetch_hit.rb +16 -0
  49. data/lib/skylight/normalizers/active_support/cache_generate.rb +16 -0
  50. data/lib/skylight/normalizers/active_support/cache_increment.rb +16 -0
  51. data/lib/skylight/normalizers/active_support/cache_read.rb +16 -0
  52. data/lib/skylight/normalizers/active_support/cache_read_multi.rb +16 -0
  53. data/lib/skylight/normalizers/active_support/cache_write.rb +16 -0
  54. data/lib/skylight/normalizers/coach/handler_finish.rb +44 -0
  55. data/lib/skylight/normalizers/coach/middleware_finish.rb +33 -0
  56. data/lib/skylight/normalizers/couch_potato/query.rb +20 -0
  57. data/lib/skylight/normalizers/data_mapper/sql.rb +12 -0
  58. data/lib/skylight/normalizers/default.rb +24 -0
  59. data/lib/skylight/normalizers/elasticsearch/request.rb +20 -0
  60. data/lib/skylight/normalizers/faraday/request.rb +38 -0
  61. data/lib/skylight/normalizers/grape/endpoint.rb +28 -0
  62. data/lib/skylight/normalizers/grape/endpoint_render.rb +25 -0
  63. data/lib/skylight/normalizers/grape/endpoint_run.rb +39 -0
  64. data/lib/skylight/normalizers/grape/endpoint_run_filters.rb +20 -0
  65. data/lib/skylight/normalizers/grape/format_response.rb +20 -0
  66. data/lib/skylight/normalizers/graphiti/render.rb +22 -0
  67. data/lib/skylight/normalizers/graphiti/resolve.rb +31 -0
  68. data/lib/skylight/normalizers/graphql/base.rb +127 -0
  69. data/lib/skylight/normalizers/render.rb +79 -0
  70. data/lib/skylight/normalizers/sequel/sql.rb +12 -0
  71. data/lib/skylight/normalizers/shrine.rb +32 -0
  72. data/lib/skylight/normalizers/sql.rb +41 -0
  73. data/lib/skylight/normalizers.rb +157 -0
  74. data/lib/skylight/probes/action_controller.rb +52 -0
  75. data/lib/skylight/probes/action_dispatch/request_id.rb +33 -0
  76. data/lib/skylight/probes/action_dispatch/routing/route_set.rb +30 -0
  77. data/lib/skylight/probes/action_dispatch.rb +2 -0
  78. data/lib/skylight/probes/action_view.rb +42 -0
  79. data/lib/skylight/probes/active_job.rb +27 -0
  80. data/lib/skylight/probes/active_job_enqueue.rb +35 -0
  81. data/lib/skylight/probes/active_model_serializers.rb +50 -0
  82. data/lib/skylight/probes/active_record_async.rb +96 -0
  83. data/lib/skylight/probes/delayed_job.rb +144 -0
  84. data/lib/skylight/probes/elasticsearch.rb +45 -0
  85. data/lib/skylight/probes/excon/middleware.rb +65 -0
  86. data/lib/skylight/probes/excon.rb +25 -0
  87. data/lib/skylight/probes/faraday.rb +23 -0
  88. data/lib/skylight/probes/graphql.rb +38 -0
  89. data/lib/skylight/probes/httpclient.rb +44 -0
  90. data/lib/skylight/probes/middleware.rb +135 -0
  91. data/lib/skylight/probes/mongo.rb +169 -0
  92. data/lib/skylight/probes/mongoid.rb +6 -0
  93. data/lib/skylight/probes/net_http.rb +54 -0
  94. data/lib/skylight/probes/rack_builder.rb +37 -0
  95. data/lib/skylight/probes/redis.rb +68 -0
  96. data/lib/skylight/probes/sequel.rb +29 -0
  97. data/lib/skylight/probes/sinatra.rb +66 -0
  98. data/lib/skylight/probes/sinatra_add_middleware.rb +10 -10
  99. data/lib/skylight/probes/tilt.rb +25 -0
  100. data/lib/skylight/probes.rb +172 -0
  101. data/lib/skylight/railtie.rb +172 -15
  102. data/lib/skylight/sidekiq.rb +47 -0
  103. data/lib/skylight/sinatra.rb +2 -2
  104. data/lib/skylight/subscriber.rb +130 -0
  105. data/lib/skylight/test.rb +147 -0
  106. data/lib/skylight/trace.rb +331 -15
  107. data/lib/skylight/user_config.rb +60 -0
  108. data/lib/skylight/util/allocation_free.rb +26 -0
  109. data/lib/skylight/util/clock.rb +57 -0
  110. data/lib/skylight/util/component.rb +47 -9
  111. data/lib/skylight/util/deploy.rb +24 -40
  112. data/lib/skylight/util/gzip.rb +20 -0
  113. data/lib/skylight/util/hostname.rb +4 -4
  114. data/lib/skylight/util/http.rb +62 -71
  115. data/lib/skylight/util/instrumenter_method.rb +26 -0
  116. data/lib/skylight/util/logging.rb +136 -0
  117. data/lib/skylight/util/lru_cache.rb +36 -0
  118. data/lib/skylight/util/platform.rb +74 -0
  119. data/lib/skylight/util/proxy.rb +13 -0
  120. data/lib/skylight/util/ssl.rb +4 -28
  121. data/lib/skylight/util.rb +12 -0
  122. data/lib/skylight/vendor/cli/thor/rake_compat.rb +1 -1
  123. data/lib/skylight/version.rb +5 -1
  124. data/lib/skylight/vm/gc.rb +60 -0
  125. data/lib/skylight.rb +213 -24
  126. metadata +171 -53
@@ -0,0 +1,147 @@
1
+ module Skylight
2
+ module Test
3
+ module Mocking
4
+ def mock!(config_opts = {}, &callback)
5
+ config_opts[:mock_submission] ||= callback || proc {}
6
+
7
+ config_class =
8
+ Class.new(Config) do
9
+ def validate_with_server
10
+ true
11
+ end
12
+ end
13
+
14
+ config = config_class.load(config_opts)
15
+ config[:authentication] ||= "zomg"
16
+
17
+ class_eval do
18
+ unless const_defined?(:OriginalInstrumenter)
19
+ const_set :OriginalInstrumenter, Instrumenter
20
+ remove_const :Instrumenter
21
+ const_set(
22
+ :Instrumenter,
23
+ Class.new(OriginalInstrumenter) do
24
+ def self.name
25
+ "Mocked Instrumenter"
26
+ end
27
+
28
+ def native_start
29
+ true
30
+ end
31
+
32
+ def native_submit_trace(trace)
33
+ config[:mock_submission].call(trace)
34
+ end
35
+
36
+ def native_stop; end
37
+ end
38
+ )
39
+
40
+ const_set :OriginalTrace, Trace
41
+ remove_const :Trace
42
+ const_set(
43
+ :Trace,
44
+ Class.new(OriginalTrace) do
45
+ def self.native_new(start, _uuid, endpoint, meta)
46
+ inst = super
47
+ inst.instance_variable_set(:@start, start)
48
+ inst.instance_variable_set(:@endpoint, endpoint)
49
+ inst.instance_variable_set(:@starting_endpoint, endpoint)
50
+ inst.instance_variable_set(:@meta, meta)
51
+ inst
52
+ end
53
+
54
+ attr_reader :endpoint, :starting_endpoint, :meta
55
+
56
+ def mock_spans
57
+ @mock_spans ||= []
58
+ end
59
+
60
+ def filter_spans
61
+ if block_given?
62
+ mock_spans.select { |span| yield span }
63
+ else
64
+ mock_spans.reject { |span| span[:cat] == "noise.gc" }
65
+ end
66
+ end
67
+
68
+ def native_get_uuid
69
+ @uuid
70
+ end
71
+
72
+ def uuid=(value)
73
+ @uuid = value
74
+ end
75
+
76
+ def native_get_started_at
77
+ @start
78
+ end
79
+
80
+ def native_set_endpoint(endpoint)
81
+ @endpoint = endpoint
82
+ end
83
+
84
+ def native_set_component(component)
85
+ @component = component
86
+ end
87
+
88
+ def native_start_span(time, cat)
89
+ span = { start: time, cat: cat }
90
+ mock_spans << span
91
+
92
+ # Return integer like the native method does
93
+ mock_spans.index(span)
94
+ end
95
+
96
+ def native_span_set_title(span, title)
97
+ mock_spans[span][:title] = title
98
+ end
99
+
100
+ def native_span_set_description(span, desc)
101
+ mock_spans[span][:desc] = desc
102
+ end
103
+
104
+ def native_span_set_meta(span, meta)
105
+ mock_spans[span][:meta] = meta
106
+ end
107
+
108
+ def native_span_started(span); end
109
+
110
+ def native_span_set_exception(span, exception_object, exception)
111
+ mock_spans[span][:exception_object] = exception_object
112
+ mock_spans[span][:exception] = exception
113
+ end
114
+
115
+ def native_stop_span(span, time)
116
+ span = mock_spans[span]
117
+ span[:duration] = time - span[:start]
118
+ nil
119
+ end
120
+
121
+ def native_use_pruning
122
+ @using_native_pruning = true
123
+ end
124
+ end
125
+ )
126
+ end
127
+ end
128
+
129
+ start!(config)
130
+ end
131
+
132
+ def unmock!
133
+ if const_defined?(:OriginalInstrumenter)
134
+ class_eval do
135
+ remove_const :Instrumenter
136
+ const_set :Instrumenter, OriginalInstrumenter
137
+ remove_const :OriginalInstrumenter
138
+
139
+ remove_const :Trace
140
+ const_set :Trace, OriginalTrace
141
+ remove_const :OriginalTrace
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
@@ -1,7 +1,56 @@
1
+ require "securerandom"
2
+ require "skylight/util/logging"
3
+
1
4
  module Skylight
2
- class Trace < Core::Trace
3
- def initialize(*)
4
- super
5
+ class Trace
6
+ GC_CAT = "noise.gc".freeze
7
+ SYNTHETIC = "<synthetic>".freeze
8
+
9
+ META_KEYS = %i[mute_children database].freeze
10
+
11
+ include Util::Logging
12
+
13
+ attr_reader :instrumenter, :endpoint, :segment, :notifications, :meta, :component
14
+
15
+ def self.new(instrumenter, endpoint, start, cat, title = nil, desc = nil, meta: nil, segment: nil, component: nil)
16
+ uuid = SecureRandom.uuid
17
+ inst = native_new(normalize_time(start), uuid, endpoint, meta)
18
+ inst.uuid = uuid
19
+ inst.send(:initialize, instrumenter, cat, title, desc, meta, component: component)
20
+ inst.endpoint = endpoint
21
+ inst.segment = segment
22
+ inst
23
+ end
24
+
25
+ # TODO: Move this into native
26
+ def self.normalize_time(time)
27
+ # At least one customer has extensions that cause integer division to produce rationals.
28
+ # Since the native code expects an integer, we force it again.
29
+ (time.to_i / 100_000).to_i
30
+ end
31
+
32
+ def initialize(instrumenter, cat, title, desc, meta, component: nil)
33
+ raise ArgumentError, "instrumenter is required" unless instrumenter
34
+
35
+ @instrumenter = instrumenter
36
+ @submitted = false
37
+ @broken = false
38
+
39
+ @notifications = []
40
+
41
+ @spans = []
42
+
43
+ preprocess_meta(meta) if meta
44
+
45
+ # create the root node
46
+ @root = start(native_get_started_at, cat, title, desc, meta, normalize: false)
47
+
48
+ # Also store meta for later access
49
+ @meta = meta
50
+
51
+ @gc = config.gc.track unless ENV.key?("SKYLIGHT_DISABLE_GC_TRACKING")
52
+
53
+ self.component = component if component
5
54
  @too_many_spans = false
6
55
  native_use_pruning if use_pruning?
7
56
  end
@@ -23,34 +72,301 @@ module Skylight
23
72
  !!@too_many_spans
24
73
  end
25
74
 
26
- def maybe_broken(e)
27
- if e.is_a?(Skylight::MaximumTraceSpansError) && config.get(:report_max_spans_exceeded)
75
+ def log_context
76
+ @log_context ||= { trace: uuid }
77
+ end
78
+
79
+ def endpoint=(value)
80
+ if muted?
81
+ maybe_warn(:endpoint_set_muted, "tried to set endpoint name while muted")
82
+ return
83
+ end
84
+ @endpoint = value
85
+ native_set_endpoint(value)
86
+ end
87
+
88
+ def segment=(value)
89
+ if muted?
90
+ maybe_warn(:segment_set_muted, "tried to set segment name while muted")
91
+ return
92
+ end
93
+ @segment = value
94
+ end
95
+
96
+ attr_accessor :compound_response_error_status
97
+
98
+ def config
99
+ @instrumenter.config
100
+ end
101
+
102
+ def muted?
103
+ !!@child_instrumentation_muted_by || @instrumenter.muted?
104
+ end
105
+
106
+ def broken?
107
+ !!@broken
108
+ end
109
+
110
+ def maybe_broken(err)
111
+ if err.is_a?(Skylight::MaximumTraceSpansError) && config.get(:report_max_spans_exceeded)
28
112
  too_many_spans!
29
113
  else
30
- super
114
+ error "failed to instrument span; msg=%s; endpoint=%s", err.message, endpoint
115
+ broken!
31
116
  end
32
117
  end
33
118
 
119
+ def instrument(cat, title = nil, desc = nil, meta = nil)
120
+ return if muted?
121
+ return if broken?
122
+
123
+ t { "instrument: #{cat}, #{title}" }
124
+
125
+ title.freeze if title.is_a?(String)
126
+ desc.freeze if desc.is_a?(String)
127
+
128
+ now = Skylight::Util::Clock.nanos
129
+
130
+ preprocess_meta(meta) if meta
131
+
132
+ start(now - gc_time, cat, title, desc, meta)
133
+ rescue StandardError => e
134
+ maybe_broken(e)
135
+ nil
136
+ end
137
+
138
+ def done(span, meta = nil)
139
+ # `span` will be `nil` if we failed to start instrumenting, such as in
140
+ # the case of too many spans in a request.
141
+ return unless span
142
+ return if broken?
143
+
144
+ if meta&.[](:defer)
145
+ deferred_spans[span] ||= (Skylight::Util::Clock.nanos - gc_time)
146
+ return
147
+ end
148
+
149
+ if meta && (meta[:exception_object] || meta[:exception])
150
+ native_span_set_exception(span, meta[:exception_object], meta[:exception])
151
+ end
152
+
153
+ stop(span, Skylight::Util::Clock.nanos - gc_time)
154
+ rescue StandardError => e
155
+ error "failed to close span; msg=%s; endpoint=%s", e.message, endpoint
156
+ log_trace "Original Backtrace:\n#{e.backtrace.join("\n")}"
157
+ broken!
158
+ nil
159
+ end
160
+
161
+ def inspect
162
+ to_s
163
+ end
164
+
165
+ def release
166
+ t { "release; is_current=#{@instrumenter.current_trace == self}" }
167
+ return unless @instrumenter.current_trace == self
168
+
169
+ @instrumenter.current_trace = nil
170
+ end
171
+
172
+ def broken!
173
+ debug "trace is broken"
174
+ @broken = true
175
+ end
176
+
34
177
  def traced
35
178
  if too_many_spans?
36
- error("[E%04d] The request exceeded the maximum number of spans allowed. It will still " \
37
- "be tracked but with reduced information. endpoint=%s", Skylight::MaximumTraceSpansError.code, endpoint)
179
+ error(
180
+ "[E%04d] The request exceeded the maximum number of spans allowed. It will still " \
181
+ "be tracked but with reduced information. endpoint=%s",
182
+ Skylight::MaximumTraceSpansError.code,
183
+ endpoint
184
+ )
38
185
  end
39
186
 
40
- super
187
+ gc = gc_time
188
+ now = Skylight::Util::Clock.nanos
189
+ track_gc(gc, now)
190
+ stop(@root, now)
191
+ end
192
+
193
+ def submit
194
+ t { "submitting trace" }
195
+
196
+ # This must always be called to clean up properly
197
+ release
198
+
199
+ if broken?
200
+ t { "broken, not submitting" }
201
+ return
202
+ end
203
+
204
+ if @submitted
205
+ t { "already submitted" }
206
+ return
207
+ end
208
+
209
+ @submitted = true
210
+
211
+ traced
212
+
213
+ @instrumenter.process(self)
214
+ rescue Exception => e
215
+ error e.message
216
+ t { e.backtrace.join("\n") }
41
217
  end
42
218
 
43
219
  private
44
220
 
45
- def track_gc(*)
46
- # This attempts to log another span which will fail if we have too many
47
- return if too_many_spans?
48
- super
221
+ def track_gc(time, now)
222
+ # This attempts to log another span which will fail if we have too many
223
+ return if too_many_spans?
224
+
225
+ if time > 0
226
+ t { fmt "tracking GC time; duration=%d", time }
227
+ meta = { source_location: SYNTHETIC }
228
+ stop(start(now - time, GC_CAT, nil, nil, meta), now)
49
229
  end
230
+ end
231
+
232
+ def start(time, cat, title, desc, meta, opts = {})
233
+ time = self.class.normalize_time(time) unless opts[:normalize] == false
50
234
 
51
- def use_pruning?
52
- config.get(:prune_large_traces)
235
+ mute_children = meta&.delete(:mute_children)
236
+
237
+ sp = native_start_span(time, cat.to_s)
238
+ native_span_set_title(sp, title.to_s) if title
239
+ native_span_set_description(sp, desc.to_s) if desc
240
+ native_span_set_meta(sp, meta) if meta
241
+ native_span_started(sp)
242
+
243
+ @spans << sp
244
+ t { "started span: #{sp} - #{cat}, #{title}" }
245
+
246
+ if mute_children
247
+ t { "muting child instrumentation for span=#{sp}" }
248
+ mute_child_instrumentation(sp)
53
249
  end
54
250
 
251
+ sp
252
+ end
253
+
254
+ def mute_child_instrumentation(span)
255
+ @child_instrumentation_muted_by = span
256
+ end
257
+
258
+ # Middleware spans that were interrupted by a throw/catch should be cached here.
259
+ # keys: span ids
260
+ # values: nsec timestamp at which the span was cached here.
261
+ def deferred_spans
262
+ @deferred_spans ||= {}
263
+ end
264
+
265
+ def stop(span, time)
266
+ t { "stopping span: #{span}" }
267
+
268
+ # If `stop` is called for a span that is not the last item in the stack,
269
+ # check to see if the last item has been marked as deferred. If so, close
270
+ # that span first, then try to close the original.
271
+ while deferred_spans[expected = @spans.pop]
272
+ normalized_stop(expected, deferred_spans.delete(expected))
273
+ end
274
+
275
+ handle_unexpected_stop(expected, span) unless span == expected
276
+
277
+ normalized_stop(span, time)
278
+ nil
279
+ end
280
+
281
+ def normalized_stop(span, time)
282
+ time = self.class.normalize_time(time)
283
+ native_stop_span(span, time)
284
+
285
+ if @child_instrumentation_muted_by == span
286
+ @child_instrumentation_muted_by = nil # restart instrumenting
287
+ end
288
+ end
289
+
290
+ # Originally extracted from `stop`.
291
+ # If we attempt to close spans out of order, and it appears to be a middleware issue,
292
+ # disable the middleware probe and mark trace as broken.
293
+ def handle_unexpected_stop(expected, span)
294
+ message =
295
+ "[E0001] Spans were closed out of order. Expected to see '#{native_span_get_title(expected)}', " \
296
+ "but got '#{native_span_get_title(span)}' instead."
297
+
298
+ if native_span_get_category(span) == "rack.middleware" && Skylight::Probes.installed.key?(:middleware)
299
+ if Skylight::Probes::Middleware::Probe.disabled?
300
+ message << "\nWe disabled the Middleware probe but unfortunately, this didn't solve the issue."
301
+ else
302
+ Skylight::Probes::Middleware::Probe.disable!
303
+ message <<
304
+ "\n#{native_span_get_title(span)} may be a Middleware that doesn't fully conform " \
305
+ "to the Rack SPEC. We've disabled the Middleware probe to see if that resolves the issue."
306
+ end
307
+ end
308
+
309
+ message << "\nThis request will not be tracked. Please contact support@skylight.io for more information."
310
+
311
+ error message
312
+
313
+ t { "expected=#{expected}, actual=#{span}" }
314
+
315
+ broken!
316
+ end
317
+
318
+ def gc_time
319
+ return 0 unless @gc
320
+
321
+ @gc.update
322
+ @gc.time
323
+ end
324
+
325
+ def use_pruning?
326
+ config.get(:prune_large_traces)
327
+ end
328
+
329
+ def resolve_component(component)
330
+ config.components[component].to_encoded_s
331
+ end
332
+
333
+ def component=(component)
334
+ resolve_component(component).tap do |c|
335
+ # Would it be better for the component getter to get from native?
336
+ @component = c
337
+ native_set_component(c)
338
+ end
339
+ end
340
+
341
+ def preprocess_meta(meta)
342
+ validate_meta(meta)
343
+ instrumenter.extensions.trace_preprocess_meta(meta)
344
+ end
345
+
346
+ def validate_meta(meta)
347
+ unknown_keys = meta.keys - allowed_meta_keys
348
+ if unknown_keys.any?
349
+ unknown_keys.each do |key|
350
+ maybe_warn("unknown_meta:#{key}", "Unknown meta key will be ignored; key=#{key.inspect}")
351
+ meta.delete(key)
352
+ end
353
+ end
354
+ end
355
+
356
+ def allowed_meta_keys
357
+ META_KEYS | instrumenter.extensions.allowed_meta_keys
358
+ end
359
+
360
+ def maybe_warn(context, msg)
361
+ return if warnings_silenced?(context)
362
+
363
+ instrumenter.silence_warnings(context)
364
+
365
+ warn(msg)
366
+ end
367
+
368
+ def warnings_silenced?(context)
369
+ instrumenter.warnings_silenced?(context)
370
+ end
55
371
  end
56
372
  end
@@ -0,0 +1,60 @@
1
+ require "yaml"
2
+ require "skylight/errors"
3
+
4
+ module Skylight
5
+ class UserConfig
6
+ attr_accessor :disable_dev_warning, :disable_env_warning
7
+
8
+ def initialize(config)
9
+ @config = config
10
+ @file_path = nil
11
+ reload
12
+ end
13
+
14
+ def file_path
15
+ return @file_path if @file_path
16
+
17
+ config_path =
18
+ @config[:user_config_path] ||
19
+ begin
20
+ require "etc"
21
+ home_dir =
22
+ ENV.fetch("HOME", nil) || Etc.getpwuid.dir ||
23
+ (ENV.fetch("USER", nil) && File.expand_path("~#{ENV.fetch("USER", nil)}"))
24
+ if home_dir
25
+ File.join(home_dir, ".skylight")
26
+ else
27
+ raise ConfigError,
28
+ "The Skylight `user_config_path` must be defined since the home directory cannot be inferred"
29
+ end
30
+ end
31
+
32
+ @file_path = File.expand_path(config_path)
33
+ end
34
+
35
+ def disable_dev_warning?
36
+ disable_dev_warning || ENV.fetch("SKYLIGHT_DISABLE_DEV_WARNING", nil) =~ /^true$/i
37
+ end
38
+
39
+ def disable_env_warning?
40
+ disable_env_warning
41
+ end
42
+
43
+ def reload
44
+ config = File.exist?(file_path) ? YAML.load_file(file_path) : false
45
+ return unless config
46
+
47
+ self.disable_dev_warning = !!config["disable_dev_warning"]
48
+ self.disable_env_warning = !!config["disable_env_warning"]
49
+ end
50
+
51
+ def save
52
+ FileUtils.mkdir_p(File.dirname(file_path))
53
+ File.open(file_path, "w") { |f| f.puts YAML.dump(to_hash) }
54
+ end
55
+
56
+ def to_hash
57
+ { "disable_dev_warning" => disable_dev_warning, "disable_env_warning" => disable_env_warning }
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,26 @@
1
+ module Skylight
2
+ module Util
3
+ # Helpers to reduce memory allocation
4
+ module AllocationFree
5
+ # Find an item in an array without allocation.
6
+ #
7
+ # @param array [Array] the array to search
8
+ # @yield a block called against each item until a match is found
9
+ # @yieldparam item an item from the array
10
+ # @yieldreturn [Boolean] whether `item` matches the criteria
11
+ # return the found item or nil, if nothing found
12
+ def array_find(array)
13
+ i = 0
14
+
15
+ while i < array.size
16
+ item = array[i]
17
+ return item if yield item
18
+
19
+ i += 1
20
+ end
21
+
22
+ nil
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,57 @@
1
+ module Skylight
2
+ module Util
3
+ # A more precise clock
4
+ class Clock
5
+ def self.use_native!
6
+ class_eval do
7
+ def tick
8
+ native_hrtime
9
+ end
10
+ end
11
+ end
12
+
13
+ # rubocop:disable Lint/DuplicateMethods
14
+ def tick
15
+ now = Time.now
16
+ (now.to_i * 1_000_000_000) + (now.usec * 1_000)
17
+ end
18
+
19
+ # rubocop:enable Lint/DuplicateMethods
20
+
21
+ # TODO: rename to secs
22
+ def absolute_secs
23
+ Time.now.to_i
24
+ end
25
+
26
+ # TODO: remove
27
+ def nanos
28
+ tick
29
+ end
30
+
31
+ # TODO: remove
32
+ def secs
33
+ nanos / 1_000_000_000
34
+ end
35
+
36
+ class << self
37
+ def absolute_secs
38
+ default.absolute_secs
39
+ end
40
+
41
+ def nanos
42
+ default.nanos
43
+ end
44
+
45
+ def secs
46
+ default.secs
47
+ end
48
+
49
+ def default
50
+ @default ||= Clock.new
51
+ end
52
+
53
+ attr_writer :default
54
+ end
55
+ end
56
+ end
57
+ end