fluent-plugin-kinesis 0.4.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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