truex-skylight 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (122) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +277 -0
  3. data/CLA.md +9 -0
  4. data/CONTRIBUTING.md +1 -0
  5. data/LICENSE.md +79 -0
  6. data/README.md +4 -0
  7. data/bin/skylight +3 -0
  8. data/ext/extconf.rb +186 -0
  9. data/ext/libskylight.yml +6 -0
  10. data/ext/skylight_memprof.c +115 -0
  11. data/ext/skylight_native.c +416 -0
  12. data/ext/skylight_native.h +20 -0
  13. data/lib/skylight.rb +2 -0
  14. data/lib/skylight/api.rb +79 -0
  15. data/lib/skylight/cli.rb +146 -0
  16. data/lib/skylight/compat.rb +47 -0
  17. data/lib/skylight/config.rb +498 -0
  18. data/lib/skylight/core.rb +122 -0
  19. data/lib/skylight/data/cacert.pem +3894 -0
  20. data/lib/skylight/formatters/http.rb +17 -0
  21. data/lib/skylight/gc.rb +107 -0
  22. data/lib/skylight/helpers.rb +137 -0
  23. data/lib/skylight/instrumenter.rb +290 -0
  24. data/lib/skylight/middleware.rb +75 -0
  25. data/lib/skylight/native.rb +69 -0
  26. data/lib/skylight/normalizers.rb +133 -0
  27. data/lib/skylight/normalizers/action_controller/process_action.rb +35 -0
  28. data/lib/skylight/normalizers/action_controller/send_file.rb +76 -0
  29. data/lib/skylight/normalizers/action_view/render_collection.rb +18 -0
  30. data/lib/skylight/normalizers/action_view/render_partial.rb +18 -0
  31. data/lib/skylight/normalizers/action_view/render_template.rb +18 -0
  32. data/lib/skylight/normalizers/active_record/sql.rb +79 -0
  33. data/lib/skylight/normalizers/active_support/cache.rb +50 -0
  34. data/lib/skylight/normalizers/active_support/cache_clear.rb +16 -0
  35. data/lib/skylight/normalizers/active_support/cache_decrement.rb +16 -0
  36. data/lib/skylight/normalizers/active_support/cache_delete.rb +16 -0
  37. data/lib/skylight/normalizers/active_support/cache_exist.rb +16 -0
  38. data/lib/skylight/normalizers/active_support/cache_fetch_hit.rb +16 -0
  39. data/lib/skylight/normalizers/active_support/cache_generate.rb +16 -0
  40. data/lib/skylight/normalizers/active_support/cache_increment.rb +16 -0
  41. data/lib/skylight/normalizers/active_support/cache_read.rb +16 -0
  42. data/lib/skylight/normalizers/active_support/cache_read_multi.rb +16 -0
  43. data/lib/skylight/normalizers/active_support/cache_write.rb +16 -0
  44. data/lib/skylight/normalizers/default.rb +21 -0
  45. data/lib/skylight/normalizers/moped/query.rb +141 -0
  46. data/lib/skylight/probes.rb +91 -0
  47. data/lib/skylight/probes/excon.rb +25 -0
  48. data/lib/skylight/probes/excon/middleware.rb +65 -0
  49. data/lib/skylight/probes/net_http.rb +44 -0
  50. data/lib/skylight/probes/redis.rb +30 -0
  51. data/lib/skylight/probes/sequel.rb +30 -0
  52. data/lib/skylight/probes/sinatra.rb +74 -0
  53. data/lib/skylight/probes/tilt.rb +27 -0
  54. data/lib/skylight/railtie.rb +122 -0
  55. data/lib/skylight/sinatra.rb +4 -0
  56. data/lib/skylight/subscriber.rb +92 -0
  57. data/lib/skylight/trace.rb +191 -0
  58. data/lib/skylight/util.rb +16 -0
  59. data/lib/skylight/util/allocation_free.rb +17 -0
  60. data/lib/skylight/util/clock.rb +53 -0
  61. data/lib/skylight/util/gzip.rb +15 -0
  62. data/lib/skylight/util/hostname.rb +17 -0
  63. data/lib/skylight/util/http.rb +218 -0
  64. data/lib/skylight/util/inflector.rb +110 -0
  65. data/lib/skylight/util/logging.rb +87 -0
  66. data/lib/skylight/util/multi_io.rb +21 -0
  67. data/lib/skylight/util/native_ext_fetcher.rb +205 -0
  68. data/lib/skylight/util/platform.rb +67 -0
  69. data/lib/skylight/util/ssl.rb +50 -0
  70. data/lib/skylight/vendor/active_support/notifications.rb +207 -0
  71. data/lib/skylight/vendor/active_support/notifications/fanout.rb +159 -0
  72. data/lib/skylight/vendor/active_support/notifications/instrumenter.rb +72 -0
  73. data/lib/skylight/vendor/active_support/per_thread_registry.rb +52 -0
  74. data/lib/skylight/vendor/cli/highline.rb +1034 -0
  75. data/lib/skylight/vendor/cli/highline/color_scheme.rb +134 -0
  76. data/lib/skylight/vendor/cli/highline/compatibility.rb +16 -0
  77. data/lib/skylight/vendor/cli/highline/import.rb +41 -0
  78. data/lib/skylight/vendor/cli/highline/menu.rb +381 -0
  79. data/lib/skylight/vendor/cli/highline/question.rb +481 -0
  80. data/lib/skylight/vendor/cli/highline/simulate.rb +48 -0
  81. data/lib/skylight/vendor/cli/highline/string_extensions.rb +111 -0
  82. data/lib/skylight/vendor/cli/highline/style.rb +181 -0
  83. data/lib/skylight/vendor/cli/highline/system_extensions.rb +242 -0
  84. data/lib/skylight/vendor/cli/thor.rb +473 -0
  85. data/lib/skylight/vendor/cli/thor/actions.rb +318 -0
  86. data/lib/skylight/vendor/cli/thor/actions/create_file.rb +105 -0
  87. data/lib/skylight/vendor/cli/thor/actions/create_link.rb +60 -0
  88. data/lib/skylight/vendor/cli/thor/actions/directory.rb +119 -0
  89. data/lib/skylight/vendor/cli/thor/actions/empty_directory.rb +137 -0
  90. data/lib/skylight/vendor/cli/thor/actions/file_manipulation.rb +314 -0
  91. data/lib/skylight/vendor/cli/thor/actions/inject_into_file.rb +109 -0
  92. data/lib/skylight/vendor/cli/thor/base.rb +652 -0
  93. data/lib/skylight/vendor/cli/thor/command.rb +136 -0
  94. data/lib/skylight/vendor/cli/thor/core_ext/hash_with_indifferent_access.rb +80 -0
  95. data/lib/skylight/vendor/cli/thor/core_ext/io_binary_read.rb +12 -0
  96. data/lib/skylight/vendor/cli/thor/core_ext/ordered_hash.rb +100 -0
  97. data/lib/skylight/vendor/cli/thor/error.rb +28 -0
  98. data/lib/skylight/vendor/cli/thor/group.rb +282 -0
  99. data/lib/skylight/vendor/cli/thor/invocation.rb +172 -0
  100. data/lib/skylight/vendor/cli/thor/parser.rb +4 -0
  101. data/lib/skylight/vendor/cli/thor/parser/argument.rb +74 -0
  102. data/lib/skylight/vendor/cli/thor/parser/arguments.rb +171 -0
  103. data/lib/skylight/vendor/cli/thor/parser/option.rb +121 -0
  104. data/lib/skylight/vendor/cli/thor/parser/options.rb +218 -0
  105. data/lib/skylight/vendor/cli/thor/rake_compat.rb +72 -0
  106. data/lib/skylight/vendor/cli/thor/runner.rb +322 -0
  107. data/lib/skylight/vendor/cli/thor/shell.rb +88 -0
  108. data/lib/skylight/vendor/cli/thor/shell/basic.rb +393 -0
  109. data/lib/skylight/vendor/cli/thor/shell/color.rb +148 -0
  110. data/lib/skylight/vendor/cli/thor/shell/html.rb +127 -0
  111. data/lib/skylight/vendor/cli/thor/util.rb +270 -0
  112. data/lib/skylight/vendor/cli/thor/version.rb +3 -0
  113. data/lib/skylight/vendor/thread_safe.rb +126 -0
  114. data/lib/skylight/vendor/thread_safe/non_concurrent_cache_backend.rb +133 -0
  115. data/lib/skylight/vendor/thread_safe/synchronized_cache_backend.rb +76 -0
  116. data/lib/skylight/version.rb +4 -0
  117. data/lib/skylight/vm/gc.rb +70 -0
  118. data/lib/sql_lexer.rb +6 -0
  119. data/lib/sql_lexer/lexer.rb +579 -0
  120. data/lib/sql_lexer/string_scanner.rb +11 -0
  121. data/lib/sql_lexer/version.rb +3 -0
  122. metadata +179 -0
@@ -0,0 +1,17 @@
1
+ module Skylight
2
+ module Formatters
3
+ module HTTP
4
+ def self.build_opts(method, scheme, host, port, path, query)
5
+ { category: "api.http.#{method.downcase}",
6
+ title: "#{method.upcase} #{host}",
7
+ annotations: {
8
+ method: method.upcase,
9
+ scheme: scheme,
10
+ host: host,
11
+ port: port ? port.to_i : nil,
12
+ path: path,
13
+ query: query }}
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,107 @@
1
+ require 'thread'
2
+
3
+ module Skylight
4
+ # @api private
5
+ class GC
6
+ METHODS = [ :enable, :total_time ]
7
+ TH_KEY = :SK_GC_CURR_WINDOW
8
+ MAX_COUNT = 1000
9
+ MAX_TIME = 30_000_000
10
+
11
+ include Util::Logging
12
+
13
+ attr_reader :config
14
+
15
+ def initialize(config, profiler)
16
+ @listeners = []
17
+ @config = config
18
+ @lock = Mutex.new
19
+ @time = 0
20
+
21
+ if METHODS.all? { |m| profiler.respond_to?(m) }
22
+ @profiler = profiler
23
+ @time = @profiler.total_time
24
+ else
25
+ debug "disabling GC profiling"
26
+ end
27
+ end
28
+
29
+ def enable
30
+ @profiler.enable if @profiler
31
+ end
32
+
33
+ def track
34
+ unless @profiler
35
+ win = Window.new(nil)
36
+ else
37
+ win = Window.new(self)
38
+
39
+ @lock.synchronize do
40
+ __update
41
+ @listeners << win
42
+
43
+ # Cleanup any listeners that might have leaked
44
+ until @listeners[0].time < MAX_TIME
45
+ @listeners.shift
46
+ end
47
+
48
+ if @listeners.length > MAX_COUNT
49
+ @listeners.shift
50
+ end
51
+ end
52
+ end
53
+
54
+ win
55
+ end
56
+
57
+ def release(win)
58
+ @lock.synchronize do
59
+ @listeners.delete(win)
60
+ end
61
+ end
62
+
63
+ def update
64
+ @lock.synchronize do
65
+ __update
66
+ end
67
+
68
+ nil
69
+ end
70
+
71
+ private
72
+
73
+ def __update
74
+ time = @profiler.total_time
75
+ diff = time - @time
76
+ @time = time
77
+
78
+ if diff > 0
79
+ @listeners.each do |l|
80
+ l.add(diff)
81
+ end
82
+ end
83
+ end
84
+
85
+ class Window
86
+ attr_reader :time
87
+
88
+ def initialize(global)
89
+ @global = global
90
+ @time = 0
91
+ end
92
+
93
+ def update
94
+ @global.update if @global
95
+ end
96
+
97
+ def add(time)
98
+ @time += time
99
+ end
100
+
101
+ def release
102
+ @global.release(self) if @global
103
+ end
104
+ end
105
+
106
+ end
107
+ end
@@ -0,0 +1,137 @@
1
+ module Skylight
2
+ # Instrumenting a specific method will cause an event to be created every time that method is called.
3
+ # The event will be inserted at the appropriate place in the Skylight trace.
4
+ #
5
+ # To instrument a method, the first thing to do is include {Skylight::Helpers Skylight::Helpers}
6
+ # into the class that you will be instrumenting. Then, annotate each method that
7
+ # you wish to instrument with {Skylight::Helpers::ClassMethods#instrument_method instrument_method}.
8
+ module Helpers
9
+
10
+ # @see Skylight::Helpers
11
+ module ClassMethods
12
+ # @api private
13
+ def method_added(name)
14
+ super
15
+
16
+ if opts = @__sk_instrument_next_method
17
+ @__sk_instrument_next_method = nil
18
+ title = "#{to_s}##{name}"
19
+ __sk_instrument_method_on(self, name, title, opts)
20
+ end
21
+ end
22
+
23
+ # @api private
24
+ def singleton_method_added(name)
25
+ super
26
+
27
+ if opts = @__sk_instrument_next_method
28
+ @__sk_instrument_next_method = nil
29
+ title = "#{to_s}.#{name}"
30
+ __sk_instrument_method_on(__sk_singleton_class, name, title, opts)
31
+ end
32
+ end
33
+
34
+ # @overload instrument_method
35
+ # Instruments the following method
36
+ #
37
+ # @example
38
+ # class MyClass
39
+ # include Skylight::Helpers
40
+ #
41
+ # instrument_method
42
+ # def my_method
43
+ # do_expensive_stuff
44
+ # end
45
+ #
46
+ # end
47
+ #
48
+ # @overload instrument_method([name], opts={})
49
+ # @param [Symbol|String] [name]
50
+ # @param [Hash] opts
51
+ # @option opts [String] :category ('app.method')
52
+ # @option opts [String] :title (ClassName#method_name)
53
+ # @option opts [String] :description
54
+ #
55
+ # You may also declare the methods to instrument at any time by passing the name
56
+ # of the method as the first argument to `instrument_method`.
57
+ #
58
+ # @example With name
59
+ # class MyClass
60
+ # include Skylight::Helpers
61
+ #
62
+ # def my_method
63
+ # do_expensive_stuff
64
+ # end
65
+ #
66
+ # instrument_method :my_method
67
+ #
68
+ # end
69
+ #
70
+ # By default, the event will be titled using the name of the class and the
71
+ # method. For example, in our previous example, the event name will be:
72
+ # +MyClass#my_method+. You can customize this by passing using the *:title* option.
73
+ #
74
+ # @example Without name
75
+ # class MyClass
76
+ # include Skylight::Helpers
77
+ #
78
+ # instrument_method title: 'Expensive work'
79
+ # def my_method
80
+ # do_expensive_stuff
81
+ # end
82
+ # end
83
+ def instrument_method(*args)
84
+ opts = args.pop if Hash === args.last
85
+
86
+ if name = args.pop
87
+ title = "#{to_s}##{name}"
88
+ __sk_instrument_method_on(self, name, title, opts || {})
89
+ else
90
+ @__sk_instrument_next_method = opts || {}
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ def __sk_instrument_method_on(klass, name, title, opts)
97
+ category = (opts[:category] || "app.method").to_s
98
+ title = (opts[:title] || title).to_s
99
+ desc = opts[:description].to_s if opts[:description]
100
+
101
+ klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
102
+ alias_method :"before_instrument_#{name}", :"#{name}"
103
+
104
+ def #{name}(*args, &blk)
105
+ span = Skylight.instrument(
106
+ category: :"#{category}",
107
+ title: #{title.inspect},
108
+ description: #{desc.inspect})
109
+
110
+ begin
111
+ before_instrument_#{name}(*args, &blk)
112
+ ensure
113
+ Skylight.done(span) if span
114
+ end
115
+ end
116
+ RUBY
117
+ end
118
+
119
+ if respond_to?(:singleton_class)
120
+ alias :__sk_singleton_class :singleton_class
121
+ else
122
+ def __sk_singleton_class
123
+ class << self; self; end
124
+ end
125
+ end
126
+ end
127
+
128
+ # @api private
129
+ def self.included(base)
130
+ base.class_eval do
131
+ @__sk_instrument_next_method = nil
132
+ extend ClassMethods
133
+ end
134
+ end
135
+
136
+ end
137
+ end
@@ -0,0 +1,290 @@
1
+ require 'thread'
2
+ require 'strscan'
3
+ require 'skylight/api'
4
+
5
+ module Skylight
6
+ # @api private
7
+ class Instrumenter
8
+ KEY = :__skylight_current_trace
9
+ LOCK = Mutex.new
10
+ DESC_LOCK = Mutex.new
11
+
12
+ TOO_MANY_UNIQUES = "<too many unique descriptions>"
13
+
14
+ include Util::Logging
15
+
16
+ class TraceInfo
17
+ def current
18
+ Thread.current[KEY]
19
+ end
20
+
21
+ def current=(trace)
22
+ Thread.current[KEY] = trace
23
+ end
24
+ end
25
+
26
+ def self.instance
27
+ @instance
28
+ end
29
+
30
+ # Do start
31
+ # @param [Config] config The config
32
+ def self.start!(config = nil)
33
+ return @instance if @instance
34
+
35
+ LOCK.synchronize do
36
+ return @instance if @instance
37
+ @instance = new(config).start!
38
+ end
39
+ rescue => e
40
+ message = sprintf("[SKYLIGHT] [#{Skylight::VERSION}] Unable to start Instrumenter; msg=%s; class=%s", e.message, e.class)
41
+ if config && config.respond_to?(:logger)
42
+ config.logger.warn message
43
+ else
44
+ warn message
45
+ end
46
+ false
47
+ end
48
+
49
+ def self.stop!
50
+ LOCK.synchronize do
51
+ return unless @instance
52
+ # This is only really helpful for getting specs to pass.
53
+ @instance.current_trace = nil
54
+
55
+ @instance.shutdown
56
+ @instance = nil
57
+ end
58
+ end
59
+
60
+ at_exit do
61
+ if RUBY_VERSION == '1.9.2'
62
+ # workaround for MRI bug losing exit status in at_exit block
63
+ # http://bugs.ruby-lang.org/issues/5218
64
+ exit_status = $!.status if $!.is_a?(SystemExit)
65
+ stop!
66
+ exit exit_status if exit_status
67
+ else
68
+ stop!
69
+ end
70
+ end
71
+
72
+ attr_reader :config, :gc, :trace_info
73
+
74
+ def self.new(config)
75
+ config ||= {}
76
+ config = Config.load(config) unless config.is_a?(Config)
77
+ config.validate!
78
+
79
+ inst = native_new(config.to_env)
80
+ inst.send(:initialize, config)
81
+ inst
82
+ end
83
+
84
+ def initialize(config)
85
+ @gc = config.gc
86
+ @config = config
87
+ @subscriber = Subscriber.new(config, self)
88
+
89
+ @trace_info = @config[:trace_info] || TraceInfo.new
90
+ @descriptions = Hash.new { |h,k| h[k] = {} }
91
+ end
92
+
93
+ def current_trace
94
+ @trace_info.current
95
+ end
96
+
97
+ def current_trace=(trace)
98
+ @trace_info.current = trace
99
+ end
100
+
101
+ def start!
102
+ # Warn if there was an error installing Skylight.
103
+ # We do this here since we can't report these issues via Gem install without stopping install entirely.
104
+ Skylight.check_install_errors(config)
105
+
106
+ unless Skylight.native?
107
+ Skylight.warn_skylight_native_missing(config)
108
+ return
109
+ end
110
+
111
+ t { "starting instrumenter" }
112
+ @config.validate!
113
+
114
+ unless validate_authentication
115
+ warn "invalid authentication token"
116
+ return
117
+ end
118
+
119
+ t { "starting native instrumenter" }
120
+ unless native_start
121
+ warn "failed to start instrumenter"
122
+ return
123
+ end
124
+
125
+ @config.gc.enable
126
+ @subscriber.register!
127
+
128
+ self
129
+
130
+ rescue Exception => e
131
+ log_error "failed to start instrumenter; msg=%s; config=%s", e.message, @config.inspect
132
+ t { e.backtrace.join("\n") }
133
+ nil
134
+ end
135
+
136
+ def shutdown
137
+ @subscriber.unregister!
138
+ native_stop
139
+ end
140
+
141
+ def trace(endpoint, cat, title=nil, desc=nil, annot=nil)
142
+ # If a trace is already in progress, continue with that one
143
+ if trace = @trace_info.current
144
+ return yield(trace) if block_given?
145
+ return trace
146
+ end
147
+
148
+ begin
149
+ trace = Trace.new(self, endpoint, Util::Clock.nanos, cat, title, desc, annot)
150
+ rescue Exception => e
151
+ log_error e.message
152
+ t { e.backtrace.join("\n") }
153
+ return
154
+ end
155
+
156
+ @trace_info.current = trace
157
+ return trace unless block_given?
158
+
159
+ begin
160
+ yield trace
161
+
162
+ ensure
163
+ @trace_info.current = nil
164
+ t { "submitting trace" }
165
+ trace.submit
166
+ end
167
+ end
168
+
169
+ def disable
170
+ @disabled = true
171
+ yield
172
+ ensure
173
+ @disabled = false
174
+ end
175
+
176
+ def disabled?
177
+ @disabled
178
+ end
179
+
180
+ @scanner = StringScanner.new('')
181
+ def self.match?(string, regex)
182
+ @scanner.string = string
183
+ @scanner.match?(regex)
184
+ end
185
+
186
+ def match?(string, regex)
187
+ self.class.match?(string, regex)
188
+ end
189
+
190
+ def done(span)
191
+ return unless trace = @trace_info.current
192
+ trace.done(span)
193
+ end
194
+
195
+ def instrument(cat, title=nil, desc=nil, annot=nil)
196
+ raise ArgumentError, 'cat is required' unless cat
197
+
198
+ unless trace = @trace_info.current
199
+ return yield if block_given?
200
+ return
201
+ end
202
+
203
+ cat = cat.to_s
204
+
205
+ unless match?(cat, CATEGORY_REGEX)
206
+ warn "invalid skylight instrumentation category; value=%s", cat
207
+ return yield if block_given?
208
+ return
209
+ end
210
+
211
+ cat = "other.#{cat}" unless match?(cat, TIER_REGEX)
212
+
213
+ unless sp = trace.instrument(cat, title, desc, annot)
214
+ return yield if block_given?
215
+ return
216
+ end
217
+
218
+ return sp unless block_given?
219
+
220
+ begin
221
+ yield sp
222
+ ensure
223
+ trace.done(sp)
224
+ end
225
+ end
226
+
227
+ def limited_description(description)
228
+ endpoint = @trace_info.current.endpoint
229
+
230
+ DESC_LOCK.synchronize do
231
+ set = @descriptions[endpoint]
232
+
233
+ if set.size >= 100
234
+ return TOO_MANY_UNIQUES
235
+ end
236
+
237
+ set[description] = true
238
+ description
239
+ end
240
+ end
241
+
242
+ def process(trace)
243
+ t { fmt "processing trace" }
244
+
245
+ if ignore?(trace)
246
+ t { fmt "ignoring trace" }
247
+ return false
248
+ end
249
+
250
+ begin
251
+ native_submit_trace(trace)
252
+ true
253
+ rescue => e
254
+ warn "failed to submit trace to worker; err=%s", e
255
+ false
256
+ end
257
+ end
258
+
259
+ def ignore?(trace)
260
+ @config.ignored_endpoints.include?(trace.endpoint)
261
+ end
262
+
263
+ # Validates that the provided authentication token is valid. This is done
264
+ # by issuing a request for a session token and checking the response
265
+ def validate_authentication
266
+ # If a session token is specified, don't bother attempting to validate
267
+ if config[:session_token]
268
+ debug "using pre-generated session token"
269
+ true
270
+ else
271
+ api = Api.new(config)
272
+ api.authentication = config[:authentication]
273
+
274
+ case res = api.validate_authentication
275
+ when :ok
276
+ true
277
+ when :invalid
278
+ false
279
+ when :unknown
280
+ warn "unable to validate authentication token"
281
+ true
282
+ else
283
+ error "[BUG] unexpected validate_token result; res=%s", res
284
+ true
285
+ end
286
+ end
287
+ end
288
+
289
+ end
290
+ end