Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

move reporting of info into Reactor, get rid of Client#setup and have…

… Client#report_info take a hash
  • Loading branch information...
commit 51286d8e935abb3842279688c0465f5ca40d8763 1 parent 9a3bd41
@qrush qrush authored
View
3  Gemfile
@@ -1,13 +1,14 @@
source "http://rubygems.org"
gem "em-http-request", "0.3.0"
+gem "em-spec", :path => "../em-spec"
+gem "em-hiredis", :path => "../em-hiredis"
gem "daemons", "1.1.0"
gem "json_pure", "1.4.6"
gem "redis", "2.1.1"
group :development do
gem "bourne"
- gem "cucumber"
gem "jeweler"
gem "rspec"
gem "timecop"
View
27 Gemfile.lock
@@ -1,17 +1,21 @@
+PATH
+ remote: ../em-hiredis
+ specs:
+ em-hiredis (0.0.1)
+ hiredis (~> 0.3.0)
+
+PATH
+ remote: ../em-spec
+ specs:
+ em-spec (0.2.2)
+
GEM
remote: http://rubygems.org/
specs:
addressable (2.2.4)
bourne (1.0)
mocha (= 0.9.8)
- builder (2.1.2)
crack (0.1.8)
- cucumber (0.9.4)
- builder (~> 2.1.2)
- diff-lcs (~> 1.1.2)
- gherkin (~> 2.2.9)
- json (~> 1.4.6)
- term-ansicolor (~> 1.0.5)
daemons (1.1.0)
diff-lcs (1.1.2)
em-http-request (0.3.0)
@@ -20,15 +24,12 @@ GEM
eventmachine (>= 0.12.9)
escape_utils (0.2.3)
eventmachine (0.12.10)
- gherkin (2.2.9)
- json (~> 1.4.6)
- term-ansicolor (~> 1.0.5)
git (1.2.5)
+ hiredis (0.3.1)
jeweler (1.5.1)
bundler (~> 1.0.0)
git (>= 1.2.5)
rake
- json (1.4.6)
json_pure (1.4.6)
mocha (0.9.8)
rake
@@ -42,7 +43,6 @@ GEM
rspec-expectations (2.1.0)
diff-lcs (~> 1.1.2)
rspec-mocks (2.1.0)
- term-ansicolor (1.0.5)
timecop (0.3.5)
webmock (1.6.1)
addressable (>= 2.2.2)
@@ -53,9 +53,10 @@ PLATFORMS
DEPENDENCIES
bourne
- cucumber
daemons (= 1.1.0)
+ em-hiredis!
em-http-request (= 0.3.0)
+ em-spec!
jeweler
json_pure (= 1.4.6)
redis (= 2.1.1)
View
3  Rakefile
@@ -22,9 +22,6 @@ RSpec::Core::RakeTask.new(:spec) do |spec|
spec.pattern = FileList['spec/**/*_spec.rb']
end
-require 'cucumber/rake/task'
-Cucumber::Rake::Task.new(:features)
-
task :default => :spec
def parse_monitor
View
2  lib/daikon.rb
@@ -7,6 +7,7 @@
require 'stringio'
require 'thread'
+require 'em-hiredis'
require 'em-http-request'
require 'daemons'
require 'json'
@@ -20,6 +21,7 @@
require 'daikon/configuration'
require 'daikon/client'
+require 'daikon/reactor'
require 'daikon/daemon'
require 'daikon/monitor'
View
16 lib/daikon/client.rb
@@ -6,23 +6,17 @@ class Client
EOFError,
JSON::ParserError]
- attr_accessor :redis, :logger, :config, :monitor
+ attr_accessor :redis, :logger, :config
- def setup(config, logger = nil)
+ def initialize(config = Daikon::Configuration.new, logger = nil)
self.config = config
self.logger = logger
- self.redis = connect
- self.monitor = Monitor.new
log "Started Daikon v#{VERSION}"
end
def connect
- Redis.connect(:url => config.redis_url)
- end
-
- def start_monitor
- Monitor.start(connect)
+ EventMachine::Hiredis.connect(config.redis_url)
end
def log(message)
@@ -76,8 +70,8 @@ def rotate_monitor(start, stop)
exception(ex)
end
- def report_info
- push :post, "/api/v1/infos.json", redis.info
+ def report_info(info)
+ push :post, "/api/v1/infos.json", info
rescue *EXCEPTIONS => ex
exception(ex)
end
View
45 lib/daikon/reactor.rb
@@ -0,0 +1,45 @@
+module Daikon
+ class Reactor
+ attr_reader :current_time, :info_collector
+ attr_writer :callback, :info_interval
+
+ def initialize(client = nil)
+ @client = client
+ end
+
+ def info_interval
+ @info_interval ||= 10
+ end
+
+ def info_collector
+ @info_collector ||= connect
+ end
+
+ def start
+ EventMachine.add_periodic_timer(@info_interval) do
+ @current_time = Time.now
+ collect_info
+ callback
+ end
+ end
+
+ def collect_info
+ info_collector.info do |info|
+ @client.report_info(info)
+ callback
+ end
+ end
+
+ private
+
+ def callback
+ if @callback && @callback.call(self)
+ EventMachine.stop
+ end
+ end
+
+ def connect
+ @client.connect
+ end
+ end
+end
View
54 spec/client_spec.rb
@@ -1,73 +1,55 @@
require 'spec_helper'
-describe Daikon::Client, "setup" do
- subject { Daikon::Client.new }
- let(:logger) { Logger.new(nil) }
- let(:redis) { 'redis instance' }
+describe Daikon::Client, "connect" do
+ subject { Daikon::Client.new(config) }
before do
- Redis.stubs(:connect => redis)
- subject.stubs(:redis=)
+ EventMachine::Hiredis.stubs(:connect => nil)
+ subject.connect
end
context "with overrides" do
let(:url) { "redis://8.8.8.8:1234" }
let(:config) { Daikon::Configuration.new(["-u", url]) }
- before do
- subject.setup(config, logger)
- end
-
it "sets redis to listen on the given port" do
- Redis.should have_received(:connect).with(:url => url).once
- subject.should have_received(:redis=).with(redis)
+ EventMachine::Hiredis.should have_received(:connect).with(url).once
end
end
context "with defaults" do
let(:config) { Daikon::Configuration.new([]) }
- before do
- subject.setup(config, logger)
- end
-
it "sets redis to listen on the given port" do
- Redis.should have_received(:connect).with(:url => "redis://0.0.0.0:6379").once
- subject.should have_received(:redis=).with(redis)
+ EventMachine::Hiredis.should have_received(:connect).with("redis://0.0.0.0:6379").once
end
end
end
describe Daikon::Client, "when server is down" do
- subject { Daikon::Client.new }
- let(:redis) { stub("redis instance", :info => {}) }
+ subject { Daikon::Client.new }
before do
- Redis.stubs(:connect => redis)
stub_request(:any, infos_url).to_timeout
- subject.setup(Daikon::Configuration.new)
end
it "does not kill the client" do
lambda {
- subject.report_info
+ subject.report_info({})
}.should_not raise_error
end
end
describe Daikon::Client, "when it returns bad json" do
- subject { Daikon::Client.new }
- let(:redis) { stub("redis instance", :info => {}) }
+ subject { Daikon::Client.new }
before do
- Redis.stubs(:connect => redis)
stub_request(:post, infos_url).to_return(:body => "{'bad':'json}")
- subject.setup(Daikon::Configuration.new)
end
it "does not commit suicide" do
lambda {
- subject.report_info
+ subject.report_info({})
}.should_not raise_error
end
end
@@ -86,7 +68,7 @@
end
describe Daikon::Client, "rotate monitor" do
- subject { Daikon::Client.new }
+ subject { Daikon::Client.new(config) }
let(:now) { "2011-01-19T18:23:55-05:00" }
let(:past) { "2011-01-19T18:23:54-05:00" }
let(:payload) do
@@ -103,7 +85,6 @@
Timecop.freeze DateTime.parse(now)
Daikon::Monitor.stubs(:pop).yields(data)
stub_request(:post, summaries_url(server)).to_return(:status => 200)
- subject.setup(config)
subject.rotate_monitor(DateTime.parse(past), DateTime.parse(now))
end
@@ -132,25 +113,22 @@
it "shoots the results back to radish" do
headers = {
"Authorization" => api_key,
- "Content-Length" => results.to_json.size.to_s,
+ "Content-Length" => info.to_json.size.to_s,
"Content-Type" => "application/json"
}
WebMock.should have_requested(:post, infos_url(server)).
- with(:body => results.to_json, :headers => headers)
+ with(:body => info.to_json, :headers => headers)
end
end
describe Daikon::Client, "report info" do
- subject { Daikon::Client.new }
- let(:results) { {"connected_clients"=>"1", "used_cpu_sys_childrens"=>"0.00"} }
- let(:redis) { stub("redis instance", :info => results) }
+ subject { Daikon::Client.new(config) }
+ let(:info) { {"connected_clients"=>"1", "used_cpu_sys_childrens"=>"0.00"} }
before do
- subject.stubs(:redis => redis)
stub_request(:post, infos_url(server)).to_return(:status => 200)
- subject.setup(config)
- subject.report_info
+ subject.report_info(info)
end
context "with default configuration" do
View
24 spec/daemon_spec.rb
@@ -1,29 +1,5 @@
require 'spec_helper'
-describe Daikon::Daemon do
- let(:client) { stub("client") }
-
- before do
- Timecop.return
- Daikon::Client.stubs(:new => client)
- Daikon::Daemon.sleep_time = 0.05
- client.stubs(:setup => true,
- :start_monitor => true,
- :rotate_monitor => true,
- :report_info => true)
- end
-
- it "submits the last minute of data" do
- thread = Thread.new do
- Daikon::Daemon.start(["run", "--", "-k", "1234"], true)
- end
- sleep 3.1
- Daikon::Daemon.run = false
- client.should have_received(:rotate_monitor)
- client.should have_received(:report_info).times(6)
- end
-end
-
describe Daikon::Daemon, "flags" do
%w[-v --version].each do |flag|
it "shows the daikon version with #{flag}" do
View
46 spec/reactor_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+
+describe Daikon::Reactor, "start" do
+ subject { Daikon::Reactor.new }
+
+ before do
+ Timecop.return
+ subject.stubs(:collect_info)
+ subject.info_interval = 0.2
+ end
+
+ it "collects info once per interval" do
+ now = Time.now
+ subject.callback = lambda { |reactor| reactor.current_time.to_f >= now.to_f + 1.1 }
+
+ em do
+ subject.start
+ end
+
+ subject.should have_received(:collect_info).times(5)
+ end
+end
+
+describe Daikon::Reactor, "collecting info" do
+ subject { Daikon::Reactor.new(client) }
+ let(:client) { Daikon::Client.new(config) }
+ let(:config) { Daikon::Configuration.new(["-u", "redis://127.0.0.1:6380"]) }
+ let(:replies) do
+ {:info => lambda { "+total_commands_processed:1" }}
+ end
+
+ before do
+ client.stubs(:report_info)
+ end
+
+ it "sends along info to the client" do
+ redis_mock(replies) do
+ em do
+ subject.collect_info
+ subject.callback = lambda do |reactor|
+ client.should have_received(:report_info).with(:total_commands_processed => "1")
+ end
+ end
+ end
+ end
+end
View
4 spec/spec_helper.rb
@@ -8,6 +8,7 @@
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
require 'bourne'
+require 'em-spec/rspec'
require 'timecop'
require 'webmock/rspec'
@@ -21,11 +22,12 @@
config.mock_with :mocha
config.include CaptureHelper
+ config.include EventMachine::SpecHelper
config.include ParseHelper
+ config.include RedisMock::Helper
config.include UrlHelper
config.before do
- Timecop.freeze(DateTime.now)
Daikon::Monitor.reset
end
View
65 spec/support/redis_mock.rb
@@ -0,0 +1,65 @@
+# nabbed from redis-rb, thanks!
+require "socket"
+
+module RedisMock
+ def self.start(port = 6380)
+ server = TCPServer.new("127.0.0.1", port)
+
+ loop do
+ session = server.accept
+
+ while line = session.gets
+ parts = Array.new(line[1..-3].to_i) do
+ bytes = session.gets[1..-3].to_i
+ argument = session.read(bytes)
+ session.read(2) # Discard \r\n
+ argument
+ end
+
+ response = yield(*parts)
+
+ if response.nil?
+ session.shutdown(Socket::SHUT_RDWR)
+ break
+ else
+ session.write(response)
+ session.write("\r\n")
+ end
+ end
+ end
+ end
+
+ module Helper
+ # Forks the current process and starts a new mock Redis server on
+ # port 6380.
+ #
+ # The server will reply with a `+OK` to all commands, but you can
+ # customize it by providing a hash. For example:
+ #
+ # redis_mock(:ping => lambda { "+PONG" }) do
+ # assert_equal "PONG", Redis.new(:port => 6380).ping
+ # end
+ #
+ def redis_mock(replies = {})
+ begin
+ pid = fork do
+ trap("TERM") { exit }
+
+ RedisMock.start do |command, *args|
+ (replies[command.to_sym] || lambda { |*_| "+OK" }).call(*args)
+ end
+ end
+
+ sleep 1 # Give time for the socket to start listening.
+
+ yield
+
+ ensure
+ if pid
+ Process.kill("TERM", pid)
+ Process.wait(pid)
+ end
+ end
+ end
+ end
+end
Please sign in to comment.
Something went wrong with that request. Please try again.