upperkut 0.1.0 → 0.1.2
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/.gitignore +1 -0
- data/Gemfile.lock +2 -2
- data/README.md +45 -5
- data/bin/upperkut +0 -2
- data/lib/upperkut/cli.rb +12 -5
- data/lib/upperkut/manager.rb +11 -2
- data/lib/upperkut/processor.rb +16 -4
- data/lib/upperkut/strategy.rb +9 -0
- data/lib/upperkut/util.rb +9 -4
- data/lib/upperkut/version.rb +1 -1
- data/lib/upperkut/worker.rb +4 -2
- data/lib/upperkut.rb +48 -1
- data/upperkut.gemspec +15 -16
- metadata +13 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7ec2e461543486232e5fef688a00cbcef1a605fa
|
4
|
+
data.tar.gz: 6a4dbf806dcf5f8e9833b2e8062dcb39b8d6f184
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c67e94633c0f33071724af9f939bc0b0ba3db29262c97526a386daba2cb66a0c89101c34ab42f284ee9bacd7b43a6664b55ffa5fbf79e91fbc7fe112e1a0d196
|
7
|
+
data.tar.gz: 06ceffd02b4bb83bdf1e2dfad936def7dbad1d6d7b2cab2c6d7faf965c119f7bda3e1bc7d7b4957ed732049f0b7851a887a281b8b2ee90683d40def39b64ba3a
|
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
# Upperkut
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
TODO: Delete this and the text above, and describe your gem
|
3
|
+
Batch background processing tool.
|
6
4
|
|
7
5
|
## Installation
|
8
6
|
|
@@ -21,8 +19,50 @@ Or install it yourself as:
|
|
21
19
|
$ gem install upperkut
|
22
20
|
|
23
21
|
## Usage
|
24
|
-
|
25
|
-
|
22
|
+
Examples:
|
23
|
+
|
24
|
+
1) Create a Worker class and the define how to process the batch;
|
25
|
+
```ruby
|
26
|
+
class MyWorker
|
27
|
+
include Upperkut::Worker
|
28
|
+
|
29
|
+
# This is optional
|
30
|
+
|
31
|
+
setup_upperkut do |s|
|
32
|
+
# Define which redis instance you want to use
|
33
|
+
s.redis = Redis.new(url: ENV['ANOTHER_REDIS_INSTANCE_URL'])
|
34
|
+
|
35
|
+
# Define the amount of items must be accumulated
|
36
|
+
s.batch_size = 2_000 # The default value is 1_000
|
37
|
+
|
38
|
+
# How frequent the Processor should hit redis looking for elegible
|
39
|
+
# batch. The default value is 5. You can also set the env
|
40
|
+
# UPPERKUT_POLLING_INTERVAL.
|
41
|
+
s.polling_interval = 4
|
42
|
+
|
43
|
+
# How long the Processor should wait to process batch even though
|
44
|
+
# the amount of items did not reached the batch_size.
|
45
|
+
s.max_wait = 300
|
46
|
+
end
|
47
|
+
|
48
|
+
def perform(batch_items)
|
49
|
+
SidekiqJobA.perform_async(batch_items)
|
50
|
+
SidekiqJobB.perform_async(batch_items)
|
51
|
+
|
52
|
+
process_metrics(batch_items)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
```
|
56
|
+
|
57
|
+
2) Start pushings items;
|
58
|
+
```ruby
|
59
|
+
Myworker.push([{'id' => SecureRandom.uuid}, 'name' => 'Robert C Hall', 'action' => 'EMAIL_OPENNED'])
|
60
|
+
```
|
61
|
+
|
62
|
+
3) Start Upperkut;
|
63
|
+
```bash
|
64
|
+
$ bundle exec upperkut --worker MyWorker --concurrency 10
|
65
|
+
```
|
26
66
|
|
27
67
|
## Development
|
28
68
|
|
data/bin/upperkut
CHANGED
data/lib/upperkut/cli.rb
CHANGED
@@ -7,6 +7,8 @@ module Upperkut
|
|
7
7
|
def initialize(args = ARGV)
|
8
8
|
@options = {}
|
9
9
|
parse_options(args)
|
10
|
+
|
11
|
+
STDOUT.puts @options.inspect
|
10
12
|
end
|
11
13
|
|
12
14
|
def start
|
@@ -20,12 +22,13 @@ module Upperkut
|
|
20
22
|
signals = %w(INT TERM)
|
21
23
|
|
22
24
|
signals.each do |signal|
|
23
|
-
|
25
|
+
trap signal do
|
26
|
+
w.puts(signal)
|
27
|
+
end
|
24
28
|
end
|
25
29
|
|
26
30
|
begin
|
27
31
|
manager.run
|
28
|
-
|
29
32
|
while readable_io = IO.select([r])
|
30
33
|
signal = readable_io.first[0].gets.strip
|
31
34
|
handle_signal(signal)
|
@@ -33,21 +36,22 @@ module Upperkut
|
|
33
36
|
rescue Interrupt
|
34
37
|
puts 'Shutting down'
|
35
38
|
manager.stop
|
39
|
+
sleep(5)
|
40
|
+
manager.kill
|
36
41
|
exit(0)
|
37
42
|
end
|
38
43
|
end
|
39
44
|
|
40
45
|
private
|
41
46
|
|
42
|
-
|
43
|
-
Upperkut.logger.debug "Got #{sig} signal"
|
47
|
+
def handle_signal(sig)
|
44
48
|
case sig
|
45
49
|
when 'INT'
|
46
50
|
raise Interrupt
|
47
51
|
when 'TERM'
|
48
52
|
raise Interrupt
|
49
53
|
end
|
50
|
-
|
54
|
+
end
|
51
55
|
|
52
56
|
def parse_options(args)
|
53
57
|
OptionParser.new do |o|
|
@@ -57,6 +61,9 @@ module Upperkut
|
|
57
61
|
o.on('-r', '--require FILE', 'Indicate a file to be required') do |arg|
|
58
62
|
@options[:file] = arg
|
59
63
|
end
|
64
|
+
o.on('-c', '--concurrency INT', 'Numbers of threads to spawn') do |arg|
|
65
|
+
@options[:concurrency] = Integer(arg)
|
66
|
+
end
|
60
67
|
|
61
68
|
end.parse!(args)
|
62
69
|
end
|
data/lib/upperkut/manager.rb
CHANGED
@@ -10,16 +10,25 @@ module Upperkut
|
|
10
10
|
def initialize(opts = {})
|
11
11
|
self.worker = opts.fetch(:worker).constantize
|
12
12
|
self.redis = worker.setup.redis
|
13
|
-
|
13
|
+
@concurrency = opts.fetch(:concurrency, 25)
|
14
14
|
@stopped = false
|
15
|
+
@processors = []
|
15
16
|
end
|
16
17
|
|
17
18
|
def run
|
18
|
-
|
19
|
+
@concurrency.times do
|
20
|
+
@processors << Processor.new(self).run
|
21
|
+
end
|
19
22
|
end
|
20
23
|
|
21
24
|
def stop
|
22
25
|
@stopped = true
|
23
26
|
end
|
27
|
+
|
28
|
+
def kill
|
29
|
+
@processors.each do |processor|
|
30
|
+
processor.kill
|
31
|
+
end
|
32
|
+
end
|
24
33
|
end
|
25
34
|
end
|
data/lib/upperkut/processor.rb
CHANGED
@@ -7,6 +7,18 @@ module Upperkut
|
|
7
7
|
@sleeping_time = 0
|
8
8
|
end
|
9
9
|
|
10
|
+
def run
|
11
|
+
@thread ||= Thread.new do
|
12
|
+
process
|
13
|
+
end
|
14
|
+
end
|
15
|
+
def kill
|
16
|
+
return if !@thread
|
17
|
+
@thread.raise Upperkut::Shutdown
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
10
22
|
def process
|
11
23
|
loop do
|
12
24
|
if should_process?
|
@@ -15,18 +27,18 @@ module Upperkut
|
|
15
27
|
next
|
16
28
|
end
|
17
29
|
|
18
|
-
puts "sleeping for #{@worker.setup.polling_interval} seconds"
|
19
30
|
@sleeping_time += sleep(@worker.setup.polling_interval)
|
20
31
|
end
|
21
32
|
end
|
22
33
|
|
23
|
-
private
|
24
|
-
|
25
34
|
def should_process?
|
35
|
+
buffer_size = @worker.size
|
36
|
+
|
26
37
|
return false if @manager.stopped
|
38
|
+
return false if buffer_size == 0
|
27
39
|
|
28
40
|
# TODO: rename #setup by config
|
29
|
-
|
41
|
+
buffer_size >= @worker.setup.batch_size ||
|
30
42
|
@sleeping_time >= @worker.setup.max_wait
|
31
43
|
end
|
32
44
|
|
data/lib/upperkut/strategy.rb
CHANGED
@@ -32,6 +32,15 @@ module Upperkut
|
|
32
32
|
redis.llen(key)
|
33
33
|
end
|
34
34
|
|
35
|
+
def latency
|
36
|
+
item = redis.lrange(key, -1, -1)
|
37
|
+
item = decode_json_items(item).first
|
38
|
+
return 0 unless item
|
39
|
+
now = Time.now.to_f
|
40
|
+
lat = now - item.fetch('enqueued_at', Time.now).to_f
|
41
|
+
lat
|
42
|
+
end
|
43
|
+
|
35
44
|
private
|
36
45
|
|
37
46
|
def key
|
data/lib/upperkut/util.rb
CHANGED
@@ -12,12 +12,17 @@ module Upperkut
|
|
12
12
|
klass_name
|
13
13
|
end
|
14
14
|
|
15
|
-
def
|
16
|
-
items.collect
|
15
|
+
def encode_json_items(items)
|
16
|
+
items = items.collect do |i|
|
17
|
+
JSON.generate(
|
18
|
+
'enqueued_at' => Time.now.to_i,
|
19
|
+
'body' => i
|
20
|
+
)
|
21
|
+
end
|
17
22
|
end
|
18
23
|
|
19
|
-
def
|
20
|
-
items.collect {|i| JSON.
|
24
|
+
def decode_json_items(items)
|
25
|
+
items.collect {|i| JSON.parse(i) }
|
21
26
|
end
|
22
27
|
end
|
23
28
|
end
|
data/lib/upperkut/version.rb
CHANGED
data/lib/upperkut/worker.rb
CHANGED
@@ -11,7 +11,9 @@ module Upperkut
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def process
|
14
|
-
items = self.class.fetch_items
|
14
|
+
items = self.class.fetch_items.collect! do |item|
|
15
|
+
item['body']
|
16
|
+
end
|
15
17
|
perform(items)
|
16
18
|
end
|
17
19
|
|
@@ -19,7 +21,7 @@ module Upperkut
|
|
19
21
|
extend Forwardable
|
20
22
|
|
21
23
|
def_delegators :setup, :strategy
|
22
|
-
def_delegators :strategy, :push_items, :
|
24
|
+
def_delegators :strategy, :push_items, :size, :latency
|
23
25
|
|
24
26
|
def push_items(items)
|
25
27
|
strategy.push_items(items)
|
data/lib/upperkut.rb
CHANGED
@@ -1,7 +1,52 @@
|
|
1
|
-
require_relative
|
1
|
+
require_relative 'upperkut/version'
|
2
2
|
require_relative 'upperkut/worker'
|
3
3
|
require 'redis'
|
4
4
|
|
5
|
+
# Public: Upperkut is a batch background processing tool for Ruby.
|
6
|
+
#
|
7
|
+
# Examples:
|
8
|
+
#
|
9
|
+
# 1) Create a Worker class and the define how to process the batch;
|
10
|
+
#
|
11
|
+
# class MyWorker
|
12
|
+
# include Upperkut::Worker
|
13
|
+
#
|
14
|
+
# # This is optional
|
15
|
+
#
|
16
|
+
# setup_upperkut do |s|
|
17
|
+
# # Define which redis instance you want to use
|
18
|
+
# s.redis = Redis.new(url: ENV['ANOTHER_REDIS_INSTANCE_URL'])
|
19
|
+
#
|
20
|
+
# # Define the amount of items must be accumulated
|
21
|
+
# s.batch_size = 2_000 # The default value is 1_000
|
22
|
+
#
|
23
|
+
# # How frequent the Processor should hit redis looking for elegible
|
24
|
+
# # batch. The default value is 5. You can also set the env
|
25
|
+
# # UPPERKUT_POLLING_INTERVAL.
|
26
|
+
# s.polling_interval = 4
|
27
|
+
#
|
28
|
+
# # How long the Processor should wait to process batch even though
|
29
|
+
# # the amount of items did not reached the batch_size.
|
30
|
+
# s.max_wait = 300
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# def perform(batch_items)
|
34
|
+
# SidekiqJobA.perform_async(batch_items)
|
35
|
+
# SidekiqJobB.perform_async(batch_items)
|
36
|
+
#
|
37
|
+
# process_metrics(batch_items)
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# 2) Start pushings items;
|
42
|
+
#
|
43
|
+
# Myworker.push([{'id' => SecureRandom.uuid}, 'name' => 'Robert C Hall', 'action' => 'EMAIL_OPENNED'])
|
44
|
+
#
|
45
|
+
# 3) Start Upperkut;
|
46
|
+
#
|
47
|
+
# $ bundle exec upperkut -worker MyWorker --concurrency 10
|
48
|
+
#
|
49
|
+
# 4) That's it :)
|
5
50
|
module Upperkut
|
6
51
|
class Configuration
|
7
52
|
attr_accessor :batch_size, :redis, :strategy, :max_wait, :polling_interval
|
@@ -15,4 +60,6 @@ module Upperkut
|
|
15
60
|
end
|
16
61
|
end
|
17
62
|
end
|
63
|
+
|
64
|
+
class Shutdown < StandardError ; end
|
18
65
|
end
|
data/upperkut.gemspec
CHANGED
@@ -1,30 +1,29 @@
|
|
1
1
|
|
2
|
-
lib = File.expand_path(
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require
|
4
|
+
require 'upperkut/version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name =
|
7
|
+
spec.name = 'upperkut'
|
8
8
|
spec.version = Upperkut::VERSION
|
9
|
-
spec.authors = [
|
10
|
-
spec.email = [
|
9
|
+
spec.authors = ['Nando Sousa']
|
10
|
+
spec.email = ['nandosousafr@gmail.com']
|
11
11
|
|
12
12
|
spec.summary = %q{Batch background processing tool}
|
13
13
|
spec.description = %q{Batch background processing tool}
|
14
|
-
spec.homepage =
|
15
|
-
spec.license =
|
16
|
-
|
17
|
-
#spec.metadata["allowed_push_host"] = "all"
|
14
|
+
spec.homepage = 'http://shipit.resultadosdigitais.com.br/open-source/'
|
15
|
+
spec.license = 'MIT'
|
18
16
|
|
19
17
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
20
18
|
f.match(%r{^(test|spec|features)/})
|
21
19
|
end
|
22
|
-
spec.
|
23
|
-
spec.
|
24
|
-
|
20
|
+
spec.executables = ['upperkut']
|
21
|
+
spec.require_paths = ['lib']
|
22
|
+
|
23
|
+
spec.required_ruby_version = '>= 2.2.2'
|
25
24
|
|
26
|
-
spec.add_dependency 'redis'
|
27
|
-
spec.add_development_dependency
|
28
|
-
spec.add_development_dependency
|
29
|
-
spec.add_development_dependency
|
25
|
+
spec.add_dependency 'redis', '>= 3.3.5', '< 5'
|
26
|
+
spec.add_development_dependency 'bundler', '~> 1.16'
|
27
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
28
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
30
29
|
end
|
metadata
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: upperkut
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nando Sousa
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
9
|
+
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
date: 2018-06-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
@@ -16,14 +16,20 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 3.3.5
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '5'
|
20
23
|
type: :runtime
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
23
26
|
requirements:
|
24
27
|
- - ">="
|
25
28
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
29
|
+
version: 3.3.5
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '5'
|
27
33
|
- !ruby/object:Gem::Dependency
|
28
34
|
name: bundler
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -69,7 +75,8 @@ dependencies:
|
|
69
75
|
description: Batch background processing tool
|
70
76
|
email:
|
71
77
|
- nandosousafr@gmail.com
|
72
|
-
executables:
|
78
|
+
executables:
|
79
|
+
- upperkut
|
73
80
|
extensions: []
|
74
81
|
extra_rdoc_files: []
|
75
82
|
files:
|
@@ -106,7 +113,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
106
113
|
requirements:
|
107
114
|
- - ">="
|
108
115
|
- !ruby/object:Gem::Version
|
109
|
-
version:
|
116
|
+
version: 2.2.2
|
110
117
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
111
118
|
requirements:
|
112
119
|
- - ">="
|