fluent-plugin-buffer-event_limited 0.1.4 → 0.1.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d9837aeb9ac272dabafad51da6d4e94209894cb2
4
- data.tar.gz: ccc9d6a9d68ae52e67679c8d0a23ad2907bb47c4
3
+ metadata.gz: 9ab9aa64fad78b7f58fb453867cee20b4c87b7fa
4
+ data.tar.gz: 5443046e647187e4bf5ee215bcb925ddc6a50092
5
5
  SHA512:
6
- metadata.gz: 22ddeaedff64fd9c1bea85e4f54e2c3ad44f24f1b4d844e9d2413103abb506322420d326b8efb20c260b8935f567d70d5cd53be0796cc75b82ea2c4a8a315317
7
- data.tar.gz: 2e7ff811aeb6f0397bcd2d62d8dd373fa034939ee7c8513c2a3e181165cce725fc6a3ab3d98f16727b46022c3ae835717452196b4a7eea68db9b7cef6c6340df
6
+ metadata.gz: 88483e3ca720d2518657c686b5fa5a0a3c3cacb8689e302725d628d08d557ea0efcec28b85c6ad1c70430c154cc702fc9766c05bfcd662249877c9d9c1c5c218
7
+ data.tar.gz: 653ab305d9e8c33fd4898494c5f8bb1c96ee14b66f2df9d463101cf8ac4183a7caa7f188e213f57af462266e9ecbe7e3b0b02e0578a24fd1c5b14adb05de5d50
data/README.md CHANGED
@@ -2,10 +2,9 @@
2
2
 
3
3
  This gem is a mutation of the [fluent-plugin-buffer-lightening](https://github.com/tagomoris/fluent-plugin-buffer-lightening) buffer plugin by [tagomoris](https://github.com/tagomoris).
4
4
 
5
- [Fluentd](http://fluentd.org) buffer plugin on memory to flush with many types of chunk limit methods:
6
- * events count limit in chunk
7
-
8
- These options are to decrease latency from emit to write, and to control chunk sizes and flush sizes.
5
+ * The buffer is able to limit the number of events that are buffered in a buffer chunk.
6
+ * The buffer only supports output plugins that return msgpack from their `#format` methods.
7
+ * The buffer doesn't check the bytesize of the buffers just the number of messages
9
8
 
10
9
  ## Installation
11
10
 
@@ -33,9 +32,6 @@ Options of `buffer_type file` are also available:
33
32
  buffer_type event_limited
34
33
  buffer_chunk_limit 10M
35
34
  buffer_chunk_records_limit 100
36
- buffer_chunk_message_separator newline
37
- # buffer_chunk_message_separator tab
38
- # buffer_chunk_message_separator msgpack
39
35
  # other options...
40
36
  </match>
41
37
  ```
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "fluent-plugin-buffer-event_limited"
5
- spec.version = "0.1.4"
5
+ spec.version = "0.1.5"
6
6
  spec.authors = ["TAGOMORI Satoshi", 'Gergo Sulymosi']
7
7
  spec.email = ["tagomoris@gmail.com", 'gergo.sulymosi@gmail.com']
8
8
  spec.description = %q{Fluentd memory buffer plugin with many types of chunk limits}
@@ -18,5 +18,6 @@ Gem::Specification.new do |spec|
18
18
  spec.add_development_dependency "bundler", "~> 1.3"
19
19
  spec.add_development_dependency "rake"
20
20
  spec.add_development_dependency "test-unit"
21
+ spec.add_development_dependency "pry"
21
22
  spec.add_runtime_dependency "fluentd", ">= 0.10.42"
22
23
  end
@@ -1,39 +1,57 @@
1
1
  require 'fluent/plugin/buf_file'
2
+ require 'stringio'
2
3
 
3
4
  module Fluent
4
- class EventLimitedBufferChunk < FileBufferChunk
5
- attr_reader :record_counter
5
+ class MessagePackFormattedBufferData
6
+ attr_reader :data
7
+
8
+ def initialize(data)
9
+ @data = data.to_str.freeze
10
+ end
6
11
 
7
- def initialize(key, path, unique_id, separator, mode = "a+", symlink_path = nil)
8
- super(key, path, unique_id, mode = "a+", symlink_path = nil)
9
- init_counter(separator)
12
+ def records
13
+ @records ||= (data.empty? ? [] : unpack(data)).freeze
10
14
  end
11
15
 
12
- def <<(data)
13
- result = super
14
- @record_counter += 1
16
+ def as_events
17
+ records.dup
18
+ end
15
19
 
16
- return result
20
+ def size
21
+ @size ||= records.size
17
22
  end
18
23
 
24
+ alias_method :to_str, :data
25
+ alias_method :as_msg_pack, :data
26
+
19
27
  private
20
28
 
21
- def init_counter(separator)
22
- old_pos = @file.pos
23
- @file.rewind
24
-
25
- @record_counter = (
26
- case separator
27
- when 'msgpack' then MessagePack::Unpacker.new(@file).each
28
- when 'newline' then @file.each($/)
29
- when 'tab' then @file.each("\t")
30
- else
31
- raise ArgumentError, "Separator #{separator.inspect} is not supported"
32
- end
33
- ).inject(0) { |c, _| c + 1 }
29
+ def unpack(data)
30
+ MessagePack::Unpacker.new(StringIO.new(data)).each.to_a
31
+ end
32
+ end
33
+
34
+ class EventLimitedBufferChunk < FileBufferChunk
35
+ attr_reader :record_count, :limit
36
+
37
+ def initialize(key, path, unique_id, limit, mode = "a+")
38
+ super(key, path, unique_id, mode = "a+")
39
+ @limit = limit
40
+ @record_count = MessagePackFormattedBufferData.new(read).size
41
+ end
42
+
43
+ def <<(data, record_count)
44
+ super(data)
45
+ @record_count += record_count
46
+ end
47
+ alias_method :write, :<<
48
+
49
+ def remaining_capacity
50
+ @limit - record_count
51
+ end
34
52
 
35
- @file.pos = old_pos
36
- $log.trace("#init_counter(#{[path, separator].join(', ')}) => #{@record_counter}")
53
+ def full?
54
+ record_count >= limit
37
55
  end
38
56
  end
39
57
 
@@ -41,11 +59,35 @@ module Fluent
41
59
  Fluent::Plugin.register_buffer('event_limited', self)
42
60
 
43
61
  config_param :buffer_chunk_records_limit, :integer, :default => Float::INFINITY
44
- config_param :buffer_chunk_message_separator, :string, :default => 'msgpack'
45
62
 
46
- def storable?(chunk, data)
47
- (chunk.record_counter < @buffer_chunk_records_limit) &&
48
- ((chunk.size + data.bytesize) <= @buffer_chunk_limit)
63
+ def emit(key, data, chain)
64
+ data = MessagePackFormattedBufferData.new(data)
65
+ key = key.to_s
66
+ flush_trigger = false
67
+
68
+ synchronize do
69
+ # Get the active chunk if it exists
70
+ chunk = (@map[key] ||= new_chunk(key))
71
+
72
+ # Partition the data into chunks that can be written into new chunks
73
+ events = data.as_events
74
+ [
75
+ events.shift(chunk.remaining_capacity),
76
+ *events.each_slice(@buffer_chunk_records_limit)
77
+ ].each do |event_group|
78
+ chunk, queue_size = rotate_chunk!(chunk, key)
79
+ # Trigger flush only when we put the first chunk into it
80
+ flush_trigger ||= (queue_size == 0)
81
+
82
+ chain.next
83
+ chunk.write(
84
+ event_group.map { |d| MessagePack.pack(d) }.join(''),
85
+ event_group.size
86
+ )
87
+ end
88
+
89
+ return flush_trigger
90
+ end
49
91
  end
50
92
 
51
93
  def new_chunk(key)
@@ -53,7 +95,7 @@ module Fluent
53
95
  path, tsuffix = make_path(encoded_key, 'b')
54
96
  unique_id = tsuffix_to_unique_id(tsuffix)
55
97
 
56
- EventLimitedBufferChunk.new(key, path, unique_id, @buffer_chunk_message_separator, 'a+', @symlink_path)
98
+ chunk_factory(key, path, unique_id, 'a+')
57
99
  end
58
100
 
59
101
  # Copied here from
@@ -74,11 +116,9 @@ module Fluent
74
116
 
75
117
  case bq
76
118
  when 'b'
77
- chunk = EventLimitedBufferChunk.new(key, path, unique_id, @buffer_chunk_message_separator, "a+")
78
- maps << [timestamp, chunk]
119
+ maps << [timestamp, chunk_factory(key, path, unique_id, 'a+')]
79
120
  when 'q'
80
- chunk = EventLimitedBufferChunk.new(key, path, unique_id, @buffer_chunk_message_separator, "r")
81
- queues << [timestamp, chunk]
121
+ queues << [timestamp, chunk_factory(key, path, unique_id, 'r')]
82
122
  end
83
123
  end
84
124
 
@@ -93,5 +133,29 @@ module Fluent
93
133
 
94
134
  return queue, map
95
135
  end
136
+
137
+ private
138
+
139
+ def rotate_chunk!(chunk, key)
140
+ queue_size = nil
141
+ return chunk unless chunk.full?
142
+
143
+ @queue.synchronize do
144
+ queue_size = @queue.size
145
+ enqueue(chunk) # this is buffer enqueue *hook*
146
+ @queue << chunk
147
+ chunk = (@map[key] = new_chunk(key))
148
+ end
149
+
150
+ return chunk, queue_size
151
+ end
152
+
153
+ def storable?(chunk, data)
154
+ (chunk.record_count + data.size) <= @buffer_chunk_records_limit
155
+ end
156
+
157
+ def chunk_factory(key, path, uniq_id, mode)
158
+ EventLimitedBufferChunk.new(key, path, uniq_id, @buffer_chunk_records_limit, mode)
159
+ end
96
160
  end
97
161
  end
@@ -29,7 +29,6 @@ class EventLimitedFileBufferTest < Test::Unit::TestCase
29
29
  flush_interval 0.1
30
30
  try_flush_interval 0.03
31
31
  buffer_chunk_records_limit 10
32
- buffer_chunk_message_separator newline
33
32
  buffer_path #{@buffer_path}
34
33
  ]
35
34
  end
@@ -43,7 +42,6 @@ class EventLimitedFileBufferTest < Test::Unit::TestCase
43
42
  def create_buffer_with_attributes(config = {})
44
43
  config = {
45
44
  'buffer_path' => @buffer_path,
46
- 'buffer_chunk_message_separator' => 'newline'
47
45
  }.merge(config)
48
46
  buf = Fluent::EventLimitedFileBuffer.new
49
47
  Fluent::EventLimitedFileBuffer.send(:class_variable_set, :'@@buffer_paths', {})
@@ -62,124 +60,123 @@ class EventLimitedFileBufferTest < Test::Unit::TestCase
62
60
  assert_equal 0.1, output.flush_interval
63
61
  assert_equal 0.03, output.try_flush_interval
64
62
  assert_equal 10, buffer.buffer_chunk_records_limit
65
- assert_equal 'newline', buffer.buffer_chunk_message_separator
66
63
  end
67
64
 
68
65
  def test_emit
69
66
  d = create_driver
70
-
71
67
  buffer = d.instance.instance_variable_get(:@buffer)
72
- assert buffer
73
- buffer.start
68
+ count_buffer_events = -> { buffer.instance_variable_get(:@map)[''].record_count }
74
69
 
75
- assert_nil buffer.instance_variable_get(:@map)['']
70
+ buffer.start
71
+ assert_nil buffer.instance_variable_get(:@map)[''], "No chunks on start"
76
72
 
77
73
  d.emit({"a" => 1})
78
- assert_equal 1, buffer.instance_variable_get(:@map)[''].record_counter
74
+ assert_equal 1, count_buffer_events.call
79
75
 
80
- d.emit({"a" => 2}); d.emit({"a" => 3}); d.emit({"a" => 4})
81
- d.emit({"a" => 5}); d.emit({"a" => 6}); d.emit({"a" => 7});
82
- d.emit({"a" => 8});
83
- assert_equal 8, buffer.instance_variable_get(:@map)[''].record_counter
76
+ (2..9).each { |i| d.emit({"a" => i}) }
77
+ assert_equal 9, count_buffer_events.call
84
78
 
85
79
  chain = DummyChain.new
86
80
  tag = d.instance.instance_variable_get(:@tag)
87
81
  time = Time.now.to_i
88
82
 
89
83
  # flush_trigger false
90
- assert !buffer.emit(tag, d.instance.format(tag, time, {"a" => 9}), chain)
91
- assert_equal 9, buffer.instance_variable_get(:@map)[''].record_counter
92
-
93
- # flush_trigger false
94
- assert !buffer.emit(tag, d.instance.format(tag, time, {"a" => 10}), chain)
95
- assert_equal 10, buffer.instance_variable_get(:@map)[''].record_counter
84
+ assert !buffer.emit(tag, d.instance.format(tag, time, {"a" => 10}), chain), "Shouldn't trigger flush"
85
+ assert_equal 10, count_buffer_events.call
96
86
 
97
87
  # flush_trigger true
98
- assert buffer.emit(tag, d.instance.format(tag, time, {"a" => 11}), chain)
99
- assert_equal 1, buffer.instance_variable_get(:@map)[''].record_counter # new chunk
88
+ assert buffer.emit(tag, d.instance.format(tag, time, {"a" => 11}), chain), "Should trigger flush"
89
+ assert_equal 1, count_buffer_events.call # new chunk
100
90
 
101
91
  # flush_trigger false
102
- assert !buffer.emit(tag, d.instance.format(tag, time, {"a" => 12}), chain)
103
- assert_equal 2, buffer.instance_variable_get(:@map)[''].record_counter
92
+ assert !buffer.emit(tag, d.instance.format(tag, time, {"a" => 12}), chain), "Shouldn't trigger flush"
93
+ assert_equal 2, count_buffer_events.call
104
94
  end
105
95
 
106
- def test_resume_from_plain_text_chunk
107
- # Setup buffer to test chunks
108
- buf1, prefix, suffix = create_buffer_with_attributes
109
- buf1.start
96
+ def test_emit_with_oversized_streams
97
+ d = create_driver
98
+ buffer = d.instance.instance_variable_get(:@buffer)
99
+ chain = DummyChain.new
100
+ tag = d.instance.instance_variable_get(:@tag)
101
+ time = Time.now.to_i
102
+ count_buffer_events = -> { buffer.instance_variable_get(:@map)[''].record_count }
103
+ count_queued_buffers = -> { buffer.instance_variable_get(:@queue).size }
110
104
 
111
- # Create chunks to test
112
- chunk1 = buf1.new_chunk('key1')
113
- chunk2 = buf1.new_chunk('key2')
114
- assert_equal 0, chunk1.record_counter
115
- assert_equal 0, chunk2.record_counter
105
+ buffer.start
116
106
 
117
- # Write data into chunks
118
- chunk1 << "data1\ndata2\n"
119
- chunk2 << "data3\ndata4\n"
107
+ events = 21.times.map { |i| [time, {a: i}] }
108
+ event_stream = d.instance.format_stream(tag, events)
109
+ assert buffer.emit(tag, event_stream, chain), "Should trigger flush"
110
+ assert_equal 2, count_queued_buffers.call, "Data should fill up two buffers"
111
+ assert_equal 1, count_buffer_events.call, "Data should overflow into a new buffer"
112
+ assert buffer.instance_variable_get(:@queue).all? { |b| b.record_count == 10 }
113
+ end
120
114
 
121
- # Enqueue chunk1 and leave chunk2 open
122
- buf1.enqueue(chunk1)
123
- assert \
124
- chunk1.path =~ /\A#{prefix}[-_.a-zA-Z0-9\%]+\.q[0-9a-f]+#{suffix}\Z/,
125
- "chunk1 must be enqueued"
126
- assert \
127
- chunk2.path =~ /\A#{prefix}[-_.a-zA-Z0-9\%]+\.b[0-9a-f]+#{suffix}\Z/,
128
- "chunk2 is not enqueued yet"
129
- buf1.shutdown
115
+ def test_emit_with_oversized_streams_and_ongoing_buffer_chunks
116
+ d = create_driver
117
+ buffer = d.instance.instance_variable_get(:@buffer)
118
+ chain = DummyChain.new
119
+ tag = d.instance.instance_variable_get(:@tag)
120
+ time = Time.now.to_i
121
+ count_buffer_events = -> { buffer.instance_variable_get(:@map)[''].record_count }
122
+ count_queued_buffers = -> { buffer.instance_variable_get(:@queue).size }
130
123
 
131
- # Setup a new buffer to test resume
132
- buf2, *_ = create_buffer_with_attributes
133
- queue, map = buf2.resume
124
+ buffer.start
134
125
 
135
- # Returns with the open and the closed buffers
136
- assert_equal 1, queue.size # closed buffer
137
- assert_equal 1, map.values.size # open buffer
126
+ data_streams = [2, 21].map do |stream_size|
127
+ d.instance.format_stream(
128
+ tag,
129
+ stream_size.times.map { |i| [time, {a: i}] }
130
+ )
131
+ end
138
132
 
139
- # The paths of the resumed chunks are the same but they themselfs are not
140
- resumed_chunk1 = queue.first
141
- resumed_chunk2 = map.values.first
142
- assert_equal chunk1.path, resumed_chunk1.path
143
- assert_equal chunk2.path, resumed_chunk2.path
144
- assert chunk1 != resumed_chunk1
145
- assert chunk2 != resumed_chunk2
133
+ assert !buffer.emit(tag, data_streams[0], chain), "Should not trigger flush"
134
+ assert buffer.emit(tag, data_streams[1], chain), "Should trigger flush"
146
135
 
147
- # Resume with the proper type of buffer chunk
148
- assert_equal Fluent::EventLimitedBufferChunk, resumed_chunk1.class
149
- assert_equal Fluent::EventLimitedBufferChunk, resumed_chunk2.class
136
+ assert_equal 2, count_queued_buffers.call, "Data should fill up two buffers"
137
+ assert_equal 3, count_buffer_events.call, "Data should overflow into a new buffer"
138
+ assert buffer.instance_variable_get(:@queue).all? { |b| b.record_count == 10 }
139
+ end
140
+
141
+ def test_new_chunk
142
+ d = create_driver
143
+ buffer = d.instance.instance_variable_get(:@buffer)
150
144
 
151
- assert_equal "data1\ndata2\n", resumed_chunk1.read
152
- assert_equal "data3\ndata4\n", resumed_chunk2.read
145
+ chunk1 = buffer.new_chunk('')
146
+ chunk2 = buffer.new_chunk('')
153
147
 
154
- assert_equal 2, resumed_chunk1.record_counter
155
- assert_equal 2, resumed_chunk2.record_counter
148
+ assert chunk1 != chunk2
149
+ assert chunk1.path != chunk2.path
156
150
  end
157
151
 
158
152
  def test_resume_from_msgpack_chunks
153
+ d = create_driver
154
+ events = 2.times.map { |i| [Time.now.to_i, {a: i}] }
155
+ event_stream = d.instance.format_stream('test', events)
159
156
  # Setup buffer to test chunks
160
- buf1, prefix, suffix = create_buffer_with_attributes({'buffer_chunk_message_separator' => 'msgpack'})
157
+ buf1, prefix, suffix = create_buffer_with_attributes
161
158
  buf1.start
162
159
 
163
160
  # Create chunks to test
164
161
  chunk1 = buf1.new_chunk('key1')
165
162
  chunk2 = buf1.new_chunk('key2')
166
- assert_equal 0, chunk1.record_counter
167
- assert_equal 0, chunk2.record_counter
163
+ assert_equal 0, chunk1.record_count
164
+ assert_equal 0, chunk2.record_count
168
165
 
169
166
  # Write data into chunks
170
- chunk1 << MessagePack.pack('data1')
171
- chunk1 << MessagePack.pack('data2')
172
- chunk2 << MessagePack.pack('data3')
173
- chunk2 << MessagePack.pack('data4')
167
+ chunk1.write(event_stream, 2)
168
+ chunk2.write(event_stream, 2)
174
169
 
175
170
  # Enqueue chunk1 and leave chunk2 open
176
171
  buf1.enqueue(chunk1)
177
- assert \
172
+ assert(
178
173
  chunk1.path =~ /\A#{prefix}[-_.a-zA-Z0-9\%]+\.q[0-9a-f]+#{suffix}\Z/,
179
174
  "chunk1 must be enqueued"
180
- assert \
175
+ )
176
+ assert(
181
177
  chunk2.path =~ /\A#{prefix}[-_.a-zA-Z0-9\%]+\.b[0-9a-f]+#{suffix}\Z/,
182
178
  "chunk2 is not enqueued yet"
179
+ )
183
180
  buf1.shutdown
184
181
 
185
182
  # Setup a new buffer to test resume
@@ -202,14 +199,10 @@ class EventLimitedFileBufferTest < Test::Unit::TestCase
202
199
  assert_equal Fluent::EventLimitedBufferChunk, resumed_chunk1.class
203
200
  assert_equal Fluent::EventLimitedBufferChunk, resumed_chunk2.class
204
201
 
205
- assert_equal \
206
- MessagePack.pack('data1') + MessagePack.pack('data2'),
207
- resumed_chunk1.read
208
- assert_equal \
209
- MessagePack.pack('data3') + MessagePack.pack('data4'),
210
- resumed_chunk2.read
202
+ assert_equal event_stream, resumed_chunk1.read
203
+ assert_equal event_stream, resumed_chunk2.read
211
204
 
212
- assert_equal 2, resumed_chunk1.record_counter
213
- assert_equal 2, resumed_chunk2.record_counter
205
+ assert_equal 2, resumed_chunk1.record_count
206
+ assert_equal 2, resumed_chunk2.record_count
214
207
  end
215
208
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-buffer-event_limited
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - TAGOMORI Satoshi
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-11-25 00:00:00.000000000 Z
12
+ date: 2015-11-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -53,6 +53,20 @@ dependencies:
53
53
  - - ">="
54
54
  - !ruby/object:Gem::Version
55
55
  version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: pry
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
56
70
  - !ruby/object:Gem::Dependency
57
71
  name: fluentd
58
72
  requirement: !ruby/object:Gem::Requirement