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.
- checksums.yaml +7 -0
- data/lib/skylight/core/config.rb +454 -0
- data/lib/skylight/core/errors.rb +6 -0
- data/lib/skylight/core/fanout.rb +44 -0
- data/lib/skylight/core/formatters/http.rb +23 -0
- data/lib/skylight/core/gc.rb +107 -0
- data/lib/skylight/core/instrumentable.rb +144 -0
- data/lib/skylight/core/instrumenter.rb +249 -0
- data/lib/skylight/core/middleware.rb +101 -0
- data/lib/skylight/core/normalizers/action_controller/process_action.rb +50 -0
- data/lib/skylight/core/normalizers/action_controller/send_file.rb +50 -0
- data/lib/skylight/core/normalizers/action_view/render_collection.rb +22 -0
- data/lib/skylight/core/normalizers/action_view/render_partial.rb +21 -0
- data/lib/skylight/core/normalizers/action_view/render_template.rb +21 -0
- data/lib/skylight/core/normalizers/active_job/enqueue_at.rb +21 -0
- data/lib/skylight/core/normalizers/active_model_serializers/render.rb +26 -0
- data/lib/skylight/core/normalizers/active_record/instantiation.rb +17 -0
- data/lib/skylight/core/normalizers/active_record/sql.rb +33 -0
- data/lib/skylight/core/normalizers/active_support/cache.rb +20 -0
- data/lib/skylight/core/normalizers/active_support/cache_clear.rb +16 -0
- data/lib/skylight/core/normalizers/active_support/cache_decrement.rb +16 -0
- data/lib/skylight/core/normalizers/active_support/cache_delete.rb +16 -0
- data/lib/skylight/core/normalizers/active_support/cache_exist.rb +16 -0
- data/lib/skylight/core/normalizers/active_support/cache_fetch_hit.rb +16 -0
- data/lib/skylight/core/normalizers/active_support/cache_generate.rb +16 -0
- data/lib/skylight/core/normalizers/active_support/cache_increment.rb +16 -0
- data/lib/skylight/core/normalizers/active_support/cache_read.rb +16 -0
- data/lib/skylight/core/normalizers/active_support/cache_read_multi.rb +16 -0
- data/lib/skylight/core/normalizers/active_support/cache_write.rb +16 -0
- data/lib/skylight/core/normalizers/coach/handler_finish.rb +36 -0
- data/lib/skylight/core/normalizers/coach/middleware_finish.rb +23 -0
- data/lib/skylight/core/normalizers/couch_potato/query.rb +20 -0
- data/lib/skylight/core/normalizers/data_mapper/sql.rb +12 -0
- data/lib/skylight/core/normalizers/default.rb +27 -0
- data/lib/skylight/core/normalizers/elasticsearch/request.rb +20 -0
- data/lib/skylight/core/normalizers/faraday/request.rb +37 -0
- data/lib/skylight/core/normalizers/grape/endpoint.rb +30 -0
- data/lib/skylight/core/normalizers/grape/endpoint_render.rb +26 -0
- data/lib/skylight/core/normalizers/grape/endpoint_run.rb +33 -0
- data/lib/skylight/core/normalizers/grape/endpoint_run_filters.rb +23 -0
- data/lib/skylight/core/normalizers/moped/query.rb +100 -0
- data/lib/skylight/core/normalizers/sequel/sql.rb +12 -0
- data/lib/skylight/core/normalizers/sql.rb +49 -0
- data/lib/skylight/core/normalizers.rb +170 -0
- data/lib/skylight/core/probes/action_controller.rb +31 -0
- data/lib/skylight/core/probes/action_view.rb +37 -0
- data/lib/skylight/core/probes/active_model_serializers.rb +55 -0
- data/lib/skylight/core/probes/elasticsearch.rb +37 -0
- data/lib/skylight/core/probes/excon/middleware.rb +72 -0
- data/lib/skylight/core/probes/excon.rb +26 -0
- data/lib/skylight/core/probes/faraday.rb +22 -0
- data/lib/skylight/core/probes/grape.rb +80 -0
- data/lib/skylight/core/probes/httpclient.rb +46 -0
- data/lib/skylight/core/probes/middleware.rb +58 -0
- data/lib/skylight/core/probes/mongo.rb +171 -0
- data/lib/skylight/core/probes/mongoid.rb +21 -0
- data/lib/skylight/core/probes/moped.rb +39 -0
- data/lib/skylight/core/probes/net_http.rb +64 -0
- data/lib/skylight/core/probes/redis.rb +71 -0
- data/lib/skylight/core/probes/sequel.rb +33 -0
- data/lib/skylight/core/probes/sinatra.rb +69 -0
- data/lib/skylight/core/probes/tilt.rb +27 -0
- data/lib/skylight/core/probes.rb +129 -0
- data/lib/skylight/core/railtie.rb +166 -0
- data/lib/skylight/core/subscriber.rb +124 -0
- data/lib/skylight/core/test.rb +98 -0
- data/lib/skylight/core/trace.rb +190 -0
- data/lib/skylight/core/user_config.rb +61 -0
- data/lib/skylight/core/util/allocation_free.rb +26 -0
- data/lib/skylight/core/util/clock.rb +56 -0
- data/lib/skylight/core/util/deploy.rb +132 -0
- data/lib/skylight/core/util/gzip.rb +21 -0
- data/lib/skylight/core/util/inflector.rb +112 -0
- data/lib/skylight/core/util/logging.rb +127 -0
- data/lib/skylight/core/util/platform.rb +77 -0
- data/lib/skylight/core/util/proxy.rb +13 -0
- data/lib/skylight/core/util.rb +14 -0
- data/lib/skylight/core/vendor/active_support/notifications.rb +207 -0
- data/lib/skylight/core/vendor/active_support/per_thread_registry.rb +52 -0
- data/lib/skylight/core/vendor/thread_safe/non_concurrent_cache_backend.rb +133 -0
- data/lib/skylight/core/vendor/thread_safe/synchronized_cache_backend.rb +76 -0
- data/lib/skylight/core/vendor/thread_safe.rb +126 -0
- data/lib/skylight/core/version.rb +6 -0
- data/lib/skylight/core/vm/gc.rb +70 -0
- data/lib/skylight/core.rb +99 -0
- 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
|