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