skylight 0.2.0.beta.3 → 0.2.0.beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2b6ffd4680bf0bfa2f2e725a0ed6601c70d8da33
4
- data.tar.gz: 7467f5495f63fe61cf9ebf342c1b9970e69f4ac5
3
+ metadata.gz: 6c34ce7c2bd1892ae5f05d4e9cc0ee5054cbb9f1
4
+ data.tar.gz: ce556c192f1f5b1d72be7680d358d8373a80cfe7
5
5
  SHA512:
6
- metadata.gz: 63aac94d7f9c0ed6f337901c37b8bbe3e4a519d800092a6a1c023e7881d095740130d299b23e9def21e7c2e9cc7c89664c26c425e6b4fdbcebd76f101a099de5
7
- data.tar.gz: 93b7b2607ddc3a677b04b648c6246b5c8201b46355c01d34892e9291d0cf1555c97308e21019cdf5ec2609b85c10821c644e86dc0f9d6cbe5096fcf2136e476c
6
+ metadata.gz: a9b4be609c1d76a2075cbb9ad116d724fc35be01dfe133e00978c3b09a8e32765a78d2db0ccb2f3c43a60182891a57e42ee8bcecd53ec8d50750a6fc2f38f138
7
+ data.tar.gz: 082e9dd9bfc81ea18b53b830c52bf26796b90943c6af65b78723b21a8d1207478f87ac835a8386c4f0741a4fb97d9d89200ed2746b8fdadb8d9945b869c1a975
@@ -1,3 +1,16 @@
1
1
  #!/usr/bin/env ruby
2
- require "skylight/cli"
3
- Skylight::CLI.start(ARGV)
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'skylight' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('skylight', 'skylight')
@@ -59,9 +59,10 @@ module Skylight
59
59
  noise
60
60
  other)
61
61
 
62
- TIER_REGEX = /^(?:#{TIERS.join('|')})(?:\.|$)/
63
- CATEGORY_REGEX = /^[a-z0-9_-]+(?:\.[a-z0-9_-]+)*$/i
62
+ TIER_REGEX = /^(?:#{TIERS.join('|')})(?:\.|$)/u
63
+ CATEGORY_REGEX = /^[a-z0-9_-]+(?:\.[a-z0-9_-]+)*$/iu
64
64
  DEFAULT_CATEGORY = "app.block".freeze
65
+ DEFAULT_OPTIONS = { category: DEFAULT_CATEGORY }
65
66
 
66
67
  def self.start!(*args)
67
68
  Instrumenter.start!(*args)
@@ -71,23 +72,27 @@ module Skylight
71
72
  Instrumenter.stop!(*args)
72
73
  end
73
74
 
74
- def self.trace(*args, &blk)
75
+ def self.trace(title=nil, desc=nil, annot=nil)
75
76
  unless inst = Instrumenter.instance
76
77
  return yield if block_given?
77
78
  return
78
79
  end
79
80
 
80
- inst.trace(*args, &blk)
81
+ if block_given?
82
+ inst.trace(title, desc, annot) { yield }
83
+ else
84
+ inst.trace(title, desc, annot)
85
+ end
81
86
  end
82
87
 
83
- def self.instrument(opts = {}, &blk)
88
+ def self.instrument(opts = DEFAULT_OPTIONS)
84
89
  unless inst = Instrumenter.instance
85
90
  return yield if block_given?
86
91
  return
87
92
  end
88
93
 
89
94
  if Hash === opts
90
- category = opts.delete(:category) || DEFAULT_CATEGORY
95
+ category = opts.delete(:category)
91
96
  title = opts.delete(:title)
92
97
  desc = opts.delete(:description)
93
98
  else
@@ -96,16 +101,20 @@ module Skylight
96
101
  desc = nil
97
102
  end
98
103
 
99
- inst.instrument(category, title, desc, &blk)
104
+ if block_given?
105
+ inst.instrument(category, title, desc) { yield }
106
+ else
107
+ inst.instrument(category, title, desc)
108
+ end
100
109
  end
101
110
 
102
- def self.disable(&block)
111
+ def self.disable
103
112
  unless inst = Instrumenter.instance
104
113
  return yield if block_given?
105
114
  return
106
115
  end
107
116
 
108
- inst.disable(&block)
117
+ inst.disable { yield }
109
118
  end
110
119
 
111
120
  RUBYBIN = File.join(
@@ -1,5 +1,6 @@
1
1
  require 'thread'
2
2
  require 'set'
3
+ require 'base64'
3
4
 
4
5
  module Skylight
5
6
  class Instrumenter
@@ -55,7 +56,7 @@ module Skylight
55
56
  @subscriber = Subscriber.new(config, self)
56
57
 
57
58
  @trace_info = @config[:trace_info] || TraceInfo.new
58
- @descriptions = Hash.new { |h,k| h[k] = Set.new }
59
+ @descriptions = Hash.new { |h,k| h[k] = {} }
59
60
  end
60
61
 
61
62
  def current_trace
@@ -87,7 +88,7 @@ module Skylight
87
88
  @worker.shutdown
88
89
  end
89
90
 
90
- def trace(endpoint, cat, *args)
91
+ def trace(endpoint, cat, title=nil, desc=nil, annot=nil)
91
92
  # If a trace is already in progress, continue with that one
92
93
  if trace = @trace_info.current
93
94
  t { "already tracing" }
@@ -96,7 +97,7 @@ module Skylight
96
97
  end
97
98
 
98
99
  begin
99
- trace = Messages::Trace::Builder.new(self, endpoint, Util::Clock.micros, cat, *args)
100
+ trace = Messages::Trace::Builder.new(self, endpoint, Util::Clock.micros, cat, title, desc, annot)
100
101
  rescue Exception => e
101
102
  error e.message
102
103
  t { e.backtrace.join("\n") }
@@ -126,7 +127,17 @@ module Skylight
126
127
  @disabled
127
128
  end
128
129
 
129
- def instrument(cat, *args)
130
+ @scanner = StringScanner.new('')
131
+ def self.match?(string, regex)
132
+ @scanner.string = string
133
+ @scanner.match?(regex)
134
+ end
135
+
136
+ def match?(string, regex)
137
+ self.class.match?(string, regex)
138
+ end
139
+
140
+ def instrument(cat, title=nil, desc=nil, annot=nil)
130
141
  unless trace = @trace_info.current
131
142
  return yield if block_given?
132
143
  return
@@ -134,15 +145,15 @@ module Skylight
134
145
 
135
146
  cat = cat.to_s
136
147
 
137
- unless cat =~ CATEGORY_REGEX
148
+ unless match?(cat, CATEGORY_REGEX)
138
149
  warn "invalid skylight instrumentation category; value=%s", cat
139
150
  return yield if block_given?
140
151
  return
141
152
  end
142
153
 
143
- cat = "other.#{cat}" unless cat =~ TIER_REGEX
154
+ cat = "other.#{cat}" unless match?(cat, TIER_REGEX)
144
155
 
145
- unless sp = trace.instrument(cat, *args)
156
+ unless sp = trace.instrument(cat, title, desc, annot)
146
157
  return yield if block_given?
147
158
  return
148
159
  end
@@ -157,16 +168,17 @@ module Skylight
157
168
  end
158
169
 
159
170
  def limited_description(description)
171
+ endpoint = nil
160
172
  endpoint = @trace_info.current.endpoint
161
173
 
162
174
  DESC_LOCK.synchronize do
163
175
  set = @descriptions[endpoint]
164
176
 
165
177
  if set.size >= 100
166
- return TOO_MANY_UNIQUES if set.size >= 100
178
+ return TOO_MANY_UNIQUES
167
179
  end
168
180
 
169
- set << description
181
+ set[description] = true
170
182
  description
171
183
  end
172
184
  end
@@ -174,6 +186,10 @@ module Skylight
174
186
  def error(reason, body)
175
187
  t { fmt "processing error; reason=%s; body=%s", reason, body }
176
188
 
189
+ if body.encoding == Encoding::BINARY || !body.valid_encoding?
190
+ body = Base64.encode64(body)
191
+ end
192
+
177
193
  message = Skylight::Messages::Error.new(reason: reason, body: body)
178
194
 
179
195
  unless @worker.submit(message)
@@ -13,14 +13,17 @@ module Skylight
13
13
 
14
14
  include Util::Logging
15
15
 
16
- attr_accessor :endpoint
17
- attr_reader :spans, :notifications
16
+ attr_reader :endpoint, :spans, :notifications
18
17
 
19
- def initialize(instrumenter, endpoint, start, cat, *args)
18
+ def endpoint=(value)
19
+ @endpoint = value.freeze
20
+ end
21
+
22
+ def initialize(instrumenter, endpoint, start, cat, title=nil, desc=nil, annot=nil)
20
23
  raise ArgumentError, 'instrumenter is required' unless instrumenter
21
24
 
22
25
  @instrumenter = instrumenter
23
- @endpoint = endpoint
26
+ @endpoint = endpoint.freeze
24
27
  @start = start
25
28
  @spans = []
26
29
  @stack = []
@@ -32,9 +35,13 @@ module Skylight
32
35
  # Track time
33
36
  @last_seen_time = start
34
37
 
35
- annot = args.pop if Hash === args
36
- title = args.shift
37
- desc = args.shift
38
+ if Hash === title
39
+ annot = title
40
+ title = desc = nil
41
+ elsif Hash === desc
42
+ annot = desc
43
+ desc = nil
44
+ end
38
45
 
39
46
  # Create the root node
40
47
  @root = start(@start, cat, title, desc, annot)
@@ -60,13 +67,27 @@ module Skylight
60
67
  nil
61
68
  end
62
69
 
63
- def instrument(cat, *args)
64
- annot = args.pop if Hash === args.last
65
- title = args.shift
66
- desc = args.shift
67
- now = adjust_for_skew(Util::Clock.micros)
70
+ def instrument(cat, title=nil, desc=nil, annot=nil)
71
+ if Hash === title
72
+ annot = title
73
+ title = desc = nil
74
+ elsif Hash === desc
75
+ annot = desc
76
+ desc = nil
77
+ end
68
78
 
69
- desc = @instrumenter.limited_description(desc)
79
+ title.freeze
80
+ desc.freeze
81
+
82
+ original_desc = desc
83
+ now = adjust_for_skew(Util::Clock.micros)
84
+ desc = @instrumenter.limited_description(desc)
85
+
86
+ if desc == Instrumenter::TOO_MANY_UNIQUES
87
+ debug "[SKYLIGHT] A payload description produced <too many uniques>"
88
+ debug "original desc=%s", original_desc
89
+ debug "cat=%s, title=%s, desc=%s, annot=%s", cat, title, desc, annot.inspect
90
+ end
70
91
 
71
92
  start(now - gc_time, cat, title, desc, annot)
72
93
  end
@@ -1,4 +1,5 @@
1
1
  require 'skylight/normalizers/default'
2
+ require 'skylight/util/allocation_free'
2
3
 
3
4
  module Skylight
4
5
  # Convert AS::N events to Skylight events
@@ -40,6 +41,8 @@ module Skylight
40
41
  end
41
42
 
42
43
  class RenderNormalizer < Normalizer
44
+ include AllocationFree
45
+
43
46
  def setup
44
47
  @paths = config['normalizers.render.view_paths'] || []
45
48
  end
@@ -54,19 +57,45 @@ module Skylight
54
57
  end
55
58
 
56
59
  def relative_path(path, annotations)
57
- return path if Pathname.new(path).relative?
60
+ return path if relative_path?(path)
58
61
 
59
- root = @paths.find { |p| path.start_with?(p) }
62
+ root = array_find(@paths) { |p| path.start_with?(p) }
60
63
 
61
64
  if root
62
- relative = path[root.size..-1]
63
- relative = relative[1..-1] if relative.start_with?("/")
64
- relative
65
+ start = root.size
66
+ start += 1 if path.getbyte(start) == SEPARATOR_BYTE
67
+ path[start, path.size]
65
68
  else
66
69
  annotations[:skylight_error] = ["absolute_path", path]
67
70
  "Absolute Path"
68
71
  end
69
72
  end
73
+
74
+ private
75
+ def relative_path?(path)
76
+ !absolute_path?(path)
77
+ end
78
+
79
+ SEPARATOR_BYTE = File::SEPARATOR.ord
80
+
81
+ if File::NULL == "NUL"
82
+ ALT_SEPARATOR_BYTE = File::ALT_SEPARATOR && File::ALT_SEPARATOR.ord
83
+ COLON_BYTE = ":".ord
84
+ def absolute_path?(path)
85
+ if alpha?(path.getbyte(0)) && path.getbyte(1) == COLON_BYTE
86
+ byte2 = path.getbyte(2)
87
+ byte2 == SEPARATOR_BYTE || byte2 == ALT_SEPARATOR_BYTE
88
+ end
89
+ end
90
+
91
+ def alpha?(byte)
92
+ byte >= 65 and byte <= 90 || byte >= 97 and byte <= 122
93
+ end
94
+ else
95
+ def absolute_path?(path)
96
+ path.getbyte(0) == SEPARATOR_BYTE
97
+ end
98
+ end
70
99
  end
71
100
 
72
101
  class Container
@@ -3,9 +3,11 @@ module Skylight
3
3
  class ProcessAction < Normalizer
4
4
  register "process_action.action_controller"
5
5
 
6
+ CAT = "app.controller.request".freeze
7
+
6
8
  def normalize(trace, name, payload)
7
9
  trace.endpoint = controller_action(payload)
8
- [ "app.controller.request", trace.endpoint, nil, normalize_payload(payload) ]
10
+ [ CAT, trace.endpoint, nil, normalize_payload(payload) ]
9
11
  end
10
12
 
11
13
  private
@@ -17,7 +19,9 @@ module Skylight
17
19
  def normalize_payload(payload)
18
20
  normalized = {}
19
21
 
20
- payload.each do |key, value|
22
+ payload.each_key do |key|
23
+ value = payload[key]
24
+
21
25
  value = value.inspect unless value.is_a?(String) || value.is_a?(Numeric)
22
26
  normalized[key] = value
23
27
  end
@@ -3,9 +3,11 @@ module Skylight
3
3
  class RenderCollection < RenderNormalizer
4
4
  register "render_collection.action_view"
5
5
 
6
+ CAT = "view.render.collection".freeze
7
+
6
8
  def normalize(trace, name, payload)
7
9
  normalize_render(
8
- "view.render.collection",
10
+ CAT,
9
11
  payload,
10
12
  count: payload[:count])
11
13
  end
@@ -3,9 +3,11 @@ module Skylight
3
3
  class RenderPartial < RenderNormalizer
4
4
  register "render_partial.action_view"
5
5
 
6
+ CAT = "view.render.template".freeze
7
+
6
8
  def normalize(trace, name, payload)
7
9
  normalize_render(
8
- "view.render.template",
10
+ CAT,
9
11
  payload,
10
12
  partial: 1)
11
13
  end
@@ -3,9 +3,11 @@ module Skylight
3
3
  class RenderTemplate < RenderNormalizer
4
4
  register "render_template.action_view"
5
5
 
6
+ CAT = "view.render.template".freeze
7
+
6
8
  def normalize(trace, name, payload)
7
9
  normalize_render(
8
- "view.render.template",
10
+ CAT,
9
11
  payload,
10
12
  partial: 0)
11
13
  end
@@ -10,6 +10,9 @@ module Skylight
10
10
  class SendFile < Normalizer
11
11
  register "send_file.action_controller"
12
12
 
13
+ CAT = "app.controller.send_file".freeze
14
+ TITLE = "send file".freeze
15
+
13
16
  def normalize(trace, name, payload)
14
17
  path = payload[:path]
15
18
 
@@ -20,31 +23,47 @@ module Skylight
20
23
  disposition: normalize_disposition(payload),
21
24
  status: normalize_status(payload) }
22
25
 
23
- title = "send file"
26
+ title = TITLE
24
27
 
25
28
  # depending on normalization, we probably want this to eventually
26
29
  # include the full path, but we need to make sure we have a good
27
30
  # deduping strategy first.
28
31
  desc = nil
29
32
 
30
- [ "app.controller.send_file", title, desc, annotations ]
33
+ [ CAT, title, desc, annotations ]
31
34
  end
32
35
 
33
36
  private
34
37
 
38
+ OCTET_STREAM = "application/octet-stream".freeze
39
+ ATTACHMENT = "attachment".freeze
40
+
41
+ def initialize(*)
42
+ super
43
+
44
+ @mimes = Mime::SET.reduce({}) do |hash, mime|
45
+ hash[mime.symbol] = mime.to_s.dup.freeze
46
+ hash
47
+ end
48
+ end
49
+
35
50
  def normalize_type(payload)
36
- type = payload[:type] || "application/octet-stream"
37
- type = Mime[type].to_s if type.is_a?(Symbol)
51
+ type = payload[:type] || OCTET_STREAM
52
+ type = @mimes[type] if type.is_a?(Symbol)
38
53
  type
39
54
  end
40
55
 
56
+ def mime_for(type)
57
+ @mimes[type] ||= Mime[type].to_s.freeze
58
+ end
59
+
41
60
  def normalize_status(payload)
42
61
  status = payload[:status] || 200
43
62
  Rack::Utils.status_code(status)
44
63
  end
45
64
 
46
65
  def normalize_disposition(payload)
47
- payload[:disposition] || "attachment"
66
+ payload[:disposition] || ATTACHMENT
48
67
  end
49
68
  end
50
69
 
@@ -6,25 +6,24 @@ module Skylight
6
6
  class SQL < Normalizer
7
7
  register "sql.active_record"
8
8
 
9
+ CAT = "db.sql.query".freeze
10
+
9
11
  def normalize(trace, name, payload)
10
12
  case payload[:name]
11
13
  when "SCHEMA", "CACHE"
12
14
  return :skip
13
15
  else
14
- name = "db.sql.query"
16
+ name = CAT
15
17
  title = payload[:name] || "SQL"
16
18
  end
17
19
 
18
- if payload[:binds].empty?
19
- extracted_title, payload[:sql], binds, error = extract_binds(payload)
20
- else
21
- extracted_title, _, _, error = extract_binds(payload)
22
- binds = payload[:binds].map { |col, val| val.inspect }
20
+ unless payload[:binds].empty?
21
+ payload[:binds] = payload[:binds].map { |col, val| val.inspect }
23
22
  end
24
23
 
24
+ extracted_title, payload[:sql], binds, error = extract_binds(payload, payload[:binds])
25
25
  title = extracted_title if extracted_title
26
26
 
27
-
28
27
  if payload[:sql]
29
28
  annotations = {
30
29
  sql: payload[:sql],
@@ -40,8 +39,8 @@ module Skylight
40
39
  end
41
40
 
42
41
  private
43
- def extract_binds(payload)
44
- title, sql, binds = SqlLexer::Lexer.bindify(payload[:sql])
42
+ def extract_binds(payload, precalculated)
43
+ title, sql, binds = SqlLexer::Lexer.bindify(payload[:sql], precalculated)
45
44
  [ title, sql, binds, nil ]
46
45
  rescue
47
46
  [ nil, nil, nil, ["sql_parse", payload[:sql]] ]
@@ -0,0 +1,15 @@
1
+ module Skylight
2
+ module AllocationFree
3
+ def array_find(array)
4
+ i = 0
5
+
6
+ while i < array.size
7
+ item = array[i]
8
+ return item if yield item
9
+ i += 1
10
+ end
11
+
12
+ nil
13
+ end
14
+ end
15
+ end
@@ -54,13 +54,20 @@ module Skylight
54
54
  end
55
55
 
56
56
  def field(rule, name, type, fn, opts)
57
- fields[fn] = Field.new(rule, name, type, fn, opts)
57
+ field = Field.new(rule, name, type, fn, opts)
58
+ indexed_fields[fn] = field
59
+ fields << field
60
+ fields.sort!
58
61
  __write_initializer
59
62
  attr_accessor name
60
63
  end
61
64
 
62
65
  def fields
63
- @fields ||= {}
66
+ @fields ||= []
67
+ end
68
+
69
+ def indexed_fields
70
+ @indexed_fields ||= {}
64
71
  end
65
72
 
66
73
  def __write_initializer
@@ -68,7 +75,7 @@ module Skylight
68
75
 
69
76
  lines << "def initialize(attrs=nil)"
70
77
  lines << "return unless attrs"
71
- fields.values.each do |fld|
78
+ fields.each do |fld|
72
79
  lines << "@#{fld.name} = attrs[:#{fld.name}]"
73
80
  end
74
81
 
@@ -93,7 +100,7 @@ module Skylight
93
100
 
94
101
  # TODO: Error if any required fields at nil
95
102
 
96
- fields.values.sort.each do |fld|
103
+ fields.each do |fld|
97
104
  if fld.opts[:packed]
98
105
  bytes = encode!(Buffer.new, fld, 0)
99
106
  buf.append_info(fld.fn, Buffer.wire_for(fld.type))
@@ -109,27 +116,36 @@ module Skylight
109
116
 
110
117
  def encode!(buf, fld, fn)
111
118
  v = self[fld.name]
112
- v = v.is_a?(Array) ? v : [v]
113
-
114
- v.compact.each do |val|
115
- case fld.type
116
- when Class # encodable
117
- # TODO: raise error if type != val.class
118
- buf.append(:string, val.encode, fn)
119
- when Module # enum
120
- if ! valid_enum?(fld.type, val)
121
- raise InvalidValueError.new(fld.name, val)
122
- end
123
119
 
124
- buf.append(:int32, val, fn)
125
- else
126
- buf.append(fld.type, val, fn)
120
+ if v.is_a?(Array)
121
+ v.each do |val|
122
+ encode_field!(buf, fld.type, val, fn)
127
123
  end
124
+ else
125
+ encode_field!(buf, fld.type, v, fn)
128
126
  end
129
127
 
130
128
  buf
131
129
  end
132
130
 
131
+ def encode_field!(buf, type, val, fn)
132
+ return if val.nil?
133
+
134
+ case type
135
+ when Class # encodable
136
+ # TODO: raise error if type != val.class
137
+ buf.append(:string, val.encode, fn)
138
+ when Module # enum
139
+ if ! valid_enum?(type, val)
140
+ raise InvalidValueError.new(fld.name, val)
141
+ end
142
+
143
+ buf.append(:int32, val, fn)
144
+ else
145
+ buf.append(type, val, fn)
146
+ end
147
+ end
148
+
133
149
  def valid_enum?(mod, val)
134
150
  !!name_for(mod, val)
135
151
  end
@@ -144,13 +160,12 @@ module Skylight
144
160
  end
145
161
 
146
162
  def validate!
147
- fields.values.each do |fld|
163
+ fields.each do |fld|
148
164
  if fld.rule == :required && self[fld.name].nil?
149
165
  raise RequiredFieldNotSetError, fld.name
150
166
  end
151
167
  end
152
168
  end
153
-
154
169
  end
155
170
 
156
171
 
@@ -165,7 +180,7 @@ module Skylight
165
180
  while buf.length > 0
166
181
  fn, wire = buf.read_info
167
182
 
168
- fld = fields[fn]
183
+ fld = indexed_fields[fn]
169
184
 
170
185
  # We don't have a field for with index fn.
171
186
  # Ignore this data and move on.
@@ -196,7 +211,7 @@ module Skylight
196
211
  end
197
212
 
198
213
  # Set defaults
199
- fields.values.each do |f|
214
+ fields.each do |f|
200
215
  next if o[f.name] == false
201
216
  o[f.name] ||= f.opts[:default]
202
217
  end
@@ -218,6 +233,10 @@ module Skylight
218
233
  self.class.fields
219
234
  end
220
235
 
236
+ def indexed_fields
237
+ self.class.indexed_fields
238
+ end
239
+
221
240
  def [](k)
222
241
  __send__(k)
223
242
  end
@@ -229,7 +248,7 @@ module Skylight
229
248
  def ==(o)
230
249
  return false if (o == nil) || (o == false)
231
250
  return false unless o.respond_to?(:[])
232
- fields.values.all? do |fld|
251
+ fields.all? do |fld|
233
252
  if fld.rule == :repeated
234
253
  Array(self[fld.name]) == Array(o[fld.name])
235
254
  else
@@ -239,7 +258,7 @@ module Skylight
239
258
  end
240
259
 
241
260
  def inspect
242
- set = fields.values.select {|fld| self[fld.name] != nil }
261
+ set = fields.select {|fld| self[fld.name] != nil }
243
262
 
244
263
  flds = set.map do |fld|
245
264
  val = self[fld.name]
@@ -259,7 +278,7 @@ module Skylight
259
278
  end
260
279
 
261
280
  def to_hash
262
- fields.values.inject({}) do |h, fld|
281
+ fields.inject({}) do |h, fld|
263
282
  if v = self[fld.name]
264
283
  h[fld.name] = v
265
284
  end
@@ -11,6 +11,8 @@ module Skylight
11
11
  MinInt64 = -(1<<63)
12
12
  MaxInt64 = (1<<63)-1
13
13
 
14
+ MaxFixnum = (1 << (1.size * 8 - 2) - 1)
15
+
14
16
  def self.wire_for(type)
15
17
  case type
16
18
  when Class
@@ -70,7 +72,7 @@ module Skylight
70
72
 
71
73
  if ''.respond_to?(:force_encoding)
72
74
  def buf=(buf)
73
- @buf = buf.force_encoding('BINARY')
75
+ @buf = buf.force_encoding(BINARY)
74
76
  end
75
77
  end
76
78
 
@@ -78,7 +80,7 @@ module Skylight
78
80
  @buf.respond_to?(:bytesize) ? @buf.bytesize : @buf.length
79
81
  end
80
82
 
81
- BINARY = 'BINARY'
83
+ BINARY = 'BINARY'.freeze
82
84
 
83
85
  # Detect a ruby encodings bug, as far as I know, this exists in
84
86
  # most versions fo JRuby as well as 1.9.2
@@ -10,7 +10,7 @@ module Skylight
10
10
  append_info(fn, wire)
11
11
  end
12
12
 
13
- __send__("append_#{type}", val)
13
+ __send__(HANDLERS[type], val)
14
14
  end
15
15
 
16
16
  def append_info(fn, wire)
@@ -26,7 +26,7 @@ module Skylight
26
26
  end
27
27
 
28
28
  def append_fixed64(n)
29
- if n < MinUint64 || n > MaxUint64
29
+ if uint64?(n)
30
30
  raise OutOfRangeError, n
31
31
  end
32
32
 
@@ -77,8 +77,18 @@ module Skylight
77
77
  append_fixed64((n << 1) ^ (n >> 63))
78
78
  end
79
79
 
80
+ def uint64?(n)
81
+ if n < MinUint64
82
+ false
83
+ elsif n < MaxFixnum
84
+ true
85
+ else
86
+ n <= MaxUint64
87
+ end
88
+ end
89
+
80
90
  def append_uint64(n)
81
- if n < MinUint64 || n > MaxUint64
91
+ unless uint64?(n)
82
92
  raise OutOfRangeError, n
83
93
  end
84
94
 
@@ -110,6 +120,13 @@ module Skylight
110
120
  end
111
121
  alias :append_bytes :append_string
112
122
 
123
+ HANDLERS = instance_methods.reduce({}) do |hash, meth|
124
+ if meth.to_s =~ /^append_(.*)$/
125
+ hash[$1.to_sym] = meth
126
+ end
127
+
128
+ hash
129
+ end
113
130
  end
114
131
  end
115
132
  end
@@ -1,4 +1,4 @@
1
1
  module Skylight
2
- VERSION = '0.2.0.beta.3'
2
+ VERSION = '0.2.0.beta.4'
3
3
  end
4
4
 
@@ -67,6 +67,9 @@ module Skylight
67
67
  def send_error(msg)
68
68
  res = @http_auth.post("/agent/error?hostname=#{escape(config[:'hostname'])}", reason: msg.reason, body: msg.body)
69
69
 
70
+ # error already handled in Util::HTTP
71
+ return unless res
72
+
70
73
  unless res.success?
71
74
  if (400..499).include? res.status
72
75
  warn "error wasn't sent successfully; status=%s", res.status
@@ -108,6 +111,9 @@ module Skylight
108
111
  def refresh_report_token(now)
109
112
  res = @http_auth.get("/agent/authenticate?hostname=#{escape(config[:'hostname'])}")
110
113
 
114
+ # error already handled in Util::HTTP
115
+ return unless res
116
+
111
117
  unless res.success?
112
118
  if (400..499).include? res.status
113
119
  warn "token request rejected; status=%s", res.status
@@ -59,21 +59,22 @@ module SqlLexer
59
59
 
60
60
  Literals = %Q<(?:NULL|TRUE|FALSE)(?=(?:[#{WS}]|#{OpPart}|#{End}))>
61
61
 
62
- TkWS = %r<[#{WS}]+>
63
- TkOptWS = %r<[#{WS}]*>
64
- TkOp = %r<[#{OpPart}]>
65
- TkPlaceholder = %r<#{Placeholder}>
66
- TkNonBind = %r<#{NonBind}>
67
- TkQuotedTable = %r<#{QuotedTable}>i
68
- TkUpdateTable = %r<UPDATE#{TableNext}>i
69
- TkInsertTable = %r<INSERT[#{WS}]+INTO#{TableNext}>i
70
- TkDeleteTable = %r<DELETE[#{WS}]+FROM#{TableNext}>i
71
- TkFromTable = %r<FROM#{TableNext}>i
72
- TkID = %r<#{ID}>
73
- TkEnd = %r<;?[#{WS}]*>
74
- TkBind = %r<#{String}|#{Number}|#{Literals}>
75
- TkIn = %r<#{InOp}>i
76
- TkSpecialOp = %r<#{SpecialOps}>i
62
+ TkWS = %r<[#{WS}]+>u
63
+ TkOptWS = %r<[#{WS}]*>u
64
+ TkOp = %r<[#{OpPart}]>u
65
+ TkPlaceholder = %r<#{Placeholder}>u
66
+ TkNonBind = %r<#{NonBind}>u
67
+ TkQuotedTable = %r<#{QuotedTable}>iu
68
+ TkUpdateTable = %r<UPDATE#{TableNext}>iu
69
+ TkInsertTable = %r<INSERT[#{WS}]+INTO#{TableNext}>iu
70
+ TkDeleteTable = %r<DELETE[#{WS}]+FROM#{TableNext}>iu
71
+ TkFromTable = %r<FROM#{TableNext}>iu
72
+ TkID = %r<#{ID}>u
73
+ TkEnd = %r<;?[#{WS}]*>u
74
+ TkBind = %r<#{String}|#{Number}|#{Literals}>u
75
+ TkIn = %r<#{InOp}>iu
76
+ TkSpecialOp = %r<#{SpecialOps}>iu
77
+ TkStartSelect = %r<SELECT(?=(?:[#{WS}]|#{OpPart}))>iu
77
78
 
78
79
  STATE_HANDLERS = {
79
80
  begin: :process_begin,
@@ -81,91 +82,159 @@ module SqlLexer
81
82
  tokens: :process_tokens,
82
83
  bind: :process_bind,
83
84
  non_bind: :process_non_bind,
85
+ placeholder: :process_placeholder,
84
86
  table_name: :process_table_name,
85
87
  end: :process_end,
86
88
  special: :process_special,
87
89
  in: :process_in
88
90
  }
89
91
 
90
- def self.bindify(string)
91
- new(string).tap do |scanner|
92
- scanner.process
93
- return scanner.title, scanner.output, scanner.binds
94
- end
92
+ def self.bindify(string, binds=nil)
93
+ scanner = instance(string)
94
+ scanner.process(binds)
95
+ [scanner.title, scanner.output, scanner.binds]
95
96
  end
96
97
 
97
98
  attr_reader :output, :binds, :title
98
99
 
99
- def initialize(string)
100
- @scanner = StringScanner.new(string)
100
+ def self.pooled_value(name, default)
101
+ key = :"__skylight_sql_#{name}"
102
+
103
+ singleton_class.class_eval do
104
+ define_method(name) do
105
+ value = Thread.current[key] ||= default.dup
106
+ value.clear
107
+ value
108
+ end
109
+ end
110
+
111
+ __send__(name)
112
+ end
113
+
114
+ SCANNER_KEY = :__skylight_sql_scanner
115
+ LEXER_KEY = :__skylight_sql_lexer
116
+
117
+ def self.scanner(string='')
118
+ scanner = Thread.current[SCANNER_KEY] ||= StringScanner.new('')
119
+ scanner.string = string
120
+ scanner
121
+ end
122
+
123
+ def self.instance(string)
124
+ lexer = Thread.current[LEXER_KEY] ||= new
125
+ lexer.init(string)
126
+ lexer
127
+ end
128
+
129
+ pooled_value :binds, []
130
+ pooled_value :table, "*" * 20
131
+
132
+ SPACE = " ".freeze
133
+
134
+ DEBUG = ENV["DEBUG"]
135
+
136
+ def init(string)
101
137
  @state = :begin
102
- @input = string
103
- @output = string.dup
104
- @binds = []
138
+ @debug = DEBUG
139
+ @binds = self.class.binds
140
+ @table = self.class.table
141
+ @title = nil
142
+ @bind = 0
143
+
144
+ self.string = string
145
+ end
146
+
147
+ def string=(value)
148
+ @input = value
149
+
150
+ @scanner = self.class.scanner(value)
151
+
152
+ # intentionally allocates; we need to return a new
153
+ # string as part of this API
154
+ @output = value.dup
105
155
  end
106
156
 
107
- def process
108
- @operation = @table = @title = nil
157
+ PLACEHOLDER = "?".freeze
158
+ UNKNOWN = "<unknown>".freeze
159
+
160
+ def process(binds)
161
+ @operation = nil
162
+ @provided_binds = binds
109
163
 
110
164
  while @state
111
- if ENV["DEBUG"]
165
+ if @debug
112
166
  p @state
113
167
  p @scanner
114
168
  end
115
169
 
116
- send STATE_HANDLERS[@state]
170
+ __send__ STATE_HANDLERS[@state]
117
171
  end
118
172
 
119
173
  pos = 0
120
174
  removed = 0
175
+
176
+ # intentionally allocates; the returned binds must
177
+ # be in a newly produced array
121
178
  extracted_binds = Array.new(@binds.size / 2)
122
179
 
123
- if @operation && @table
124
- table = @input[@table[0], @table[1] - @table[0]]
125
- @title = "#{@operation} #{table}"
180
+ if @operation && !@table.empty?
181
+ @title = "" << @operation << SPACE << @table
126
182
  end
127
183
 
128
184
  while pos < @binds.size
129
- slice = @output.slice!(@binds[pos] - removed, @binds[pos+1])
130
- @output.insert(@binds[pos] - removed, '?')
131
- extracted_binds[pos/2] = slice
132
- removed += slice.size - 1
185
+ if @binds[pos] == nil
186
+ extracted_binds[pos/2] = @binds[pos+1]
187
+ else
188
+ slice = @output[@binds[pos] - removed, @binds[pos+1]]
189
+ @output[@binds[pos] - removed, @binds[pos+1]] = PLACEHOLDER
190
+
191
+ extracted_binds[pos/2] = slice
192
+ removed += (@binds[pos+1] - 1)
193
+ end
194
+
133
195
  pos += 2
134
196
  end
135
197
 
136
198
  @binds = extracted_binds
199
+ nil
137
200
  end
138
201
 
202
+ EMPTY = "".freeze
203
+
139
204
  def process_begin
140
- @scanner.scan(TkOptWS)
205
+ @scanner.skip(TkOptWS)
141
206
  @state = :first_token
142
207
  end
143
208
 
209
+ OP_SELECT_FROM = "SELECT FROM".freeze
210
+ OP_UPDATE = "UPDATE".freeze
211
+ OP_INSERT_INTO = "INSERT INTO".freeze
212
+ OP_DELETE_FROM = "DELETE FROM".freeze
213
+
144
214
  def process_first_token
145
- if @scanner.skip(/SELECT\s+/i)
146
- @operation = :"SELECT FROM"
215
+ if @scanner.skip(TkStartSelect)
216
+ @operation = OP_SELECT_FROM
147
217
  @state = :tokens
148
- return
149
- end
218
+ else
219
+ if @scanner.skip(TkUpdateTable)
220
+ @operation = OP_UPDATE
221
+ elsif @scanner.skip(TkInsertTable)
222
+ @operation = OP_INSERT_INTO
223
+ elsif @scanner.skip(TkDeleteTable)
224
+ @operation = OP_DELETE_FROM
225
+ end
150
226
 
151
- if @scanner.skip(TkUpdateTable)
152
- @operation = :UPDATE
153
- elsif @scanner.skip(TkInsertTable)
154
- @operation = :"INSERT INTO"
155
- elsif @scanner.skip(TkDeleteTable)
156
- @operation = :"DELETE FROM"
227
+ @state = :table_name
157
228
  end
158
-
159
- @state = :table_name
160
229
  end
161
230
 
162
231
  def process_table_name
163
232
  pos = @scanner.pos
164
233
 
165
234
  if @scanner.skip(TkQuotedTable)
166
- @table = [pos + 1, @scanner.pos - 1]
235
+ copy_substr(@input, @table, pos + 1, @scanner.pos - 1)
167
236
  elsif @scanner.skip(TkID)
168
- @table = [pos, @scanner.pos]
237
+ copy_substr(@input, @table, pos, @scanner.pos)
169
238
  end
170
239
 
171
240
  @state = :tokens
@@ -174,12 +243,14 @@ module SqlLexer
174
243
  def process_tokens
175
244
  @scanner.skip(TkOptWS)
176
245
 
177
- if @operation == :"SELECT FROM" && !@table && @scanner.skip(TkFromTable)
246
+ if @operation == OP_SELECT_FROM && @table.empty? && @scanner.skip(TkFromTable)
178
247
  @state = :table_name
179
248
  elsif @scanner.match?(TkSpecialOp)
180
249
  @state = :special
181
250
  elsif @scanner.match?(TkBind)
182
251
  @state = :bind
252
+ elsif @scanner.match?(TkPlaceholder)
253
+ @state = :placeholder
183
254
  elsif @scanner.match?(TkNonBind)
184
255
  @state = :non_bind
185
256
  else
@@ -187,10 +258,28 @@ module SqlLexer
187
258
  end
188
259
  end
189
260
 
261
+ def process_placeholder
262
+ @scanner.skip(TkPlaceholder)
263
+
264
+ binds << nil
265
+
266
+ if !@provided_binds
267
+ @binds << UNKNOWN
268
+ elsif !@provided_binds[@bind]
269
+ @binds << UNKNOWN
270
+ else
271
+ @binds << @provided_binds[@bind]
272
+ end
273
+
274
+ @bind += 1
275
+
276
+ @state = :tokens
277
+ end
278
+
190
279
  def process_special
191
280
  if @scanner.skip(TkIn)
192
281
  @scanner.skip(TkOptWS)
193
- @scanner.skip(/\(/)
282
+ @scanner.skip(/\(/u)
194
283
  @state = :in
195
284
  end
196
285
  end
@@ -209,16 +298,16 @@ module SqlLexer
209
298
  raise "The SQL '#{@scanner.string}' could not be parsed because of too many iterations in IN"
210
299
  end
211
300
 
212
- if ENV["DEBUG"]
301
+ if @debug
213
302
  p @state
214
303
  p @scanner
215
304
  p nest
216
305
  end
217
306
 
218
- if @scanner.skip(/\(/)
307
+ if @scanner.skip(/\(/u)
219
308
  nest += 1
220
309
  process_tokens
221
- elsif @scanner.skip(/\)/)
310
+ elsif @scanner.skip(/\)/u)
222
311
  nest -= 1
223
312
  break if nest.zero?
224
313
  process_tokens
@@ -263,5 +352,19 @@ module SqlLexer
263
352
 
264
353
  @state = nil
265
354
  end
355
+
356
+ private
357
+ def copy_substr(source, target, start_pos, end_pos)
358
+ pos = start_pos
359
+
360
+ while pos < end_pos
361
+ target.concat source.getbyte(pos)
362
+ pos += 1
363
+ end
364
+ end
365
+
366
+ scanner
367
+ instance('')
368
+
266
369
  end
267
370
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: skylight
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0.beta.3
4
+ version: 0.2.0.beta.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tilde, Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-11-03 00:00:00.000000000 Z
11
+ date: 2013-11-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -62,6 +62,7 @@ files:
62
62
  - lib/skylight/normalizers/sql.rb
63
63
  - lib/skylight/railtie.rb
64
64
  - lib/skylight/subscriber.rb
65
+ - lib/skylight/util/allocation_free.rb
65
66
  - lib/skylight/util/clock.rb
66
67
  - lib/skylight/util/gzip.rb
67
68
  - lib/skylight/util/http.rb
@@ -153,7 +154,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
153
154
  version: 1.3.1
154
155
  requirements: []
155
156
  rubyforge_project:
156
- rubygems_version: 2.0.3
157
+ rubygems_version: 2.1.9
157
158
  signing_key:
158
159
  specification_version: 4
159
160
  summary: Skylight is a ruby application monitoring tool.