franz 1.3.1 → 1.4.14
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.
- 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
|