fluent-plugin-kinesis 0.4.1 → 1.0.0

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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +13 -18
  3. data/.travis.yml +9 -9
  4. data/CHANGELOG.md +9 -0
  5. data/CONTRIBUTORS.txt +1 -1
  6. data/Gemfile +12 -9
  7. data/LICENSE.txt +39 -201
  8. data/Makefile +40 -0
  9. data/NOTICE.txt +1 -1
  10. data/README-v0.4.md +348 -0
  11. data/README.md +398 -183
  12. data/Rakefile +20 -14
  13. data/benchmark/dummer.conf +13 -0
  14. data/benchmark/firehose.conf +24 -0
  15. data/benchmark/producer.conf +28 -0
  16. data/benchmark/streams.conf +24 -0
  17. data/fluent-plugin-kinesis.gemspec +34 -23
  18. data/gemfiles/Gemfile.fluentd-0.10.58 +20 -0
  19. data/lib/fluent/plugin/kinesis_helper.rb +30 -0
  20. data/lib/fluent/plugin/kinesis_helper/api.rb +164 -0
  21. data/lib/fluent/plugin/kinesis_helper/class_methods.rb +120 -0
  22. data/lib/fluent/plugin/kinesis_helper/client.rb +36 -0
  23. data/lib/fluent/plugin/kinesis_helper/credentials.rb +51 -0
  24. data/lib/fluent/plugin/kinesis_helper/error.rb +38 -0
  25. data/lib/fluent/plugin/kinesis_helper/format.rb +85 -0
  26. data/lib/fluent/plugin/kinesis_helper/initialize.rb +58 -0
  27. data/lib/fluent/plugin/kinesis_helper/kpl.rb +81 -0
  28. data/lib/fluent/plugin/out_kinesis.rb +13 -11
  29. data/lib/fluent/plugin/out_kinesis_firehose.rb +44 -0
  30. data/lib/fluent/plugin/out_kinesis_producer.rb +38 -0
  31. data/lib/fluent/plugin/out_kinesis_streams.rb +47 -0
  32. data/lib/fluent/plugin/patched_detach_process_impl.rb +103 -0
  33. data/lib/fluent_plugin_kinesis/version.rb +17 -0
  34. data/lib/kinesis_producer.rb +24 -0
  35. data/lib/kinesis_producer/binary.rb +10 -0
  36. data/lib/kinesis_producer/daemon.rb +238 -0
  37. data/lib/kinesis_producer/library.rb +122 -0
  38. data/lib/kinesis_producer/protobuf/config.pb.rb +66 -0
  39. data/lib/kinesis_producer/protobuf/messages.pb.rb +151 -0
  40. data/lib/kinesis_producer/tasks/binary.rake +73 -0
  41. metadata +196 -36
  42. data/lib/fluent/plugin/version.rb +0 -16
  43. data/test/helper.rb +0 -32
  44. data/test/plugin/test_out_kinesis.rb +0 -641
@@ -0,0 +1,47 @@
1
+ #
2
+ # Copyright 2014-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ #
4
+ # Licensed under the Amazon Software License (the "License").
5
+ # You may not use this file except in compliance with the License.
6
+ # A copy of the License is located at
7
+ #
8
+ # http://aws.amazon.com/asl/
9
+ #
10
+ # or in the "license" file accompanying this file. This file is distributed
11
+ # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12
+ # express or implied. See the License for the specific language governing
13
+ # permissions and limitations under the License.
14
+
15
+ require 'fluent/plugin/kinesis_helper'
16
+
17
+ module Fluent
18
+ class KinesisStreamsOutput < BufferedOutput
19
+ include KinesisHelper
20
+ Fluent::Plugin.register_output('kinesis_streams', self)
21
+ config_param_for_streams
22
+
23
+ def write(chunk)
24
+ records = convert_to_records(chunk)
25
+ split_to_batches(records).each do |batch|
26
+ batch_request_with_retry(batch)
27
+ end
28
+ log.debug("Written #{records.size} records")
29
+ end
30
+
31
+ private
32
+
33
+ def convert_format(tag, time, record)
34
+ {
35
+ data: data_format(tag, time, record),
36
+ partition_key: key(record),
37
+ }
38
+ end
39
+
40
+ def batch_request(batch)
41
+ client.put_records(
42
+ stream_name: @stream_name,
43
+ records: batch,
44
+ )
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,103 @@
1
+ #
2
+ # Copyright 2014-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ #
4
+ # Licensed under the Amazon Software License (the "License").
5
+ # You may not use this file except in compliance with the License.
6
+ # A copy of the License is located at
7
+ #
8
+ # http://aws.amazon.com/asl/
9
+ #
10
+ # or in the "license" file accompanying this file. This file is distributed
11
+ # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12
+ # express or implied. See the License for the specific language governing
13
+ # permissions and limitations under the License.
14
+
15
+ module Fluent
16
+ module PatchedDetachProcessImpl
17
+ def on_detach_process(i)
18
+ end
19
+
20
+ def on_exit_process(i)
21
+ end
22
+
23
+ private
24
+
25
+ def detach_process_impl(num, &block)
26
+ children = []
27
+
28
+ num.times do |i|
29
+ pid, forward_thread = DetachProcessManager.instance.fork(self)
30
+
31
+ if pid
32
+ # parent process
33
+ $log.info "detached process", :class=>self.class, :pid=>pid
34
+ children << [pid, forward_thread]
35
+ next
36
+ end
37
+
38
+ # child process
39
+ begin
40
+ on_detach_process(i)
41
+
42
+ block.call
43
+
44
+ # disable Engine.stop called by signal handler
45
+ Engine.define_singleton_method(:stop) do
46
+ # do nothing
47
+ end
48
+ # override signal handlers called by parent process
49
+ fin = ::Fluent::DetachProcessImpl::FinishWait.new
50
+ trap :INT do
51
+ fin.stop
52
+ end
53
+ trap :TERM do
54
+ fin.stop
55
+ end
56
+ #forward_thread.join # TODO this thread won't stop because parent doesn't close pipe
57
+ fin.wait
58
+
59
+ on_exit_process(i)
60
+ exit! 0
61
+ ensure
62
+ $log.error "unknown error while shutting down this child process", :error=>$!.to_s, :pid=>Process.pid
63
+ $log.error_backtrace
64
+ end
65
+
66
+ exit! 1
67
+ end
68
+
69
+ # parent process
70
+ # override shutdown method to kill child processes
71
+ define_singleton_method(:shutdown) do
72
+ children.each {|pair|
73
+ begin
74
+ pid = pair[0]
75
+ forward_thread = pair[1]
76
+ if pid
77
+ Process.kill(:TERM, pid)
78
+ forward_thread.join # wait until child closes pipe
79
+ Process.waitpid(pid)
80
+ pair[0] = nil
81
+ end
82
+ rescue
83
+ $log.error "unknown error while shutting down remote child process", :error=>$!.to_s
84
+ $log.error_backtrace
85
+ end
86
+ }
87
+ end
88
+
89
+ # override target.emit and write event stream to the pipe
90
+ forwarders = children.map {|pair| pair[1].forwarder }
91
+ if forwarders.length > 1
92
+ # use roundrobin
93
+ fwd = DetachProcessManager::MultiForwarder.new(forwarders)
94
+ else
95
+ fwd = forwarders[0]
96
+ end
97
+ define_singleton_method(:emit) do |tag,es,chain|
98
+ chain.next
99
+ fwd.emit(tag, es)
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,17 @@
1
+ #
2
+ # Copyright 2014-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ #
4
+ # Licensed under the Amazon Software License (the "License").
5
+ # You may not use this file except in compliance with the License.
6
+ # A copy of the License is located at
7
+ #
8
+ # http://aws.amazon.com/asl/
9
+ #
10
+ # or in the "license" file accompanying this file. This file is distributed
11
+ # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12
+ # express or implied. See the License for the specific language governing
13
+ # permissions and limitations under the License.
14
+
15
+ module FluentPluginKinesis
16
+ VERSION = '1.0.0'
17
+ end
@@ -0,0 +1,24 @@
1
+ #
2
+ # Copyright 2014-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ #
4
+ # Licensed under the Amazon Software License (the "License").
5
+ # You may not use this file except in compliance with the License.
6
+ # A copy of the License is located at
7
+ #
8
+ # http://aws.amazon.com/asl/
9
+ #
10
+ # or in the "license" file accompanying this file. This file is distributed
11
+ # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12
+ # express or implied. See the License for the specific language governing
13
+ # permissions and limitations under the License.
14
+
15
+ require 'protobuf'
16
+ require 'kinesis_producer/binary'
17
+ require 'kinesis_producer/library'
18
+ require 'kinesis_producer/daemon'
19
+ require 'kinesis_producer/protobuf/config.pb'
20
+ require 'kinesis_producer/protobuf/messages.pb'
21
+
22
+ module KinesisProducer
23
+ ConfigurationFields = Protobuf::Configuration.all_fields.reject(&:repeated?)
24
+ end
@@ -0,0 +1,10 @@
1
+ module KinesisProducer
2
+ class Binary
3
+ Dir = 'amazon-kinesis-producer-native-binaries'
4
+ Files = {
5
+ 'linux' => File.join(Dir, 'linux', 'kinesis_producer'),
6
+ 'osx' => File.join(Dir, 'osx', 'kinesis_producer'),
7
+ 'windows' => File.join(Dir, 'windows', 'kinesis_producer.exe'),
8
+ }
9
+ end
10
+ end
@@ -0,0 +1,238 @@
1
+ #
2
+ # Copyright 2014-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ #
4
+ # Licensed under the Amazon Software License (the "License").
5
+ # You may not use this file except in compliance with the License.
6
+ # A copy of the License is located at
7
+ #
8
+ # http://aws.amazon.com/asl/
9
+ #
10
+ # or in the "license" file accompanying this file. This file is distributed
11
+ # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12
+ # express or implied. See the License for the specific language governing
13
+ # permissions and limitations under the License.
14
+
15
+ require 'tempfile'
16
+ require 'concurrent'
17
+
18
+ module KinesisProducer
19
+ class Daemon
20
+ FixnumMax = (2 ** (64 - 2)) - 1
21
+
22
+ def initialize(binary, handler, options)
23
+ @binary = binary
24
+ @handler = handler
25
+
26
+ @configuration = options[:configuration] || {}
27
+ @credentials = options[:credentials]
28
+ @metrics_credentials = options[:metrics_credentials]
29
+ @credentials_refresh_delay = options[:credentials_refresh_delay] || 5000
30
+ @logger = options[:logger]
31
+ @debug = options[:debug]
32
+
33
+ @executor = Concurrent::CachedThreadPool.new
34
+ @shutdown = Concurrent::AtomicBoolean.new(false)
35
+ @outgoing_messages = Queue.new
36
+ @incoming_messages = Queue.new
37
+
38
+ if debug?
39
+ @meters = {
40
+ add_message: Meter.new,
41
+ send_message: Meter.new,
42
+ receive_message: Meter.new,
43
+ return_message: Meter.new,
44
+ }
45
+ end
46
+ end
47
+
48
+ def start
49
+ @executor.post do
50
+ create_pipes
51
+ start_child
52
+ end
53
+ end
54
+
55
+ def destroy
56
+ @shutdown.make_true
57
+ if @pid
58
+ Process.kill("TERM", @pid)
59
+ Process.waitpid(@pid)
60
+ sleep 1 # TODO
61
+ end
62
+ delete_pipes
63
+ end
64
+
65
+ def add(message)
66
+ @outgoing_messages.push(message)
67
+ @meters[:add_message].mark if debug?
68
+ end
69
+
70
+ private
71
+
72
+ def create_pipes
73
+ @in_pipe = temp_pathname('amz-aws-kpl-in-pipe-')
74
+ @out_pipe = temp_pathname('amz-aws-kpl-out-pipe-')
75
+ system("mkfifo", @in_pipe.to_path, @out_pipe.to_path)
76
+ sleep 1 # TODO
77
+ end
78
+
79
+ def delete_pipes
80
+ @in_channel.close unless @in_channel.nil?
81
+ @out_channel.close unless @out_channel.nil?
82
+ @in_pipe.unlink
83
+ @out_pipe.unlink
84
+ rescue Errno::ENOENT
85
+ end
86
+
87
+ def temp_pathname(basename)
88
+ tempfile = Tempfile.new(basename)
89
+ ObjectSpace.undefine_finalizer(tempfile)
90
+ file = tempfile.path
91
+ File.delete(file)
92
+ Pathname.new(file)
93
+ end
94
+
95
+ def start_child
96
+ start_child_daemon
97
+ connect_to_child
98
+ start_loops
99
+ end
100
+
101
+ def start_child_daemon
102
+ @pid = Process.fork do
103
+ Process.setsid
104
+ configuration = make_configuration_message
105
+ credentials = make_set_credentials_message
106
+ command = [@binary, @out_pipe.to_path, @in_pipe.to_path, to_hex(configuration), to_hex(credentials)]
107
+ if @metrics_credentials
108
+ metrics_credentials = make_set_metrics_credentials_message
109
+ command.push(to_hex(metrics_credentials))
110
+ end
111
+ exec(*command)
112
+ end
113
+ sleep 1 # TODO
114
+ end
115
+
116
+ def connect_to_child
117
+ @in_channel = @in_pipe.open('r')
118
+ @out_channel = @out_pipe.open('w')
119
+ end
120
+
121
+ def start_loops
122
+ start_loop_for(:send_message)
123
+ start_loop_for(:receive_message)
124
+ start_loop_for(:return_message)
125
+ start_loop_for(:update_credentials)
126
+ start_loop_for(:tick) if debug?
127
+ end
128
+
129
+ def start_loop_for(method)
130
+ @executor.post do
131
+ while @shutdown.false?
132
+ send(method)
133
+ @meters[method].mark if debug? and @meters.include?(method)
134
+ end
135
+ end
136
+ end
137
+
138
+ def send_message
139
+ message = @outgoing_messages.pop
140
+ size = [message.size].pack('N*')
141
+ @out_channel.write(size)
142
+ @out_channel.write(message)
143
+ @out_channel.flush
144
+ end
145
+
146
+ def receive_message
147
+ size = @in_channel.read(4)
148
+ data = @in_channel.read(size.unpack('N*').first)
149
+ @incoming_messages.push(data)
150
+ end
151
+
152
+ def return_message
153
+ data = @incoming_messages.pop
154
+ message = KinesisProducer::Protobuf::Message.decode(data)
155
+ @handler.on_message(message)
156
+ end
157
+
158
+ def update_credentials
159
+ add(make_set_credentials_message)
160
+ add(make_set_metrics_credentials_message) if @metrics_credentials
161
+ sleep @credentials_refresh_delay
162
+ end
163
+
164
+ def make_configuration_message
165
+ configuration = @configuration
166
+ KinesisProducer::ConfigurationFields.each do |field|
167
+ if configuration[field.name].nil?
168
+ configuration[field.name] = field.default_value
169
+ end
170
+ end
171
+ config = KinesisProducer::Protobuf::Configuration.new(configuration)
172
+ make_message(0, :configuration, config)
173
+ end
174
+
175
+ def make_set_credentials_message
176
+ make_set_credential_message(@credentials)
177
+ end
178
+
179
+ def make_set_metrics_credentials_message
180
+ make_set_credential_message(@metrics_credentials, true)
181
+ end
182
+
183
+ def make_set_credential_message(credentials, for_metrics = false)
184
+ return nil if credentials.nil?
185
+ cred = KinesisProducer::Protobuf::Credentials.new(
186
+ akid: credentials.access_key_id,
187
+ secret_key: credentials.secret_access_key,
188
+ token: credentials.session_token
189
+ )
190
+ set_credentials = KinesisProducer::Protobuf::SetCredentials.new(credentials: cred, for_metrics: for_metrics)
191
+ make_message(FixnumMax, :set_credentials, set_credentials)
192
+ end
193
+
194
+ def make_message(id, target, value)
195
+ KinesisProducer::Protobuf::Message.new(id: id, target => value).encode
196
+ end
197
+
198
+ def to_hex(message)
199
+ message.unpack('H*').first
200
+ end
201
+
202
+ def tick
203
+ out = @meters.each_value.map do |meter|
204
+ sprintf("%5d", meter.tick)
205
+ end
206
+ @logger.debug("[#{Thread.current.object_id}] "+out.join(" ")) if debug?
207
+ sleep 1
208
+ end
209
+
210
+ def debug?
211
+ @debug
212
+ end
213
+
214
+ class Meter
215
+ def initialize
216
+ @count = Concurrent::AtomicFixnum.new(0)
217
+ @previous_tick_time = Time.now.to_f
218
+ @current_rate = 0.0
219
+ tick
220
+ end
221
+
222
+ def mark(count = 1)
223
+ @count.increment(count)
224
+ end
225
+
226
+ def tick
227
+ @current_rate = @count.value.to_f / (Time.now.to_f - @previous_tick_time)
228
+ @count.value = 0
229
+ @previous_tick_time = Time.now.to_f
230
+ current_rate
231
+ end
232
+
233
+ def current_rate
234
+ @current_rate
235
+ end
236
+ end
237
+ end
238
+ end
@@ -0,0 +1,122 @@
1
+ #
2
+ # Copyright 2014-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ #
4
+ # Licensed under the Amazon Software License (the "License").
5
+ # You may not use this file except in compliance with the License.
6
+ # A copy of the License is located at
7
+ #
8
+ # http://aws.amazon.com/asl/
9
+ #
10
+ # or in the "license" file accompanying this file. This file is distributed
11
+ # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12
+ # express or implied. See the License for the specific language governing
13
+ # permissions and limitations under the License.
14
+
15
+ require 'concurrent'
16
+ require 'os'
17
+
18
+ module KinesisProducer
19
+ class Library
20
+ class Handler
21
+ def initialize(futures)
22
+ @futures = futures
23
+ end
24
+
25
+ def on_message(message)
26
+ if !message.put_record_result.nil?
27
+ on_put_record_result(message)
28
+ elsif !message.metrics_response.nil?
29
+ on_metrics_response(message)
30
+ end
31
+ end
32
+
33
+ def on_put_record_result(message)
34
+ source_id = message.source_id
35
+ result = message.put_record_result
36
+ f = @futures.fetch(source_id)
37
+ @futures.delete(source_id)
38
+ if result.success
39
+ f.set(result)
40
+ else
41
+ f.fail(StandardError.new(result.to_hash))
42
+ end
43
+ end
44
+
45
+ def on_metrics_response(message)
46
+ source_id = message.source_id
47
+ response = message.metrics_response
48
+ f = @futures.fetch(source_id)
49
+ @futures.delete(source_id)
50
+ f.set(response.metrics)
51
+ end
52
+ end
53
+
54
+ class << self
55
+ def binary
56
+ case
57
+ when OS.linux?; Binary::Files['linux']
58
+ when OS.osx?; Binary::Files['osx']
59
+ when OS.windows?; Binary::Files['windows']
60
+ else; raise
61
+ end
62
+ end
63
+
64
+ def default_binary_path
65
+ root_dir = File.expand_path('../../..', __FILE__)
66
+ File.join(root_dir, binary)
67
+ end
68
+ end
69
+
70
+ def initialize(options)
71
+ @binary_path = options.delete(:binary_path) || self.class.default_binary_path
72
+ @message_id = Concurrent::AtomicFixnum.new(1)
73
+ @futures = Concurrent::Map.new
74
+ @child = Daemon.new(@binary_path, Handler.new(@futures), options)
75
+ @child.start
76
+ end
77
+
78
+ def destroy
79
+ flush_sync
80
+ @child.destroy
81
+ end
82
+
83
+ def put_record(options)
84
+ f = Concurrent::IVar.new
85
+ id = add_message(:put_record, KinesisProducer::Protobuf::PutRecord.new(options))
86
+ @futures[id] = f
87
+ f
88
+ end
89
+
90
+ def get_metrics(options = {})
91
+ f = Concurrent::IVar.new
92
+ id = add_message(:metrics_request, KinesisProducer::Protobuf::MetricsRequest.new(options))
93
+ @futures[id] = f
94
+ f.wait
95
+ f.value
96
+ end
97
+
98
+ def flush(options = {})
99
+ add_message(:flush, KinesisProducer::Protobuf::Flush.new(options))
100
+ end
101
+
102
+ def flush_sync
103
+ while @futures.size > 0
104
+ flush()
105
+ sleep 0.5
106
+ end
107
+ end
108
+
109
+ private
110
+
111
+ def add_message(target, value)
112
+ id = get_and_inc_message_id
113
+ message = KinesisProducer::Protobuf::Message.new(id: id, target => value)
114
+ @child.add(message.encode)
115
+ id
116
+ end
117
+
118
+ def get_and_inc_message_id
119
+ @message_id.increment
120
+ end
121
+ end
122
+ end