kafka 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rubocop.yml +210 -0
- data/.travis.yml +45 -0
- data/CHANGELOG.md +3 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +182 -0
- data/Rakefile +69 -0
- data/examples/consumer.rb +55 -0
- data/examples/producer.rb +46 -0
- data/ext/Rakefile +69 -0
- data/kafka.gemspec +39 -0
- data/lib/kafka/admin.rb +141 -0
- data/lib/kafka/config.rb +145 -0
- data/lib/kafka/consumer.rb +87 -0
- data/lib/kafka/error.rb +44 -0
- data/lib/kafka/ffi/admin/admin_options.rb +121 -0
- data/lib/kafka/ffi/admin/config_entry.rb +97 -0
- data/lib/kafka/ffi/admin/config_resource.rb +101 -0
- data/lib/kafka/ffi/admin/delete_topic.rb +19 -0
- data/lib/kafka/ffi/admin/new_partitions.rb +77 -0
- data/lib/kafka/ffi/admin/new_topic.rb +91 -0
- data/lib/kafka/ffi/admin/result.rb +66 -0
- data/lib/kafka/ffi/admin/topic_result.rb +32 -0
- data/lib/kafka/ffi/admin.rb +16 -0
- data/lib/kafka/ffi/broker_metadata.rb +32 -0
- data/lib/kafka/ffi/client.rb +640 -0
- data/lib/kafka/ffi/config.rb +382 -0
- data/lib/kafka/ffi/consumer.rb +342 -0
- data/lib/kafka/ffi/error.rb +25 -0
- data/lib/kafka/ffi/event.rb +215 -0
- data/lib/kafka/ffi/group_info.rb +75 -0
- data/lib/kafka/ffi/group_list.rb +27 -0
- data/lib/kafka/ffi/group_member_info.rb +52 -0
- data/lib/kafka/ffi/message/header.rb +205 -0
- data/lib/kafka/ffi/message.rb +205 -0
- data/lib/kafka/ffi/metadata.rb +58 -0
- data/lib/kafka/ffi/opaque.rb +81 -0
- data/lib/kafka/ffi/opaque_pointer.rb +73 -0
- data/lib/kafka/ffi/partition_metadata.rb +61 -0
- data/lib/kafka/ffi/producer.rb +144 -0
- data/lib/kafka/ffi/queue.rb +65 -0
- data/lib/kafka/ffi/topic.rb +32 -0
- data/lib/kafka/ffi/topic_config.rb +126 -0
- data/lib/kafka/ffi/topic_metadata.rb +42 -0
- data/lib/kafka/ffi/topic_partition.rb +43 -0
- data/lib/kafka/ffi/topic_partition_list.rb +167 -0
- data/lib/kafka/ffi.rb +624 -0
- data/lib/kafka/poller.rb +28 -0
- data/lib/kafka/producer/delivery_report.rb +120 -0
- data/lib/kafka/producer.rb +127 -0
- data/lib/kafka/version.rb +8 -0
- data/lib/kafka.rb +11 -0
- metadata +159 -0
@@ -0,0 +1,342 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ffi"
|
4
|
+
require "kafka/ffi/client"
|
5
|
+
|
6
|
+
module Kafka::FFI
|
7
|
+
class Consumer < Kafka::FFI::Client
|
8
|
+
native_type :pointer
|
9
|
+
|
10
|
+
def self.new(config = nil)
|
11
|
+
super(:consumer, config)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Retrieve the Consumer's broker assigned group Member ID.
|
15
|
+
#
|
16
|
+
# @return [String] broker assigned group Member ID
|
17
|
+
def member_id
|
18
|
+
id = ::Kafka::FFI.rd_kafka_memberid(self)
|
19
|
+
|
20
|
+
if id.null?
|
21
|
+
return nil
|
22
|
+
end
|
23
|
+
|
24
|
+
id.read_string
|
25
|
+
ensure
|
26
|
+
::Kafka::FFI.rd_kafka_mem_free(self, id)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Get the last known (cached) low and high offsets for the partition. This
|
30
|
+
# differs from query_watermark_offsets in that does not query the brokers.
|
31
|
+
#
|
32
|
+
# @see query_watermark_offsets
|
33
|
+
#
|
34
|
+
# @param topic [String] Name of the topic
|
35
|
+
# @param partition [Integer] Topic partition
|
36
|
+
#
|
37
|
+
# @raise [Kafka::ResponseError] Error that occurred retrieving offsets
|
38
|
+
#
|
39
|
+
# @return [Array<(Integer, Integer)>] low and high offsets. If either is
|
40
|
+
# unknown the RD_KAFKA_OFFSET_INVALID is returned for that value
|
41
|
+
def get_watermark_offsets(topic, partition)
|
42
|
+
low = ::FFI::MemoryPointer.new(:int64)
|
43
|
+
high = ::FFI::MemoryPointer.new(:int64)
|
44
|
+
|
45
|
+
err = ::Kafka::FFI.rd_kafka_get_watermark_offsets(self, topic, partition, low, high)
|
46
|
+
if err != :ok
|
47
|
+
raise ::Kafka::ResponseError, err
|
48
|
+
end
|
49
|
+
|
50
|
+
[low.read_int64, high.read_int64]
|
51
|
+
end
|
52
|
+
|
53
|
+
# rubocop:disable Naming/AccessorMethodName
|
54
|
+
|
55
|
+
# Returns a reference to the consume queue. This is the queue served by
|
56
|
+
# consumer_poll.
|
57
|
+
#
|
58
|
+
# @note Caller must call #destroy when done with the Queue.
|
59
|
+
#
|
60
|
+
# @return [Queue] Consumer queue
|
61
|
+
def get_consumer_queue
|
62
|
+
::Kafka::FFI.rd_kafka_queue_get_consumer(self)
|
63
|
+
end
|
64
|
+
# rubocop:enable Naming/AccessorMethodName
|
65
|
+
|
66
|
+
# Returns a reference to the partition's queue.
|
67
|
+
#
|
68
|
+
# @note Caller must call #destroy when done with the Queue.
|
69
|
+
#
|
70
|
+
# @return [Queue] Partition Queue
|
71
|
+
def get_partition_queue(topic, partition)
|
72
|
+
::Kafka::FFI.rd_kafka_queue_get_partition(self, topic, partition)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Redirect the main event queue to the Consumer's queue so the consumer
|
76
|
+
# doesn't need to poll from it separately for event callbacks to fire.
|
77
|
+
#
|
78
|
+
# @note It is not permitted to call #poll after redirecting the main queue
|
79
|
+
# with poll_set_consumer.
|
80
|
+
#
|
81
|
+
# @raise [Kafka::ResponseError] Error occurred redirecting the main
|
82
|
+
# queue.
|
83
|
+
def poll_set_consumer
|
84
|
+
err = ::Kafka::FFI.rd_kafka_poll_set_consumer(self)
|
85
|
+
if err != :ok
|
86
|
+
raise ::Kafka::ResponseError, err
|
87
|
+
end
|
88
|
+
|
89
|
+
nil
|
90
|
+
end
|
91
|
+
|
92
|
+
# Subscribe the consumer to receive Messages for a set of topics. The
|
93
|
+
# current set of subscriptions will be replaced.
|
94
|
+
#
|
95
|
+
# @example Subscribe to multiple topics
|
96
|
+
# client.subscribe("signals", "events", "changes")
|
97
|
+
#
|
98
|
+
# @param [String, Array<String>] Topic name or list of topics to subscribe
|
99
|
+
# to.
|
100
|
+
#
|
101
|
+
# @raise [Kafka::ResponseError] Error occurred subscribing to the topics.
|
102
|
+
def subscribe(topic, *rest)
|
103
|
+
topics = [topic, rest].flatten
|
104
|
+
|
105
|
+
tpl = TopicPartitionList.new(topics.length)
|
106
|
+
topics.each do |t|
|
107
|
+
tpl.add(t)
|
108
|
+
end
|
109
|
+
|
110
|
+
err = ::Kafka::FFI.rd_kafka_subscribe(self, tpl)
|
111
|
+
if err != :ok
|
112
|
+
raise ::Kafka::ResponseError, err
|
113
|
+
end
|
114
|
+
|
115
|
+
nil
|
116
|
+
ensure
|
117
|
+
tpl.destroy
|
118
|
+
end
|
119
|
+
|
120
|
+
# Unsubscribe from the current subscription set (e.g. all current
|
121
|
+
# subscriptions).
|
122
|
+
#
|
123
|
+
# @raise [Kafka::ResponseError] Error unsubscribing from topics
|
124
|
+
def unsubscribe
|
125
|
+
err = ::Kafka::FFI.rd_kafka_unsubscribe(self)
|
126
|
+
if err != :ok
|
127
|
+
raise ::Kafka::ResponseError, err
|
128
|
+
end
|
129
|
+
|
130
|
+
nil
|
131
|
+
end
|
132
|
+
|
133
|
+
# List the current topic subscriptions for the consumer.
|
134
|
+
#
|
135
|
+
# @raise [Kafka::ResponseError] Error that occurred retrieving the
|
136
|
+
# subscriptions
|
137
|
+
#
|
138
|
+
# @return [Array<String>] List of current subscribed topics
|
139
|
+
def subscription
|
140
|
+
ptr = ::FFI::MemoryPointer.new(:pointer)
|
141
|
+
|
142
|
+
err = ::Kafka::FFI.rd_kafka_subscription(self, ptr)
|
143
|
+
if err != :ok
|
144
|
+
raise ::Kafka::ResponseError, err
|
145
|
+
end
|
146
|
+
|
147
|
+
begin
|
148
|
+
tpl = ::Kafka::FFI::TopicPartitionList.new(ptr.read_pointer)
|
149
|
+
|
150
|
+
# Map the topic partition list to topic names.
|
151
|
+
tpl.elements.map(&:topic)
|
152
|
+
ensure
|
153
|
+
tpl.destroy
|
154
|
+
end
|
155
|
+
ensure
|
156
|
+
ptr.free
|
157
|
+
end
|
158
|
+
|
159
|
+
# Alias to subscriptions since it does return a list of topics
|
160
|
+
alias subscriptions subscription
|
161
|
+
|
162
|
+
# Atomically assign the set of partitions to consume. This will replace the
|
163
|
+
# existing assignment.
|
164
|
+
#
|
165
|
+
# @see rdkafka.h rd_kafka_assign for semantics on use from callbacks and
|
166
|
+
# how empty vs NULL lists affect internal state.
|
167
|
+
#
|
168
|
+
# @param list [TopicPartitionList] List of topic+partition assignments
|
169
|
+
#
|
170
|
+
# @raise [Kafka::ResponseError] Error processing assignments
|
171
|
+
def assign(list)
|
172
|
+
err = ::Kafka::FFI.rd_kafka_assign(self, list)
|
173
|
+
if err != :ok
|
174
|
+
raise ::Kafka::ResponseError, err
|
175
|
+
end
|
176
|
+
|
177
|
+
nil
|
178
|
+
end
|
179
|
+
|
180
|
+
# List the current partition assignment(s) for the consumer.
|
181
|
+
#
|
182
|
+
# @raise [Kafka::ResponseError] Error that occurred retrieving the
|
183
|
+
# assignments.
|
184
|
+
#
|
185
|
+
# @return [Hash{String => Array<Integer>}] Current assignments for the
|
186
|
+
# consumer. Hash keys are topic names and values are the list of assigned
|
187
|
+
# partitions.
|
188
|
+
def assignment
|
189
|
+
ptr = ::FFI::MemoryPointer.new(:pointer)
|
190
|
+
|
191
|
+
err = ::Kafka::FFI.rd_kafka_assignment(self, ptr)
|
192
|
+
if err != :ok
|
193
|
+
raise ::Kafka::ResponseError, err
|
194
|
+
end
|
195
|
+
|
196
|
+
begin
|
197
|
+
tpl = ::Kafka::FFI::TopicPartitionList.new(ptr.read_pointer)
|
198
|
+
|
199
|
+
# { "topic" => [1, 2, 3] }
|
200
|
+
tpl.elements.inject({}) do |memo, tp|
|
201
|
+
(memo[tp.topic] ||= []) << tp.partition
|
202
|
+
memo
|
203
|
+
end
|
204
|
+
ensure
|
205
|
+
tpl.destroy
|
206
|
+
end
|
207
|
+
ensure
|
208
|
+
ptr.free
|
209
|
+
end
|
210
|
+
|
211
|
+
# Alias assignment since it returns a set
|
212
|
+
alias assignments assignment
|
213
|
+
|
214
|
+
# Retrieve committed offsets for topics + partitions. The offset field for
|
215
|
+
# each TopicPartition in list will be set to the stored offset or
|
216
|
+
# RD_KAFKA_OFFSET_INVALID in case there was no stored offset for that
|
217
|
+
# partition. The error field is set if there was an error with the
|
218
|
+
# TopicPartition.
|
219
|
+
#
|
220
|
+
# @param list [TopicPartitionList] List of topic+partitions to fetch
|
221
|
+
# current offsets. The list will be updated to set the committed offset
|
222
|
+
# or error as appropriate.
|
223
|
+
# @param timeout [Integer] Maximum time to wait in milliseconds
|
224
|
+
#
|
225
|
+
# @raise [Kafka::ResponseError] Error with the request (likely a
|
226
|
+
# timeout). Errors with individual topic+partition combinations are set
|
227
|
+
# in the returned TopicPartitionList
|
228
|
+
#
|
229
|
+
# @return [TopicPartitionList] the updated list
|
230
|
+
def committed(list, timeout: 1000)
|
231
|
+
if list.nil?
|
232
|
+
raise ArgumentError, "list cannot be nil"
|
233
|
+
end
|
234
|
+
|
235
|
+
err = ::Kafka::FFI.rd_kafka_committed(self, list, timeout)
|
236
|
+
if err != :ok
|
237
|
+
raise ::Kafka::ResponseError, err
|
238
|
+
end
|
239
|
+
|
240
|
+
# Return the list that was passed in as it should now be augmented with
|
241
|
+
# the committed offsets and any errors fetching said offsets.
|
242
|
+
list
|
243
|
+
end
|
244
|
+
|
245
|
+
# Poll the consumer's queue for a waiting Message and yields that message.
|
246
|
+
# The yielded message must not be cached in the application as it becomes
|
247
|
+
# unusable once the block completes.
|
248
|
+
#
|
249
|
+
# @see max.poll.interal.ms configuration option.
|
250
|
+
#
|
251
|
+
# @param timeout [Integer] How long to wait for a message in milliseconds.
|
252
|
+
#
|
253
|
+
# @raise [ArgumentError] consumer_poll was called without a block.
|
254
|
+
# @raise [Kafka::ResponseError] Error occurred while polling.
|
255
|
+
#
|
256
|
+
# @yield [message]
|
257
|
+
# @yieldparam message [Message] Message received from Kafka. Application
|
258
|
+
# must not call #destroy as it is owned by the Consumer.
|
259
|
+
#
|
260
|
+
# @return Either nil or the result of the block
|
261
|
+
def consumer_poll(timeout)
|
262
|
+
if !block_given?
|
263
|
+
raise ArgumentError, "consumer_poll must be passed a block"
|
264
|
+
end
|
265
|
+
|
266
|
+
msg = ::Kafka::FFI.rd_kafka_consumer_poll(self, timeout.to_i)
|
267
|
+
|
268
|
+
# No message was available
|
269
|
+
if msg.null?
|
270
|
+
return nil
|
271
|
+
end
|
272
|
+
|
273
|
+
begin
|
274
|
+
if msg.error
|
275
|
+
raise msg.error
|
276
|
+
end
|
277
|
+
|
278
|
+
yield(msg)
|
279
|
+
ensure
|
280
|
+
msg.destroy
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
# Commit the set of offsets from the given TopicPartitionList.
|
285
|
+
#
|
286
|
+
# @param offsets [TopicPartitionList] Set of topic+partition with offset
|
287
|
+
# (and maybe metadata) to be committed. If offsets is nil the current
|
288
|
+
# partition assignment set will be used instead.
|
289
|
+
# @param async [Boolean] If async is false this operation will block until
|
290
|
+
# the broker offset commit is done.
|
291
|
+
#
|
292
|
+
# @raise [Kafka::ResponseError] Error committing offsets. Only raise if
|
293
|
+
# async is false.
|
294
|
+
def commit(offsets, async)
|
295
|
+
err = ::Kafka::FFI.rd_kafka_commit(self, offsets, async)
|
296
|
+
if err != :ok
|
297
|
+
raise ::Kafka::ResponseError, err
|
298
|
+
end
|
299
|
+
|
300
|
+
nil
|
301
|
+
end
|
302
|
+
|
303
|
+
# Commit the message's offset on the broker for the message's partition.
|
304
|
+
#
|
305
|
+
# @param message [Message] The message to commit as processed
|
306
|
+
# @param async [Boolean] True to allow commit to happen in the background.
|
307
|
+
#
|
308
|
+
# @raise [Kafka::ResponseError] Error that occurred commiting the message
|
309
|
+
def commit_message(message, async)
|
310
|
+
if message.nil? || message.null?
|
311
|
+
raise ArgumentError, "message cannot but nil/null"
|
312
|
+
end
|
313
|
+
|
314
|
+
err = ::Kafka::FFI.rd_kafka_commit_message(message, async)
|
315
|
+
if err
|
316
|
+
raise ::Kafka::ResponseError, err
|
317
|
+
end
|
318
|
+
|
319
|
+
nil
|
320
|
+
end
|
321
|
+
|
322
|
+
# Close down the consumer. This will block until the consumer has revoked
|
323
|
+
# its assignment(s), committed offsets, and left the consumer group. The
|
324
|
+
# maximum blocking time is roughly limited to the `session.timeout.ms`
|
325
|
+
# config option.
|
326
|
+
#
|
327
|
+
# Ensure that `destroy` is called after the consumer is closed to free up
|
328
|
+
# resources.
|
329
|
+
#
|
330
|
+
# @note Maximum blocking time is roughly limited to `session.timeout.ms`
|
331
|
+
#
|
332
|
+
# @raise [Kafka::ResponseError] Error occurred closing the consumer
|
333
|
+
def close
|
334
|
+
err = ::Kafka::FFI.rd_kafka_consumer_close(self)
|
335
|
+
if err != :ok
|
336
|
+
raise ::Kafka::ResponseError, err
|
337
|
+
end
|
338
|
+
|
339
|
+
nil
|
340
|
+
end
|
341
|
+
end
|
342
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kafka::FFI
|
4
|
+
class Error < Kafka::Error; end
|
5
|
+
|
6
|
+
# TopicAlreadyConfigured is raised by Client#topic when passing a config to a
|
7
|
+
# topic that has already been initialized for the Client.
|
8
|
+
class TopicAlreadyConfiguredError < Error; end
|
9
|
+
|
10
|
+
# ConfigError is raised when making changes to the global config.
|
11
|
+
class ConfigError < Error
|
12
|
+
attr_reader :key
|
13
|
+
attr_reader :value
|
14
|
+
|
15
|
+
def initialize(key, value, message)
|
16
|
+
super(message)
|
17
|
+
|
18
|
+
@key = key
|
19
|
+
@value = value
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class UnknownConfigKey < ConfigError; end
|
24
|
+
class InvalidConfigValue < ConfigError; end
|
25
|
+
end
|
@@ -0,0 +1,215 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "kafka/ffi/opaque_pointer"
|
4
|
+
|
5
|
+
module Kafka::FFI
|
6
|
+
class Event < OpaquePointer
|
7
|
+
# LogMessage is attached to RD_KAFKA_EVENT_LOG events.
|
8
|
+
LogMessage = Struct.new(:facility, :message, :level) do
|
9
|
+
# @attr facility [String] Log facility
|
10
|
+
# @attr message [String] Log message
|
11
|
+
# @attr level [Integer] Verbosity level of the message
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
message
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns the event's type
|
19
|
+
#
|
20
|
+
# @see RD_KAFKA_EVENT_*
|
21
|
+
#
|
22
|
+
# @return [Symbol] Type of the event
|
23
|
+
def type
|
24
|
+
::Kafka::FFI.rd_kafka_event_type(self)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns the name of the event's type.
|
28
|
+
#
|
29
|
+
# @return [String] Name of the type of event
|
30
|
+
def name
|
31
|
+
::Kafka::FFI.rd_kafka_event_name(self)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Retrieve the set of messages. Can take a block to iterate over the set of
|
35
|
+
# Messages rather than return them.
|
36
|
+
#
|
37
|
+
# Events:
|
38
|
+
# - RD_KAFKA_EVENT_FETCH
|
39
|
+
# - RD_KAFKA_EVENT_DR
|
40
|
+
#
|
41
|
+
# @note Do not call #destroy on the Messages
|
42
|
+
#
|
43
|
+
# @yield [message] Iterate over available messages
|
44
|
+
# @yieldparam [Message]
|
45
|
+
#
|
46
|
+
# @return [Array<Message>] Messages attached to the Event
|
47
|
+
# @return [nil] Event does not have any Messages or a block was given.
|
48
|
+
def messages
|
49
|
+
# This departs from the librdkafka API due to having a collection of
|
50
|
+
# methods that have funky semantics for Ruby.
|
51
|
+
|
52
|
+
# @todo Messages are only on RD_KAFKA_EVENT_FETCH and RD_KAFKA_EVENT_DR.
|
53
|
+
# Need to test what happens with other event types.
|
54
|
+
|
55
|
+
# No block so fetch all of the messages and return them as an array.
|
56
|
+
if !block_given?
|
57
|
+
count = ::Kafka::FFI.rd_kafka_event_message_count(self)
|
58
|
+
if count == 0
|
59
|
+
return []
|
60
|
+
end
|
61
|
+
|
62
|
+
begin
|
63
|
+
# Allocates enough memory for the full set but only converts as many
|
64
|
+
# as were returned.
|
65
|
+
# @todo Retrieve all until sum(ret) == count?
|
66
|
+
ptr = ::FFI::MemoryPointer.new(:pointer, count)
|
67
|
+
ret = ::Kafka::FFI.rd_kafka_event_message_array(self, ptr, count)
|
68
|
+
|
69
|
+
# Map the return pointers to Messages
|
70
|
+
return ptr.read_array_of_pointer(ret).map! { |p| Message.from_native(p) }
|
71
|
+
ensure
|
72
|
+
ptr.free
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Block was passed so use rd_kafka_event_message_next
|
77
|
+
begin
|
78
|
+
ptr = ::FFI::MemoryPointer.new(:pointer)
|
79
|
+
|
80
|
+
loop do
|
81
|
+
msg = ::Kafka::FFI.rd_kafka_event_message_next(self)
|
82
|
+
if msg.null?
|
83
|
+
break
|
84
|
+
end
|
85
|
+
|
86
|
+
yield(msg)
|
87
|
+
end
|
88
|
+
ensure
|
89
|
+
ptr.free
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Returns the configuration for the event or nil if the configuration
|
94
|
+
# property is not set or not applicable for the event type.
|
95
|
+
#
|
96
|
+
# Events:
|
97
|
+
# - RD_KAFKA_EVENT_OAUTHBEARER_TOKEN_REFRESH
|
98
|
+
#
|
99
|
+
# @return [String] Configuration string for the event
|
100
|
+
# @return [nil] Property not set or not applicable.
|
101
|
+
def config_string
|
102
|
+
::Kafka::FFI.rd_kafka_event_string(self)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns the error code for the event or nil if there was no error.
|
106
|
+
#
|
107
|
+
# @see error_is_fatal to detect if it is a fatal error.
|
108
|
+
#
|
109
|
+
# @return [nil] No error for the Event
|
110
|
+
# @return [Kafka::ResponseError] Error code for the event.
|
111
|
+
def error
|
112
|
+
err = ::Kafka::FFI.rd_kafka_event_error(self)
|
113
|
+
if err != :ok
|
114
|
+
::Kafka::ResponseError.new(err, error_string)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Returns a description of the error or nil when there is no error.
|
119
|
+
#
|
120
|
+
# @return [nil] No error for the Event
|
121
|
+
# @return [String] Description of the error
|
122
|
+
def error_string
|
123
|
+
::Kafka::FFI.rd_kafka_event_error_string(self)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Returns true or false if the Event represents a fatal error.
|
127
|
+
#
|
128
|
+
# @return [Boolean] There is an error for the Event and it is fatal.
|
129
|
+
def error_is_fatal
|
130
|
+
error && ::Kafka::FFI.rd_kafka_event_error_is_fatal(self)
|
131
|
+
end
|
132
|
+
alias error_is_fatal? error_is_fatal
|
133
|
+
|
134
|
+
# Returns the log message attached to the event.
|
135
|
+
#
|
136
|
+
# Events:
|
137
|
+
# - RD_KAFKA_EVENT_LOG
|
138
|
+
#
|
139
|
+
# @return [Event::LogMessage] Attach log entry
|
140
|
+
def log
|
141
|
+
facility = ::FFI::MemoryPointer.new(:pointer)
|
142
|
+
message = ::FFI::MemoryPointer.new(:pointer)
|
143
|
+
level = ::FFI::MemoryPointer.new(:pointer)
|
144
|
+
|
145
|
+
exists = ::Kafka::FFI.rd_kafka_event_log(self, facility, message, level)
|
146
|
+
if exists != 0
|
147
|
+
# Event type does not support log messages.
|
148
|
+
return nil
|
149
|
+
end
|
150
|
+
|
151
|
+
LogMessage.new(
|
152
|
+
facility.read_pointer.read_string,
|
153
|
+
message.read_pointer.read_string,
|
154
|
+
level.read_int,
|
155
|
+
)
|
156
|
+
ensure
|
157
|
+
facility.free
|
158
|
+
message.free
|
159
|
+
level.free
|
160
|
+
end
|
161
|
+
|
162
|
+
# Extracts stats from the event
|
163
|
+
#
|
164
|
+
# Events:
|
165
|
+
# - RD_KAFKA_EVENT_STATS
|
166
|
+
#
|
167
|
+
# @return [nil] Event type does not support stats
|
168
|
+
# @return [String] JSON encoded stats information.
|
169
|
+
def stats
|
170
|
+
# Calling stats on an unsupported type causes a segfault with librdkafka
|
171
|
+
# 1.3.0.
|
172
|
+
if type != :stats
|
173
|
+
return nil
|
174
|
+
end
|
175
|
+
|
176
|
+
::Kafka::FFI.rd_kafka_event_stats(self)
|
177
|
+
end
|
178
|
+
|
179
|
+
# Returns the topic partition list from the Event.
|
180
|
+
#
|
181
|
+
# @note Application MUST NOT call #destroy on the list
|
182
|
+
#
|
183
|
+
# Events:
|
184
|
+
# - RD_KAFKA_EVENT_REBALANCE
|
185
|
+
# - RD_KAFKA_EVENT_OFFSET_COMMIT
|
186
|
+
#
|
187
|
+
# @return [TopicPartitionList]
|
188
|
+
def topic_partition_list
|
189
|
+
::Kafka::FFI.rd_kafka_event_topic_partition_list(self)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Returns the topic partition from the Event.
|
193
|
+
#
|
194
|
+
# @note The application MUST call #destroy on the TopicPartition when done.
|
195
|
+
#
|
196
|
+
# Events:
|
197
|
+
# - RD_KAFKA_EVENT_ERROR
|
198
|
+
#
|
199
|
+
# @return [TopicPartition]
|
200
|
+
def topic_partition
|
201
|
+
::Kafka::FFI.rd_kafka_event_topic_partition(self)
|
202
|
+
end
|
203
|
+
|
204
|
+
# Destroy the event, releasing it's resources back to the system.
|
205
|
+
#
|
206
|
+
# @todo Is the applicaiton responsible for calling #destroy?
|
207
|
+
def destroy
|
208
|
+
# It is safe to call destroy even if the Event's pointer is NULL but it
|
209
|
+
# doesn't do anything so might as well guard against it just in case.
|
210
|
+
if !pointer.null?
|
211
|
+
::Kafka::FFI.rd_kafka_event_destroy(self)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ffi"
|
4
|
+
require "kafka/ffi/broker_metadata"
|
5
|
+
require "kafka/ffi/group_member_info"
|
6
|
+
|
7
|
+
module Kafka::FFI
|
8
|
+
class GroupInfo < ::FFI::Struct
|
9
|
+
layout(
|
10
|
+
:broker, BrokerMetadata.by_value,
|
11
|
+
:group, :string,
|
12
|
+
:err, :error_code,
|
13
|
+
:state, :string,
|
14
|
+
:protocol_type, :string,
|
15
|
+
:protocol, :string,
|
16
|
+
:members, :pointer,
|
17
|
+
:member_cnt, :int
|
18
|
+
)
|
19
|
+
|
20
|
+
# Returns information about the broker that originated the group info.
|
21
|
+
#
|
22
|
+
# @return [Kafka::FFI::BrokerMetadata] Broker info
|
23
|
+
def broker
|
24
|
+
self[:broker]
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns the name of the group
|
28
|
+
#
|
29
|
+
# @return [String] Group name
|
30
|
+
def group
|
31
|
+
self[:group]
|
32
|
+
end
|
33
|
+
alias name group
|
34
|
+
|
35
|
+
# Returns any broker orignated error for the consumer group.
|
36
|
+
#
|
37
|
+
# @return [nil] No error
|
38
|
+
# @return [Kafka::ResponseError] Broker originated error
|
39
|
+
def error
|
40
|
+
if self[:err] != :ok
|
41
|
+
::Kafka::ResponseError.new(self[:err])
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns the current state of the group
|
46
|
+
#
|
47
|
+
# @return [String] Group state
|
48
|
+
def state
|
49
|
+
self[:state]
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns the group protocol type
|
53
|
+
#
|
54
|
+
# @return [String] Group protocol type
|
55
|
+
def protocol_type
|
56
|
+
self[:protocol_type]
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns the group protocol
|
60
|
+
#
|
61
|
+
# @return [String] Group protocol
|
62
|
+
def protocol
|
63
|
+
self[:protocol]
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns information about the members of the consumer group
|
67
|
+
#
|
68
|
+
# @return [Array<GroupMemberInfo>] Member information
|
69
|
+
def members
|
70
|
+
self[:member_cnt].times.map do |i|
|
71
|
+
GroupMemberInfo.new(self[:members] + (i * GroupMemberInfo.size))
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ffi"
|
4
|
+
require "kafka/ffi/group_info"
|
5
|
+
|
6
|
+
module Kafka::FFI
|
7
|
+
class GroupList < ::FFI::Struct
|
8
|
+
layout(
|
9
|
+
:groups, :pointer,
|
10
|
+
:group_cnt, :int
|
11
|
+
)
|
12
|
+
|
13
|
+
# Returns information about the consumer groups in the cluster.
|
14
|
+
#
|
15
|
+
# @return [Array<GroupInfo>] Group metadata
|
16
|
+
def groups
|
17
|
+
self[:group_cnt].times.map do |i|
|
18
|
+
GroupInfo.new(self[:groups] + (i * GroupInfo.size))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Release the resources used by the group list back to the system
|
23
|
+
def destroy
|
24
|
+
::Kafka::FFI.rd_kafka_group_list_destroy(self)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|