instrumental_agent 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -20,8 +20,8 @@ module Instrumental
20
20
  @logger = l
21
21
  end
22
22
 
23
- def self.logger
24
- @logger ||= Logger.new('/dev/null')
23
+ def self.logger(force = false)
24
+ @logger ||= Logger.new(File.open('/dev/null', 'a')) # append mode so it's forksafe
25
25
  end
26
26
 
27
27
  def self.all
@@ -52,11 +52,14 @@ module Instrumental
52
52
  @port = (collector[1] || 8000).to_i
53
53
  @enabled = options[:enabled]
54
54
  @test_mode = options[:test_mode]
55
+ @pid = Process.pid
56
+
55
57
 
56
58
  if @enabled
57
59
  @failures = 0
58
60
  @queue = Queue.new
59
- start_connection_thread
61
+ start_connection_worker
62
+ setup_cleanup_at_exit
60
63
  end
61
64
  end
62
65
 
@@ -95,7 +98,7 @@ module Instrumental
95
98
  end
96
99
 
97
100
  def connected?
98
- connection && connection.connected
101
+ @socket && !@socket.closed?
99
102
  end
100
103
 
101
104
  def logger
@@ -125,6 +128,14 @@ module Instrumental
125
128
 
126
129
  def send_command(cmd, *args)
127
130
  if enabled?
131
+ if @pid != Process.pid
132
+ logger.info "Detected fork"
133
+ @pid = Process.pid
134
+ @socket = nil
135
+ @queue = Queue.new
136
+ start_connection_worker
137
+ end
138
+
128
139
  cmd = "%s %s\n" % [cmd, args.collect(&:to_s).join(" ")]
129
140
  if @queue.size < MAX_BUFFER
130
141
  logger.debug "Queueing: #{cmd.chomp}"
@@ -137,7 +148,7 @@ module Instrumental
137
148
  end
138
149
  end
139
150
 
140
- def test_server_connection
151
+ def test_connection
141
152
  # FIXME: Test connection state hack
142
153
  begin
143
154
  @socket.read_nonblock(1) # TODO: put data back?
@@ -146,43 +157,57 @@ module Instrumental
146
157
  end
147
158
  end
148
159
 
149
- def start_connection_thread
150
- logger.info "Starting thread"
151
- @thread = Thread.new do
152
- begin
153
- @socket = TCPSocket.new(host, port)
154
- @failures = 0
155
- logger.info "connected to collector"
156
- @socket.puts "hello version #{Instrumental::VERSION} test_mode #{@test_mode}"
157
- @socket.puts "authenticate #{@api_key}"
160
+ def start_connection_worker
161
+ if enabled?
162
+ disconnect
163
+ logger.info "Starting thread"
164
+ @thread = Thread.new do
158
165
  loop do
159
- command_and_args = @queue.pop
160
- begin
161
- test_server_connection
162
- rescue Exception => err
163
- @queue << command_and_args # connection dead, requeue
164
- raise err
165
- end
166
-
167
- if command_and_args == 'exit'
168
- logger.info "exiting, #{@queue.size} commands remain"
169
- @socket.flush
170
- Thread.exit
171
- else
172
- logger.debug "Sending: #{command_and_args.chomp}"
173
- @socket.puts command_and_args
174
- end
166
+ break if connection_worker
175
167
  end
176
- rescue Exception => err
177
- logger.error err.to_s
178
- # FIXME: not always a disconnect
179
- @failures += 1
180
- delay = [(@failures - 1) ** BACKOFF, MAX_RECONNECT_DELAY].min
181
- logger.info "disconnected, reconnect in #{delay}..."
182
- sleep delay
183
- retry
184
168
  end
185
169
  end
170
+ end
171
+
172
+ def connection_worker
173
+ command_and_args = nil
174
+ logger.info "connecting to collector"
175
+ @socket = TCPSocket.new(host, port)
176
+ @failures = 0
177
+ logger.info "connected to collector at #{host}:#{port}"
178
+ @socket.puts "hello version #{Instrumental::VERSION} test_mode #{@test_mode}"
179
+ @socket.puts "authenticate #{@api_key}"
180
+ loop do
181
+ command_and_args = @queue.pop
182
+ test_connection
183
+
184
+ case command_and_args
185
+ when 'exit'
186
+ logger.info "exiting, #{@queue.size} commands remain"
187
+ return true
188
+ else
189
+ logger.debug "Sending: #{command_and_args.chomp}"
190
+ @socket.puts command_and_args
191
+ command_and_args = nil
192
+ end
193
+ end
194
+ rescue Exception => err
195
+ logger.error err.to_s
196
+ if command_and_args
197
+ logger.debug "requeueing: #{command_and_args}"
198
+ @queue << command_and_args
199
+ end
200
+ disconnect
201
+ @failures += 1
202
+ delay = [(@failures - 1) ** BACKOFF, MAX_RECONNECT_DELAY].min
203
+ logger.info "disconnected, reconnect in #{delay}..."
204
+ sleep delay
205
+ retry
206
+ ensure
207
+ disconnect
208
+ end
209
+
210
+ def setup_cleanup_at_exit
186
211
  at_exit do
187
212
  if !@queue.empty? && @thread.alive?
188
213
  if @failures > 0
@@ -196,6 +221,16 @@ module Instrumental
196
221
  end
197
222
  end
198
223
  end
224
+
225
+ def disconnect
226
+ if connected?
227
+ logger.info "Disconnecting..."
228
+ @socket.flush
229
+ @socket.close
230
+ end
231
+ @socket = nil
232
+ end
233
+
199
234
  end
200
235
 
201
236
  end
@@ -1,3 +1,3 @@
1
1
  module Instrumental
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
data/spec/agent_spec.rb CHANGED
@@ -19,7 +19,7 @@ describe Instrumental::Agent, "disabled" do
19
19
  @server.connect_count.should == 0
20
20
  end
21
21
 
22
- it "should not connect to the server" do
22
+ it "should not connect to the server after receiving a metric" do
23
23
  wait
24
24
  @agent.gauge('disabled_test', 1)
25
25
  wait
@@ -149,6 +149,21 @@ describe Instrumental::Agent, "enabled" do
149
149
  @server.commands.last.should == "increment reconnect_test 1 1234"
150
150
  end
151
151
 
152
+ it "should automatically reconnect when forked" do
153
+ wait
154
+ @agent.increment('fork_reconnect_test', 1, 2)
155
+ fork do
156
+ @agent.increment('fork_reconnect_test', 1, 3) # triggers reconnect
157
+ end
158
+ wait
159
+ @agent.increment('fork_reconnect_test', 1, 4) # triggers reconnect
160
+ wait
161
+ @server.connect_count.should == 2
162
+ @server.commands.should include("increment fork_reconnect_test 1 2")
163
+ @server.commands.should include("increment fork_reconnect_test 1 3")
164
+ @server.commands.should include("increment fork_reconnect_test 1 4")
165
+ end
166
+
152
167
  it "should never let an exception reach the user" do
153
168
  @agent.stub!(:send_command).and_raise(Exception.new("Test Exception"))
154
169
  @agent.increment('throws_exception', 2).should be_nil
data/spec/test_server.rb CHANGED
@@ -5,6 +5,7 @@ class TestServer
5
5
  @connect_count = 0
6
6
  @connections = []
7
7
  @commands = []
8
+ @host = 'localhost'
8
9
  listen
9
10
  end
10
11
 
@@ -14,18 +15,22 @@ class TestServer
14
15
  Thread.new do
15
16
  begin
16
17
  # puts "listening"
17
- socket = @server.accept
18
- @connect_count += 1
19
- @connections << socket
20
- # puts "connection received"
21
18
  loop do
22
- command = socket.gets.strip
23
- # puts "got: #{command}"
24
- commands << command
19
+ socket = @server.accept
20
+ Thread.new do
21
+ @connect_count += 1
22
+ @connections << socket
23
+ # puts "connection received"
24
+ loop do
25
+ command = socket.gets.strip
26
+ # puts "got: #{command}"
27
+ commands << command
28
+ end
29
+ end
25
30
  end
26
31
  rescue Exception => err
27
32
  unless @stopping
28
- # puts "EXCEPTION:", err unless @stopping
33
+ puts "EXCEPTION:", err unless @stopping
29
34
  retry
30
35
  end
31
36
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: instrumental_agent
3
3
  version: !ruby/object:Gem::Version
4
- hash: 19
4
+ hash: 15
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 3
8
+ - 4
9
9
  - 0
10
- version: 0.3.0
10
+ version: 0.4.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Elijah Miller
@@ -17,7 +17,7 @@ autorequire:
17
17
  bindir: bin
18
18
  cert_chain: []
19
19
 
20
- date: 2011-11-17 00:00:00 -05:00
20
+ date: 2011-12-01 00:00:00 -05:00
21
21
  default_executable:
22
22
  dependencies:
23
23
  - !ruby/object:Gem::Dependency