skylight 0.1.6.alpha1 → 0.1.6.alpha3

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: 563ae386de883f09e3a0adf7a14042c38d2c7fd2
4
- data.tar.gz: 90420768b5b5dcbc2cfbb89e68b416f6e38baa4b
3
+ metadata.gz: bd3fcb8b023de4a803c0afc513b516b4cf427d21
4
+ data.tar.gz: 6df2ed3b9dececb6b58f3a7e8201137418345f61
5
5
  SHA512:
6
- metadata.gz: ad206b26816c8d5bac219bf4878bf7661de49850bfe785f2e181374bfd787c93fcd3b85898fbf78ef37f70891d6e4219e4e03bc3997314dace6dd1e28b1691b2
7
- data.tar.gz: a4e9254b1d9ec3f348453df808c9905a3fe7e0f68956820aca5d8f8edcc8b3fe1aa8c025fa77409e19314edffd0f2f486edceae49fd4c06fb9eeb2f7c0d8479c
6
+ metadata.gz: f94b8e72f33d0c3c3446d4b7d9ea3448bb5e33ccc7deb16da4837bbcca7e486b8d9a5c38f430de4af4c1b960cea879fadda5da2f3bc5dcd505e8a70882167a6f
7
+ data.tar.gz: 156b54dfa5b95ebdac49ec7e67642370ed2f9005d9c357894056b3758cd27a2af6eda8e3a0410cb664caf8599ae27b37e7b078df0e12234e89ec7498ec390cae
@@ -1,5 +1,10 @@
1
1
  ## unreleased ##
2
2
 
3
+ * [BUG] Fix unix domain socket write function in standalone agent
4
+ * Performance improvements
5
+ * Tolerate invalid trace building
6
+ * Fix Skylight on Rails 4
7
+
3
8
  ## 0.1.5 (May 31, 2013)
4
9
 
5
10
  * Provide a default CA cert when one is not already present
@@ -69,12 +69,20 @@ module Skylight
69
69
  end
70
70
 
71
71
  def self.trace(*args, &blk)
72
- return yield unless inst = Instrumenter.instance
72
+ unless inst = Instrumenter.instance
73
+ return yield if block_given?
74
+ return
75
+ end
76
+
73
77
  inst.trace(*args, &blk)
74
78
  end
75
79
 
76
80
  def self.instrument(*args, &blk)
77
- return yield unless inst = Instrumenter.instance
81
+ unless inst = Instrumenter.instance
82
+ return yield if block_given?
83
+ return
84
+ end
85
+
78
86
  inst.instrument(*args, &blk)
79
87
  end
80
88
 
@@ -15,6 +15,7 @@ module Skylight
15
15
  'SK_AGENT_KEEPALIVE' => :'agent.keepalive',
16
16
  'SK_AGENT_SAMPLE_SIZE' => :'agent.sample',
17
17
  'SK_AGENT_SOCKFILE_PATH' => :'agent.sockfile_path',
18
+ 'SK_AGENT_STRATEGY' => :'agent.strategy',
18
19
  'SK_REPORT_HOST' => :'report.host',
19
20
  'SK_REPORT_PORT' => :'report.port',
20
21
  'SK_REPORT_SSL' => :'report.ssl',
@@ -2,25 +2,13 @@ require 'thread'
2
2
 
3
3
  module Skylight
4
4
  class GC
5
- METHODS = [ :enable, :total_time ]
6
- TH_KEY = :SK_GC_CURR_WINDOW
5
+ METHODS = [ :enable, :total_time ]
6
+ TH_KEY = :SK_GC_CURR_WINDOW
7
+ MAX_COUNT = 1000
8
+ MAX_TIME = 30_000_000
7
9
 
8
10
  include Util::Logging
9
11
 
10
- def self.update
11
- if win = Thread.current[TH_KEY]
12
- win.update
13
- end
14
- end
15
-
16
- def self.time
17
- if win = Thread.current[TH_KEY]
18
- win.time
19
- else
20
- 0
21
- end
22
- end
23
-
24
12
  attr_reader :config
25
13
 
26
14
  def initialize(config, profiler)
@@ -41,9 +29,7 @@ module Skylight
41
29
  @profiler.enable if @profiler
42
30
  end
43
31
 
44
- def start_track
45
- return if Thread.current[TH_KEY]
46
-
32
+ def track
47
33
  unless @profiler
48
34
  win = Window.new(nil)
49
35
  else
@@ -52,29 +38,19 @@ module Skylight
52
38
  @lock.synchronize do
53
39
  __update
54
40
  @listeners << win
55
- end
56
- end
57
41
 
58
- Thread.current[TH_KEY] = win
59
- end
42
+ # Cleanup any listeners that might have leaked
43
+ until @listeners[0].time < MAX_TIME
44
+ @listeners.shift
45
+ end
60
46
 
61
- def stop_track
62
- if win = Thread.current[TH_KEY]
63
- Thread.current[TH_KEY] = nil
64
- win.release
47
+ if @listeners.length > MAX_COUNT
48
+ @listeners.shift
49
+ end
50
+ end
65
51
  end
66
- end
67
52
 
68
- def track
69
- return unless block_given?
70
-
71
- start_track
72
-
73
- begin
74
- yield
75
- ensure
76
- stop_track
77
- end
53
+ win
78
54
  end
79
55
 
80
56
  def release(win)
@@ -70,65 +70,64 @@ module Skylight
70
70
  @worker.shutdown
71
71
  end
72
72
 
73
- def trace(endpoint = 'Unknown')
73
+ def trace(endpoint, cat, *args)
74
74
  # If a trace is already in progress, continue with that one
75
75
  if trace = Instrumenter.current_trace
76
76
  t { "already tracing" }
77
- return yield(trace)
77
+ return yield(trace) if block_given?
78
+ return trace
78
79
  end
79
80
 
80
- trace = Messages::Trace::Builder.new(endpoint, Util::Clock.micros, @config)
81
-
82
81
  begin
82
+ trace = Messages::Trace::Builder.new(self, endpoint, Util::Clock.micros, cat, *args)
83
+ rescue Exception => e
84
+ error e.message
85
+ t { e.backtrace.join("\n") }
86
+ return
87
+ end
88
+
89
+ Instrumenter.current_trace = trace
90
+ return trace unless block_given?
83
91
 
84
- Instrumenter.current_trace = trace
92
+ begin
85
93
  yield trace
86
94
 
87
95
  ensure
88
96
  Instrumenter.current_trace = nil
89
-
90
- begin
91
- built = trace.build
92
-
93
- if built && built.valid?
94
- process(built)
95
- else
96
- if built && built.spans.empty?
97
- debug "trace invalid -- dropping; spans=0"
98
- elsif built
99
- debug "trace invalid -- dropping; spans=%d; started_at=%d",
100
- built.spans.length, built.spans[-1].started_at
101
- else
102
- debug "trace invalid -- dropping; trace=nil"
103
- end
104
- end
105
- rescue Exception => e
106
- error e
107
- end
97
+ trace.submit
108
98
  end
109
99
  end
110
100
 
111
101
  def instrument(cat, *args)
102
+ unless trace = Instrumenter.current_trace
103
+ return yield if block_given?
104
+ return
105
+ end
106
+
112
107
  cat = cat.to_s
113
108
 
114
109
  unless cat =~ CATEGORY_REGEX
115
110
  warn "invalid skylight instrumentation category; value=%s", cat
116
- return yield
111
+ return yield if block_given?
112
+ return
117
113
  end
118
114
 
119
115
  cat = "other.#{cat}" unless cat =~ TIER_REGEX
120
116
 
121
- return yield unless sp = @subscriber.instrument(cat, *args)
117
+ unless sp = trace.instrument(cat, *args)
118
+ return yield if block_given?
119
+ return
120
+ end
121
+
122
+ return sp unless block_given?
122
123
 
123
124
  begin
124
125
  yield sp
125
126
  ensure
126
- @subscriber.done
127
+ sp.done
127
128
  end
128
129
  end
129
130
 
130
- private
131
-
132
131
  def process(trace)
133
132
  t { fmt "processing trace; spans=%d; duration=%d",
134
133
  trace.spans.length, trace.spans[-1].duration }
@@ -19,6 +19,8 @@ module Skylight
19
19
 
20
20
  class Builder
21
21
 
22
+ include Util::Logging
23
+
22
24
  attr_reader \
23
25
  :time,
24
26
  :category,
@@ -40,10 +42,21 @@ module Skylight
40
42
  self.description = desc
41
43
  end
42
44
 
45
+ def config
46
+ @trace.config
47
+ end
48
+
43
49
  def endpoint=(name)
44
50
  @trace.endpoint = name
45
51
  end
46
52
 
53
+ def done
54
+ @trace.done(self) unless built?
55
+ rescue Exception => e
56
+ error e.message
57
+ t { e.backtrace.join("\n") }
58
+ end
59
+
47
60
  def built?
48
61
  @built
49
62
  end
@@ -8,160 +8,155 @@ module Skylight
8
8
  optional :endpoint, :string, 2
9
9
  repeated :spans, Span, 3
10
10
 
11
- def valid?
12
- return false unless spans && spans.length > 0
13
- spans[-1].started_at == 0
14
- end
15
-
16
11
  class Builder
12
+ GC_CAT = 'noise.gc'.freeze
13
+
17
14
  include Util::Logging
18
15
 
19
16
  attr_accessor :endpoint
20
- attr_reader :spans, :config
21
-
22
- def initialize(endpoint = "Unknown", start = Util::Clock.micros, config = nil)
23
- @endpoint = endpoint
24
- @busted = false
25
- @config = config
26
- @start = start
27
- @spans = []
28
- @stack = []
29
- @parents = []
30
-
31
- # Track time
32
- @last_seen_time = start
33
- end
17
+ attr_reader :spans, :notifications
34
18
 
35
- def root(cat, title = nil, desc = nil, annot = {})
36
- return unless block_given?
37
- return yield unless @stack == []
38
- return yield unless config
19
+ def initialize(instrumenter, endpoint, start, cat, *args)
20
+ raise ArgumentError, 'instrumenter is required' unless instrumenter
39
21
 
40
- gc = config.gc
41
- start(@start, cat, title, desc, annot)
22
+ @instrumenter = instrumenter
23
+ @endpoint = endpoint
24
+ @start = start
25
+ @spans = []
26
+ @stack = []
27
+ @submitted = false
42
28
 
43
- begin
44
- gc.start_track
29
+ # Tracks the AS::N stack
30
+ @notifications = []
45
31
 
46
- begin
47
- yield
48
- ensure
49
- unless @busted
50
- now = Util::Clock.micros
51
-
52
- GC.update
53
- gc_time = GC.time
32
+ # Track time
33
+ @last_seen_time = start
54
34
 
55
- if gc_time > 0
56
- t { fmt "tracking GC time; duration=%d", gc_time }
57
- start(now - gc_time, 'noise.gc')
58
- stop(now)
59
- end
35
+ annot = args.pop if Hash === args
36
+ title = args.shift
37
+ desc = args.shift
60
38
 
61
- stop(now)
62
- end
63
- end
64
- ensure
65
- gc.stop_track
66
- end
39
+ # Create the root node
40
+ @root = start(@start, cat, title, desc, annot)
41
+ @gc = config.gc.track
67
42
  end
68
43
 
69
- def record(time, cat, title = nil, desc = nil, annot = {})
70
- return if @busted
71
-
72
- time = adjust_for_skew(time)
73
-
74
- sp = span(time, cat, title, desc, annot)
44
+ def config
45
+ @instrumenter.config
46
+ end
75
47
 
76
- return if :skip == sp
48
+ def record(cat, *args)
49
+ annot = args.pop if Hash === args
50
+ title = args.shift
51
+ desc = args.shift
52
+ now = adjust_for_skew(Util::Clock.micros)
77
53
 
54
+ sp = span(now - gc_time, cat, title, desc, annot)
78
55
  inc_children
79
56
  @spans << sp.build(0)
80
57
 
81
58
  nil
82
59
  end
83
60
 
84
- def start(time, cat, title = nil, desc = nil, annot = {})
85
- return if @busted
86
-
87
- time = adjust_for_skew(time)
88
-
89
- sp = span(time, cat, title, desc, annot)
90
-
91
- push(sp)
61
+ def instrument(cat, *args)
62
+ annot = args.pop if Hash === args
63
+ title = args.shift
64
+ desc = args.shift
65
+ now = adjust_for_skew(Util::Clock.micros)
92
66
 
93
- return if :skip == sp
67
+ start(now - gc_time, cat, title, desc, annot)
68
+ end
94
69
 
95
- sp
70
+ def done(span)
71
+ return unless span
72
+ stop(span, adjust_for_skew(Util::Clock.micros) - gc_time)
96
73
  end
97
74
 
98
- def stop(time)
99
- return if @busted
75
+ def release
76
+ return unless Instrumenter.current_trace == self
77
+ Instrumenter.current_trace = nil
78
+ end
100
79
 
101
- time = adjust_for_skew(time)
80
+ def submit
81
+ return if @submitted
102
82
 
103
- sp = pop
83
+ release
84
+ @submitted = true
104
85
 
105
- return if :skip == sp
86
+ now = adjust_for_skew(Util::Clock.micros)
106
87
 
107
- @spans << sp.build(relativize(time) - sp.started_at)
88
+ # Pop everything that is left
89
+ while sp = pop
90
+ @spans << sp.build(relativize(now) - sp.started_at)
91
+ end
108
92
 
109
- nil
110
- end
93
+ time = gc_time
111
94
 
112
- def build
113
- return if @busted
114
- unless @stack.empty?
115
- remaining = @stack.map do |sp|
116
- sp == :skip ? :skip : sp.category
117
- end
95
+ if time > 0
96
+ t { fmt "tracking GC time; duration=%d", time }
97
+ noise = start(now - time, GC_CAT, nil, nil, {})
98
+ stop(noise, now)
99
+ end
118
100
 
119
- raise TraceError, "trace unbalanced; remaining=#{remaining.inspect}"
101
+ if sp = @stack.pop
102
+ @spans << sp.build(relativize(now) - sp.started_at)
120
103
  end
121
104
 
122
- Trace.new(
105
+ t = Trace.new(
123
106
  uuid: 'TODO',
124
107
  endpoint: endpoint,
125
108
  spans: spans)
109
+
110
+ @instrumenter.process(t)
111
+ rescue Exception => e
112
+ error e
113
+ t { e.backtrace.join("\n") }
126
114
  end
127
115
 
128
116
  private
129
117
 
130
- def span(time, cat, title, desc, annot)
131
- return cat if :skip == cat
118
+ def start(time, cat, title, desc, annot)
119
+ sp = span(time, cat, title, desc, annot)
132
120
 
133
- Span::Builder.new(
134
- self, time, relativize(time),
135
- cat, title, desc, annot)
136
- end
121
+ push(sp)
137
122
 
138
- def push(sp)
139
- @stack << sp
123
+ sp
124
+ end
140
125
 
141
- unless :skip == sp
142
- inc_children
143
- @parents << sp
126
+ def stop(span, time)
127
+ until span == (sp = pop)
128
+ return unless sp
129
+ @spans << sp.build(relativize(time) - sp.started_at)
144
130
  end
131
+
132
+ @spans << span.build(relativize(time) - sp.started_at)
133
+
134
+ nil
145
135
  end
146
136
 
147
- def pop
148
- unless sp = @stack.pop
149
- @busted = true
150
- raise TraceError, "closing span -- trace unbalanced"
151
- end
137
+ def span(time, cat, title, desc, annot)
138
+ Span::Builder.new(
139
+ self, time, relativize(time),
140
+ cat, title, desc, annot)
141
+ end
152
142
 
153
- @parents.pop if :skip != sp
143
+ def pop
144
+ return unless @stack.length > 1
145
+ @stack.pop
146
+ end
154
147
 
155
- sp
148
+ def push(sp)
149
+ inc_children
150
+ @stack << sp
156
151
  end
157
152
 
158
153
  def inc_children
159
- return unless sp = @parents.last
154
+ return unless sp = @stack[-1]
160
155
  sp.children += 1
161
156
  end
162
157
 
163
158
  def relativize(time)
164
- if parent = @parents[-1]
159
+ if parent = @stack[-1]
165
160
  ((time - parent.time) / 100).to_i
166
161
  else
167
162
  ((time - @start) / 100).to_i
@@ -179,8 +174,13 @@ module Skylight
179
174
  time
180
175
  end
181
176
 
182
- end
177
+ def gc_time
178
+ return 0 unless @gc
179
+ @gc.update
180
+ @gc.time
181
+ end
183
182
 
183
+ end
184
184
  end
185
185
  end
186
186
  end
@@ -1,15 +1,59 @@
1
1
  module Skylight
2
2
  class Middleware
3
3
 
4
+ class BodyProxy
5
+ def initialize(body, &block)
6
+ @body, @block, @closed = body, block, false
7
+ end
8
+
9
+ def respond_to?(*args)
10
+ return false if args.first.to_s =~ /^to_ary$/
11
+ super or @body.respond_to?(*args)
12
+ end
13
+
14
+ def close
15
+ return if @closed
16
+ @closed = true
17
+ begin
18
+ @body.close if @body.respond_to? :close
19
+ ensure
20
+ @block.call
21
+ end
22
+ end
23
+
24
+ def closed?
25
+ @closed
26
+ end
27
+
28
+ # N.B. This method is a special case to address the bug described by #434.
29
+ # We are applying this special case for #each only. Future bugs of this
30
+ # class will be handled by requesting users to patch their ruby
31
+ # implementation, to save adding too many methods in this class.
32
+ def each(*args, &block)
33
+ @body.each(*args, &block)
34
+ end
35
+
36
+ def method_missing(*args, &block)
37
+ super if args.first.to_s =~ /^to_ary$/
38
+ @body.__send__(*args, &block)
39
+ end
40
+ end
41
+
4
42
  def initialize(app)
5
43
  @app = app
6
44
  end
7
45
 
8
46
  def call(env)
9
- Skylight.trace "Rack" do |trace|
10
- trace.root 'app.rack.request' do
11
- @app.call(env)
12
- end
47
+ begin
48
+ trace = Skylight.trace "Rack", 'app.rack.request'
49
+ resp = @app.call(env)
50
+ resp[2] = BodyProxy.new(resp[2]) { trace.submit } if trace
51
+ resp
52
+ rescue Exception
53
+ trace.submit if trace
54
+ raise
55
+ ensure
56
+ trace.release if trace
13
57
  end
14
58
  end
15
59
  end
@@ -17,7 +17,7 @@ module Skylight
17
17
  Instrumenter.start!(config)
18
18
  app.middleware.insert 0, Middleware
19
19
 
20
- Rails.logger.info "[SKYLIGHT] Skylight agent enabled"
20
+ puts "[SKYLIGHT] Skylight agent enabled"
21
21
  end
22
22
  end
23
23
  end
@@ -29,7 +29,7 @@ module Skylight
29
29
  path = nil unless File.exist?(path)
30
30
 
31
31
  unless tmp = app.config.paths['tmp'].first
32
- Rails.logger.warn "[SKYLIGHT] tmp directory missing from rails configuration"
32
+ puts "[SKYLIGHT] tmp directory missing from rails configuration"
33
33
  return nil
34
34
  end
35
35
 
@@ -41,7 +41,7 @@ module Skylight
41
41
  config
42
42
 
43
43
  rescue ConfigError => e
44
- Rails.logger.warn "[SKYLIGHT] #{e.message}; disabling Skylight agent"
44
+ puts "[SKYLIGHT] #{e.message}; disabling Skylight agent"
45
45
  nil
46
46
  end
47
47
 
@@ -20,43 +20,49 @@ module Skylight
20
20
  @subscriber = nil
21
21
  end
22
22
 
23
- def instrument(category, *args)
24
- return unless trace = Instrumenter.current_trace
25
-
26
- annot = args.pop if Hash === args
27
- title = args.shift
28
- desc = args.shift
29
-
30
- trace.start(now - gc_time, category, title, desc, annot)
31
- end
32
-
33
- def done
34
- return unless trace = Instrumenter.current_trace
35
- trace.stop(now - gc_time)
36
- end
37
-
38
23
  #
39
24
  #
40
25
  # ===== ActiveSupport::Notifications API
41
26
  #
42
27
  #
43
28
 
29
+ class Notification
30
+ attr_reader :name, :span
31
+
32
+ def initialize(name, span)
33
+ @name, @span = name, span
34
+ end
35
+ end
36
+
44
37
  def start(name, id, payload)
45
38
  return unless trace = Instrumenter.current_trace
46
39
 
47
40
  cat, title, desc, annot = normalize(trace, name, payload)
48
- trace.start(now - gc_time, cat, title, desc, annot)
49
41
 
50
- trace
42
+ unless cat == :skip
43
+ span = trace.instrument(cat, title, desc, annot)
44
+ end
45
+
46
+ trace.notifications << Notification.new(name, span)
47
+
51
48
  rescue Exception => e
52
49
  error "Subscriber#start error; msg=%s", e.message
50
+ nil
53
51
  end
54
52
 
55
53
  def finish(name, id, payload)
56
54
  return unless trace = Instrumenter.current_trace
57
- trace.stop(now - gc_time)
55
+
56
+ while curr = trace.notifications.pop
57
+ if curr.name == name
58
+ curr.span.done if curr.span
59
+ return
60
+ end
61
+ end
62
+
58
63
  rescue Exception => e
59
64
  error "Subscriber#finish error; msg=%s", e.message
65
+ nil
60
66
  end
61
67
 
62
68
  def publish(name, *args)
@@ -69,14 +75,5 @@ module Skylight
69
75
  @normalizers.normalize(*args)
70
76
  end
71
77
 
72
- def gc_time
73
- GC.update
74
- GC.time
75
- end
76
-
77
- def now
78
- Util::Clock.micros
79
- end
80
-
81
78
  end
82
79
  end
@@ -3,7 +3,7 @@ require 'thread'
3
3
  module Skylight
4
4
  module Util
5
5
  # Simple thread-safe queue backed by a ring buffer. Will only block when
6
- # poping.
6
+ # poping. Single consumer only
7
7
  class Queue
8
8
 
9
9
  def initialize(max)
@@ -15,7 +15,7 @@ module Skylight
15
15
  @values = [nil] * max
16
16
  @consume = 0
17
17
  @produce = 0
18
- @waiting = []
18
+ @waiting = nil
19
19
  @mutex = Mutex.new
20
20
  end
21
21
 
@@ -39,11 +39,8 @@ module Skylight
39
39
  ret = __length
40
40
 
41
41
  # Wakeup a blocked thread
42
- begin
43
- t = @waiting.shift
44
- t.wakeup if t
45
- rescue ThreadError
46
- retry
42
+ if t = @waiting
43
+ t.run rescue nil
47
44
  end
48
45
  end
49
46
 
@@ -58,11 +55,13 @@ module Skylight
58
55
  @mutex.synchronize do
59
56
  if __empty?
60
57
  if !timeout || timeout > 0
61
- t = Thread.current
62
- @waiting << t
63
- @mutex.sleep(timeout)
64
- # Ensure that the thread is not in the waiting list
65
- @waiting.delete(t)
58
+ return if @waiting
59
+ @waiting = Thread.current
60
+ begin
61
+ @mutex.sleep(timeout)
62
+ ensure
63
+ @waiting = nil
64
+ end
66
65
  else
67
66
  return
68
67
  end
@@ -34,6 +34,13 @@ module Skylight
34
34
 
35
35
 
36
36
  module Dsl
37
+ def self.extended(base)
38
+ initializer = Module.new
39
+ base.instance_variable_set(:@__initializer, initializer)
40
+ base.__write_initializer
41
+ base.send(:include, initializer)
42
+ end
43
+
37
44
  def required(name, type, fn, opts={})
38
45
  field(:required, name, type, fn, opts)
39
46
  end
@@ -48,12 +55,27 @@ module Skylight
48
55
 
49
56
  def field(rule, name, type, fn, opts)
50
57
  fields[fn] = Field.new(rule, name, type, fn, opts)
58
+ __write_initializer
51
59
  attr_accessor name
52
60
  end
53
61
 
54
62
  def fields
55
63
  @fields ||= {}
56
64
  end
65
+
66
+ def __write_initializer
67
+ lines = []
68
+
69
+ lines << "def initialize(attrs=nil)"
70
+ lines << "return unless attrs"
71
+ fields.values.each do |fld|
72
+ lines << "@#{fld.name} = attrs[:#{fld.name}]"
73
+ end
74
+
75
+ lines << "end"
76
+
77
+ @__initializer.module_eval(lines.join("\n"), __FILE__, __LINE__+1)
78
+ end
57
79
  end
58
80
 
59
81
  module Encode
@@ -133,7 +155,8 @@ module Skylight
133
155
 
134
156
 
135
157
  module Decode
136
- def decode(buf, o=self.new)
158
+ # Use allocate instead of new for performance
159
+ def decode(buf, o=self.allocate)
137
160
  if ! buf.is_a?(Buffer)
138
161
  buf = Buffer.new(buf)
139
162
  end
@@ -191,12 +214,6 @@ module Skylight
191
214
  o.send(:include, Encode)
192
215
  end
193
216
 
194
- def initialize(attrs={})
195
- fields.values.each do |fld|
196
- self[fld.name] = attrs[fld.name]
197
- end
198
- end
199
-
200
217
  def fields
201
218
  self.class.fields
202
219
  end
@@ -34,8 +34,7 @@ module Skylight
34
34
 
35
35
  def self.encodable?(type)
36
36
  return false if ! type.is_a?(Class)
37
- pims = type.public_instance_methods
38
- pims.include?(:encode) || pims.include?("encode")
37
+ type < Message
39
38
  end
40
39
 
41
40
  attr_accessor :buf
@@ -1,4 +1,4 @@
1
1
  module Skylight
2
- VERSION = '0.1.6.alpha1'
2
+ VERSION = '0.1.6.alpha3'
3
3
  end
4
4
 
@@ -18,6 +18,7 @@ module Skylight
18
18
  @size = config[:'agent.sample']
19
19
  @batch = nil
20
20
  @interval = config[:'agent.interval']
21
+ @buf = ""
21
22
 
22
23
  t { fmt "starting collector; interval=%d; size=%d", @interval, @size }
23
24
  end
@@ -53,8 +54,10 @@ module Skylight
53
54
  def flush(batch)
54
55
  return if batch.empty?
55
56
 
56
- trace "flushing batch; size=%d", batch.sample.count
57
- @http.post(ENDPOINT, batch.encode, CONTENT_TYPE => SKYLIGHT_V1)
57
+ debug "flushing batch; size=%d", batch.sample.count
58
+
59
+ @buf.clear
60
+ @http.post(ENDPOINT, batch.encode(@buf), CONTENT_TYPE => SKYLIGHT_V1)
58
61
  end
59
62
 
60
63
  def new_batch(now)
@@ -93,7 +96,7 @@ module Skylight
93
96
  @sample << trace
94
97
  end
95
98
 
96
- def encode
99
+ def encode(buf)
97
100
  endpoints = {}
98
101
 
99
102
  sample.each do |trace|
@@ -115,7 +118,9 @@ module Skylight
115
118
  Messages::Batch.new(
116
119
  timestamp: from,
117
120
  endpoints: endpoints.values).
118
- encode.to_s
121
+ encode(buf)
122
+
123
+ buf
119
124
  end
120
125
  end
121
126
 
@@ -2,6 +2,7 @@ require 'socket'
2
2
  require 'thread'
3
3
  require 'fileutils'
4
4
 
5
+ # TODO: Handle cool-off
5
6
  module Skylight
6
7
  module Worker
7
8
  # Handle to the agent subprocess. Manages creation, communication, and
@@ -146,17 +147,17 @@ module Skylight
146
147
  # is still healthy but is simply reloading itself, this should work
147
148
  # just fine.
148
149
  if sock = connect(@pid)
149
- trace "reconnected to worker"
150
+ t { "reconnected to worker" }
150
151
  @sock = sock
151
152
  # TODO: Should HELLO be sent again?
152
153
  return true
153
154
  end
154
155
 
155
- t { "failed to reconnect -- attempting worker respawn" }
156
+ debug "failed to reconnect -- attempting worker respawn"
156
157
 
157
158
  # Attempt to respawn the agent process
158
159
  unless __spawn
159
- t { "could not respawn -- shutting down" }
160
+ debug "could not respawn -- shutting down"
160
161
 
161
162
  @pid = nil
162
163
  @sock = nil
@@ -205,10 +206,13 @@ module Skylight
205
206
  @sock = nil
206
207
  sock.close rescue nil
207
208
 
208
- # TODO: Respawn the agent
209
- repair
209
+ unless repair
210
+ return false
211
+ end
210
212
  end
211
213
 
214
+ debug "could not handle message; msg=%s", msg.class
215
+
212
216
  false
213
217
  end
214
218
 
@@ -231,23 +235,39 @@ module Skylight
231
235
  end
232
236
  end
233
237
 
234
- def write(sock, msg, timeout = 0.01)
238
+ def write(sock, msg, timeout = 5)
235
239
  msg = msg.to_s
236
- cnt = 50
237
-
238
- while 0 <= (cnt -= 1)
239
- ret = sock.syswrite msg rescue nil
240
-
241
- return true unless ret
242
-
243
- if ret == msg.bytesize
244
- return true
245
- elsif ret > 0
246
- msg = msg.byteslice(ret..-1)
240
+ cnt = 10
241
+
242
+ begin
243
+ while true
244
+ res = sock.write_nonblock(msg)
245
+
246
+ if res == msg.bytesize
247
+ return true
248
+ elsif res > 0
249
+ msg = msg.byteslice(res..-1)
250
+ cnt = 10
251
+ else
252
+ if 0 <= (cnt -= 1)
253
+ t { "write failed -- max attempts" }
254
+ return false
255
+ end
256
+ end
257
+ end
258
+ rescue Errno::EAGAIN, Errno::EWOULDBLOCK
259
+ _, socks, = IO.select([], [sock], [], timeout)
260
+ unless socks == [sock]
261
+ t { "write timed out" }
262
+ return false
247
263
  end
264
+ retry
265
+ rescue Errno::EINTR
266
+ raise
267
+ rescue SystemCallError => e
268
+ t { fmt "write failed; err=%s", e.class }
269
+ return false
248
270
  end
249
-
250
- return false
251
271
  end
252
272
 
253
273
  # Spawn the worker process.
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.1.6.alpha1
4
+ version: 0.1.6.alpha3
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-05-31 00:00:00.000000000 Z
11
+ date: 2013-06-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -24,20 +24,6 @@ dependencies:
24
24
  - - '>='
25
25
  - !ruby/object:Gem::Version
26
26
  version: 3.0.0
27
- - !ruby/object:Gem::Dependency
28
- name: actionpack
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - '>='
32
- - !ruby/object:Gem::Version
33
- version: 3.0.0
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - '>='
39
- - !ruby/object:Gem::Version
40
- version: 3.0.0
41
27
  description: Currently in pre-alpha.
42
28
  email:
43
29
  - engineering@tilde.io