skylight-core 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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