kafka-rest-rb 0.1.0.alpha2 → 0.1.0.alpha3

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: ef34042990b7684ddc6a59d1ac9ba8ee44f87d30
4
- data.tar.gz: 95dd2134ff748d790ffdb5b6ab216d99d326b59c
3
+ metadata.gz: 62669def0b5abd426e87fd604d686d80bb7b35cb
4
+ data.tar.gz: 34b0f48d653206b00b66d323bf70907eb9a6235c
5
5
  SHA512:
6
- metadata.gz: 246f29462bbf834ae1952c7b010bea02a7bfbd8b0613b6cbfd506755144d8dcab06291346ce6db3f5b2556ed9a0002e14257152c9403c91b8c5816c190b04d35
7
- data.tar.gz: eb2a8e82e9740b6e677605b848a2e2cf70330e76670533392059689acf317dac87380014fc864f0cc666185981a56a0aa6920b69aefd965bc94c4cddff117e25
6
+ metadata.gz: 1d3b74192ffd3bc11cb8493c0b1688cbcf973b20f3c572ae38a9edc1024bd334d3e4e2a32a360aeb512230f186a7cc849e23dd9a608aafc2a60686ef6d3ac043
7
+ data.tar.gz: e382353163f1fe427a970051e50476713f1f794efdacfdf7e23ce8b201cac596489211f46020e1750a4e504cf74a50a81006dfd405ece307f4645fcc34ac8da5
data/.gitignore CHANGED
@@ -9,3 +9,4 @@
9
9
  /tmp/
10
10
 
11
11
  *.gem
12
+ .ruby-version
data/README.md CHANGED
@@ -1,3 +1,4 @@
1
- # Kafka-REST client, producer/consumer DSLs and worker daemon for Ruby/Rails apps. [![CircleCI](https://circleci.com/gh/konukhov/kafka-rest-rb.svg?style=shield)](https://circleci.com/gh/konukhov/kafka-rest-rb)
1
+ # Kafka-REST client, producer/consumer DSLs and worker daemon for Ruby/Rails apps.
2
+ [![Gem Version](https://badge.fury.io/rb/kafka-rest-rb.svg)](https://badge.fury.io/rb/kafka-rest-rb) [![CircleCI](https://circleci.com/gh/konukhov/kafka-rest-rb.svg?style=shield)](https://circleci.com/gh/konukhov/kafka-rest-rb)
2
3
 
3
- In early development stage. Come back later to see the docs!
4
+ In early development stage. Come back later to read the docs or check specs to get the idea of what's going on!
@@ -20,9 +20,18 @@ Gem::Specification.new do |spec|
20
20
  spec.require_paths = ['lib']
21
21
 
22
22
  spec.add_runtime_dependency 'faraday', '~> 0.9'
23
+ spec.add_runtime_dependency 'net-http-persistent', '~> 2.9'
23
24
  spec.add_runtime_dependency 'faraday_middleware', '~> 0.10'
25
+
24
26
  spec.add_runtime_dependency 'concurrent-ruby', '~> 1.0'
25
- spec.add_runtime_dependency 'oj', '~> 2.17'
27
+ spec.add_runtime_dependency 'multi_json', '~> 1.12'
28
+ spec.add_runtime_dependency 'connection_pool', '~> 2.2'
29
+
30
+ if RUBY_PLATFORM == 'java'
31
+ spec.add_runtime_dependency 'jrjackson', '~> 0.3.4'
32
+ else
33
+ spec.add_runtime_dependency 'oj', '>= 2.9'
34
+ end
26
35
 
27
36
  spec.add_development_dependency "bundler", "~> 1.12"
28
37
  spec.add_development_dependency "rake", "~> 10.0"
@@ -1,6 +1,6 @@
1
1
  require 'faraday'
2
2
  require 'faraday_middleware/response_middleware'
3
- require 'oj'
3
+ require 'multi_json'
4
4
 
5
5
  module KafkaRest
6
6
  class Client
@@ -32,7 +32,7 @@ module KafkaRest
32
32
  class JsonRequest < Faraday::Middleware
33
33
  def call(env)
34
34
  if env[:body]
35
- env[:body] = Oj.dump env[:body], mode: :compat, symbol_keys: false
35
+ env[:body] = MultiJson.dump env[:body]
36
36
  end
37
37
 
38
38
  @app.call(env)
@@ -41,7 +41,7 @@ module KafkaRest
41
41
 
42
42
  class JsonResponse < FaradayMiddleware::ResponseMiddleware
43
43
  define_parser do |body|
44
- Oj.load(body)
44
+ MultiJson.load(body)
45
45
  end
46
46
  end
47
47
 
@@ -1,41 +1,54 @@
1
1
  require 'kafka_rest/client/middleware.rb'
2
2
  require 'faraday'
3
+ require 'connection_pool'
3
4
 
4
5
  module KafkaRest
5
6
  class Client
6
7
  def initialize
7
- @conn = Faraday.new(url: KafkaRest.config.url) do |c|
8
- c.request :encode_json
9
- c.request :default_headers, default_headers
8
+ @conn = ConnectionPool.new(size: 8, timeout: 5) do
9
+ Faraday.new(url: KafkaRest.config.url) do |c|
10
+ c.request :encode_json
11
+ c.request :default_headers, default_headers
10
12
 
11
- c.response :raise_exception
12
- c.response :decode_json
13
+ c.response :raise_exception
14
+ c.response :decode_json
13
15
 
14
- c.adapter :net_http_persistent
16
+ c.adapter :net_http_persistent
17
+ end
15
18
  end
16
19
  end
17
20
 
21
+ %w(get post put delete).each do |m|
22
+ class_eval %Q{
23
+ def #{m}(*args)
24
+ @conn.with do |c|
25
+ c.#{m}(*args)
26
+ end
27
+ end
28
+ }
29
+ end
30
+
18
31
  # Get list of topics
19
32
  ### returns: array of topics
20
33
  def topics
21
- @conn.get("/topics")
34
+ get("/topics")
22
35
  end
23
36
 
24
37
  # Get topic metadata by name
25
38
  ### returns: name, configs, partitions
26
39
  def topic(topic)
27
- @conn.get("/topics/#{topic}")
40
+ get("/topics/#{topic}")
28
41
  end
29
42
 
30
43
  # Get topic's partitions
31
44
  ###
32
45
  def topic_partitions(topic)
33
- @conn.get("/topics/#{topic}/partitions")
46
+ get("/topics/#{topic}/partitions")
34
47
  end
35
48
 
36
49
  # Get topic's partition metadata
37
50
  def topic_partition(topic, partition)
38
- @conn.get("/topics/#{topic}/partitions/#{partition}")
51
+ get("/topics/#{topic}/partitions/#{partition}")
39
52
  end
40
53
 
41
54
  # Get messages from topic's partition.
@@ -43,7 +56,7 @@ module KafkaRest
43
56
  params[:count] ||= 1
44
57
  format = params.delete(:format) || 'binary'
45
58
 
46
- @conn.get(
59
+ get(
47
60
  "/topics/#{topic}/partitions/#{partition}/messages",
48
61
  params,
49
62
  accept(format)
@@ -55,7 +68,7 @@ module KafkaRest
55
68
  records: records.is_a?(Array) ? records : [records]
56
69
  )
57
70
 
58
- @conn.post(path, body, content_type(format))
71
+ post(path, body, content_type(format))
59
72
  end
60
73
  private :produce_message
61
74
 
@@ -81,21 +94,21 @@ module KafkaRest
81
94
  body['auto.commit.enable'] = params[:auto_commit_enable] == true || false
82
95
  body['format'] = params[:format] || 'json'
83
96
 
84
- @conn.post("consumers/#{group_name}", body)
97
+ post("consumers/#{group_name}", body)
85
98
  end
86
99
 
87
100
  def consumer_commit_offsets(group_name, consumer_id)
88
- @conn.post("consumers/#{group_name}/instances/#{consumer_id}/offsets")
101
+ post("consumers/#{group_name}/instances/#{consumer_id}/offsets")
89
102
  end
90
103
 
91
104
  def consumer_remove(group_name, consumer_id)
92
- @conn.delete("consumers/#{group_name}/instances/#{consumer_id}")
105
+ delete("consumers/#{group_name}/instances/#{consumer_id}")
93
106
  end
94
107
 
95
108
  def consumer_consume_from_topic(group_name, consumer_id, topic, params = {})
96
109
  format = params.delete(:format) || 'json'
97
110
 
98
- @conn.get(
111
+ get(
99
112
  "consumers/#{group_name}/instances/#{consumer_id}/topics/#{topic}",
100
113
  params,
101
114
  accept(format)
@@ -103,7 +116,7 @@ module KafkaRest
103
116
  end
104
117
 
105
118
  def brokers
106
- @conn.get("/brokers")
119
+ get("/brokers")
107
120
  end
108
121
 
109
122
  private
@@ -1,3 +1,3 @@
1
1
  module KafkaRest
2
- VERSION = '0.1.0.alpha2'
2
+ VERSION = '0.1.0.alpha3'
3
3
  end
data/perf/perftest ADDED
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler'
4
+ Bundler.load_gemspec(File.expand_path('../../kafka-rest-rb.gemspec', __FILE__))
5
+ Bundler.require(:default)
6
+
7
+ require 'kafka_rest'
8
+ require 'securerandom'
9
+ require 'thread'
10
+ require 'ostruct'
11
+
12
+ ZK_URL = ENV['ZOOKEEPER'] || ':2181'
13
+ KAFKA_BOOTSTRAP_SERVER = ENV['KAFKA_BOOTSTRAP_SERVER'] || ':9092'
14
+
15
+ config = {
16
+ send_interval: ENV['SEND_INTERVAL'] && ENV['SEND_INTERVAL'].to_i,
17
+ parallelism: ENV['PARALLELISM'] && ENV['PARALLELISM'].to_i,
18
+ msg_count: ENV['MSG_COUNT'] && ENV['MSG_COUNT'].to_i
19
+ }
20
+
21
+ KafkaRest.configure do |c|
22
+ c.serialization_adapter = Class.new(KafkaRest::Producer::Serialization::Adapter) do
23
+ def serialize(obj, opts = {})
24
+ obj.id
25
+ end
26
+ end
27
+ end
28
+
29
+ class Test
30
+ FORMATS = %w(json avro binary).map(&:to_sym)
31
+
32
+ def initialize(config)
33
+ @topics = 3.times.map {|_| "topic-#{SecureRandom.uuid}" }
34
+ @producers = @topics.zip(FORMATS).map do |topic, format|
35
+ build_producer(topic, format)
36
+ end
37
+
38
+ @workers = []
39
+
40
+ @parallelism = config[:parallelism] || 4
41
+ @msg_count = config[:msg_count] || 1000
42
+ @send_interval = config[:send_interval] || 0.1
43
+
44
+ @success_count = 0
45
+ @errored_count = 0
46
+ @produce_time = 0
47
+
48
+ @errors = {}
49
+
50
+ @mutex = Mutex.new
51
+ end
52
+
53
+ def run!
54
+ begin_time = Time.now
55
+
56
+ if RUBY_PLATFORM == 'java'
57
+ trap('SIGTERM') { raise SignalException, 'SIGTERM' }
58
+ trap('SIGHUP') { raise SignalException, 'SIGHUP' }
59
+ trap('SIGINT') { raise Interrupt }
60
+ end
61
+
62
+ begin
63
+ @topics.each { |t| create_topic!(t) }
64
+
65
+ workers = @parallelism.times.map do |_|
66
+ Thread.new do |t|
67
+ log(t, "Worker initialized")
68
+ processed = 0
69
+
70
+ loop do
71
+ begin
72
+ t1 = Time.now
73
+ @producers.sample.generate_and_send!
74
+ t2 = (Time.now - t1) * 1000
75
+
76
+ @mutex.synchronize do
77
+ @success_count += 1
78
+ processed = @success_count + @errored_count
79
+ @produce_time += t2
80
+ end
81
+ rescue => e
82
+ @mutex.synchronize do
83
+ log(t, "Error while sending", false)
84
+
85
+ puts "#{e.class}: #{e.message}"
86
+ e.backtrace.each do |e|
87
+ puts e
88
+ end
89
+
90
+ @errors["#{e.class.name}: #{e.message}"] ||= 0
91
+ @errors["#{e.class.name}: #{e.message}"] += 1
92
+
93
+ @errored_count += 1
94
+ processed = @success_count + @errored_count
95
+ end
96
+ end
97
+ if processed >= @msg_count
98
+ raise StopIteration
99
+ else
100
+ sleep(@send_interval)
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ workers.map(&:join)
107
+ puts "Sent all messages"
108
+ ensure
109
+ test_time = Time.now - begin_time
110
+
111
+ @topics.each { |t| remove_topic!(t) }
112
+ puts "Total test time: #{test_time}s"
113
+ puts "Total messages: #{@msg_count}"
114
+ puts "Total succeeded sends: #{@success_count}"
115
+ puts "Total failed sends: #{@errored_count}"
116
+ puts "AVG produce time: #{@produce_time / (@success_count + @errored_count)}ms"
117
+
118
+ if @errors.any?
119
+ puts "Errors encountered:"
120
+ @errors.each do |e, count|
121
+ puts "\t#{e}: #{count} times"
122
+ end
123
+ end
124
+ end
125
+ end
126
+
127
+ private
128
+
129
+ def log(thread, msg, lock = true)
130
+ log = ->{ "[Worker thread-#{thread.object_id}] #{msg}" }
131
+
132
+ if lock
133
+ @mutex.synchronize &log
134
+ else
135
+ log.call
136
+ end
137
+ end
138
+
139
+ def run_command(cmd)
140
+ out = nil
141
+
142
+ IO.popen(cmd) do |io|
143
+ out = io.read
144
+ end
145
+
146
+ if $?.to_i == 0
147
+ yield if block_given?
148
+ else
149
+ puts "Command `#{cmd}` finished with an error."
150
+ end
151
+ end
152
+
153
+ def create_topic!(topic)
154
+ cmd = <<-CMD
155
+ kafka-topics --create \
156
+ --zookeeper #{ZK_URL} \
157
+ --topic #{topic} \
158
+ --partitions 2 \
159
+ --replication-factor 1
160
+ CMD
161
+
162
+ run_command(cmd) do
163
+ puts "Successfully created topic `#{topic}`"
164
+ end
165
+ end
166
+
167
+ def remove_topic!(topic)
168
+ run_command("/usr/local/bin/kafka-topics --delete --topic #{topic} --zookeeper #{ZK_URL}") do
169
+ puts "Successfully removed topic `#{topic}`"
170
+ end
171
+ end
172
+
173
+ def build_producer(_topic, _format)
174
+ Class.new do
175
+ include KafkaRest::Producer
176
+
177
+ topic _topic
178
+ format _format
179
+
180
+ if _format == :avro
181
+ value_schema <<-SCHEMA
182
+ {
183
+ "name":"test_schema",
184
+ "type":"string"
185
+ }
186
+ SCHEMA
187
+ end
188
+
189
+ key :id
190
+
191
+ def self.generate_and_send!
192
+ self.send!(OpenStruct.new(id: SecureRandom.uuid))
193
+ end
194
+ end
195
+ end
196
+ end
197
+
198
+ Test.new(config).run!
metadata CHANGED
@@ -1,111 +1,153 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kafka-rest-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.alpha2
4
+ version: 0.1.0.alpha3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Theodore Konukhov
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-10-12 00:00:00.000000000 Z
11
+ date: 2016-10-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: faraday
15
14
  requirement: !ruby/object:Gem::Requirement
16
15
  requirements:
17
- - - "~>"
16
+ - - ~>
18
17
  - !ruby/object:Gem::Version
19
18
  version: '0.9'
20
- type: :runtime
19
+ name: faraday
21
20
  prerelease: false
21
+ type: :runtime
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ~>
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0.9'
27
27
  - !ruby/object:Gem::Dependency
28
- name: faraday_middleware
29
28
  requirement: !ruby/object:Gem::Requirement
30
29
  requirements:
31
- - - "~>"
30
+ - - ~>
32
31
  - !ruby/object:Gem::Version
33
- version: '0.10'
32
+ version: '2.9'
33
+ name: net-http-persistent
34
+ prerelease: false
34
35
  type: :runtime
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '2.9'
41
+ - !ruby/object:Gem::Dependency
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ version: '0.10'
47
+ name: faraday_middleware
35
48
  prerelease: false
49
+ type: :runtime
36
50
  version_requirements: !ruby/object:Gem::Requirement
37
51
  requirements:
38
- - - "~>"
52
+ - - ~>
39
53
  - !ruby/object:Gem::Version
40
54
  version: '0.10'
41
55
  - !ruby/object:Gem::Dependency
42
- name: concurrent-ruby
43
56
  requirement: !ruby/object:Gem::Requirement
44
57
  requirements:
45
- - - "~>"
58
+ - - ~>
46
59
  - !ruby/object:Gem::Version
47
60
  version: '1.0'
48
- type: :runtime
61
+ name: concurrent-ruby
49
62
  prerelease: false
63
+ type: :runtime
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
52
- - - "~>"
66
+ - - ~>
53
67
  - !ruby/object:Gem::Version
54
68
  version: '1.0'
55
69
  - !ruby/object:Gem::Dependency
56
- name: oj
57
70
  requirement: !ruby/object:Gem::Requirement
58
71
  requirements:
59
- - - "~>"
72
+ - - ~>
60
73
  - !ruby/object:Gem::Version
61
- version: '2.17'
74
+ version: '1.12'
75
+ name: multi_json
76
+ prerelease: false
62
77
  type: :runtime
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: '1.12'
83
+ - !ruby/object:Gem::Dependency
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ~>
87
+ - !ruby/object:Gem::Version
88
+ version: '2.2'
89
+ name: connection_pool
63
90
  prerelease: false
91
+ type: :runtime
64
92
  version_requirements: !ruby/object:Gem::Requirement
65
93
  requirements:
66
- - - "~>"
94
+ - - ~>
67
95
  - !ruby/object:Gem::Version
68
- version: '2.17'
96
+ version: '2.2'
97
+ - !ruby/object:Gem::Dependency
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ~>
101
+ - !ruby/object:Gem::Version
102
+ version: 0.3.4
103
+ name: jrjackson
104
+ prerelease: false
105
+ type: :runtime
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ~>
109
+ - !ruby/object:Gem::Version
110
+ version: 0.3.4
69
111
  - !ruby/object:Gem::Dependency
70
- name: bundler
71
112
  requirement: !ruby/object:Gem::Requirement
72
113
  requirements:
73
- - - "~>"
114
+ - - ~>
74
115
  - !ruby/object:Gem::Version
75
116
  version: '1.12'
76
- type: :development
117
+ name: bundler
77
118
  prerelease: false
119
+ type: :development
78
120
  version_requirements: !ruby/object:Gem::Requirement
79
121
  requirements:
80
- - - "~>"
122
+ - - ~>
81
123
  - !ruby/object:Gem::Version
82
124
  version: '1.12'
83
125
  - !ruby/object:Gem::Dependency
84
- name: rake
85
126
  requirement: !ruby/object:Gem::Requirement
86
127
  requirements:
87
- - - "~>"
128
+ - - ~>
88
129
  - !ruby/object:Gem::Version
89
130
  version: '10.0'
90
- type: :development
131
+ name: rake
91
132
  prerelease: false
133
+ type: :development
92
134
  version_requirements: !ruby/object:Gem::Requirement
93
135
  requirements:
94
- - - "~>"
136
+ - - ~>
95
137
  - !ruby/object:Gem::Version
96
138
  version: '10.0'
97
139
  - !ruby/object:Gem::Dependency
98
- name: rspec
99
140
  requirement: !ruby/object:Gem::Requirement
100
141
  requirements:
101
- - - "~>"
142
+ - - ~>
102
143
  - !ruby/object:Gem::Version
103
144
  version: '3.0'
104
- type: :development
145
+ name: rspec
105
146
  prerelease: false
147
+ type: :development
106
148
  version_requirements: !ruby/object:Gem::Requirement
107
149
  requirements:
108
- - - "~>"
150
+ - - ~>
109
151
  - !ruby/object:Gem::Version
110
152
  version: '3.0'
111
153
  description: Kafka-REST client, DSLs and consumer workers for Ruby.
@@ -116,9 +158,9 @@ executables:
116
158
  extensions: []
117
159
  extra_rdoc_files: []
118
160
  files:
119
- - ".gitignore"
120
- - ".rspec"
121
- - ".travis.yml"
161
+ - .gitignore
162
+ - .rspec
163
+ - .travis.yml
122
164
  - CODE_OF_CONDUCT.md
123
165
  - Gemfile
124
166
  - LICENSE.txt
@@ -149,28 +191,29 @@ files:
149
191
  - lib/kafka_rest/version.rb
150
192
  - lib/kafka_rest/worker.rb
151
193
  - lib/kafka_rest/worker/consumer_manager.rb
194
+ - perf/perftest
152
195
  homepage: https://github.com/konukhov/kafka-rest-rb
153
196
  licenses:
154
197
  - MIT
155
198
  metadata: {}
156
- post_install_message:
199
+ post_install_message:
157
200
  rdoc_options: []
158
201
  require_paths:
159
202
  - lib
160
203
  required_ruby_version: !ruby/object:Gem::Requirement
161
204
  requirements:
162
- - - ">="
205
+ - - '>='
163
206
  - !ruby/object:Gem::Version
164
207
  version: '0'
165
208
  required_rubygems_version: !ruby/object:Gem::Requirement
166
209
  requirements:
167
- - - ">"
210
+ - - '>'
168
211
  - !ruby/object:Gem::Version
169
212
  version: 1.3.1
170
213
  requirements: []
171
- rubyforge_project:
172
- rubygems_version: 2.4.5
173
- signing_key:
214
+ rubyforge_project:
215
+ rubygems_version: 2.1.9
216
+ signing_key:
174
217
  specification_version: 4
175
218
  summary: Kafka-REST proxy client for Ruby on Rails.
176
219
  test_files: []