skylight 0.1.6.alpha1 → 0.1.6.alpha3

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: 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