ruby-kafka 0.3.18.beta2 → 0.4.0.beta1
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/CHANGELOG.md +6 -0
- data/Gemfile.lock +3 -1
- data/README.md +36 -4
- data/lib/kafka/client.rb +17 -7
- data/lib/kafka/connection_builder.rb +23 -1
- data/lib/kafka/consumer.rb +6 -3
- data/lib/kafka/protocol/sasl_handshake_request.rb +1 -1
- data/lib/kafka/sasl_plain_authenticator.rb +37 -0
- data/lib/kafka/statsd.rb +209 -0
- data/lib/kafka/version.rb +1 -1
- data/ruby-kafka.gemspec +1 -0
- metadata +18 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9ac9f20c365f90e85bd6df6000838fbc349b893b
|
|
4
|
+
data.tar.gz: fc16d9bf3784a21d1a82ec106b5b528bf57bfbe2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 22c77ea79b8e971916e1671253b709b207501a3e068d29e5629efea30733e9ab224748f068cc916c6472f7507c2e1073f7b0a3e9ee2cdbcafee21bd27e21f277
|
|
7
|
+
data.tar.gz: e5ecd931e2348ee989bcaf70387b30e64d9b24560231745b1c4d0e275cfcc0f65202cdf018b45bcc1678699ac05648bb4f0a5e5689eee030958823a864e73fb1
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@ Changes and additions to the library will be listed here.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## v0.4.0
|
|
8
|
+
|
|
9
|
+
- Support SASL authentication (#334 and #370)
|
|
10
|
+
- Allow loading SSL certificates from files (#371)
|
|
11
|
+
- Add Statsd metric reporting (#373)
|
|
12
|
+
|
|
7
13
|
## v0.3.17
|
|
8
14
|
|
|
9
15
|
- Re-commit previously committed offsets periodically with an interval of half
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
ruby-kafka (0.3.18.
|
|
4
|
+
ruby-kafka (0.3.18.beta2)
|
|
5
5
|
gssapi (>= 1.2.0)
|
|
6
6
|
|
|
7
7
|
GEM
|
|
@@ -58,6 +58,7 @@ GEM
|
|
|
58
58
|
ruby-prof (0.15.9)
|
|
59
59
|
slop (3.6.0)
|
|
60
60
|
snappy (0.0.12)
|
|
61
|
+
statsd-ruby (1.4.0)
|
|
61
62
|
thread_safe (0.3.5)
|
|
62
63
|
timecop (0.8.0)
|
|
63
64
|
tzinfo (1.2.2)
|
|
@@ -81,6 +82,7 @@ DEPENDENCIES
|
|
|
81
82
|
ruby-kafka!
|
|
82
83
|
ruby-prof
|
|
83
84
|
snappy
|
|
85
|
+
statsd-ruby
|
|
84
86
|
timecop
|
|
85
87
|
|
|
86
88
|
RUBY VERSION
|
data/README.md
CHANGED
|
@@ -31,7 +31,8 @@ Although parts of this library work with Kafka 0.8 – specifically, the Produce
|
|
|
31
31
|
5. [Logging](#logging)
|
|
32
32
|
6. [Instrumentation](#instrumentation)
|
|
33
33
|
7. [Monitoring](#monitoring)
|
|
34
|
-
1. [Reporting Metrics to
|
|
34
|
+
1. [Reporting Metrics to Statsd](#reporting-metrics-to-statsd)
|
|
35
|
+
2. [Reporting Metrics to Datadog](#reporting-metrics-to-datadog)
|
|
35
36
|
8. [Understanding Timeouts](#understanding-timeouts)
|
|
36
37
|
9. [Security](#security)
|
|
37
38
|
1. [Encryption and Authentication using SSL](#encryption-and-authentication-using-ssl)
|
|
@@ -732,7 +733,25 @@ It is highly recommended that you monitor your Kafka client applications in prod
|
|
|
732
733
|
* consumer processing errors, indicating exceptions are being raised in the processing code;
|
|
733
734
|
* frequent consumer rebalances, which may indicate unstable network conditions or consumer configurations.
|
|
734
735
|
|
|
735
|
-
You can quite easily build monitoring on top of the provided [instrumentation hooks](#instrumentation). In order to further help with monitoring, a prebuilt [Datadog](https://www.datadoghq.com/) reporter is included with ruby-kafka.
|
|
736
|
+
You can quite easily build monitoring on top of the provided [instrumentation hooks](#instrumentation). In order to further help with monitoring, a prebuilt [Statsd](https://github.com/etsy/statsd) and [Datadog](https://www.datadoghq.com/) reporter is included with ruby-kafka.
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
#### Reporting Metrics to Statsd
|
|
740
|
+
|
|
741
|
+
The Statsd reporter is automatically enabled when the `kafka/statsd` library is required. You can optionally change the configuration.
|
|
742
|
+
|
|
743
|
+
```ruby
|
|
744
|
+
require "kafka/statds"
|
|
745
|
+
|
|
746
|
+
# Default is "ruby_kafka".
|
|
747
|
+
Kafka::Statsd.namespace = "custom-namespace"
|
|
748
|
+
|
|
749
|
+
# Default is "127.0.0.1".
|
|
750
|
+
Kafka::Statsd.host = "statsd.something.com"
|
|
751
|
+
|
|
752
|
+
# Default is 8125.
|
|
753
|
+
Kafka::Statsd.port = 1234
|
|
754
|
+
```
|
|
736
755
|
|
|
737
756
|
|
|
738
757
|
#### Reporting Metrics to Datadog
|
|
@@ -815,9 +834,9 @@ Typically, Kafka certificates come in the JKS format, which isn't supported by r
|
|
|
815
834
|
|
|
816
835
|
#### Authentication using SASL
|
|
817
836
|
|
|
818
|
-
Kafka has support for using SASL to authenticate clients. Currently
|
|
837
|
+
Kafka has support for using SASL to authenticate clients. Currently GSSAPI and PLAIN mechanisms are supported by ruby-kafka.
|
|
819
838
|
|
|
820
|
-
In order to authenticate using
|
|
839
|
+
In order to authenticate using GSSAPI, set your principal and optionally your keytab when initializing the Kafka client:
|
|
821
840
|
|
|
822
841
|
```ruby
|
|
823
842
|
kafka = Kafka.new(
|
|
@@ -827,6 +846,19 @@ kafka = Kafka.new(
|
|
|
827
846
|
)
|
|
828
847
|
```
|
|
829
848
|
|
|
849
|
+
In order to authenticate using PLAIN, you must set your username and password when initializing the Kafka client:
|
|
850
|
+
|
|
851
|
+
```ruby
|
|
852
|
+
kafka = Kafka.new(
|
|
853
|
+
ssl_ca_cert: File.read('/etc/openssl/cert.pem'), # Optional but highly recommended
|
|
854
|
+
sasl_plain_username: 'username',
|
|
855
|
+
sasl_plain_password: 'password'
|
|
856
|
+
# ...
|
|
857
|
+
)
|
|
858
|
+
```
|
|
859
|
+
|
|
860
|
+
**NOTE**: It is __highly__ recommended that you use SSL for encryption when using SASL_PLAIN
|
|
861
|
+
|
|
830
862
|
## Design
|
|
831
863
|
|
|
832
864
|
The library has been designed as a layered system, with each layer having a clear responsibility:
|
data/lib/kafka/client.rb
CHANGED
|
@@ -34,6 +34,9 @@ module Kafka
|
|
|
34
34
|
# @param ssl_ca_cert [String, Array<String>, nil] a PEM encoded CA cert, or an Array of
|
|
35
35
|
# PEM encoded CA certs, to use with an SSL connection.
|
|
36
36
|
#
|
|
37
|
+
# @param ssl_ca_cert_file_path [String, nil] a path on the filesystem to a PEM encoded CA cert
|
|
38
|
+
# to use with an SSL connection.
|
|
39
|
+
#
|
|
37
40
|
# @param ssl_client_cert [String, nil] a PEM encoded client cert to use with an
|
|
38
41
|
# SSL connection. Must be used in combination with ssl_client_cert_key.
|
|
39
42
|
#
|
|
@@ -46,13 +49,14 @@ module Kafka
|
|
|
46
49
|
#
|
|
47
50
|
# @return [Client]
|
|
48
51
|
def initialize(seed_brokers:, client_id: "ruby-kafka", logger: nil, connect_timeout: nil, socket_timeout: nil,
|
|
49
|
-
ssl_ca_cert: nil, ssl_client_cert: nil, ssl_client_cert_key: nil,
|
|
50
|
-
sasl_gssapi_principal: nil, sasl_gssapi_keytab: nil
|
|
52
|
+
ssl_ca_cert_file_path: nil, ssl_ca_cert: nil, ssl_client_cert: nil, ssl_client_cert_key: nil,
|
|
53
|
+
sasl_gssapi_principal: nil, sasl_gssapi_keytab: nil,
|
|
54
|
+
sasl_plain_authzid: '', sasl_plain_username: nil, sasl_plain_password: nil)
|
|
51
55
|
@logger = logger || Logger.new(nil)
|
|
52
56
|
@instrumenter = Instrumenter.new(client_id: client_id)
|
|
53
57
|
@seed_brokers = normalize_seed_brokers(seed_brokers)
|
|
54
58
|
|
|
55
|
-
ssl_context = build_ssl_context(ssl_ca_cert, ssl_client_cert, ssl_client_cert_key)
|
|
59
|
+
ssl_context = build_ssl_context(ssl_ca_cert_file_path, ssl_ca_cert, ssl_client_cert, ssl_client_cert_key)
|
|
56
60
|
|
|
57
61
|
@connection_builder = ConnectionBuilder.new(
|
|
58
62
|
client_id: client_id,
|
|
@@ -62,7 +66,10 @@ module Kafka
|
|
|
62
66
|
logger: @logger,
|
|
63
67
|
instrumenter: @instrumenter,
|
|
64
68
|
sasl_gssapi_principal: sasl_gssapi_principal,
|
|
65
|
-
sasl_gssapi_keytab: sasl_gssapi_keytab
|
|
69
|
+
sasl_gssapi_keytab: sasl_gssapi_keytab,
|
|
70
|
+
sasl_plain_authzid: sasl_plain_authzid,
|
|
71
|
+
sasl_plain_username: sasl_plain_username,
|
|
72
|
+
sasl_plain_password: sasl_plain_password
|
|
66
73
|
)
|
|
67
74
|
|
|
68
75
|
@cluster = initialize_cluster
|
|
@@ -464,8 +471,8 @@ module Kafka
|
|
|
464
471
|
)
|
|
465
472
|
end
|
|
466
473
|
|
|
467
|
-
def build_ssl_context(ca_cert, client_cert, client_cert_key)
|
|
468
|
-
return nil unless ca_cert || client_cert || client_cert_key
|
|
474
|
+
def build_ssl_context(ca_cert_file_path, ca_cert, client_cert, client_cert_key)
|
|
475
|
+
return nil unless ca_cert_file_path || ca_cert || client_cert || client_cert_key
|
|
469
476
|
|
|
470
477
|
ssl_context = OpenSSL::SSL::SSLContext.new
|
|
471
478
|
|
|
@@ -480,11 +487,14 @@ module Kafka
|
|
|
480
487
|
raise ArgumentError, "Kafka client initialized with `ssl_client_cert_key`, but no `ssl_client_cert`. Please provide both."
|
|
481
488
|
end
|
|
482
489
|
|
|
483
|
-
if ca_cert
|
|
490
|
+
if ca_cert || ca_cert_file_path
|
|
484
491
|
store = OpenSSL::X509::Store.new
|
|
485
492
|
Array(ca_cert).each do |cert|
|
|
486
493
|
store.add_cert(OpenSSL::X509::Certificate.new(cert))
|
|
487
494
|
end
|
|
495
|
+
if ca_cert_file_path
|
|
496
|
+
store.add_file(ca_cert_file_path)
|
|
497
|
+
end
|
|
488
498
|
ssl_context.cert_store = store
|
|
489
499
|
end
|
|
490
500
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
require 'kafka/sasl_gssapi_authenticator'
|
|
2
|
+
require 'kafka/sasl_plain_authenticator'
|
|
2
3
|
|
|
3
4
|
module Kafka
|
|
4
5
|
class ConnectionBuilder
|
|
5
|
-
def initialize(client_id:, logger:, instrumenter:, connect_timeout:, socket_timeout:, ssl_context:, sasl_gssapi_principal:, sasl_gssapi_keytab:)
|
|
6
|
+
def initialize(client_id:, logger:, instrumenter:, connect_timeout:, socket_timeout:, ssl_context:, sasl_gssapi_principal:, sasl_gssapi_keytab:, sasl_plain_authzid:, sasl_plain_username:, sasl_plain_password:)
|
|
6
7
|
@client_id = client_id
|
|
7
8
|
@logger = logger
|
|
8
9
|
@instrumenter = instrumenter
|
|
@@ -11,6 +12,9 @@ module Kafka
|
|
|
11
12
|
@ssl_context = ssl_context
|
|
12
13
|
@sasl_gssapi_principal = sasl_gssapi_principal
|
|
13
14
|
@sasl_gssapi_keytab = sasl_gssapi_keytab
|
|
15
|
+
@sasl_plain_authzid = sasl_plain_authzid
|
|
16
|
+
@sasl_plain_username = sasl_plain_username
|
|
17
|
+
@sasl_plain_password = sasl_plain_password
|
|
14
18
|
end
|
|
15
19
|
|
|
16
20
|
def build_connection(host, port)
|
|
@@ -27,6 +31,8 @@ module Kafka
|
|
|
27
31
|
|
|
28
32
|
if authenticate_using_sasl_gssapi?
|
|
29
33
|
sasl_gssapi_authenticate(connection)
|
|
34
|
+
elsif authenticate_using_sasl_plain?
|
|
35
|
+
sasl_plain_authenticate(connection)
|
|
30
36
|
end
|
|
31
37
|
|
|
32
38
|
connection
|
|
@@ -45,8 +51,24 @@ module Kafka
|
|
|
45
51
|
auth.authenticate!
|
|
46
52
|
end
|
|
47
53
|
|
|
54
|
+
def sasl_plain_authenticate(connection)
|
|
55
|
+
auth = SaslPlainAuthenticator.new(
|
|
56
|
+
connection: connection,
|
|
57
|
+
logger: @logger,
|
|
58
|
+
authzid: @sasl_plain_authzid,
|
|
59
|
+
username: @sasl_plain_username,
|
|
60
|
+
password: @sasl_plain_password
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
auth.authenticate!
|
|
64
|
+
end
|
|
65
|
+
|
|
48
66
|
def authenticate_using_sasl_gssapi?
|
|
49
67
|
!@ssl_context && @sasl_gssapi_principal && !@sasl_gssapi_principal.empty?
|
|
50
68
|
end
|
|
69
|
+
|
|
70
|
+
def authenticate_using_sasl_plain?
|
|
71
|
+
@sasl_plain_authzid && @sasl_plain_username && @sasl_plain_password
|
|
72
|
+
end
|
|
51
73
|
end
|
|
52
74
|
end
|
data/lib/kafka/consumer.rb
CHANGED
|
@@ -258,13 +258,13 @@ module Kafka
|
|
|
258
258
|
begin
|
|
259
259
|
yield batch
|
|
260
260
|
rescue => e
|
|
261
|
-
offset_range = batch.
|
|
261
|
+
offset_range = (batch.first_offset .. batch.last_offset)
|
|
262
262
|
location = "#{batch.topic}/#{batch.partition} in offset range #{offset_range}"
|
|
263
263
|
backtrace = e.backtrace.join("\n")
|
|
264
264
|
|
|
265
265
|
@logger.error "Exception raised when processing #{location} -- #{e.class}: #{e}\n#{backtrace}"
|
|
266
266
|
|
|
267
|
-
raise
|
|
267
|
+
raise ProcessingError.new(batch.topic, batch.partition, offset_range)
|
|
268
268
|
end
|
|
269
269
|
end
|
|
270
270
|
|
|
@@ -308,6 +308,9 @@ module Kafka
|
|
|
308
308
|
@logger.error "Leader not available; waiting 1s before retrying"
|
|
309
309
|
@cluster.mark_as_stale!
|
|
310
310
|
sleep 1
|
|
311
|
+
rescue SignalException => e
|
|
312
|
+
@logger.warn "Received signal #{e.message}, shutting down"
|
|
313
|
+
@running = false
|
|
311
314
|
end
|
|
312
315
|
end
|
|
313
316
|
ensure
|
|
@@ -320,7 +323,7 @@ module Kafka
|
|
|
320
323
|
|
|
321
324
|
def make_final_offsets_commit!(attempts = 3)
|
|
322
325
|
@offset_manager.commit_offsets
|
|
323
|
-
rescue ConnectionError
|
|
326
|
+
rescue ConnectionError, EOFError
|
|
324
327
|
# It's important to make sure final offsets commit is done
|
|
325
328
|
# As otherwise messages that have been processed after last auto-commit
|
|
326
329
|
# will be processed again and that may be huge amount of messages
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Kafka
|
|
2
|
+
class SaslPlainAuthenticator
|
|
3
|
+
PLAIN_IDENT = "PLAIN"
|
|
4
|
+
|
|
5
|
+
def initialize(connection:, logger:, authzid:, username:, password:)
|
|
6
|
+
@connection = connection
|
|
7
|
+
@logger = logger
|
|
8
|
+
@authzid = authzid
|
|
9
|
+
@username = username
|
|
10
|
+
@password = password
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def authenticate!
|
|
14
|
+
response = @connection.send_request(Kafka::Protocol::SaslHandshakeRequest.new(PLAIN_IDENT))
|
|
15
|
+
|
|
16
|
+
@encoder = @connection.encoder
|
|
17
|
+
@decoder = @connection.decoder
|
|
18
|
+
|
|
19
|
+
unless response.error_code == 0 && response.enabled_mechanisms.include?(PLAIN_IDENT)
|
|
20
|
+
raise Kafka::Error, "#{PLAIN_IDENT} is not supported."
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# SASL PLAIN
|
|
24
|
+
msg = [@authzid.to_s,
|
|
25
|
+
@username.to_s,
|
|
26
|
+
@password.to_s].join("\000").force_encoding('utf-8')
|
|
27
|
+
@encoder.write_bytes(msg)
|
|
28
|
+
begin
|
|
29
|
+
msg = @decoder.bytes
|
|
30
|
+
raise Kafka::Error, 'SASL PLAIN authentication failed: unknown error' unless msg
|
|
31
|
+
rescue Errno::ETIMEDOUT, EOFError => e
|
|
32
|
+
raise Kafka::Error, "SASL PLAIN authentication failed: #{e.message}"
|
|
33
|
+
end
|
|
34
|
+
@logger.debug 'SASL PLAIN authentication successful.'
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
data/lib/kafka/statsd.rb
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
begin
|
|
2
|
+
require "statsd"
|
|
3
|
+
rescue LoadError
|
|
4
|
+
$stderr.puts "In order to report Kafka client metrics to Statsd you need to install the `statsd-ruby` gem."
|
|
5
|
+
raise
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
require "active_support/subscriber"
|
|
9
|
+
|
|
10
|
+
module Kafka
|
|
11
|
+
# Reports operational metrics to a Statsd agent.
|
|
12
|
+
#
|
|
13
|
+
# require "kafka/statds"
|
|
14
|
+
#
|
|
15
|
+
# # Default is "ruby_kafka".
|
|
16
|
+
# Kafka::Statsd.namespace = "custom-namespace"
|
|
17
|
+
#
|
|
18
|
+
# # Default is "127.0.0.1".
|
|
19
|
+
# Kafka::Statsd.host = "statsd.something.com"
|
|
20
|
+
#
|
|
21
|
+
# # Default is 8125.
|
|
22
|
+
# Kafka::Statsd.port = 1234
|
|
23
|
+
#
|
|
24
|
+
# Once the file has been required, no further configuration is needed – all operational
|
|
25
|
+
# metrics are automatically emitted.
|
|
26
|
+
module Statsd
|
|
27
|
+
DEFAULT_NAMESPACE = "ruby_kafka"
|
|
28
|
+
DEFAULT_HOST = '127.0.0.1'
|
|
29
|
+
DEFAULT_PORT = 8125
|
|
30
|
+
|
|
31
|
+
def self.statsd
|
|
32
|
+
@statsd ||= ::Statsd.new(DEFAULT_HOST, DEFAULT_PORT).tap{ |sd| sd.namespace = DEFAULT_NAMESPACE }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.host=(host)
|
|
36
|
+
statsd.host = host
|
|
37
|
+
statsd.connect if statsd.respond_to?(:connect)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.port=(port)
|
|
41
|
+
statsd.port = port
|
|
42
|
+
statsd.connect if statsd.respond_to?(:connect)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.namespace=(namespace)
|
|
46
|
+
statsd.namespace = namespace
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class StatsdSubscriber < ActiveSupport::Subscriber
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
%w[increment count timing gauge].each do |type|
|
|
53
|
+
define_method(type) do |*args|
|
|
54
|
+
Kafka::Statsd.statsd.send(type, *args)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
class ConnectionSubscriber < StatsdSubscriber
|
|
60
|
+
def request(event)
|
|
61
|
+
client = event.payload.fetch(:client_id)
|
|
62
|
+
api = event.payload.fetch(:api, "unknown")
|
|
63
|
+
request_size = event.payload.fetch(:request_size, 0)
|
|
64
|
+
response_size = event.payload.fetch(:response_size, 0)
|
|
65
|
+
broker = event.payload.fetch(:broker_host)
|
|
66
|
+
|
|
67
|
+
timing("api.#{client}.#{api}.#{broker}.latency", event.duration)
|
|
68
|
+
increment("api.#{client}.#{api}.#{broker}.calls")
|
|
69
|
+
|
|
70
|
+
timing("api.#{client}.#{api}.#{broker}.request_size", request_size)
|
|
71
|
+
timing("api.#{client}.#{api}.#{broker}.response_size", response_size)
|
|
72
|
+
|
|
73
|
+
if event.payload.key?(:exception)
|
|
74
|
+
increment("api.#{client}.#{api}.#{broker}.errors")
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
attach_to "connection.kafka"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
class ConsumerSubscriber < StatsdSubscriber
|
|
82
|
+
def process_message(event)
|
|
83
|
+
lag = event.payload.fetch(:offset_lag)
|
|
84
|
+
client = event.payload.fetch(:client_id)
|
|
85
|
+
group_id = event.payload.fetch(:group_id)
|
|
86
|
+
topic = event.payload.fetch(:topic)
|
|
87
|
+
partition = event.payload.fetch(:partition)
|
|
88
|
+
|
|
89
|
+
if event.payload.key?(:exception)
|
|
90
|
+
increment("consumer.#{client}.#{group_id}.#{topic}.#{partition}.process_message.errors")
|
|
91
|
+
else
|
|
92
|
+
timing("consumer.#{client}.#{group_id}.#{topic}.#{partition}.process_message.latency", event.duration)
|
|
93
|
+
increment("consumer.#{client}.#{group_id}.#{topic}.#{partition}.messages")
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
gauge("consumer.#{client}.#{group_id}.#{topic}.#{partition}.lag", lag)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def process_batch(event)
|
|
100
|
+
messages = event.payload.fetch(:message_count)
|
|
101
|
+
client = event.payload.fetch(:client_id)
|
|
102
|
+
group_id = event.payload.fetch(:group_id)
|
|
103
|
+
topic = event.payload.fetch(:topic)
|
|
104
|
+
partition = event.payload.fetch(:partition)
|
|
105
|
+
|
|
106
|
+
if event.payload.key?(:exception)
|
|
107
|
+
increment("consumer.#{client}.#{group_id}.#{topic}.#{partition}.process_batch.errors")
|
|
108
|
+
else
|
|
109
|
+
timing("consumer.#{client}.#{group_id}.#{topic}.#{partition}.process_batch.latency", event.duration)
|
|
110
|
+
count("consumer.#{client}.#{group_id}.#{topic}.#{partition}.messages", messages)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
attach_to "consumer.kafka"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
class ProducerSubscriber < StatsdSubscriber
|
|
118
|
+
def produce_message(event)
|
|
119
|
+
client = event.payload.fetch(:client_id)
|
|
120
|
+
topic = event.payload.fetch(:topic)
|
|
121
|
+
message_size = event.payload.fetch(:message_size)
|
|
122
|
+
buffer_size = event.payload.fetch(:buffer_size)
|
|
123
|
+
max_buffer_size = event.payload.fetch(:max_buffer_size)
|
|
124
|
+
buffer_fill_ratio = buffer_size.to_f / max_buffer_size.to_f
|
|
125
|
+
|
|
126
|
+
# This gets us the write rate.
|
|
127
|
+
increment("producer.#{client}.#{topic}.produce.messages")
|
|
128
|
+
|
|
129
|
+
timing("producer.#{client}.#{topic}.produce.message_size", message_size)
|
|
130
|
+
|
|
131
|
+
# This gets us the avg/max buffer size per producer.
|
|
132
|
+
timing("producer.#{client}.buffer.size", buffer_size)
|
|
133
|
+
|
|
134
|
+
# This gets us the avg/max buffer fill ratio per producer.
|
|
135
|
+
timing("producer.#{client}.buffer.fill_ratio", buffer_fill_ratio)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def buffer_overflow(event)
|
|
139
|
+
client = event.payload.fetch(:client_id)
|
|
140
|
+
topic = event.payload.fetch(:topic)
|
|
141
|
+
|
|
142
|
+
increment("producer.#{client}.#{topic}.produce.errors")
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def deliver_messages(event)
|
|
146
|
+
client = event.payload.fetch(:client_id)
|
|
147
|
+
message_count = event.payload.fetch(:delivered_message_count)
|
|
148
|
+
attempts = event.payload.fetch(:attempts)
|
|
149
|
+
|
|
150
|
+
if event.payload.key?(:exception)
|
|
151
|
+
increment("producer.#{client}.deliver.errors")
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
timing("producer.#{client}.deliver.latency", event.duration)
|
|
155
|
+
|
|
156
|
+
# Messages delivered to Kafka:
|
|
157
|
+
count("producer.#{client}.deliver.messages", message_count)
|
|
158
|
+
|
|
159
|
+
# Number of attempts to deliver messages:
|
|
160
|
+
timing("producer.#{client}.deliver.attempts", attempts)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def ack_message(event)
|
|
164
|
+
client = event.payload.fetch(:client_id)
|
|
165
|
+
topic = event.payload.fetch(:topic)
|
|
166
|
+
|
|
167
|
+
# Number of messages ACK'd for the topic.
|
|
168
|
+
increment("producer.#{client}.#{topic}.ack.messages")
|
|
169
|
+
|
|
170
|
+
# Histogram of delay between a message being produced and it being ACK'd.
|
|
171
|
+
timing("producer.#{client}.#{topic}.ack.delay", event.payload.fetch(:delay))
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def topic_error(event)
|
|
175
|
+
client = event.payload.fetch(:client_id)
|
|
176
|
+
topic = event.payload.fetch(:topic)
|
|
177
|
+
|
|
178
|
+
increment("producer.#{client}.#{topic}.ack.errors")
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
attach_to "producer.kafka"
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
class AsyncProducerSubscriber < StatsdSubscriber
|
|
185
|
+
def enqueue_message(event)
|
|
186
|
+
client = event.payload.fetch(:client_id)
|
|
187
|
+
topic = event.payload.fetch(:topic)
|
|
188
|
+
queue_size = event.payload.fetch(:queue_size)
|
|
189
|
+
max_queue_size = event.payload.fetch(:max_queue_size)
|
|
190
|
+
queue_fill_ratio = queue_size.to_f / max_queue_size.to_f
|
|
191
|
+
|
|
192
|
+
# This gets us the avg/max queue size per producer.
|
|
193
|
+
timing("async_producer.#{client}.#{topic}.queue.size", queue_size)
|
|
194
|
+
|
|
195
|
+
# This gets us the avg/max queue fill ratio per producer.
|
|
196
|
+
timing("async_producer.#{client}.#{topic}.queue.fill_ratio", queue_fill_ratio)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def buffer_overflow(event)
|
|
200
|
+
client = event.payload.fetch(:client_id)
|
|
201
|
+
topic = event.payload.fetch(:topic)
|
|
202
|
+
|
|
203
|
+
increment("async_producer.#{client}.#{topic}.produce.errors")
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
attach_to "async_producer.kafka"
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
data/lib/kafka/version.rb
CHANGED
data/ruby-kafka.gemspec
CHANGED
|
@@ -39,6 +39,7 @@ Gem::Specification.new do |spec|
|
|
|
39
39
|
spec.add_development_dependency "colored"
|
|
40
40
|
spec.add_development_dependency "rspec_junit_formatter", "0.2.2"
|
|
41
41
|
spec.add_development_dependency "dogstatsd-ruby", ">= 2.0.0"
|
|
42
|
+
spec.add_development_dependency "statsd-ruby"
|
|
42
43
|
spec.add_development_dependency "ruby-prof"
|
|
43
44
|
spec.add_development_dependency "timecop"
|
|
44
45
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruby-kafka
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0.beta1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Daniel Schierbeck
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2017-
|
|
11
|
+
date: 2017-07-18 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: gssapi
|
|
@@ -192,6 +192,20 @@ dependencies:
|
|
|
192
192
|
- - ">="
|
|
193
193
|
- !ruby/object:Gem::Version
|
|
194
194
|
version: 2.0.0
|
|
195
|
+
- !ruby/object:Gem::Dependency
|
|
196
|
+
name: statsd-ruby
|
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
|
198
|
+
requirements:
|
|
199
|
+
- - ">="
|
|
200
|
+
- !ruby/object:Gem::Version
|
|
201
|
+
version: '0'
|
|
202
|
+
type: :development
|
|
203
|
+
prerelease: false
|
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
205
|
+
requirements:
|
|
206
|
+
- - ">="
|
|
207
|
+
- !ruby/object:Gem::Version
|
|
208
|
+
version: '0'
|
|
195
209
|
- !ruby/object:Gem::Dependency
|
|
196
210
|
name: ruby-prof
|
|
197
211
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -309,9 +323,11 @@ files:
|
|
|
309
323
|
- lib/kafka/protocol/topic_metadata_request.rb
|
|
310
324
|
- lib/kafka/round_robin_assignment_strategy.rb
|
|
311
325
|
- lib/kafka/sasl_gssapi_authenticator.rb
|
|
326
|
+
- lib/kafka/sasl_plain_authenticator.rb
|
|
312
327
|
- lib/kafka/snappy_codec.rb
|
|
313
328
|
- lib/kafka/socket_with_timeout.rb
|
|
314
329
|
- lib/kafka/ssl_socket_with_timeout.rb
|
|
330
|
+
- lib/kafka/statsd.rb
|
|
315
331
|
- lib/kafka/version.rb
|
|
316
332
|
- lib/ruby-kafka.rb
|
|
317
333
|
- performance/profile.rb
|