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

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