franz 1.3.1 → 1.4.14
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Readme.md +2 -2
- data/VERSION +1 -1
- data/bin/franz +35 -29
- data/franz.gemspec +1 -2
- data/lib/franz/agg.rb +71 -27
- data/lib/franz/discover.rb +26 -31
- data/lib/franz/input.rb +39 -32
- data/lib/franz/output.rb +33 -8
- data/lib/franz/sash.rb +16 -1
- data/lib/franz/tail.rb +42 -103
- data/lib/franz/watch.rb +61 -19
- data/lib/franz.rb +0 -3
- data/test/test_franz_agg.rb +34 -4
- data/test/test_franz_discover.rb +7 -7
- data/test/test_franz_tail.rb +22 -24
- data/test/test_franz_watch.rb +7 -7
- data/test/test_performance.rb +126 -0
- metadata +6 -18
data/lib/franz/tail.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'thread'
|
2
2
|
require 'logger'
|
3
3
|
|
4
|
-
require '
|
4
|
+
require 'eventmachine'
|
5
5
|
|
6
6
|
module Franz
|
7
7
|
|
@@ -17,42 +17,23 @@ module Franz
|
|
17
17
|
@watch_events = opts[:watch_events] || []
|
18
18
|
@tail_events = opts[:tail_events] || []
|
19
19
|
|
20
|
-
@
|
21
|
-
@block_size
|
22
|
-
@
|
23
|
-
@
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
@watch_events, @tail_events
|
28
|
-
]
|
29
|
-
|
30
|
-
@buffer = Hash.new { |h, k| h[k] = BufferedTokenizer.new }
|
31
|
-
@file = Hash.new
|
32
|
-
@changed = Hash.new
|
33
|
-
@reading = Hash.new
|
34
|
-
@stop = false
|
35
|
-
|
36
|
-
@evict_thread = Thread.new do
|
37
|
-
until @stop
|
38
|
-
evict
|
39
|
-
sleep eviction_interval
|
40
|
-
end
|
41
|
-
evict true
|
42
|
-
end
|
20
|
+
@line_limit = opts[:line_limit] || 10_240 # 10 KiB
|
21
|
+
@block_size = opts[:block_size] || 32_768 # 32 KiB
|
22
|
+
@cursors = opts[:cursors] || Hash.new
|
23
|
+
@logger = opts[:logger] || Logger.new(STDOUT)
|
24
|
+
|
25
|
+
@buffer = Hash.new { |h, k| h[k] = BufferedTokenizer.new("\n", @line_limit) }
|
26
|
+
@stop = false
|
43
27
|
|
44
28
|
@tail_thread = Thread.new do
|
45
|
-
until @stop
|
46
|
-
if @file.size >= OPEN_FILE_LIMIT
|
47
|
-
log.debug 'Sleeping until file descriptors become available...'
|
48
|
-
sleep 5
|
49
|
-
else
|
50
|
-
handle(watch_events.shift)
|
51
|
-
end
|
52
|
-
end
|
29
|
+
handle(watch_events.shift) until @stop
|
53
30
|
end
|
54
31
|
|
55
|
-
log.debug
|
32
|
+
log.debug \
|
33
|
+
event: 'tail started',
|
34
|
+
watch_events: watch_events,
|
35
|
+
tail_events: tail_events,
|
36
|
+
block_size: block_size
|
56
37
|
end
|
57
38
|
|
58
39
|
# Stop the Tail thread. Effectively only once.
|
@@ -62,9 +43,8 @@ module Franz
|
|
62
43
|
return state if @stop
|
63
44
|
@stop = true
|
64
45
|
@watch_thread.kill rescue nil
|
65
|
-
@evict_thread.kill rescue nil
|
66
46
|
@tail_thread.kill rescue nil
|
67
|
-
log.debug 'stopped
|
47
|
+
log.debug event: 'tail stopped'
|
68
48
|
return state
|
69
49
|
end
|
70
50
|
|
@@ -74,93 +54,51 @@ module Franz
|
|
74
54
|
end
|
75
55
|
|
76
56
|
private
|
77
|
-
attr_reader :watch_events, :tail_events, :
|
57
|
+
attr_reader :watch_events, :tail_events, :block_size, :cursors, :buffer, :reading
|
78
58
|
|
79
59
|
def log ; @logger end
|
80
60
|
|
81
|
-
def open path
|
82
|
-
if file.size > OPEN_FILE_LIMIT
|
83
|
-
log.fatal 'Absolutely too many open files!'
|
84
|
-
raise Errno::EMFILE
|
85
|
-
end
|
86
|
-
|
87
|
-
return true unless file[path].nil?
|
88
|
-
pos = @cursors.include?(path) ? @cursors[path] : 0
|
89
|
-
begin
|
90
|
-
file[path] = File.open(path)
|
91
|
-
file[path].sysseek pos, IO::SEEK_SET
|
92
|
-
@cursors[path] = pos
|
93
|
-
@changed[path] = Time.now.to_i
|
94
|
-
rescue Errno::EMFILE
|
95
|
-
log.debug 'skipping: path=%s (too many open files)' % path.inspect
|
96
|
-
return false
|
97
|
-
rescue Errno::ENOENT
|
98
|
-
log.debug 'skipping: path=%s (file does not exist)' % path.inspect
|
99
|
-
return false
|
100
|
-
end
|
101
|
-
log.trace 'opened: path=%s' % path.inspect
|
102
|
-
return true
|
103
|
-
end
|
104
|
-
|
105
61
|
def read path, size
|
106
|
-
|
107
|
-
|
108
|
-
|
62
|
+
log.trace \
|
63
|
+
event: 'read',
|
64
|
+
path: path,
|
65
|
+
size: size
|
66
|
+
@cursors[path] ||= 0
|
109
67
|
loop do
|
110
|
-
|
111
|
-
break if file[path].pos >= size
|
112
|
-
rescue NoMethodError
|
113
|
-
break unless open(path)
|
114
|
-
break if file[path].pos >= size
|
115
|
-
end
|
68
|
+
break if @cursors[path] >= size
|
116
69
|
|
117
70
|
begin
|
118
|
-
data =
|
71
|
+
data = IO::read path, @block_size, @cursors[path]
|
119
72
|
buffer[path].extract(data).each do |line|
|
120
|
-
|
73
|
+
size = line.bytesize
|
74
|
+
if size > @line_limit
|
75
|
+
log.fatal \
|
76
|
+
event: 'killed',
|
77
|
+
reason: 'line overflow',
|
78
|
+
path: path,
|
79
|
+
size: size,
|
80
|
+
limit: @line_limit,
|
81
|
+
pid: $$
|
82
|
+
exit(2)
|
83
|
+
end
|
121
84
|
tail_events.push path: path, line: line
|
122
85
|
end
|
123
|
-
|
86
|
+
@cursors[path] += data.bytesize
|
87
|
+
rescue EOFError, Errno::ENOENT, NoMethodError
|
124
88
|
# we're done here
|
125
89
|
end
|
126
|
-
|
127
|
-
last_pos = @cursors[path]
|
128
|
-
@cursors[path] = file[path].pos
|
129
|
-
bytes_read += @cursors[path] - last_pos
|
130
90
|
end
|
131
|
-
|
132
|
-
log.trace 'read: path=%s size=%s cursor=%s' % [
|
133
|
-
path.inspect, size.inspect, @cursors[path].inspect
|
134
|
-
]
|
135
|
-
|
136
|
-
@changed[path] = Time.now.to_i
|
137
|
-
@reading.delete path
|
138
91
|
end
|
139
92
|
|
140
93
|
def close path
|
141
|
-
|
142
|
-
|
143
|
-
@cursors.delete(path)
|
144
|
-
@changed.delete(path)
|
145
|
-
@reading.delete(path)
|
146
|
-
log.debug 'closed: path=%s' % path.inspect
|
147
|
-
end
|
148
|
-
|
149
|
-
def evict force=false
|
150
|
-
cutoff = Time.now.to_i - eviction_interval
|
151
|
-
file.keys.each do |path|
|
152
|
-
unless force
|
153
|
-
next if @reading[path]
|
154
|
-
next unless @changed[path] < cutoff
|
155
|
-
next unless file.include? path
|
156
|
-
end
|
157
|
-
file.delete(path).close
|
158
|
-
log.debug 'evicted: path=%s' % path.inspect
|
159
|
-
end
|
94
|
+
log.trace event: 'close', path: path
|
95
|
+
@cursors[path] = 0
|
160
96
|
end
|
161
97
|
|
162
98
|
def handle event
|
163
|
-
log.trace
|
99
|
+
log.trace \
|
100
|
+
event: 'handle',
|
101
|
+
raw: event
|
164
102
|
case event[:name]
|
165
103
|
when :created
|
166
104
|
when :replaced
|
@@ -176,6 +114,7 @@ module Franz
|
|
176
114
|
else
|
177
115
|
raise 'invalid event'
|
178
116
|
end
|
117
|
+
return event[:path]
|
179
118
|
end
|
180
119
|
end
|
181
120
|
end
|
data/lib/franz/watch.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
1
3
|
require 'logger'
|
2
4
|
|
3
5
|
module Franz
|
@@ -21,35 +23,57 @@ module Franz
|
|
21
23
|
@deletions = opts[:deletions] || []
|
22
24
|
@watch_events = opts[:watch_events] || []
|
23
25
|
|
26
|
+
# Check if files older than STALE_INTERVAL have been updated every
|
27
|
+
# SKIP_INTERVAL seconds. Useful if you've got tons of old files.
|
28
|
+
@stale_interval = opts[:stale_interval] || 900 # 15 minutes
|
29
|
+
@skip_interval = opts[:skip_interval] || 120 # 2 minutes
|
30
|
+
|
31
|
+
@play_catchup = opts[:play_catchup?].nil? ? true : opts[:play_catchup?]
|
24
32
|
@watch_interval = opts[:watch_interval] || 10
|
25
33
|
@stats = opts[:stats] || Hash.new
|
26
34
|
@logger = opts[:logger] || Logger.new(STDOUT)
|
27
35
|
|
28
|
-
|
29
|
-
|
30
|
-
|
36
|
+
@num_skipped = 0
|
37
|
+
|
38
|
+
# Make sure we're up-to-date by rewinding our old stats to our cursors
|
39
|
+
if @play_catchup
|
40
|
+
log.debug event: 'play catchup'
|
41
|
+
stats.keys.each do |path|
|
42
|
+
stats[path][:size] = opts[:full_state][path][:cursor] || 0
|
43
|
+
end
|
31
44
|
end
|
32
45
|
|
33
46
|
@stop = false
|
34
|
-
|
35
|
-
log.debug 'watch: discoveries=%s deletions=%s watch_events=%s' % [
|
36
|
-
@discoveries, @deletions, @watch_events
|
37
|
-
]
|
38
|
-
|
39
47
|
@thread = Thread.new do
|
48
|
+
stale_updated = Time.now - @stale_interval
|
49
|
+
|
40
50
|
until @stop
|
51
|
+
started = Time.now
|
41
52
|
until discoveries.empty?
|
42
53
|
@stats[discoveries.shift] = nil
|
43
54
|
end
|
44
|
-
|
55
|
+
|
56
|
+
skip_stale = true
|
57
|
+
if stale_updated < started - @skip_interval
|
58
|
+
skip_stale = false
|
59
|
+
stale_updated = Time.now
|
60
|
+
end
|
61
|
+
|
62
|
+
watch(skip_stale).each do |deleted|
|
45
63
|
@stats.delete deleted
|
46
64
|
deletions.push deleted
|
47
65
|
end
|
66
|
+
|
48
67
|
sleep watch_interval
|
49
68
|
end
|
50
69
|
end
|
51
70
|
|
52
|
-
log.debug
|
71
|
+
log.debug \
|
72
|
+
event: 'watch started',
|
73
|
+
discoveries: discoveries,
|
74
|
+
deletions: deletions,
|
75
|
+
watch_events: watch_events,
|
76
|
+
watch_interval: watch_interval
|
53
77
|
end
|
54
78
|
|
55
79
|
# Stop the Watch thread. Effectively only once.
|
@@ -59,7 +83,7 @@ module Franz
|
|
59
83
|
return state if @stop
|
60
84
|
@stop = true
|
61
85
|
@thread.kill
|
62
|
-
log.debug 'stopped
|
86
|
+
log.debug event: 'watch stopped'
|
63
87
|
return state
|
64
88
|
end
|
65
89
|
|
@@ -74,21 +98,38 @@ module Franz
|
|
74
98
|
def log ; @logger end
|
75
99
|
|
76
100
|
def enqueue name, path, size=nil
|
77
|
-
log.trace
|
78
|
-
|
79
|
-
|
101
|
+
log.trace \
|
102
|
+
event: 'enqueue',
|
103
|
+
name: name,
|
104
|
+
path: path,
|
105
|
+
size: size
|
80
106
|
watch_events.push name: name, path: path, size: size
|
81
107
|
end
|
82
108
|
|
83
|
-
def watch
|
109
|
+
def watch skip_stale=true
|
110
|
+
log.debug \
|
111
|
+
event: 'watch',
|
112
|
+
skip_stale: skip_stale
|
84
113
|
deleted = []
|
114
|
+
skip_past = Time.now - @stale_interval
|
115
|
+
|
85
116
|
stats.keys.each do |path|
|
86
|
-
|
87
|
-
|
117
|
+
# Hacks for logs we've removed
|
118
|
+
next if File.basename(path) =~ /^rtpstat/
|
119
|
+
next if File.basename(path) == 'zuora.log'
|
120
|
+
|
121
|
+
old_stat = stats[path]
|
122
|
+
|
123
|
+
next if skip_stale \
|
124
|
+
&& old_stat \
|
125
|
+
&& old_stat[:mtime] \
|
126
|
+
&& old_stat[:mtime] < skip_past
|
127
|
+
|
128
|
+
stat = stat_for path
|
88
129
|
stats[path] = stat
|
89
130
|
|
90
131
|
if file_created? old_stat, stat
|
91
|
-
enqueue :created, path
|
132
|
+
# enqueue :created, path
|
92
133
|
elsif file_deleted? old_stat, stat
|
93
134
|
enqueue :deleted, path
|
94
135
|
deleted << path
|
@@ -119,7 +160,8 @@ module Franz
|
|
119
160
|
maj: stat.dev_major,
|
120
161
|
min: stat.dev_minor
|
121
162
|
},
|
122
|
-
size: stat.size
|
163
|
+
size: stat.size,
|
164
|
+
mtime: stat.mtime
|
123
165
|
}
|
124
166
|
rescue Errno::ENOENT
|
125
167
|
nil
|
data/lib/franz.rb
CHANGED
data/test/test_franz_agg.rb
CHANGED
@@ -33,7 +33,7 @@ class TestFranzAgg < MiniTest::Test
|
|
33
33
|
tmp.write sample
|
34
34
|
tmp.flush
|
35
35
|
tmp.close
|
36
|
-
start_agg
|
36
|
+
start_agg multiline: /^multiline/
|
37
37
|
sleep 3
|
38
38
|
seqs = stop_agg
|
39
39
|
path = realpath tmp.path
|
@@ -42,6 +42,37 @@ class TestFranzAgg < MiniTest::Test
|
|
42
42
|
assert seqs[path] == 1 # should be one line
|
43
43
|
end
|
44
44
|
|
45
|
+
def test_handles_singular_drop
|
46
|
+
sample = "drop this\nbut not this\n"
|
47
|
+
tmp = tempfile %w[ test1 .log ]
|
48
|
+
tmp.write sample
|
49
|
+
tmp.flush
|
50
|
+
tmp.close
|
51
|
+
start_agg drop: /^drop/
|
52
|
+
sleep 3
|
53
|
+
seqs = stop_agg
|
54
|
+
path = realpath tmp.path
|
55
|
+
assert seqs.include?(path)
|
56
|
+
assert_equal sample.lines.last.strip, @agg_events.shift[:message]
|
57
|
+
assert seqs[path] == 1 # should be one line
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_handles_plural_drop
|
61
|
+
sample = "drop this\nbut not this\nignore this too\nreally\n"
|
62
|
+
tmp = tempfile %w[ test1 .log ]
|
63
|
+
tmp.write sample
|
64
|
+
tmp.flush
|
65
|
+
tmp.close
|
66
|
+
start_agg drop: [ /^drop/, /^ignore/ ]
|
67
|
+
sleep 5
|
68
|
+
seqs = stop_agg
|
69
|
+
path = realpath tmp.path
|
70
|
+
assert seqs.include?(path)
|
71
|
+
assert_equal sample.lines[1].strip, @agg_events.shift[:message]
|
72
|
+
assert_equal sample.lines[3].strip, @agg_events.shift[:message]
|
73
|
+
assert seqs[path] == 2 # should be two lines
|
74
|
+
end
|
75
|
+
|
45
76
|
private
|
46
77
|
def tempfile prefix=nil
|
47
78
|
Tempfile.new prefix, @tmpdir
|
@@ -51,13 +82,12 @@ private
|
|
51
82
|
Pathname.new(path).realpath.to_s
|
52
83
|
end
|
53
84
|
|
54
|
-
def start_agg opts={}
|
85
|
+
def start_agg config, opts={}
|
55
86
|
configs = [{
|
56
87
|
type: :test,
|
57
|
-
multiline: /^multiline/,
|
58
88
|
includes: [ "#{@tmpdir}/*.log", "#{realpath @tmpdir}/*.log" ],
|
59
89
|
excludes: [ "#{@tmpdir}/exclude*" ]
|
60
|
-
}]
|
90
|
+
}.merge(config)]
|
61
91
|
|
62
92
|
@discover = Franz::Discover.new({
|
63
93
|
discover_interval: 1,
|
data/test/test_franz_discover.rb
CHANGED
@@ -24,23 +24,23 @@ class TestFranzDiscover < MiniTest::Test
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def test_discovers_existing_file
|
27
|
-
tmp = tempfile %w[
|
27
|
+
tmp = tempfile %w[ test1 .log ]
|
28
28
|
start_discovery known: []
|
29
|
-
sleep
|
29
|
+
sleep 2 # Time to discover
|
30
30
|
known = stop_discovery
|
31
31
|
assert known.include?(tmp.path)
|
32
32
|
end
|
33
33
|
|
34
34
|
def test_discovers_new_file
|
35
35
|
start_discovery known: []
|
36
|
-
tmp = tempfile %w[
|
37
|
-
sleep
|
36
|
+
tmp = tempfile %w[ test2 .log ]
|
37
|
+
sleep 3 # Time to discover
|
38
38
|
known = stop_discovery
|
39
39
|
assert known.include?(tmp.path)
|
40
40
|
end
|
41
41
|
|
42
42
|
def test_deletes_deleted_file
|
43
|
-
tmp = tempfile %w[
|
43
|
+
tmp = tempfile %w[ test3 .log ]
|
44
44
|
start_discovery known: []
|
45
45
|
# at this point, we know Discover has already picked up tmp
|
46
46
|
delete tmp.path
|
@@ -50,11 +50,11 @@ class TestFranzDiscover < MiniTest::Test
|
|
50
50
|
end
|
51
51
|
|
52
52
|
def test_deletes_unknown_file
|
53
|
-
tmp = tempfile %w[
|
53
|
+
tmp = tempfile %w[ test4 .log ]
|
54
54
|
delete tmp.path
|
55
55
|
# tmp never exists as far as Discover is aware
|
56
56
|
start_discovery known: []
|
57
|
-
sleep
|
57
|
+
sleep 2
|
58
58
|
known = stop_discovery
|
59
59
|
assert !known.include?(tmp.path)
|
60
60
|
end
|
data/test/test_franz_tail.rb
CHANGED
@@ -25,6 +25,27 @@ class TestFranzTail < MiniTest::Test
|
|
25
25
|
FileUtils.rm_rf @tmpdir
|
26
26
|
end
|
27
27
|
|
28
|
+
def test_handles_reading_after_deletion
|
29
|
+
sample = "Hello, world!\n"
|
30
|
+
start_tail
|
31
|
+
tmp = tempfile %w[ test4 .log ]
|
32
|
+
path = tmp.path
|
33
|
+
tmp.write sample
|
34
|
+
tmp.flush
|
35
|
+
tmp.close
|
36
|
+
sleep 1
|
37
|
+
FileUtils.rm_rf path
|
38
|
+
sleep 2
|
39
|
+
File.open(path, 'w') do |f|
|
40
|
+
f.write sample
|
41
|
+
f.flush
|
42
|
+
end
|
43
|
+
sleep 4
|
44
|
+
cursors = stop_tail
|
45
|
+
assert cursors.include?(tmp.path)
|
46
|
+
assert cursors[tmp.path] == sample.length
|
47
|
+
end
|
48
|
+
|
28
49
|
def test_handles_existing_file
|
29
50
|
sample = "Hello, world!\n"
|
30
51
|
tmp = tempfile %w[ test1 .log ]
|
@@ -57,7 +78,7 @@ class TestFranzTail < MiniTest::Test
|
|
57
78
|
eviction_interval = 2
|
58
79
|
start_tail eviction_interval: eviction_interval
|
59
80
|
sleep 0
|
60
|
-
tmp = tempfile %w[
|
81
|
+
tmp = tempfile %w[ test3 .log ]
|
61
82
|
tmp.write sample
|
62
83
|
tmp.flush
|
63
84
|
sleep eviction_interval / 2
|
@@ -70,29 +91,6 @@ class TestFranzTail < MiniTest::Test
|
|
70
91
|
assert cursors[tmp.path] == sample.length * 2
|
71
92
|
end
|
72
93
|
|
73
|
-
def test_handles_reading_after_deletion
|
74
|
-
sample = "Hello, world!\n"
|
75
|
-
eviction_interval = 2
|
76
|
-
start_tail eviction_interval: eviction_interval
|
77
|
-
sleep 0
|
78
|
-
tmp = tempfile %w[ test2 .log ]
|
79
|
-
path = tmp.path
|
80
|
-
tmp.write sample
|
81
|
-
tmp.flush
|
82
|
-
tmp.close
|
83
|
-
sleep eviction_interval / 2
|
84
|
-
FileUtils.rm_rf path
|
85
|
-
sleep eviction_interval
|
86
|
-
File.open(path, 'w') do |f|
|
87
|
-
f.write sample
|
88
|
-
f.flush
|
89
|
-
end
|
90
|
-
sleep eviction_interval * 2
|
91
|
-
cursors = stop_tail
|
92
|
-
assert cursors.include?(tmp.path)
|
93
|
-
assert cursors[tmp.path] == sample.length
|
94
|
-
end
|
95
|
-
|
96
94
|
private
|
97
95
|
def tempfile prefix=nil
|
98
96
|
Tempfile.new prefix, @tmpdir
|
data/test/test_franz_watch.rb
CHANGED
@@ -25,7 +25,7 @@ class TestFranzWatch < MiniTest::Test
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def test_handles_existing_file
|
28
|
-
tmp = tempfile %w[
|
28
|
+
tmp = tempfile %w[ test1 .log ]
|
29
29
|
start_watch
|
30
30
|
sleep 2
|
31
31
|
stats = stop_watch
|
@@ -35,7 +35,7 @@ class TestFranzWatch < MiniTest::Test
|
|
35
35
|
|
36
36
|
def test_handles_existing_file_with_content
|
37
37
|
content = "Hello, world!\n"
|
38
|
-
tmp = tempfile %w[
|
38
|
+
tmp = tempfile %w[ test2 .log ]
|
39
39
|
tmp.write content
|
40
40
|
tmp.flush
|
41
41
|
start_watch
|
@@ -47,7 +47,7 @@ class TestFranzWatch < MiniTest::Test
|
|
47
47
|
|
48
48
|
def test_handles_new_file
|
49
49
|
start_watch
|
50
|
-
tmp = tempfile %w[
|
50
|
+
tmp = tempfile %w[ test3 .log ]
|
51
51
|
sleep 3
|
52
52
|
stats = stop_watch
|
53
53
|
assert stats.include?(tmp.path)
|
@@ -57,7 +57,7 @@ class TestFranzWatch < MiniTest::Test
|
|
57
57
|
def test_handles_new_file_with_content
|
58
58
|
start_watch
|
59
59
|
content = "Hello, world!\n"
|
60
|
-
tmp = tempfile %w[
|
60
|
+
tmp = tempfile %w[ test4 .log ]
|
61
61
|
tmp.write content
|
62
62
|
tmp.flush
|
63
63
|
sleep 3
|
@@ -70,7 +70,7 @@ class TestFranzWatch < MiniTest::Test
|
|
70
70
|
long_content = "Hello, world!\n"
|
71
71
|
short_content = "Bye!\n"
|
72
72
|
|
73
|
-
tmp = tempfile %w[
|
73
|
+
tmp = tempfile %w[ test5 .log ]
|
74
74
|
tmp.write long_content
|
75
75
|
tmp.flush
|
76
76
|
|
@@ -91,12 +91,12 @@ class TestFranzWatch < MiniTest::Test
|
|
91
91
|
content1 = "Hello, world!\n"
|
92
92
|
content2 = "Bye!\n"
|
93
93
|
|
94
|
-
tmp1 = tempfile %w[
|
94
|
+
tmp1 = tempfile %w[ test6 .log ]
|
95
95
|
tmp1.write content1
|
96
96
|
tmp1.flush
|
97
97
|
tmp1.close
|
98
98
|
|
99
|
-
tmp2 = tempfile %w[
|
99
|
+
tmp2 = tempfile %w[ exclude6 .log ]
|
100
100
|
tmp2.write content2
|
101
101
|
tmp2.flush
|
102
102
|
tmp2.close
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'tmpdir'
|
3
|
+
require 'tempfile'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'pathname'
|
6
|
+
require 'minitest/autorun'
|
7
|
+
|
8
|
+
require 'deep_merge'
|
9
|
+
|
10
|
+
require_relative '../lib/franz'
|
11
|
+
|
12
|
+
Thread.abort_on_exception = true
|
13
|
+
|
14
|
+
class TestPerformance < MiniTest::Test
|
15
|
+
def setup
|
16
|
+
@ulimit = Process.getrlimit(:NOFILE).first
|
17
|
+
|
18
|
+
@discover_interval = 2
|
19
|
+
@watch_interval = 2
|
20
|
+
@eviction_interval = 2
|
21
|
+
@flush_interval = 2
|
22
|
+
|
23
|
+
@tmpdir = File.join(Dir.pwd, 'tmp')
|
24
|
+
@discoveries = Queue.new
|
25
|
+
@deletions = Queue.new
|
26
|
+
@watch_events = Queue.new
|
27
|
+
@tail_events = Queue.new
|
28
|
+
@agg_events = Queue.new
|
29
|
+
@logger = Logger.new STDERR
|
30
|
+
@logger.level = Logger::WARN
|
31
|
+
|
32
|
+
FileUtils.rm_rf @tmpdir
|
33
|
+
FileUtils.mkdir_p @tmpdir
|
34
|
+
end
|
35
|
+
|
36
|
+
def teardown
|
37
|
+
# nop
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_handles_too_many_files_for_ulimit
|
41
|
+
sample = "Why, hello there, World! How lovely to see you this morning."
|
42
|
+
num_files = @ulimit * 2
|
43
|
+
num_lines_per_file = 100
|
44
|
+
|
45
|
+
paths = []
|
46
|
+
num_files.times do |i|
|
47
|
+
path = File.join(@tmpdir, "test.#{i}.log")
|
48
|
+
File.open(path, 'w') do |f|
|
49
|
+
num_lines_per_file.times do
|
50
|
+
f.puts sample
|
51
|
+
end
|
52
|
+
end
|
53
|
+
paths << path
|
54
|
+
end
|
55
|
+
|
56
|
+
num_events = num_files * num_lines_per_file
|
57
|
+
|
58
|
+
start_agg
|
59
|
+
started = Time.now
|
60
|
+
until @agg_events.size == num_events
|
61
|
+
sleep 1
|
62
|
+
end
|
63
|
+
seqs = stop_agg
|
64
|
+
elapsed = Time.now - started
|
65
|
+
|
66
|
+
@logger.fatal('%ds elapsed' % elapsed)
|
67
|
+
@logger.fatal('%d events' % num_events)
|
68
|
+
@logger.fatal('%f events/s' % ( (1.0 * num_events) / (1.0 * elapsed) ))
|
69
|
+
assert_equal(paths.size, seqs.keys.size)
|
70
|
+
assert_equal(paths.size * num_lines_per_file, @agg_events.size)
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
|
75
|
+
private
|
76
|
+
def tempfile prefix=nil
|
77
|
+
Tempfile.new prefix, @tmpdir
|
78
|
+
end
|
79
|
+
|
80
|
+
def realpath path
|
81
|
+
Pathname.new(path).realpath.to_s.gsub(/^\/private/, '')
|
82
|
+
end
|
83
|
+
|
84
|
+
def start_agg opts={}
|
85
|
+
configs = [{
|
86
|
+
type: :test,
|
87
|
+
includes: [ "#{@tmpdir}/*.log" ],
|
88
|
+
excludes: [ "#{@tmpdir}/exclude*" ]
|
89
|
+
}]
|
90
|
+
|
91
|
+
@discover = Franz::Discover.new({
|
92
|
+
discover_interval: @discover_interval,
|
93
|
+
discoveries: @discoveries,
|
94
|
+
deletions: @deletions,
|
95
|
+
logger: @logger,
|
96
|
+
configs: configs
|
97
|
+
}.deep_merge!(opts))
|
98
|
+
|
99
|
+
@watch = Franz::Watch.new({
|
100
|
+
watch_interval: @watch_interval,
|
101
|
+
watch_events: @watch_events,
|
102
|
+
discoveries: @discoveries,
|
103
|
+
deletions: @deletions,
|
104
|
+
logger: @logger
|
105
|
+
}.deep_merge!(opts))
|
106
|
+
|
107
|
+
@tail = Franz::Tail.new({
|
108
|
+
eviction_interval: @eviction_interval,
|
109
|
+
watch_events: @watch_events,
|
110
|
+
tail_events: @tail_events,
|
111
|
+
logger: @logger
|
112
|
+
}.deep_merge!(opts))
|
113
|
+
|
114
|
+
@agg = Franz::Agg.new({
|
115
|
+
configs: configs,
|
116
|
+
flush_interval: @flush_interval,
|
117
|
+
tail_events: @tail_events,
|
118
|
+
agg_events: @agg_events,
|
119
|
+
logger: @logger
|
120
|
+
}.deep_merge!(opts))
|
121
|
+
end
|
122
|
+
|
123
|
+
def stop_agg
|
124
|
+
@agg.stop
|
125
|
+
end
|
126
|
+
end
|