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

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