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.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.travis.yml +6 -0
  4. data/CHANGELOG.md +20 -0
  5. data/Gemfile +3 -0
  6. data/Guardfile +8 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +172 -0
  9. data/ROADMAP.md +18 -0
  10. data/Rakefile +11 -0
  11. data/bin/sneakers +5 -0
  12. data/examples/benchmark_worker.rb +20 -0
  13. data/examples/max_retry_handler.rb +78 -0
  14. data/examples/metrics_worker.rb +28 -0
  15. data/examples/newrelic_metrics_worker.rb +40 -0
  16. data/examples/profiling_worker.rb +69 -0
  17. data/examples/sneakers.conf.rb.example +11 -0
  18. data/examples/title_scraper.rb +23 -0
  19. data/examples/workflow_worker.rb +23 -0
  20. data/lib/sneakers.rb +83 -0
  21. data/lib/sneakers/cli.rb +115 -0
  22. data/lib/sneakers/concerns/logging.rb +34 -0
  23. data/lib/sneakers/concerns/metrics.rb +34 -0
  24. data/lib/sneakers/configuration.rb +59 -0
  25. data/lib/sneakers/handlers/maxretry.rb +191 -0
  26. data/lib/sneakers/handlers/oneshot.rb +30 -0
  27. data/lib/sneakers/metrics/logging_metrics.rb +16 -0
  28. data/lib/sneakers/metrics/newrelic_metrics.rb +37 -0
  29. data/lib/sneakers/metrics/null_metrics.rb +13 -0
  30. data/lib/sneakers/metrics/statsd_metrics.rb +21 -0
  31. data/lib/sneakers/publisher.rb +34 -0
  32. data/lib/sneakers/queue.rb +65 -0
  33. data/lib/sneakers/runner.rb +82 -0
  34. data/lib/sneakers/spawner.rb +27 -0
  35. data/lib/sneakers/support/production_formatter.rb +11 -0
  36. data/lib/sneakers/support/utils.rb +18 -0
  37. data/lib/sneakers/tasks.rb +34 -0
  38. data/lib/sneakers/version.rb +3 -0
  39. data/lib/sneakers/worker.rb +151 -0
  40. data/lib/sneakers/workergroup.rb +47 -0
  41. data/sneakers.gemspec +35 -0
  42. data/spec/fixtures/require_worker.rb +17 -0
  43. data/spec/sneakers/cli_spec.rb +63 -0
  44. data/spec/sneakers/concerns/logging_spec.rb +39 -0
  45. data/spec/sneakers/concerns/metrics_spec.rb +38 -0
  46. data/spec/sneakers/configuration_spec.rb +75 -0
  47. data/spec/sneakers/publisher_spec.rb +83 -0
  48. data/spec/sneakers/queue_spec.rb +115 -0
  49. data/spec/sneakers/runner_spec.rb +26 -0
  50. data/spec/sneakers/sneakers_spec.rb +75 -0
  51. data/spec/sneakers/support/utils_spec.rb +44 -0
  52. data/spec/sneakers/worker_handlers_spec.rb +390 -0
  53. data/spec/sneakers/worker_spec.rb +463 -0
  54. data/spec/spec_helper.rb +13 -0
  55. 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,11 @@
1
+ workers 2
2
+ amqp "amqp://guest:guest@localhost:55672"
3
+
4
+ before_fork do
5
+ Sneakers::logger.info " ** im before-fork'en ** "
6
+ end
7
+
8
+
9
+ after_fork do
10
+ Sneakers::logger.info " !! im after forke'n !! "
11
+ end
@@ -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
+
@@ -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
+
@@ -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