sneakers_custom_bunny 1.0.4

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