fwd 0.3.2 → 0.3.3
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.
- data/Gemfile.lock +1 -1
- data/benchmark/performance.rb +21 -16
- data/fwd.gemspec +1 -1
- data/lib/fwd.rb +2 -1
- data/lib/fwd/buffer.rb +2 -3
- data/lib/fwd/input.rb +4 -7
- data/lib/fwd/output.rb +30 -13
- data/spec/fwd/buffer_spec.rb +13 -13
- data/spec/fwd/input_spec.rb +5 -7
- data/spec/fwd/output_spec.rb +16 -12
- data/spec/spec_helper.rb +5 -0
- metadata +2 -2
data/Gemfile.lock
CHANGED
data/benchmark/performance.rb
CHANGED
@@ -6,29 +6,31 @@ require 'socket'
|
|
6
6
|
require 'fileutils'
|
7
7
|
|
8
8
|
root = Pathname.new(File.expand_path('../..', __FILE__))
|
9
|
-
|
10
|
-
|
11
|
-
FileUtils.mkdir_p tmp
|
9
|
+
TMP = root.join("tmp/benchmark")
|
10
|
+
OUT = TMP.join('out.txt')
|
12
11
|
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
FileUtils.rm_rf TMP
|
13
|
+
FileUtils.mkdir_p TMP
|
14
|
+
FileUtils.touch(OUT)
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
Process.kill(:TERM, NCC)
|
20
|
-
end
|
16
|
+
FWD = fork { exec "#{root}/bin/fwd-rb --flush 10000:2 -F tcp://0.0.0.0:7291 --path #{TMP} -v" }
|
17
|
+
NCC = fork { exec "nc -vlp 7291 > #{OUT}" }
|
21
18
|
|
22
|
-
sleep(
|
19
|
+
sleep(5)
|
23
20
|
|
24
21
|
EVENTS = 10_000_000
|
25
22
|
LENGTH = 100
|
26
23
|
DATA = "A" * LENGTH
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
24
|
+
CCUR = 5
|
25
|
+
|
26
|
+
ds = Benchmark.realtime do
|
27
|
+
(1..CCUR).map do
|
28
|
+
fork do
|
29
|
+
sock = TCPSocket.new "127.0.0.1", 7289
|
30
|
+
(EVENTS / CCUR).times { sock.write DATA }
|
31
|
+
sock.close
|
32
|
+
end
|
33
|
+
end.each {|t| Process.wait(t) }
|
32
34
|
end
|
33
35
|
|
34
36
|
rs = Benchmark.realtime do
|
@@ -44,3 +46,6 @@ puts "--> Completed in : #{(ds + rs).round(1)}s"
|
|
44
46
|
puts "--> FWD RSS : #{(`ps -o rss= -p #{FWD}`.to_f / 1024).round(1)}M"
|
45
47
|
puts "--> Processed : #{EVENTS} events"
|
46
48
|
puts "--> Written : #{(OUT.size / 1024.0 / 1024.0).round(1)}M"
|
49
|
+
|
50
|
+
Process.kill(:TERM, FWD)
|
51
|
+
Process.kill(:TERM, NCC)
|
data/fwd.gemspec
CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |s|
|
|
9
9
|
s.name = File.basename(__FILE__, '.gemspec')
|
10
10
|
s.summary = "fwd >>"
|
11
11
|
s.description = "The minimalistic stream forwarder"
|
12
|
-
s.version = "0.3.
|
12
|
+
s.version = "0.3.3"
|
13
13
|
|
14
14
|
s.authors = ["Black Square Media"]
|
15
15
|
s.email = "info@blacksquaremedia.com"
|
data/lib/fwd.rb
CHANGED
@@ -87,7 +87,8 @@ class Fwd
|
|
87
87
|
# Starts the server
|
88
88
|
def listen!
|
89
89
|
logger.info "Starting server on #{@bind}"
|
90
|
-
|
90
|
+
buffer = Fwd::Buffer.new(self)
|
91
|
+
EM.start_server @bind.host, @bind.port, Fwd::Input, self, buffer
|
91
92
|
end
|
92
93
|
|
93
94
|
# Initiates flush
|
data/lib/fwd/buffer.rb
CHANGED
@@ -13,9 +13,7 @@ class Fwd::Buffer
|
|
13
13
|
@rate = (core.opts[:flush_rate] || 10_000).to_i
|
14
14
|
@limit = [core.opts[:buffer_limit].to_i, MAX_LIMIT].reject(&:zero?).min
|
15
15
|
@count = 0
|
16
|
-
|
17
16
|
reschedule!
|
18
|
-
rotate!
|
19
17
|
end
|
20
18
|
|
21
19
|
# @param [String] data binary data
|
@@ -50,7 +48,8 @@ class Fwd::Buffer
|
|
50
48
|
end
|
51
49
|
|
52
50
|
@fd = new_file
|
53
|
-
rescue Errno::ENOENT
|
51
|
+
rescue Errno::ENOENT => e
|
52
|
+
logger.warn "Rotation delayed: #{e.message}"
|
54
53
|
end
|
55
54
|
|
56
55
|
# @return [Boolean] true if rotation is due
|
data/lib/fwd/input.rb
CHANGED
@@ -5,13 +5,10 @@ class Fwd::Input < EM::Connection
|
|
5
5
|
attr_reader :core, :buffer
|
6
6
|
|
7
7
|
# @param [Fwd] core
|
8
|
-
# @param [
|
9
|
-
def initialize(core)
|
10
|
-
@core
|
11
|
-
|
12
|
-
|
13
|
-
def post_init
|
14
|
-
@buffer = Fwd::Buffer.new(core)
|
8
|
+
# @param [Fwd::Buffer] buffer
|
9
|
+
def initialize(core, buffer)
|
10
|
+
@core = core
|
11
|
+
@buffer = buffer
|
15
12
|
end
|
16
13
|
|
17
14
|
# When receiving data, concat it to the buffer
|
data/lib/fwd/output.rb
CHANGED
@@ -2,7 +2,8 @@ class Fwd::Output
|
|
2
2
|
extend Forwardable
|
3
3
|
def_delegators :core, :logger, :root, :prefix
|
4
4
|
|
5
|
-
|
5
|
+
CHUNK_SIZE = 16 * 1024
|
6
|
+
RESCUABLE = [
|
6
7
|
Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EHOSTUNREACH, Errno::EPIPE,
|
7
8
|
Errno::ENETUNREACH, Errno::ENETDOWN, Errno::EINVAL, Errno::ETIMEDOUT,
|
8
9
|
IOError, EOFError
|
@@ -22,19 +23,26 @@ class Fwd::Output
|
|
22
23
|
|
23
24
|
# Callback
|
24
25
|
def forward!
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
return if @forwarding
|
27
|
+
|
28
|
+
@forwarding = true
|
29
|
+
begin
|
30
|
+
Dir[root.join("#{prefix}.*.closed")].sort.each do |file|
|
31
|
+
ok = reserve(file) do |io|
|
32
|
+
logger.debug { "Flushing #{File.basename(io.path)}, #{io.size.fdiv(1024).round} kB" }
|
33
|
+
write(io)
|
34
|
+
end
|
35
|
+
ok or break
|
29
36
|
end
|
30
|
-
|
37
|
+
ensure
|
38
|
+
@forwarding = false
|
31
39
|
end
|
32
40
|
end
|
33
41
|
|
34
|
-
# @param [
|
35
|
-
def write(
|
42
|
+
# @param [IO] io source stream
|
43
|
+
def write(io)
|
36
44
|
pool.any? do |backend|
|
37
|
-
forward(backend,
|
45
|
+
forward(backend, io)
|
38
46
|
end
|
39
47
|
end
|
40
48
|
|
@@ -46,7 +54,11 @@ class Fwd::Output
|
|
46
54
|
target = Pathname.new(file.sub(/\.closed$/, ".reserved"))
|
47
55
|
FileUtils.mv file, target.to_s
|
48
56
|
|
49
|
-
result =
|
57
|
+
result = false
|
58
|
+
target.open("r") do |io|
|
59
|
+
result = yield(io)
|
60
|
+
end
|
61
|
+
|
50
62
|
if result
|
51
63
|
target.unlink
|
52
64
|
else
|
@@ -55,12 +67,17 @@ class Fwd::Output
|
|
55
67
|
end
|
56
68
|
|
57
69
|
result
|
58
|
-
rescue Errno::ENOENT
|
70
|
+
rescue Errno::ENOENT => e
|
59
71
|
# Ignore if file was alread flushed by another process
|
72
|
+
logger.warn "Flushing of #{File.basename(file)} postponed: #{e.message}"
|
60
73
|
end
|
61
74
|
|
62
|
-
def forward(backend,
|
63
|
-
|
75
|
+
def forward(backend, io)
|
76
|
+
io.rewind
|
77
|
+
until io.eof?
|
78
|
+
backend.write(io.read(CHUNK_SIZE))
|
79
|
+
end
|
80
|
+
true
|
64
81
|
rescue *RESCUABLE => e
|
65
82
|
logger.error "Backend #{backend} failed: #{e.class.name} #{e.message}"
|
66
83
|
backend.close
|
data/spec/fwd/buffer_spec.rb
CHANGED
@@ -7,14 +7,9 @@ describe Fwd::Buffer do
|
|
7
7
|
end
|
8
8
|
|
9
9
|
let(:buffer) { described_class.new core }
|
10
|
-
let(:timer) { mock("Timer", cancel: true) }
|
11
10
|
subject { buffer }
|
12
|
-
before do
|
13
|
-
EM.stub add_periodic_timer: timer
|
14
|
-
end
|
15
11
|
|
16
12
|
its(:root) { should == root }
|
17
|
-
its(:root) { should be_exist }
|
18
13
|
its(:prefix) { should == "buffer" }
|
19
14
|
its(:core) { should be(core) }
|
20
15
|
its(:count) { should be(0) }
|
@@ -22,21 +17,26 @@ describe Fwd::Buffer do
|
|
22
17
|
its(:rate) { should be(20) }
|
23
18
|
its(:limit) { should be(2048) }
|
24
19
|
its(:timer) { should be(timer) }
|
25
|
-
its(:fd) { should
|
20
|
+
its(:fd) { should be_nil }
|
26
21
|
its(:logger) { should be(Fwd.logger) }
|
27
22
|
|
28
23
|
describe "concat" do
|
29
24
|
it 'should concat data' do
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
25
|
+
subject.concat("x" * 1024)
|
26
|
+
subject.fd.size.should == 1024
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should create the path' do
|
30
|
+
lambda { buffer.concat("x") }.should change { buffer.root.exist? }.to(true)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should rotate' do
|
34
|
+
lambda { buffer.concat("x") }.should change { buffer.fd }.to(instance_of(File))
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
38
|
describe "rotate" do
|
39
|
-
before { buffer }
|
39
|
+
before { buffer.send(:rotate!) }
|
40
40
|
subject { lambda { buffer.rotate! } }
|
41
41
|
|
42
42
|
it 'should trigger when buffer limit is reached' do
|
@@ -58,7 +58,7 @@ describe Fwd::Buffer do
|
|
58
58
|
end
|
59
59
|
|
60
60
|
describe "when unchanged" do
|
61
|
-
it { should_not change { buffer.fd.
|
61
|
+
it { should_not change { buffer.fd.size }.from(0) }
|
62
62
|
it { should_not change { files.size } }
|
63
63
|
end
|
64
64
|
end
|
data/spec/fwd/input_spec.rb
CHANGED
@@ -2,20 +2,18 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Fwd::Input do
|
4
4
|
|
5
|
+
let(:buffer) { Fwd::Buffer.new(core) }
|
5
6
|
subject do
|
6
7
|
input = described_class.allocate
|
7
|
-
input.send(:initialize, core)
|
8
|
+
input.send(:initialize, core, buffer)
|
8
9
|
input
|
9
10
|
end
|
11
|
+
|
10
12
|
before { EM.stub :add_periodic_timer }
|
11
13
|
|
12
14
|
it { should be_a(EM::Connection) }
|
13
|
-
its(:
|
15
|
+
its(:core) { should be(core) }
|
16
|
+
its(:buffer) { should be(buffer) }
|
14
17
|
its(:logger) { should be(Fwd.logger) }
|
15
18
|
|
16
|
-
describe "post init" do
|
17
|
-
before { subject.post_init }
|
18
|
-
its(:buffer) { should be_instance_of(Fwd::Buffer) }
|
19
|
-
end
|
20
|
-
|
21
19
|
end
|
data/spec/fwd/output_spec.rb
CHANGED
@@ -48,30 +48,34 @@ describe Fwd::Output do
|
|
48
48
|
end
|
49
49
|
|
50
50
|
describe "writing" do
|
51
|
+
def write(data)
|
52
|
+
subject.write(StringIO.new(data))
|
53
|
+
end
|
51
54
|
|
52
55
|
it 'should forward data to backends' do
|
53
56
|
servers(7291, 7292) do
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
57
|
+
write("A").should be(true)
|
58
|
+
write("B").should be(true)
|
59
|
+
write("C").should be(true)
|
60
|
+
write("D").should be(true)
|
58
61
|
end.should == { 7291=>"BD", 7292=>"AC" }
|
59
62
|
end
|
60
63
|
|
61
64
|
it 'should handle partial fallouts' do
|
62
65
|
servers(7291) do
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
66
|
+
write("A").should be(true)
|
67
|
+
write("B").should be(true)
|
68
|
+
write("C").should be(true)
|
69
|
+
write("D").should be(true)
|
70
|
+
sleep(1)
|
67
71
|
end.should == { 7291=>"ABCD" }
|
68
72
|
end
|
69
73
|
|
70
74
|
it 'should handle full fallouts' do
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
+
write("A").should be(false)
|
76
|
+
write("B").should be(false)
|
77
|
+
write("C").should be(false)
|
78
|
+
write("D").should be(false)
|
75
79
|
end
|
76
80
|
|
77
81
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -26,6 +26,10 @@ module Fwd::TestHelper
|
|
26
26
|
forward: ["tcp://127.0.0.1:7291", "tcp://127.0.0.1:7292"]
|
27
27
|
end
|
28
28
|
|
29
|
+
def timer
|
30
|
+
@_timer ||= mock("Timer", cancel: true)
|
31
|
+
end
|
32
|
+
|
29
33
|
end
|
30
34
|
|
31
35
|
RSpec.configure do |c|
|
@@ -35,5 +39,6 @@ RSpec.configure do |c|
|
|
35
39
|
end
|
36
40
|
c.before(:each) do
|
37
41
|
FileUtils.rm_rf root.to_s
|
42
|
+
EM.stub add_periodic_timer: timer
|
38
43
|
end
|
39
44
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fwd
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-02-
|
12
|
+
date: 2013-02-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: eventmachine-le
|