skylight 0.2.2 → 0.2.3

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: 54d9a76b353b8d0a2a6fecce094a8a32802a1b9d
4
- data.tar.gz: c773bed69979daa2fe511a1fef1698699aa6ba42
3
+ metadata.gz: 38880fd1b62fad365c461e3fb6517e979f14baa6
4
+ data.tar.gz: 9c62ce619478170a9e1b38e788bc35e61cd41b94
5
5
  SHA512:
6
- metadata.gz: ad2f6c9f9edec7ec1417a89faa026cafbb5b36b9adc6e47c35063793178b8ff4bd75d683c404463fb37e0581be526eeeb7443e9df2314be060fd78d9419a51b6
7
- data.tar.gz: f4b98d9d9b50b111b7c5f33ae57343c7985be52bcaa0cced2781c322bc1231b2186dd8f1ffda1a3b9eb098afec2c98e3572ed3492e21c39b4c8693acd3399dc8
6
+ metadata.gz: f0aaab6c04ff20b4fcb06da895808f9c84ae9fda59fd33a62639355b9ae5933ad417a80224c2409de46121a5dcd6fe8d45d09434fbd02d04cf7007659e6a0254
7
+ data.tar.gz: 2c7a866a02d2746130638408246c9d751a46dd7395f6ae98533081e984a7dffd3334de731a8dfa5b88a52b019af25e69c0a32f463d202f0dfa6b68ac7ffb68d8
@@ -1,3 +1,9 @@
1
+ ## 0.2.3 (December 20, 2013)
2
+
3
+ * Fix SQL lexing for comments, arrays, double-colon casting, and multiple queries
4
+ * Handle template paths from gems
5
+ * Status and exception reports for agent debugging
6
+
1
7
  ## 0.2.2 (December 10, 2013)
2
8
 
3
9
  * Added support for Mongoid/Moped
@@ -30,6 +30,7 @@ module Skylight
30
30
  'AGENT_SAMPLE_SIZE' => :'agent.sample',
31
31
  'AGENT_SOCKFILE_PATH' => :'agent.sockfile_path',
32
32
  'AGENT_STRATEGY' => :'agent.strategy',
33
+ 'AGENT_MAX_MEMORY' => :'agent.max_memory',
33
34
  'REPORT_HOST' => :'report.host',
34
35
  'REPORT_PORT' => :'report.port',
35
36
  'REPORT_SSL' => :'report.ssl',
@@ -49,6 +50,7 @@ module Skylight
49
50
  :'agent.keepalive' => 60,
50
51
  :'agent.interval' => 5,
51
52
  :'agent.sample' => 200,
53
+ :'agent.max_memory' => 256, # MB
52
54
  :'report.host' => 'agent.skylight.io'.freeze,
53
55
  :'report.port' => 443,
54
56
  :'report.ssl' => true,
@@ -79,7 +79,7 @@ module Skylight
79
79
  self
80
80
 
81
81
  rescue Exception => e
82
- error "failed to start instrumenter; msg=%s", e.message
82
+ log_error "failed to start instrumenter; msg=%s", e.message
83
83
  nil
84
84
  end
85
85
 
@@ -99,7 +99,7 @@ module Skylight
99
99
  begin
100
100
  trace = Messages::Trace::Builder.new(self, endpoint, Util::Clock.micros, cat, title, desc, annot)
101
101
  rescue Exception => e
102
- error e.message
102
+ log_error e.message
103
103
  t { e.backtrace.join("\n") }
104
104
  return
105
105
  end
@@ -183,14 +183,10 @@ module Skylight
183
183
  end
184
184
  end
185
185
 
186
- def error(reason, body)
187
- t { fmt "processing error; reason=%s; body=%s", reason, body }
186
+ def error(type, description, details=nil)
187
+ t { fmt "processing error; type=%s; description=%s", type, description }
188
188
 
189
- if body.encoding == Encoding::BINARY || !body.valid_encoding?
190
- body = Base64.encode64(body)
191
- end
192
-
193
- message = Skylight::Messages::Error.new(reason: reason, body: body)
189
+ message = Skylight::Messages::Error.new(type: type, description: description, details: details && details.to_json)
194
190
 
195
191
  unless @worker.submit(message)
196
192
  warn "failed to submit error to worker"
@@ -3,8 +3,9 @@ require 'skylight/messages/base'
3
3
  module Skylight
4
4
  module Messages
5
5
  class Error < Base
6
- required :reason, :string, 1
7
- required :body, :string, 2
6
+ required :type, :string, 1
7
+ required :description, :string, 2
8
+ optional :details, :string, 3
8
9
  end
9
10
  end
10
11
  end
@@ -60,11 +60,21 @@ module Skylight
60
60
  return path if relative_path?(path)
61
61
 
62
62
  root = array_find(@paths) { |p| path.start_with?(p) }
63
+ type = :project
64
+
65
+ unless root
66
+ root = array_find(Gem.path) { |p| path.start_with?(p) }
67
+ type = :gem
68
+ end
63
69
 
64
70
  if root
65
71
  start = root.size
66
72
  start += 1 if path.getbyte(start) == SEPARATOR_BYTE
67
- path[start, path.size]
73
+ if type == :gem
74
+ "$GEM_PATH/#{path[start, path.size]}"
75
+ else
76
+ path[start, path.size]
77
+ end
68
78
  else
69
79
  annotations[:skylight_error] = ["absolute_path", path]
70
80
  "Absolute Path"
@@ -45,9 +45,8 @@ module Skylight
45
45
  title, sql, binds = SqlLexer::Lexer.bindify(payload[:sql], precalculated)
46
46
  [ title, sql, binds, nil ]
47
47
  rescue
48
- # Encode this since we may have improperly incoded strings that can't be to_json'ed
49
- encoded = encode(payload: payload, precalculated: precalculated)
50
- [ nil, nil, nil, ["sql_parse", encoded.to_json] ]
48
+ error = ["sql_parse", encode(payload[:sql]), encode(payload: payload, precalculated: precalculated)]
49
+ [ nil, nil, nil, error ]
51
50
  end
52
51
 
53
52
  def encode(body)
@@ -87,8 +87,8 @@ module Skylight
87
87
  end
88
88
 
89
89
  def execute(req, body=nil)
90
- t { fmt "executing HTTP request; host=%s; port=%s; body=%s",
91
- @host, @port, body && body.bytesize }
90
+ t { fmt "executing HTTP request; host=%s; port=%s; path=%s, body=%s",
91
+ @host, @port, req.path, body && body.bytesize }
92
92
 
93
93
  if body
94
94
  body = Gzip.compress(body) if @deflate
@@ -35,7 +35,13 @@ module Skylight
35
35
  log :error, msg, *args
36
36
  end
37
37
 
38
- alias fmt sprintf
38
+ alias log_trace trace
39
+ alias log_debug debug
40
+ alias log_info info
41
+ alias log_warn warn
42
+ alias log_error error
43
+
44
+ alias fmt sprintf
39
45
 
40
46
  def log(level, msg, *args)
41
47
  return unless respond_to?(:config)
@@ -1,4 +1,4 @@
1
1
  module Skylight
2
- VERSION = '0.2.2'
2
+ VERSION = '0.2.3'
3
3
  end
4
4
 
@@ -62,27 +62,37 @@ module Skylight
62
62
  true
63
63
  end
64
64
 
65
+ def send_status(status)
66
+ post_data(:status, status)
67
+ end
68
+
69
+ def send_exception(exception)
70
+ post_data(:exception, exception)
71
+ end
72
+
65
73
  private
66
74
 
67
- def send_error(msg)
68
- res = @http_auth.post("/agent/error?hostname=#{escape(config[:'hostname'])}", reason: msg.reason, body: msg.body)
75
+ def post_data(type, data)
76
+ t { "posting data (#{type}): #{data.inspect}" }
77
+
78
+ res = @http_auth.post("/agent/#{type}?hostname=#{escape(config[:'hostname'])}", data)
69
79
 
70
80
  # error already handled in Util::HTTP
71
81
  return unless res
72
82
 
73
83
  unless res.success?
74
- if (400..499).include? res.status
75
- warn "error wasn't sent successfully; status=%s", res.status
76
- end
77
-
78
- warn "could not fetch report session token; status=%s", res.status
79
- return
84
+ warn "#{type} wasn't sent successfully; status=%s", res.status
80
85
  end
81
86
  rescue Exception => e
82
87
  error "exception; msg=%s; class=%s", e.message, e.class
83
88
  t { e.backtrace.join("\n") }
84
89
  end
85
90
 
91
+ def send_error(msg)
92
+ details = msg.details ? JSON.parse(msg.details) : nil
93
+ post_data(:error, type: msg.type, description: msg.description, details: details)
94
+ end
95
+
86
96
  def finish
87
97
  t { fmt "collector finishing up" }
88
98
 
@@ -2,8 +2,6 @@ require 'socket'
2
2
 
3
3
  module Skylight
4
4
  module Worker
5
- # TODO:
6
- # - Shutdown if no connections for over a minute
7
5
  class Server
8
6
  LOCKFILE_PATH = 'SKYLIGHT_LOCKFILE_PATH'.freeze
9
7
  LOCKFILE_ENV_KEY = 'SKYLIGHT_LOCKFILE_FD'.freeze
@@ -18,7 +16,10 @@ module Skylight
18
16
  :config,
19
17
  :keepalive,
20
18
  :lockfile_path,
21
- :sockfile_path
19
+ :sockfile_path,
20
+ :status_interval,
21
+ :last_status_update,
22
+ :max_memory
22
23
 
23
24
  def initialize(config, lockfile, srv, lockfile_path)
24
25
  unless lockfile && srv
@@ -37,6 +38,8 @@ module Skylight
37
38
  @connections = {}
38
39
  @lockfile_path = lockfile_path
39
40
  @sockfile_path = @config[:'agent.sockfile_path']
41
+ @status_interval = 60
42
+ @max_memory = @config[:'agent.max_memory']
40
43
  end
41
44
 
42
45
  # Called from skylight.rb on require
@@ -132,6 +135,7 @@ module Skylight
132
135
  now = Time.now.to_i
133
136
  next_sanity_check_at = now + tick
134
137
  had_client_at = now
138
+ last_status_update = now
135
139
 
136
140
  trace "starting IO loop"
137
141
  begin
@@ -179,9 +183,16 @@ module Skylight
179
183
  if keepalive < now - had_client_at
180
184
  info "no clients for #{keepalive} sec - shutting down"
181
185
  @run = false
182
- elsif next_sanity_check_at <= now
183
- next_sanity_check_at = now + tick
184
- sanity_check
186
+ else
187
+ if next_sanity_check_at <= now
188
+ next_sanity_check_at = now + tick
189
+ sanity_check
190
+ end
191
+
192
+ if status_interval < now - last_status_update
193
+ last_status_update = now
194
+ status_check
195
+ end
185
196
  end
186
197
 
187
198
  rescue SignalException => e
@@ -193,9 +204,11 @@ module Skylight
193
204
  rescue Exception => e
194
205
  error "Loop exception: %s (%s)", e.message, e.class
195
206
  puts e.backtrace
207
+ @collector.send_exception(class_name: e.class.name, message: e.message, backtrace: e.backtrace)
196
208
  return false
197
209
  rescue Object => o
198
210
  error "Unknown object thrown: `%s`", o.to_s
211
+ @collector.send_exception(class_name: o.class.name)
199
212
  return false
200
213
  end while @run
201
214
 
@@ -301,6 +314,22 @@ module Skylight
301
314
  raise WorkerStateError, "sockfile gone"
302
315
  end
303
316
  end
317
+
318
+ def status_check
319
+ memory_usage = get_memory_usage
320
+
321
+ @collector.send_status(memory: memory_usage, max_memory: max_memory)
322
+
323
+ if memory_usage > max_memory
324
+ raise WorkerStateError, "Memory limit exceeded: #{memory_usage} (max: #{max_memory})"
325
+ end
326
+ end
327
+
328
+ def get_memory_usage
329
+ `ps -o rss= -p #{Process.pid}`.to_i / 1024
330
+ rescue Errno::ENOENT
331
+ 0
332
+ end
304
333
  end
305
334
  end
306
335
  end
@@ -19,7 +19,10 @@ module SqlLexer
19
19
  End = %Q<;|$>
20
20
 
21
21
  InOp = %q<IN>
22
- SpecialOps = %Q<#{InOp}(?=[#{WS}])>
22
+ ArrayOp = %q<ARRAY>
23
+ ColonColonOp = %Q<::(?=[#{StartID}])>
24
+ ArrayIndexOp = %q<\\[(?:\-?\d+(?::\-?\d+)?|NULL)\\]>
25
+ SpecialOps = %Q<#{InOp}(?=[#{WS}])|#{ColonColonOp}|#{ArrayOp}|#{ArrayIndexOp}>
23
26
 
24
27
  StartQuotedID = %Q<">
25
28
  StartTickedID = %Q<`>
@@ -35,13 +38,14 @@ module SqlLexer
35
38
  StartAnyId = %Q<"#{StartID}>
36
39
  Placeholder = %q<\$\p{Digit}+>
37
40
 
38
- AfterID = %Q<[#{WS};#{StartNonBind}]|(?:#{OpPart})|$>
41
+ AfterID = %Q<[#{WS};#{StartNonBind}]|(?:#{OpPart})|(?:#{ColonColonOp})|(?:#{ArrayIndexOp})|$>
39
42
  ID = %Q<[#{StartID}][#{PartID}]*(?=#{AfterID})>
40
43
  AfterOp = %Q<[#{WS}]|[#{StartAnyId}]|[#{StartBind}]|(#{StartNonBind})|$>
41
44
  Op = %Q<(?:#{OpPart})+(?=#{AfterOp})>
42
45
  QuotedID = %Q<#{StartQuotedID}(?:[^"]|"")*">
43
46
  TickedID = %Q<#{StartTickedID}(?:[^`]|``)*`>
44
47
  NonBind = %Q<#{ID}|#{Op}|#{QuotedID}|#{TickedID}|#{Placeholder}>
48
+ Type = %Q<[#{StartID}][#{PartID}]*(?:\\(\d+\\)|\\[\\])?(?=#{AfterID})>
45
49
  QuotedTable = %Q<#{TickedID}|#{QuotedID}>
46
50
 
47
51
  StringBody = %q<(?:''|\x5C'|[^'])*>
@@ -62,8 +66,12 @@ module SqlLexer
62
66
  TkWS = %r<[#{WS}]+>u
63
67
  TkOptWS = %r<[#{WS}]*>u
64
68
  TkOp = %r<[#{OpPart}]>u
69
+ TkComment = %r<^#{OptWS}--.*$>u
70
+ TkBlockCommentStart = %r</\*>u
71
+ TkBlockCommentEnd = %r<\*/>u
65
72
  TkPlaceholder = %r<#{Placeholder}>u
66
73
  TkNonBind = %r<#{NonBind}>u
74
+ TkType = %r<#{Type}>u
67
75
  TkQuotedTable = %r<#{QuotedTable}>iu
68
76
  TkUpdateTable = %r<UPDATE#{TableNext}>iu
69
77
  TkInsertTable = %r<INSERT[#{WS}]+INTO#{TableNext}>iu
@@ -73,6 +81,9 @@ module SqlLexer
73
81
  TkEnd = %r<;?[#{WS}]*>u
74
82
  TkBind = %r<#{String}|#{Number}|#{Literals}>u
75
83
  TkIn = %r<#{InOp}>iu
84
+ TkColonColon = %r<#{ColonColonOp}>u
85
+ TkArray = %r<#{ArrayOp}>iu
86
+ TkArrayIndex = %r<#{ArrayIndexOp}>iu
76
87
  TkSpecialOp = %r<#{SpecialOps}>iu
77
88
  TkStartSelect = %r<SELECT(?=(?:[#{WS}]|#{OpPart}))>iu
78
89
 
@@ -86,7 +97,8 @@ module SqlLexer
86
97
  table_name: :process_table_name,
87
98
  end: :process_end,
88
99
  special: :process_special,
89
- in: :process_in
100
+ in: :process_in,
101
+ array: :process_array
90
102
  }
91
103
 
92
104
  def self.bindify(string, binds=nil)
@@ -158,6 +170,8 @@ module SqlLexer
158
170
  UNKNOWN = "<unknown>".freeze
159
171
 
160
172
  def process(binds)
173
+ process_comments
174
+
161
175
  @operation = nil
162
176
  @provided_binds = binds
163
177
 
@@ -201,6 +215,64 @@ module SqlLexer
201
215
 
202
216
  EMPTY = "".freeze
203
217
 
218
+ def process_comments
219
+ # SQL treats comments as similar to whitespace
220
+ # Here we replace all comments with spaces of the same length so as to not affect binds
221
+
222
+ # Remove block comments
223
+ # SQL allows for nested comments so this takes a bit more work
224
+ while @scanner.skip_until(TkBlockCommentStart)
225
+ count = 1
226
+ pos = @scanner.pos - 2
227
+
228
+ while true
229
+ # Determine whether we close the comment or start nesting
230
+ next_open = @scanner.skip_until(TkBlockCommentStart)
231
+ @scanner.unscan if next_open
232
+ next_close = @scanner.skip_until(TkBlockCommentEnd)
233
+ @scanner.unscan if next_close
234
+
235
+ if next_open && next_open < next_close
236
+ # We're nesting
237
+ count += 1
238
+ @scanner.skip_until(TkBlockCommentStart)
239
+ else
240
+ # We're closing
241
+ count -= 1
242
+ @scanner.skip_until(TkBlockCommentEnd)
243
+ end
244
+
245
+ if count > 10_000
246
+ raise "The SQL '#{@scanner.string}' could not be parsed because of too many iterations in block comments"
247
+ end
248
+
249
+ if count == 0
250
+ # We've closed all comments
251
+ length = @scanner.pos - pos
252
+ # Dup the string if necessary so we aren't destructive to the original value
253
+ @scanner.string = @input.dup if @scanner.string == @input
254
+ # Replace the comment with spaces
255
+ @scanner.string[pos, length] = SPACE*length
256
+ break
257
+ end
258
+ end
259
+ end
260
+
261
+ @scanner.reset
262
+
263
+ # Remove single line comments
264
+ while @scanner.skip_until(TkComment)
265
+ pos = @scanner.pos
266
+ len = @scanner.matched_size
267
+ # Dup the string if necessary so we aren't destructive to the original value
268
+ @scanner.string = @input.dup if @scanner.string == @input
269
+ # Replace the comment with spaces
270
+ @scanner.string[pos-len, len] = SPACE*len
271
+ end
272
+
273
+ @scanner.reset
274
+ end
275
+
204
276
  def process_begin
205
277
  @scanner.skip(TkOptWS)
206
278
  @state = :first_token
@@ -281,6 +353,17 @@ module SqlLexer
281
353
  @scanner.skip(TkOptWS)
282
354
  @scanner.skip(/\(/u)
283
355
  @state = :in
356
+ elsif @scanner.skip(TkArray)
357
+ @scanner.skip(/\[/u)
358
+ @state = :array
359
+ elsif @scanner.skip(TkColonColon)
360
+ if @scanner.skip(TkType)
361
+ @state = :tokens
362
+ else
363
+ @state = :end
364
+ end
365
+ elsif @scanner.skip(TkArrayIndex)
366
+ @state = :tokens
284
367
  end
285
368
  end
286
369
 
@@ -315,7 +398,57 @@ module SqlLexer
315
398
  process_tokens
316
399
  end
317
400
 
318
- send STATE_HANDLERS[@state]
401
+ __send__ STATE_HANDLERS[@state]
402
+ end
403
+
404
+ @binds << pos
405
+ @binds << @scanner.pos - pos
406
+
407
+ @skip_binds = false
408
+
409
+ @state = :tokens
410
+ end
411
+
412
+ def process_array
413
+ nest = 1
414
+ iterations = 0
415
+
416
+ @skip_binds = true
417
+ pos = @scanner.pos - 6
418
+
419
+ while nest > 0
420
+ iterations += 1
421
+
422
+ if iterations > 10_000
423
+ raise "The SQL '#{@scanner.string}' could not be parsed because of too many iterations in ARRAY"
424
+ end
425
+
426
+ if @debug
427
+ p "array loop"
428
+ p @state
429
+ p @scanner
430
+ end
431
+
432
+ if @scanner.skip(/\[/u)
433
+ nest += 1
434
+ elsif @scanner.skip(/\]/u)
435
+ nest -= 1
436
+
437
+ break if nest.zero?
438
+
439
+ # End of final nested array
440
+ next if @scanner.skip(/#{TkOptWS}(?=\])/u)
441
+ end
442
+
443
+ # A NULL array
444
+ next if @scanner.skip(/NULL/iu)
445
+
446
+ # Another nested array
447
+ next if @scanner.skip(/#{TkOptWS},#{TkOptWS}(?=\[)/u)
448
+
449
+ process_tokens
450
+
451
+ __send__ STATE_HANDLERS[@state]
319
452
  end
320
453
 
321
454
  @binds << pos
@@ -344,13 +477,18 @@ module SqlLexer
344
477
  end
345
478
 
346
479
  def process_end
347
- @scanner.skip(TkEnd)
480
+ if @scanner.skip(TkEnd)
481
+ if @scanner.eos?
482
+ @state = nil
483
+ else
484
+ process_tokens
485
+ end
486
+ end
348
487
 
349
- unless @scanner.eos?
488
+ # We didn't hit EOS and we couldn't process any tokens
489
+ if @state == :end
350
490
  raise "The SQL '#{@scanner.string}' could not be parsed"
351
491
  end
352
-
353
- @state = nil
354
492
  end
355
493
 
356
494
  private
@@ -1,3 +1,3 @@
1
1
  module SqlLexer
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.3"
3
3
  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.2
4
+ version: 0.2.3
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-12-10 00:00:00.000000000 Z
11
+ date: 2013-12-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport