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 +4 -4
- data/bin/skylight +15 -2
- data/lib/skylight.rb +18 -9
- data/lib/skylight/instrumenter.rb +25 -9
- data/lib/skylight/messages/trace.rb +34 -13
- data/lib/skylight/normalizers.rb +34 -5
- data/lib/skylight/normalizers/process_action.rb +6 -2
- data/lib/skylight/normalizers/render_collection.rb +3 -1
- data/lib/skylight/normalizers/render_partial.rb +3 -1
- data/lib/skylight/normalizers/render_template.rb +3 -1
- data/lib/skylight/normalizers/send_file.rb +24 -5
- data/lib/skylight/normalizers/sql.rb +8 -9
- data/lib/skylight/util/allocation_free.rb +15 -0
- data/lib/skylight/vendor/beefcake.rb +44 -25
- data/lib/skylight/vendor/beefcake/buffer.rb +4 -2
- data/lib/skylight/vendor/beefcake/encode.rb +20 -3
- data/lib/skylight/version.rb +1 -1
- data/lib/skylight/worker/collector.rb +6 -0
- data/lib/sql_lexer/lexer.rb +159 -56
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6c34ce7c2bd1892ae5f05d4e9cc0ee5054cbb9f1
|
4
|
+
data.tar.gz: ce556c192f1f5b1d72be7680d358d8373a80cfe7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a9b4be609c1d76a2075cbb9ad116d724fc35be01dfe133e00978c3b09a8e32765a78d2db0ccb2f3c43a60182891a57e42ee8bcecd53ec8d50750a6fc2f38f138
|
7
|
+
data.tar.gz: 082e9dd9bfc81ea18b53b830c52bf26796b90943c6af65b78723b21a8d1207478f87ac835a8386c4f0741a4fb97d9d89200ed2746b8fdadb8d9945b869c1a975
|
data/bin/skylight
CHANGED
@@ -1,3 +1,16 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
|
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')
|
data/lib/skylight.rb
CHANGED
@@ -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_-]+)*$/
|
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(
|
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
|
-
|
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 =
|
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)
|
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
|
-
|
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
|
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
|
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] =
|
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,
|
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,
|
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
|
-
|
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
|
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
|
154
|
+
cat = "other.#{cat}" unless match?(cat, TIER_REGEX)
|
144
155
|
|
145
|
-
unless sp = trace.instrument(cat,
|
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
|
178
|
+
return TOO_MANY_UNIQUES
|
167
179
|
end
|
168
180
|
|
169
|
-
set
|
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
|
-
|
17
|
-
attr_reader :spans, :notifications
|
16
|
+
attr_reader :endpoint, :spans, :notifications
|
18
17
|
|
19
|
-
def
|
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
|
-
|
36
|
-
|
37
|
-
|
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,
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
-
|
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
|
data/lib/skylight/normalizers.rb
CHANGED
@@ -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
|
60
|
+
return path if relative_path?(path)
|
58
61
|
|
59
|
-
root = @paths
|
62
|
+
root = array_find(@paths) { |p| path.start_with?(p) }
|
60
63
|
|
61
64
|
if root
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
-
[
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
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 =
|
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
|
-
[
|
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] ||
|
37
|
-
type =
|
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] ||
|
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 =
|
16
|
+
name = CAT
|
15
17
|
title = payload[:name] || "SQL"
|
16
18
|
end
|
17
19
|
|
18
|
-
|
19
|
-
|
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]] ]
|
@@ -54,13 +54,20 @@ module Skylight
|
|
54
54
|
end
|
55
55
|
|
56
56
|
def field(rule, name, type, fn, opts)
|
57
|
-
|
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.
|
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.
|
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
|
-
|
125
|
-
|
126
|
-
buf
|
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.
|
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 =
|
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.
|
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.
|
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.
|
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.
|
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(
|
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__(
|
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
|
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
|
-
|
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
|
data/lib/skylight/version.rb
CHANGED
@@ -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
|
data/lib/sql_lexer/lexer.rb
CHANGED
@@ -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}>
|
68
|
-
TkUpdateTable = %r<UPDATE#{TableNext}>
|
69
|
-
TkInsertTable = %r<INSERT[#{WS}]+INTO#{TableNext}>
|
70
|
-
TkDeleteTable = %r<DELETE[#{WS}]+FROM#{TableNext}>
|
71
|
-
TkFromTable = %r<FROM#{TableNext}>
|
72
|
-
TkID = %r<#{ID}>
|
73
|
-
TkEnd = %r<;?[#{WS}]*>
|
74
|
-
TkBind = %r<#{String}|#{Number}|#{Literals}>
|
75
|
-
TkIn = %r<#{InOp}>
|
76
|
-
TkSpecialOp = %r<#{SpecialOps}>
|
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
|
-
|
92
|
-
|
93
|
-
|
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
|
100
|
-
|
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
|
-
@
|
103
|
-
@
|
104
|
-
@
|
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
|
-
|
108
|
-
|
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
|
165
|
+
if @debug
|
112
166
|
p @state
|
113
167
|
p @scanner
|
114
168
|
end
|
115
169
|
|
116
|
-
|
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 &&
|
124
|
-
|
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
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
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.
|
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(
|
146
|
-
@operation =
|
215
|
+
if @scanner.skip(TkStartSelect)
|
216
|
+
@operation = OP_SELECT_FROM
|
147
217
|
@state = :tokens
|
148
|
-
|
149
|
-
|
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
|
-
|
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
|
235
|
+
copy_substr(@input, @table, pos + 1, @scanner.pos - 1)
|
167
236
|
elsif @scanner.skip(TkID)
|
168
|
-
@table
|
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 ==
|
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
|
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.
|
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-
|
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.
|
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.
|