sneakers_custom_bunny 1.0.4
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 +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +20 -0
- data/Gemfile +3 -0
- data/Guardfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +172 -0
- data/ROADMAP.md +18 -0
- data/Rakefile +11 -0
- data/bin/sneakers +5 -0
- data/examples/benchmark_worker.rb +20 -0
- data/examples/max_retry_handler.rb +78 -0
- data/examples/metrics_worker.rb +28 -0
- data/examples/newrelic_metrics_worker.rb +40 -0
- data/examples/profiling_worker.rb +69 -0
- data/examples/sneakers.conf.rb.example +11 -0
- data/examples/title_scraper.rb +23 -0
- data/examples/workflow_worker.rb +23 -0
- data/lib/sneakers.rb +83 -0
- data/lib/sneakers/cli.rb +115 -0
- data/lib/sneakers/concerns/logging.rb +34 -0
- data/lib/sneakers/concerns/metrics.rb +34 -0
- data/lib/sneakers/configuration.rb +59 -0
- data/lib/sneakers/handlers/maxretry.rb +191 -0
- data/lib/sneakers/handlers/oneshot.rb +30 -0
- data/lib/sneakers/metrics/logging_metrics.rb +16 -0
- data/lib/sneakers/metrics/newrelic_metrics.rb +37 -0
- data/lib/sneakers/metrics/null_metrics.rb +13 -0
- data/lib/sneakers/metrics/statsd_metrics.rb +21 -0
- data/lib/sneakers/publisher.rb +34 -0
- data/lib/sneakers/queue.rb +65 -0
- data/lib/sneakers/runner.rb +82 -0
- data/lib/sneakers/spawner.rb +27 -0
- data/lib/sneakers/support/production_formatter.rb +11 -0
- data/lib/sneakers/support/utils.rb +18 -0
- data/lib/sneakers/tasks.rb +34 -0
- data/lib/sneakers/version.rb +3 -0
- data/lib/sneakers/worker.rb +151 -0
- data/lib/sneakers/workergroup.rb +47 -0
- data/sneakers.gemspec +35 -0
- data/spec/fixtures/require_worker.rb +17 -0
- data/spec/sneakers/cli_spec.rb +63 -0
- data/spec/sneakers/concerns/logging_spec.rb +39 -0
- data/spec/sneakers/concerns/metrics_spec.rb +38 -0
- data/spec/sneakers/configuration_spec.rb +75 -0
- data/spec/sneakers/publisher_spec.rb +83 -0
- data/spec/sneakers/queue_spec.rb +115 -0
- data/spec/sneakers/runner_spec.rb +26 -0
- data/spec/sneakers/sneakers_spec.rb +75 -0
- data/spec/sneakers/support/utils_spec.rb +44 -0
- data/spec/sneakers/worker_handlers_spec.rb +390 -0
- data/spec/sneakers/worker_spec.rb +463 -0
- data/spec/spec_helper.rb +13 -0
- metadata +306 -0
@@ -0,0 +1,69 @@
|
|
1
|
+
$: << File.expand_path('../lib', File.dirname(__FILE__))
|
2
|
+
require 'sneakers'
|
3
|
+
require 'sneakers/runner'
|
4
|
+
require 'logger'
|
5
|
+
|
6
|
+
|
7
|
+
profiling = ARGV[0]
|
8
|
+
messages = 100_000
|
9
|
+
|
10
|
+
|
11
|
+
if profiling
|
12
|
+
require 'ruby-prof'
|
13
|
+
messages /= 100 # profiling makes everything much slower (around 300req/s)
|
14
|
+
end
|
15
|
+
|
16
|
+
Sneakers.configure
|
17
|
+
Sneakers.logger.level = Logger::ERROR
|
18
|
+
|
19
|
+
Sneakers::Worker.configure_logger(Logger.new('/dev/null'))
|
20
|
+
|
21
|
+
puts "feeding messages in"
|
22
|
+
messages.times {
|
23
|
+
Sneakers.publish("{}", :to_queue => 'downloads')
|
24
|
+
}
|
25
|
+
puts "done"
|
26
|
+
|
27
|
+
|
28
|
+
class ProfilingWorker
|
29
|
+
include Sneakers::Worker
|
30
|
+
from_queue 'downloads',
|
31
|
+
:ack => true,
|
32
|
+
:threads => 50,
|
33
|
+
:prefetch => 50,
|
34
|
+
:timeout_job_after => 1,
|
35
|
+
:exchange => 'sneakers',
|
36
|
+
:heartbeat => 5
|
37
|
+
def work(msg)
|
38
|
+
ack!
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
|
44
|
+
r = Sneakers::Runner.new([ProfilingWorker])
|
45
|
+
|
46
|
+
# ctrl-c and Ruby 2.0 breaks signal handling
|
47
|
+
# Sidekiq has same issues
|
48
|
+
# https://github.com/mperham/sidekiq/issues/728
|
49
|
+
#
|
50
|
+
# so we use a timeout and a thread that kills profiling
|
51
|
+
if profiling
|
52
|
+
puts "profiling start"
|
53
|
+
RubyProf.start
|
54
|
+
|
55
|
+
|
56
|
+
Thread.new do
|
57
|
+
sleep 10
|
58
|
+
puts "stopping profiler"
|
59
|
+
result = RubyProf.stop
|
60
|
+
|
61
|
+
# Print a flat profile to text
|
62
|
+
printer = RubyProf::FlatPrinter.new(result)
|
63
|
+
printer.print(STDOUT)
|
64
|
+
exit(0)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
r.run
|
69
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "sneakers"
|
2
|
+
require 'open-uri'
|
3
|
+
require 'nokogiri'
|
4
|
+
|
5
|
+
require 'logger'
|
6
|
+
|
7
|
+
Sneakers.configure :log => STDOUT
|
8
|
+
Sneakers.logger.level = Logger::INFO
|
9
|
+
|
10
|
+
class TitleScraper
|
11
|
+
include Sneakers::Worker
|
12
|
+
|
13
|
+
from_queue 'downloads'
|
14
|
+
|
15
|
+
def work(msg)
|
16
|
+
doc = Nokogiri::HTML(open(msg))
|
17
|
+
worker_trace "FOUND <#{doc.css('title').text}>"
|
18
|
+
ack!
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
$: << File.expand_path('../lib', File.dirname(__FILE__))
|
2
|
+
require 'sneakers'
|
3
|
+
|
4
|
+
class WorkflowWorker
|
5
|
+
include Sneakers::Worker
|
6
|
+
from_queue 'downloads',
|
7
|
+
:durable => false,
|
8
|
+
:ack => true,
|
9
|
+
:threads => 50,
|
10
|
+
:prefetch => 50,
|
11
|
+
:timeout_job_after => 1,
|
12
|
+
:exchange => 'dummy',
|
13
|
+
:heartbeat => 5
|
14
|
+
|
15
|
+
def work(msg)
|
16
|
+
logger.info("Seriously, i'm DONE.")
|
17
|
+
publish "cleaned up", :to_queue => "foobar"
|
18
|
+
logger.info("Published to 'foobar'")
|
19
|
+
ack!
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
data/lib/sneakers.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
require "sneakers/version"
|
2
|
+
require 'thread/pool'
|
3
|
+
require 'bunny'
|
4
|
+
require 'logger'
|
5
|
+
|
6
|
+
module Sneakers
|
7
|
+
module Handlers
|
8
|
+
end
|
9
|
+
module Concerns
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
require 'sneakers/configuration'
|
14
|
+
require 'sneakers/support/production_formatter'
|
15
|
+
require 'sneakers/concerns/logging'
|
16
|
+
require 'sneakers/concerns/metrics'
|
17
|
+
require 'sneakers/handlers/oneshot'
|
18
|
+
require 'sneakers/worker'
|
19
|
+
require 'sneakers/publisher'
|
20
|
+
|
21
|
+
module Sneakers
|
22
|
+
extend self
|
23
|
+
|
24
|
+
CONFIG = Configuration.new
|
25
|
+
|
26
|
+
def configure(opts={})
|
27
|
+
# worker > userland > defaults
|
28
|
+
CONFIG.merge!(opts)
|
29
|
+
|
30
|
+
setup_general_logger!
|
31
|
+
setup_worker_concerns!
|
32
|
+
setup_general_publisher!
|
33
|
+
@configured = true
|
34
|
+
end
|
35
|
+
|
36
|
+
def clear!
|
37
|
+
CONFIG.clear
|
38
|
+
@logger = nil
|
39
|
+
@publisher = nil
|
40
|
+
@configured = false
|
41
|
+
end
|
42
|
+
|
43
|
+
def daemonize!(loglevel=Logger::INFO)
|
44
|
+
CONFIG[:log] = 'sneakers.log'
|
45
|
+
CONFIG[:daemonize] = true
|
46
|
+
setup_general_logger!
|
47
|
+
logger.level = loglevel
|
48
|
+
end
|
49
|
+
|
50
|
+
def logger
|
51
|
+
@logger
|
52
|
+
end
|
53
|
+
|
54
|
+
def publish(msg, routing)
|
55
|
+
@publisher.publish(msg, routing)
|
56
|
+
end
|
57
|
+
|
58
|
+
def configured?
|
59
|
+
@configured
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def setup_general_logger!
|
65
|
+
if [:info, :debug, :error, :warn].all?{ |meth| CONFIG[:log].respond_to?(meth) }
|
66
|
+
@logger = CONFIG[:log]
|
67
|
+
else
|
68
|
+
@logger = Logger.new(CONFIG[:log])
|
69
|
+
@logger.formatter = Sneakers::Support::ProductionFormatter
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def setup_worker_concerns!
|
74
|
+
Worker.configure_logger(Sneakers::logger)
|
75
|
+
Worker.configure_metrics(CONFIG[:metrics])
|
76
|
+
CONFIG[:handler] ||= Sneakers::Handlers::Oneshot
|
77
|
+
end
|
78
|
+
|
79
|
+
def setup_general_publisher!
|
80
|
+
@publisher = Sneakers::Publisher.new
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
data/lib/sneakers/cli.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'sneakers/runner'
|
3
|
+
|
4
|
+
|
5
|
+
#
|
6
|
+
# $ sneakers run TitleWorker,FooWorker
|
7
|
+
# $ sneakers stop
|
8
|
+
# $ sneakers recycle
|
9
|
+
# $ sneakers reload
|
10
|
+
# $ sneakers init
|
11
|
+
#
|
12
|
+
#
|
13
|
+
module Sneakers
|
14
|
+
class CLI < Thor
|
15
|
+
|
16
|
+
SNEAKERS=<<-EOF
|
17
|
+
|
18
|
+
__
|
19
|
+
,--' > Sneakers
|
20
|
+
`=====
|
21
|
+
|
22
|
+
EOF
|
23
|
+
|
24
|
+
BANNER = SNEAKERS
|
25
|
+
|
26
|
+
method_option :debug
|
27
|
+
method_option :daemonize
|
28
|
+
method_option :require
|
29
|
+
|
30
|
+
desc "work FirstWorker,SecondWorker ... ,NthWorker", "Run workers"
|
31
|
+
def work(workers)
|
32
|
+
opts = {
|
33
|
+
:daemonize => !!options[:daemonize]
|
34
|
+
}
|
35
|
+
|
36
|
+
opts[:log] = opts[:daemonize] ? 'sneakers.log' : STDOUT
|
37
|
+
|
38
|
+
if opts[:daemonize]
|
39
|
+
puts "*** DEPRACATED: self-daemonization '--daemonize' is considered a bad practice, which is why this feature will be removed in future versions. Please run Sneakers in front, and use things like upstart, systemd, or supervisor to manage it as a daemon."
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
Sneakers.configure(opts)
|
44
|
+
|
45
|
+
require_boot File.expand_path(options[:require]) if options[:require]
|
46
|
+
|
47
|
+
workers, missing_workers = Sneakers::Utils.parse_workers(workers)
|
48
|
+
|
49
|
+
unless missing_workers.empty?
|
50
|
+
say "Missing workers: #{missing_workers.join(', ')}" if missing_workers
|
51
|
+
say "Did you `require` properly?"
|
52
|
+
return
|
53
|
+
end
|
54
|
+
|
55
|
+
if workers.empty?
|
56
|
+
say <<-EOF
|
57
|
+
Error: No workers found.
|
58
|
+
Please require your worker classes before specifying in CLI
|
59
|
+
|
60
|
+
$ sneakers run FooWorker
|
61
|
+
^- require this in your code
|
62
|
+
|
63
|
+
EOF
|
64
|
+
return
|
65
|
+
end
|
66
|
+
|
67
|
+
r = Sneakers::Runner.new(workers)
|
68
|
+
|
69
|
+
pid = Sneakers::CONFIG[:pid_path]
|
70
|
+
|
71
|
+
say SNEAKERS
|
72
|
+
say "Workers ....: #{em workers.join(', ')}"
|
73
|
+
say "Log ........: #{em (Sneakers::CONFIG[:log] == STDOUT ? 'Console' : Sneakers::CONFIG[:log]) }"
|
74
|
+
say "PID ........: #{em pid}"
|
75
|
+
say ""
|
76
|
+
say (" "*31)+"Process control"
|
77
|
+
say "="*80
|
78
|
+
say "Stop (nicely) ..............: kill -SIGTERM `cat #{pid}`"
|
79
|
+
say "Stop (immediate) ...........: kill -SIGQUIT `cat #{pid}`"
|
80
|
+
say "Restart (nicely) ...........: kill -SIGUSR1 `cat #{pid}`"
|
81
|
+
say "Restart (immediate) ........: kill -SIGHUP `cat #{pid}`"
|
82
|
+
say "Reconfigure ................: kill -SIGUSR2 `cat #{pid}`"
|
83
|
+
say "Scale workers ..............: reconfigure, then restart"
|
84
|
+
say "="*80
|
85
|
+
say ""
|
86
|
+
|
87
|
+
if options[:debug]
|
88
|
+
say "==== configuration ==="
|
89
|
+
say Sneakers::CONFIG.inspect
|
90
|
+
say "======================"
|
91
|
+
end
|
92
|
+
|
93
|
+
r.run
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
private
|
98
|
+
def require_boot(file)
|
99
|
+
load file
|
100
|
+
end
|
101
|
+
|
102
|
+
def em(text)
|
103
|
+
shell.set_color(text, nil, true)
|
104
|
+
end
|
105
|
+
|
106
|
+
def ok(detail=nil)
|
107
|
+
text = detail ? "OK, #{detail}." : "OK."
|
108
|
+
say text, :green
|
109
|
+
end
|
110
|
+
|
111
|
+
def error(detail)
|
112
|
+
say detail, :red
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Sneakers
|
2
|
+
module Concerns
|
3
|
+
module Logging
|
4
|
+
def self.included(base)
|
5
|
+
base.extend ClassMethods
|
6
|
+
base.send :define_method, :logger do
|
7
|
+
base.logger
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def logger
|
13
|
+
@logger
|
14
|
+
end
|
15
|
+
|
16
|
+
def logger=(logger)
|
17
|
+
@logger = logger
|
18
|
+
end
|
19
|
+
|
20
|
+
def configure_logger(log=nil)
|
21
|
+
if log
|
22
|
+
@logger = log
|
23
|
+
else
|
24
|
+
@logger = Logger.new(STDOUT)
|
25
|
+
@logger.level = Logger::INFO
|
26
|
+
@logger.formatter = Sneakers::Support::ProductionFormatter
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'sneakers/metrics/null_metrics'
|
2
|
+
|
3
|
+
module Sneakers
|
4
|
+
module Concerns
|
5
|
+
module Metrics
|
6
|
+
def self.included(base)
|
7
|
+
base.extend ClassMethods
|
8
|
+
base.send :define_method, :metrics do
|
9
|
+
base.metrics
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def metrics
|
15
|
+
@metrics
|
16
|
+
end
|
17
|
+
|
18
|
+
def metrics=(metrics)
|
19
|
+
@metrics = metrics
|
20
|
+
end
|
21
|
+
|
22
|
+
def configure_metrics(metrics=nil)
|
23
|
+
if metrics
|
24
|
+
@metrics = metrics
|
25
|
+
else
|
26
|
+
@metrics = Sneakers::Metrics::NullMetrics.new
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Sneakers
|
4
|
+
class Configuration
|
5
|
+
|
6
|
+
extend Forwardable
|
7
|
+
def_delegators :@hash, :to_hash, :[], :[]=, :==, :fetch, :delete
|
8
|
+
|
9
|
+
DEFAULTS = {
|
10
|
+
# runner
|
11
|
+
:runner_config_file => nil,
|
12
|
+
:metrics => nil,
|
13
|
+
:daemonize => false,
|
14
|
+
:start_worker_delay => 0.2,
|
15
|
+
:workers => 4,
|
16
|
+
:log => STDOUT,
|
17
|
+
:pid_path => 'sneakers.pid',
|
18
|
+
|
19
|
+
# workers
|
20
|
+
:timeout_job_after => 5,
|
21
|
+
:prefetch => 10,
|
22
|
+
:threads => 10,
|
23
|
+
:durable => true,
|
24
|
+
:ack => true,
|
25
|
+
:heartbeat => 2,
|
26
|
+
:exchange => 'sneakers',
|
27
|
+
:exchange_type => :direct,
|
28
|
+
:hooks => {}
|
29
|
+
}.freeze
|
30
|
+
|
31
|
+
|
32
|
+
def initialize
|
33
|
+
clear
|
34
|
+
end
|
35
|
+
|
36
|
+
def clear
|
37
|
+
@hash = DEFAULTS.dup
|
38
|
+
@hash[:amqp] = ENV.fetch('RABBITMQ_URL', 'amqp://guest:guest@localhost:5672')
|
39
|
+
@hash[:vhost] = AMQ::Settings.parse_amqp_url(@hash[:amqp]).fetch(:vhost, '/')
|
40
|
+
end
|
41
|
+
|
42
|
+
def merge!(hash)
|
43
|
+
# parse vhost from amqp if vhost is not specified explicitly
|
44
|
+
if hash[:vhost].nil? && !hash[:amqp].nil?
|
45
|
+
hash = hash.dup
|
46
|
+
hash[:vhost] = AMQ::Settings.parse_amqp_url(hash[:amqp]).fetch(:vhost, '/')
|
47
|
+
end
|
48
|
+
|
49
|
+
@hash.merge!(hash)
|
50
|
+
end
|
51
|
+
|
52
|
+
def merge(hash)
|
53
|
+
instance = self.class.new
|
54
|
+
instance.merge! to_hash
|
55
|
+
instance.merge! hash
|
56
|
+
instance
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|