skylight-core 2.0.0.beta1

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 (86) hide show
  1. checksums.yaml +7 -0
  2. data/lib/skylight/core/config.rb +454 -0
  3. data/lib/skylight/core/errors.rb +6 -0
  4. data/lib/skylight/core/fanout.rb +44 -0
  5. data/lib/skylight/core/formatters/http.rb +23 -0
  6. data/lib/skylight/core/gc.rb +107 -0
  7. data/lib/skylight/core/instrumentable.rb +144 -0
  8. data/lib/skylight/core/instrumenter.rb +249 -0
  9. data/lib/skylight/core/middleware.rb +101 -0
  10. data/lib/skylight/core/normalizers/action_controller/process_action.rb +50 -0
  11. data/lib/skylight/core/normalizers/action_controller/send_file.rb +50 -0
  12. data/lib/skylight/core/normalizers/action_view/render_collection.rb +22 -0
  13. data/lib/skylight/core/normalizers/action_view/render_partial.rb +21 -0
  14. data/lib/skylight/core/normalizers/action_view/render_template.rb +21 -0
  15. data/lib/skylight/core/normalizers/active_job/enqueue_at.rb +21 -0
  16. data/lib/skylight/core/normalizers/active_model_serializers/render.rb +26 -0
  17. data/lib/skylight/core/normalizers/active_record/instantiation.rb +17 -0
  18. data/lib/skylight/core/normalizers/active_record/sql.rb +33 -0
  19. data/lib/skylight/core/normalizers/active_support/cache.rb +20 -0
  20. data/lib/skylight/core/normalizers/active_support/cache_clear.rb +16 -0
  21. data/lib/skylight/core/normalizers/active_support/cache_decrement.rb +16 -0
  22. data/lib/skylight/core/normalizers/active_support/cache_delete.rb +16 -0
  23. data/lib/skylight/core/normalizers/active_support/cache_exist.rb +16 -0
  24. data/lib/skylight/core/normalizers/active_support/cache_fetch_hit.rb +16 -0
  25. data/lib/skylight/core/normalizers/active_support/cache_generate.rb +16 -0
  26. data/lib/skylight/core/normalizers/active_support/cache_increment.rb +16 -0
  27. data/lib/skylight/core/normalizers/active_support/cache_read.rb +16 -0
  28. data/lib/skylight/core/normalizers/active_support/cache_read_multi.rb +16 -0
  29. data/lib/skylight/core/normalizers/active_support/cache_write.rb +16 -0
  30. data/lib/skylight/core/normalizers/coach/handler_finish.rb +36 -0
  31. data/lib/skylight/core/normalizers/coach/middleware_finish.rb +23 -0
  32. data/lib/skylight/core/normalizers/couch_potato/query.rb +20 -0
  33. data/lib/skylight/core/normalizers/data_mapper/sql.rb +12 -0
  34. data/lib/skylight/core/normalizers/default.rb +27 -0
  35. data/lib/skylight/core/normalizers/elasticsearch/request.rb +20 -0
  36. data/lib/skylight/core/normalizers/faraday/request.rb +37 -0
  37. data/lib/skylight/core/normalizers/grape/endpoint.rb +30 -0
  38. data/lib/skylight/core/normalizers/grape/endpoint_render.rb +26 -0
  39. data/lib/skylight/core/normalizers/grape/endpoint_run.rb +33 -0
  40. data/lib/skylight/core/normalizers/grape/endpoint_run_filters.rb +23 -0
  41. data/lib/skylight/core/normalizers/moped/query.rb +100 -0
  42. data/lib/skylight/core/normalizers/sequel/sql.rb +12 -0
  43. data/lib/skylight/core/normalizers/sql.rb +49 -0
  44. data/lib/skylight/core/normalizers.rb +170 -0
  45. data/lib/skylight/core/probes/action_controller.rb +31 -0
  46. data/lib/skylight/core/probes/action_view.rb +37 -0
  47. data/lib/skylight/core/probes/active_model_serializers.rb +55 -0
  48. data/lib/skylight/core/probes/elasticsearch.rb +37 -0
  49. data/lib/skylight/core/probes/excon/middleware.rb +72 -0
  50. data/lib/skylight/core/probes/excon.rb +26 -0
  51. data/lib/skylight/core/probes/faraday.rb +22 -0
  52. data/lib/skylight/core/probes/grape.rb +80 -0
  53. data/lib/skylight/core/probes/httpclient.rb +46 -0
  54. data/lib/skylight/core/probes/middleware.rb +58 -0
  55. data/lib/skylight/core/probes/mongo.rb +171 -0
  56. data/lib/skylight/core/probes/mongoid.rb +21 -0
  57. data/lib/skylight/core/probes/moped.rb +39 -0
  58. data/lib/skylight/core/probes/net_http.rb +64 -0
  59. data/lib/skylight/core/probes/redis.rb +71 -0
  60. data/lib/skylight/core/probes/sequel.rb +33 -0
  61. data/lib/skylight/core/probes/sinatra.rb +69 -0
  62. data/lib/skylight/core/probes/tilt.rb +27 -0
  63. data/lib/skylight/core/probes.rb +129 -0
  64. data/lib/skylight/core/railtie.rb +166 -0
  65. data/lib/skylight/core/subscriber.rb +124 -0
  66. data/lib/skylight/core/test.rb +98 -0
  67. data/lib/skylight/core/trace.rb +190 -0
  68. data/lib/skylight/core/user_config.rb +61 -0
  69. data/lib/skylight/core/util/allocation_free.rb +26 -0
  70. data/lib/skylight/core/util/clock.rb +56 -0
  71. data/lib/skylight/core/util/deploy.rb +132 -0
  72. data/lib/skylight/core/util/gzip.rb +21 -0
  73. data/lib/skylight/core/util/inflector.rb +112 -0
  74. data/lib/skylight/core/util/logging.rb +127 -0
  75. data/lib/skylight/core/util/platform.rb +77 -0
  76. data/lib/skylight/core/util/proxy.rb +13 -0
  77. data/lib/skylight/core/util.rb +14 -0
  78. data/lib/skylight/core/vendor/active_support/notifications.rb +207 -0
  79. data/lib/skylight/core/vendor/active_support/per_thread_registry.rb +52 -0
  80. data/lib/skylight/core/vendor/thread_safe/non_concurrent_cache_backend.rb +133 -0
  81. data/lib/skylight/core/vendor/thread_safe/synchronized_cache_backend.rb +76 -0
  82. data/lib/skylight/core/vendor/thread_safe.rb +126 -0
  83. data/lib/skylight/core/version.rb +6 -0
  84. data/lib/skylight/core/vm/gc.rb +70 -0
  85. data/lib/skylight/core.rb +99 -0
  86. metadata +254 -0
@@ -0,0 +1,166 @@
1
+ require 'skylight/core'
2
+ require 'rails'
3
+
4
+ module Skylight::Core
5
+ # @api private
6
+ module Railtie
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ def self.root_key; :skylight end
11
+ def self.config_class; Config end
12
+ def self.middleware_class; Middleware end
13
+ def self.gem_name; 'Skylight' end
14
+ def self.log_file_name; 'skylight' end
15
+ def self.namespace; Skylight end
16
+ def self.version; Skylight::Core::VERSION end
17
+ end
18
+
19
+ private
20
+
21
+ def log_prefix
22
+ "[#{self.class.gem_name.upcase}] [#{self.class.version}]"
23
+ end
24
+
25
+ def development_warning
26
+ "#{log_prefix} Running #{self.class.gem_name} in development mode. No data will be reported until you deploy your app."
27
+ end
28
+
29
+ def run_initializer(app)
30
+ # Load probes even when agent is inactive to catch probe related bugs sooner
31
+ load_probes
32
+
33
+ config = load_skylight_config(app)
34
+
35
+ if activate?
36
+ if config
37
+ begin
38
+ if self.class.namespace.start!(config)
39
+ set_middleware_position(app, config)
40
+ Rails.logger.info "#{log_prefix} #{self.class.gem_name} agent enabled"
41
+ else
42
+ Rails.logger.info "#{log_prefix} Unable to start, see the #{self.class.gem_name} logs for more details"
43
+ end
44
+ rescue ConfigError => e
45
+ Rails.logger.error "#{log_prefix} #{e.message}; disabling #{self.class.gem_name} agent"
46
+ end
47
+ end
48
+ elsif Rails.env.development?
49
+ # FIXME: The CLI isn't part of core so we should change this message
50
+ unless config.user_config.disable_dev_warning?
51
+ log_warning config, development_warning
52
+ end
53
+ elsif !Rails.env.test?
54
+ unless config.user_config.disable_env_warning?
55
+ log_warning config, "#{log_prefix} You are running in the #{Rails.env} environment but haven't added it to config.#{self.class.root_key}.environments, so no data will be sent to skylight.io."
56
+ end
57
+ end
58
+ end
59
+
60
+ def log_warning(config, msg)
61
+ if config
62
+ config.alert_logger.warn(msg)
63
+ else
64
+ Rails.logger.warn(msg)
65
+ end
66
+ end
67
+
68
+ def existent_paths(paths)
69
+ paths.respond_to?(:existent) ? paths.existent : paths.select { |f| File.exists?(f) }
70
+ end
71
+
72
+ def load_skylight_config(app)
73
+ path = config_path(app)
74
+ path = nil unless File.exist?(path)
75
+
76
+ unless tmp = app.config.paths['tmp'].first
77
+ Rails.logger.error "#{log_prefix} tmp directory missing from rails configuration"
78
+ return nil
79
+ end
80
+
81
+ config = self.class.config_class.load(file: path, environment: Rails.env.to_s)
82
+ config[:root] = Rails.root
83
+
84
+ configure_logging(config, app)
85
+
86
+ config[:'daemon.sockdir_path'] ||= tmp
87
+ config[:'normalizers.render.view_paths'] = existent_paths(app.config.paths["app/views"]) + [Rails.root.to_s]
88
+ config
89
+ end
90
+
91
+ def configure_logging(config, app)
92
+ if logger = skylight_config(app).logger
93
+ config.logger = logger
94
+ else
95
+ # Configure the log file destination
96
+ if log_file = skylight_config(app).log_file
97
+ config['log_file'] = log_file
98
+ elsif !config.key?('log_file') && !config.on_heroku?
99
+ config['log_file'] = File.join(Rails.root, "log/#{self.class.log_file_name}.log")
100
+ end
101
+
102
+ # Configure the log level
103
+ if level = skylight_config(app).log_level
104
+ config['log_level'] = level
105
+ elsif !config.key?('log_level')
106
+ if level = app.config.log_level
107
+ config['log_level'] = level
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ def config_path(app)
114
+ File.expand_path(skylight_config.config_path, app.root)
115
+ end
116
+
117
+ def environments
118
+ Array(skylight_config.environments).map { |e| e && e.to_s }.compact
119
+ end
120
+
121
+ def activate?
122
+ key = "#{self.class.config_class.native_env_prefix}ENABLED"
123
+ if ENV.key?(key)
124
+ ENV[key] !~ /^false$/i
125
+ else
126
+ environments.include?(Rails.env.to_s)
127
+ end
128
+ end
129
+
130
+ def load_probes
131
+ probes = skylight_config.probes || []
132
+ Probes.probe(*probes)
133
+ end
134
+
135
+ def middleware_position
136
+ skylight_config.middleware_position.is_a?(Hash) ? skylight_config.middleware_position.symbolize_keys : skylight_config.middleware_position
137
+ end
138
+
139
+ def insert_middleware(app, config)
140
+ if middleware_position.has_key?(:after)
141
+ app.middleware.insert_after(middleware_position[:after], self.class.middleware_class, config: config)
142
+ elsif middleware_position.has_key?(:before)
143
+ app.middleware.insert_before(middleware_position[:before], self.class.middleware_class, config: config)
144
+ else
145
+ raise "The middleware position you have set is invalid. Please be sure `config.#{self.class.root_key}.middleware_position` is set up correctly."
146
+ end
147
+ end
148
+
149
+ def set_middleware_position(app, config)
150
+ if middleware_position.is_a?(Integer)
151
+ app.middleware.insert middleware_position, self.class.middleware_class, config: config
152
+ elsif middleware_position.is_a?(Hash) && middleware_position.keys.count == 1
153
+ insert_middleware(app, config)
154
+ elsif middleware_position.nil?
155
+ app.middleware.insert 0, self.class.middleware_class, config: config
156
+ else
157
+ raise "The middleware position you have set is invalid. Please be sure `config.#{self.class.root_key}.middleware_position` is set up correctly."
158
+ end
159
+ end
160
+
161
+ def skylight_config(target=self)
162
+ target.config.send(self.class.root_key)
163
+ end
164
+
165
+ end
166
+ end
@@ -0,0 +1,124 @@
1
+ module Skylight::Core
2
+ # @api private
3
+ class Subscriber
4
+ include Util::Logging
5
+
6
+ attr_reader :config
7
+
8
+ def initialize(config, instrumenter)
9
+ @config = config
10
+ @subscriber = nil
11
+ @normalizers = Normalizers.build(config)
12
+ @instrumenter = instrumenter
13
+ end
14
+
15
+ def register!
16
+ unregister! if @subscriber
17
+ pattern = ArrayPattern.new(@normalizers.keys)
18
+ @subscriber = ActiveSupport::Notifications.subscribe pattern, self
19
+ end
20
+
21
+ def unregister!
22
+ ActiveSupport::Notifications.unsubscribe @subscriber
23
+ @subscriber = nil
24
+ end
25
+
26
+ class ArrayPattern
27
+
28
+ def initialize(keys)
29
+ @keys = Set.new keys
30
+ end
31
+
32
+ def ===(item)
33
+ @keys.include?(item)
34
+ end
35
+
36
+ end
37
+
38
+ #
39
+ #
40
+ # ===== ActiveSupport::Notifications API
41
+ #
42
+ #
43
+
44
+ class Notification
45
+ attr_reader :name, :span
46
+
47
+ def initialize(name, span)
48
+ @name, @span = name, span
49
+ end
50
+ end
51
+
52
+ def start(name, id, payload)
53
+ return if @instrumenter.disabled?
54
+ return unless trace = @instrumenter.current_trace
55
+
56
+ result = normalize(trace, name, payload)
57
+
58
+ unless result == :skip
59
+ case result.size
60
+ when 4
61
+ cat, title, desc, meta = result
62
+ when 3
63
+ cat, title, desc = result
64
+ else
65
+ raise "Invalid normalizer result: #{result.inspect}"
66
+ end
67
+
68
+ span = trace.instrument(cat, title, desc, meta)
69
+ end
70
+
71
+ trace.notifications << Notification.new(name, span)
72
+ rescue Exception => e
73
+ error "Subscriber#start error; msg=%s", e.message
74
+ debug "trace=%s", trace.inspect
75
+ debug "in: name=%s", name.inspect
76
+ debug "in: payload=%s", payload.inspect
77
+ debug "out: cat=%s, title=%s, desc=%s", cat.inspect, name.inspect, desc.inspect
78
+ t { e.backtrace.join("\n") }
79
+ nil
80
+ end
81
+
82
+ def finish(name, id, payload)
83
+ return if @instrumenter.disabled?
84
+ return unless trace = @instrumenter.current_trace
85
+
86
+ while curr = trace.notifications.pop
87
+ if curr.name == name
88
+ begin
89
+ normalize_after(trace, curr.span, name, payload)
90
+ ensure
91
+ meta = { }
92
+ meta[:exception] = payload[:exception] if payload[:exception]
93
+ meta[:exception_object] = payload[:exception_object] if payload[:exception_object]
94
+ trace.done(curr.span, meta) if curr.span
95
+ end
96
+ return
97
+ end
98
+ end
99
+
100
+ rescue Exception => e
101
+ error "Subscriber#finish error; msg=%s", e.message
102
+ debug "trace=%s", trace.inspect
103
+ debug "in: name=%s", name.inspect
104
+ debug "in: payload=%s", payload.inspect
105
+ t { e.backtrace.join("\n") }
106
+ nil
107
+ end
108
+
109
+ def publish(name, *args)
110
+ # Ignored for now because nothing in rails uses it
111
+ end
112
+
113
+ private
114
+
115
+ def normalize(*args)
116
+ @normalizers.normalize(*args)
117
+ end
118
+
119
+ def normalize_after(*args)
120
+ @normalizers.normalize_after(*args)
121
+ end
122
+
123
+ end
124
+ end
@@ -0,0 +1,98 @@
1
+ module Skylight
2
+ module Core
3
+ module Test
4
+ module Mocking
5
+
6
+ def mock!(config_opts={}, &callback)
7
+ config_opts[:mock_submission] ||= callback || proc {}
8
+ config = config_class.load(config_opts)
9
+
10
+ mock_instrumenter_klass = Class.new(instrumenter_class) do
11
+ def self.trace_class
12
+ @trace_class ||= Class.new(super) do
13
+ def self.native_new(start, _uuid, endpoint, meta)
14
+ inst = allocate
15
+ inst.instance_variable_set(:@start, start)
16
+ inst.instance_variable_set(:@endpoint, endpoint)
17
+ inst.instance_variable_set(:@starting_endpoint, endpoint)
18
+ inst.instance_variable_set(:@meta, meta)
19
+ inst
20
+ end
21
+
22
+ attr_reader :endpoint, :starting_endpoint, :meta
23
+
24
+ def mock_spans
25
+ @mock_spans ||= []
26
+ end
27
+
28
+ def native_get_started_at
29
+ @start
30
+ end
31
+
32
+ def native_set_endpoint(endpoint)
33
+ @endpoint = endpoint
34
+ end
35
+
36
+ def native_start_span(time, cat)
37
+ span = {
38
+ start: time,
39
+ cat: cat
40
+ }
41
+ mock_spans << span
42
+ # Return integer like the native method does
43
+ mock_spans.index(span)
44
+ end
45
+
46
+ def native_span_set_title(sp, title)
47
+ mock_spans[sp][:title] = title
48
+
49
+ end
50
+
51
+ def native_span_set_description(sp, desc)
52
+ mock_spans[sp][:desc] = desc
53
+ end
54
+
55
+ def native_span_set_meta(sp, meta)
56
+ mock_spans[sp][:meta] = meta
57
+ end
58
+
59
+ def native_span_set_exception(sp, exception_object, exception)
60
+ mock_spans[sp][:exception_object] = exception_object
61
+ mock_spans[sp][:exception] = exception
62
+ end
63
+
64
+ def native_stop_span(span, time)
65
+ span = mock_spans[span]
66
+ span[:duration] = time - span[:start]
67
+ nil
68
+ end
69
+ end
70
+ end
71
+
72
+ def self.native_new(*)
73
+ allocate
74
+ end
75
+
76
+ def native_start
77
+ true
78
+ end
79
+
80
+ def native_submit_trace(trace)
81
+ config[:mock_submission].call(trace)
82
+ end
83
+
84
+ def native_stop
85
+ end
86
+
87
+ def limited_description(description)
88
+ description
89
+ end
90
+ end
91
+
92
+ @instrumenter = mock_instrumenter_klass.new(config).start!
93
+ end
94
+
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,190 @@
1
+ module Skylight::Core
2
+ class Trace
3
+ GC_CAT = 'noise.gc'.freeze
4
+
5
+ include Util::Logging
6
+
7
+ attr_reader :instrumenter, :endpoint, :notifications, :meta
8
+
9
+ def self.new(instrumenter, endpoint, start, cat, title=nil, desc=nil, meta=nil)
10
+ inst = native_new(normalize_time(start), "TODO", endpoint, meta)
11
+ inst.send(:initialize, instrumenter, cat, title, desc, meta)
12
+ inst.endpoint = endpoint
13
+ inst
14
+ end
15
+
16
+ # TODO: Move this into native
17
+ def self.normalize_time(time)
18
+ # At least one customer has extensions that cause integer division to produce rationals.
19
+ # Since the native code expects an integer, we force it again.
20
+ (time.to_i / 100_000).to_i
21
+ end
22
+
23
+ def initialize(instrumenter, cat, title, desc, meta)
24
+ raise ArgumentError, 'instrumenter is required' unless instrumenter
25
+
26
+ @instrumenter = instrumenter
27
+ @submitted = false
28
+ @broken = false
29
+
30
+ @notifications = []
31
+
32
+ @spans = []
33
+
34
+ # create the root node
35
+ @root = start(native_get_started_at, cat, title, desc, meta, normalize: false)
36
+
37
+ # Also store meta for later access
38
+ @meta = meta
39
+
40
+ @gc = config.gc.track unless ENV.key?("SKYLIGHT_DISABLE_GC_TRACKING")
41
+ end
42
+
43
+ def endpoint=(value)
44
+ @endpoint = value
45
+ native_set_endpoint(value)
46
+ value
47
+ end
48
+
49
+ def config
50
+ @instrumenter.config
51
+ end
52
+
53
+ def record(cat, title=nil, desc=nil)
54
+ return if @broken
55
+
56
+ title.freeze if title.is_a?(String)
57
+ desc.freeze if desc.is_a?(String)
58
+
59
+ desc = @instrumenter.limited_description(desc)
60
+
61
+ time = Util::Clock.nanos - gc_time
62
+
63
+ stop(start(time, cat, title, desc, nil), time)
64
+
65
+ nil
66
+ rescue => e
67
+ error "failed to record span; msg=%s", e.message
68
+ @broken = true
69
+ nil
70
+ end
71
+
72
+ def instrument(cat, title=nil, desc=nil, meta=nil)
73
+ return if @broken
74
+ t { "instrument: #{cat}, #{title}" }
75
+
76
+ title.freeze if title.is_a?(String)
77
+ desc.freeze if desc.is_a?(String)
78
+
79
+ original_desc = desc
80
+ now = Util::Clock.nanos
81
+ desc = @instrumenter.limited_description(desc)
82
+
83
+ if desc == Instrumenter::TOO_MANY_UNIQUES
84
+ debug "[SKYLIGHT] [#{Skylight::Core::VERSION}] A payload description produced <too many uniques>"
85
+ debug "original desc=%s", original_desc
86
+ debug "cat=%s, title=%s, desc=%s", cat, title, desc
87
+ end
88
+
89
+ start(now - gc_time, cat, title, desc, meta)
90
+ rescue => e
91
+ error "failed to instrument span; msg=%s", e.message
92
+ @broken = true
93
+ nil
94
+ end
95
+
96
+ def span_correlation_header(span)
97
+ native_span_get_correlation_header(span)
98
+ end
99
+
100
+ def done(span, meta=nil)
101
+ return unless span
102
+ return if @broken
103
+
104
+ if meta && (meta[:exception_object] || meta[:exception])
105
+ native_span_set_exception(span, meta[:exception_object], meta[:exception])
106
+ end
107
+
108
+ stop(span, Util::Clock.nanos - gc_time)
109
+ rescue => e
110
+ error "failed to close span; msg=%s", e.message
111
+ @broken = true
112
+ nil
113
+ end
114
+
115
+ def release
116
+ return unless @instrumenter.current_trace == self
117
+ @instrumenter.current_trace = nil
118
+ end
119
+
120
+ def traced
121
+ time = gc_time
122
+ now = Util::Clock.nanos
123
+
124
+ if time > 0
125
+ t { fmt "tracking GC time; duration=%d", time }
126
+ stop(start(now - time, GC_CAT, nil, nil, nil), now)
127
+ end
128
+
129
+ stop(@root, now)
130
+ end
131
+
132
+ def submit
133
+ return if @broken
134
+
135
+ t { "submitting trace" }
136
+
137
+ if @submitted
138
+ t { "already submitted" }
139
+ return
140
+ end
141
+
142
+ release
143
+ @submitted = true
144
+
145
+ traced
146
+
147
+ @instrumenter.process(self)
148
+ rescue Exception => e
149
+ error e.message
150
+ t { e.backtrace.join("\n") }
151
+ end
152
+
153
+ private
154
+
155
+ def start(time, cat, title, desc, meta, opts={})
156
+ time = self.class.normalize_time(time) unless opts[:normalize] == false
157
+
158
+ sp = native_start_span(time, cat.to_s)
159
+ native_span_set_title(sp, title.to_s) if title
160
+ native_span_set_description(sp, desc.to_s) if desc
161
+ native_span_set_meta(sp, meta) if meta
162
+
163
+ @spans << sp
164
+ t { "started span: #{sp} - #{cat}, #{title}" }
165
+
166
+ sp
167
+ end
168
+
169
+ def stop(span, time)
170
+ t { "stopping span: #{span}" }
171
+
172
+ expected = @spans.pop
173
+ unless span == expected
174
+ error "invalid span nesting"
175
+ # TODO: Actually log span title here
176
+ t { "expected=#{expected}, actual=#{span}" }
177
+ end
178
+
179
+ time = self.class.normalize_time(time)
180
+ native_stop_span(span, time)
181
+ nil
182
+ end
183
+
184
+ def gc_time
185
+ return 0 unless @gc
186
+ @gc.update
187
+ @gc.time
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,61 @@
1
+ require 'yaml'
2
+ require 'skylight/core/errors'
3
+
4
+ module Skylight::Core
5
+ class UserConfig
6
+
7
+ attr_accessor :disable_dev_warning, :disable_env_warning
8
+
9
+ def initialize(config)
10
+ @config = config
11
+ reload
12
+ end
13
+
14
+ def file_path
15
+ return @file_path if @file_path
16
+
17
+ config_path = @config[:user_config_path] || begin
18
+ require "etc"
19
+ home_dir = ENV['HOME'] || Etc.getpwuid.dir || (ENV["USER"] && File.expand_path("~#{ENV['USER']}"))
20
+ if home_dir
21
+ File.join(home_dir, ".skylight")
22
+ else
23
+ raise ConfigError, "The Skylight `user_config_path` must be defined since the home directory cannot be inferred"
24
+ end
25
+ end
26
+
27
+ @file_path = File.expand_path(config_path)
28
+ end
29
+
30
+ def disable_dev_warning?
31
+ disable_dev_warning
32
+ end
33
+
34
+ def disable_env_warning?
35
+ disable_env_warning
36
+ end
37
+
38
+ def reload
39
+ config = File.exist?(file_path) ? YAML.load_file(file_path) : false
40
+ return unless config
41
+
42
+ self.disable_dev_warning = !!config['disable_dev_warning']
43
+ self.disable_env_warning = !!config['disable_env_warning']
44
+ end
45
+
46
+ def save
47
+ FileUtils.mkdir_p(File.dirname(file_path))
48
+ File.open(file_path, 'w') do |f|
49
+ f.puts YAML.dump(to_hash)
50
+ end
51
+ end
52
+
53
+ def to_hash
54
+ {
55
+ 'disable_dev_warning' => disable_dev_warning,
56
+ 'disable_env_warning' => disable_env_warning
57
+ }
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,26 @@
1
+ module Skylight::Core
2
+ module Util
3
+ # Helpers to reduce memory allocation
4
+ module AllocationFree
5
+
6
+ # Find an item in an array without allocation.
7
+ #
8
+ # @param array [Array] the array to search
9
+ # @yield a block called against each item until a match is found
10
+ # @yieldparam item an item from the array
11
+ # @yieldreturn [Boolean] whether `item` matches the criteria
12
+ # return the found item or nil, if nothing found
13
+ def array_find(array)
14
+ i = 0
15
+
16
+ while i < array.size
17
+ item = array[i]
18
+ return item if yield item
19
+ i += 1
20
+ end
21
+
22
+ nil
23
+ end
24
+ end
25
+ end
26
+ end