boardintel_frenzy_bunnies 0.0.9

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 (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/Gemfile +4 -0
  4. data/Guardfile +19 -0
  5. data/LICENSE +22 -0
  6. data/README.md +165 -0
  7. data/Rakefile +2 -0
  8. data/bin/frenzy_bunnies +6 -0
  9. data/examples/feed.rb +20 -0
  10. data/examples/feed_worker.rb +33 -0
  11. data/examples/feed_workers_bin.rb +21 -0
  12. data/fb-cap.png +0 -0
  13. data/frenzy_bunnies.gemspec +27 -0
  14. data/lib/frenzy_bunnies/cli.rb +29 -0
  15. data/lib/frenzy_bunnies/context.rb +40 -0
  16. data/lib/frenzy_bunnies/handlers/maxretry.rb +199 -0
  17. data/lib/frenzy_bunnies/handlers/oneshot.rb +31 -0
  18. data/lib/frenzy_bunnies/health/collector.rb +21 -0
  19. data/lib/frenzy_bunnies/health/providers/jvm.rb +43 -0
  20. data/lib/frenzy_bunnies/health.rb +10 -0
  21. data/lib/frenzy_bunnies/queue_factory.rb +23 -0
  22. data/lib/frenzy_bunnies/version.rb +3 -0
  23. data/lib/frenzy_bunnies/web/public/css/bootstrap.min.css +9 -0
  24. data/lib/frenzy_bunnies/web/public/img/bunny16.png +0 -0
  25. data/lib/frenzy_bunnies/web/public/img/bunny32.png +0 -0
  26. data/lib/frenzy_bunnies/web/public/index.html +225 -0
  27. data/lib/frenzy_bunnies/web/public/js/app.coffee +90 -0
  28. data/lib/frenzy_bunnies/web/public/js/app.js +202 -0
  29. data/lib/frenzy_bunnies/web/public/js/backbone-min.js +40 -0
  30. data/lib/frenzy_bunnies/web/public/js/bootstrap.js +2027 -0
  31. data/lib/frenzy_bunnies/web/public/js/bootstrap.min.js +6 -0
  32. data/lib/frenzy_bunnies/web/public/js/jquery-1.8.0.min.js +2 -0
  33. data/lib/frenzy_bunnies/web/public/js/jquery.filesize.js +52 -0
  34. data/lib/frenzy_bunnies/web/public/js/jquery.timeago.js +152 -0
  35. data/lib/frenzy_bunnies/web/public/js/underscore-min.js +32 -0
  36. data/lib/frenzy_bunnies/web.rb +51 -0
  37. data/lib/frenzy_bunnies/worker.rb +102 -0
  38. data/lib/frenzy_bunnies.rb +15 -0
  39. data/spec/frenzy_bunnies/worker_spec.rb +117 -0
  40. data/spec/spec_helper.rb +35 -0
  41. metadata +197 -0
@@ -0,0 +1,51 @@
1
+ require 'sinatra/base'
2
+ require 'json'
3
+
4
+ class FrenzyBunnies::Web < Sinatra::Base
5
+ configure do
6
+ # disable logging
7
+ set :public_folder, File.expand_path('web/public', File.dirname(__FILE__))
8
+ end
9
+
10
+ before do
11
+ content_type 'application/json'
12
+ end
13
+
14
+ not_found do
15
+ 'Cant find that, sorry.'
16
+ end
17
+
18
+ error do
19
+ 'Oops. There was an error - ' + env['sinatra.error'].name
20
+ end
21
+
22
+ get '/ping' do
23
+ 'ok'
24
+ end
25
+
26
+ get '/health' do
27
+ settings.health_collector.collect.to_json
28
+ end
29
+
30
+ get '/stats' do
31
+ jobs.map do |klass|
32
+ { :name => klass.name,
33
+ :stats => klass.jobs_stats }
34
+ end.to_json
35
+ end
36
+
37
+ get '/' do
38
+ redirect '/index.html'
39
+ end
40
+
41
+ def self.run_with(jobs, opts={})
42
+ set :jobs, (jobs || [])
43
+ set :health_collector, FrenzyBunnies::Health::Collector.new({:jvm => {:threadfilter => opts[:threadfilter]}})
44
+ @logger = opts[:logger]
45
+ @logger.info "* running web dashboard bound to #{opts[:host]} on port #{opts[:port]}."
46
+ Rack::Handler::WEBrick.run self, :Host => opts[:host], :Port => opts[:port], :Logger => WEBrick::Log.new("/dev/null"), :AccessLog => [nil, nil]
47
+ end
48
+ def jobs
49
+ settings.jobs
50
+ end
51
+ end
@@ -0,0 +1,102 @@
1
+ require 'atomic'
2
+
3
+ module FrenzyBunnies::Worker
4
+ def ack!
5
+ true
6
+ end
7
+
8
+ def work
9
+ end
10
+
11
+ def self.included(base)
12
+ base.extend ClassMethods
13
+ end
14
+
15
+ module ClassMethods
16
+
17
+ def from_queue(q, opts={})
18
+ @queue_name = q
19
+ @queue_opts = opts
20
+ end
21
+
22
+ def start(context)
23
+ @jobs_stats = { :failed => Atomic.new(0), :passed => Atomic.new(0) }
24
+ @working_since = Time.now
25
+
26
+ @logger = context.logger
27
+
28
+ queue_name = "#{@queue_name}_#{context.env}"
29
+
30
+ @queue_opts[:prefetch] ||= 10
31
+ @queue_opts[:durable] ||= false
32
+ @queue_opts[:timeout_job_after] = 5 if @queue_opts[:timeout_job_after].nil?
33
+ @queue_opts[:handler] ||= FrenzyBunnies::Handlers::Oneshot
34
+
35
+ if @queue_opts[:threads]
36
+ @thread_pool = MarchHare::ThreadPools.fixed_of_size(@queue_opts[:threads])
37
+ else
38
+ @thread_pool = MarchHare::ThreadPools.dynamically_growing
39
+ end
40
+
41
+ q = context.queue_factory.build_queue(queue_name, @queue_opts)
42
+
43
+ say "#{@queue_opts[:threads] ? "#{@queue_opts[:threads]} threads " : ''}with #{@queue_opts[:prefetch]} prefetch on <#{queue_name}>."
44
+
45
+ q.subscribe(:ack => true, :blocking => false, :executor => @thread_pool) do |headers, msg|
46
+ worker = new
47
+ handler = @queue_opts[:handler].new(headers.channel, q, @logger, { queue_options: @queue_opts })
48
+ begin
49
+ Timeout::timeout(@queue_opts[:timeout_job_after]) do
50
+ if worker.work(msg)
51
+ handler.acknowledge(headers, msg)
52
+ incr! :passed
53
+ else
54
+ handler.reject(headers, msg)
55
+ incr! :failed
56
+ error "REJECTED", msg
57
+ end
58
+ end
59
+ rescue Timeout::Error
60
+ handler.timeout(headers, msg)
61
+ incr! :failed
62
+ error "TIMEOUT #{@queue_opts[:timeout_job_after]}s", msg
63
+ rescue
64
+ handler.reject(headers, msg)
65
+ incr! :failed
66
+ error "ERROR #{$!}", msg
67
+ end
68
+ end
69
+
70
+ say "workers up."
71
+ end
72
+
73
+ def stop
74
+ say "stopping"
75
+ @thread_pool.shutdown_now
76
+ say "pool shutdown"
77
+ # @s.cancel #for some reason when the channel socket is broken, this is holding the process up and we're zombie.
78
+ say "stopped"
79
+ end
80
+
81
+ def queue_opts
82
+ @queue_opts
83
+ end
84
+
85
+ def jobs_stats
86
+ Hash[ @jobs_stats.map{ |k,v| [k, v.value] } ].merge({ :since => @working_since.to_i })
87
+ end
88
+ private
89
+ def say(text)
90
+ @logger.info "[#{self.name}] #{text}"
91
+ end
92
+
93
+ def error(text, msg)
94
+ @logger.error "[#{self.name}] #{text} <#{msg}>"
95
+ end
96
+
97
+ def incr!(what)
98
+ @jobs_stats[what].update { |v| v + 1 }
99
+ end
100
+ end
101
+ end
102
+
@@ -0,0 +1,15 @@
1
+ require 'march_hare'
2
+ require 'timeout'
3
+
4
+ module FrenzyBunnies
5
+ end
6
+
7
+
8
+ require "frenzy_bunnies/version"
9
+ require 'frenzy_bunnies/health'
10
+ require 'frenzy_bunnies/queue_factory'
11
+ require 'frenzy_bunnies/context'
12
+ require 'frenzy_bunnies/handlers/oneshot'
13
+ require 'frenzy_bunnies/worker'
14
+ require 'frenzy_bunnies/web'
15
+
@@ -0,0 +1,117 @@
1
+ require 'spec_helper'
2
+ require 'frenzy_bunnies'
3
+
4
+ class DummyWorker
5
+ include FrenzyBunnies::Worker
6
+ from_queue 'new.feeds'
7
+
8
+ def work(msg)
9
+ end
10
+ end
11
+
12
+ class CustomWorker
13
+ include FrenzyBunnies::Worker
14
+ from_queue 'new.feeds', :prefetch => 20, :durable => true, :timeout_job_after => 13, :threads => 25
15
+
16
+ def work(msg)
17
+ end
18
+ end
19
+
20
+ def with_test_queuefactory(ctx, ack=true, msg=nil, nowork=false)
21
+ qf = Object.new
22
+ q = Object.new
23
+ s = Object.new
24
+ hdr = Object.new
25
+ mock(qf).build_queue(anything, anything) { q }
26
+ mock(q).subscribe(anything){ s }
27
+
28
+ mock(s).each(anything) { |h,b| b.call(hdr, msg) unless nowork }
29
+ mock(hdr).ack{true} if !nowork && ack
30
+ mock(hdr).reject{true} if !nowork && !ack
31
+
32
+ mock(ctx).queue_factory { qf } # should return our own
33
+ end
34
+
35
+ describe FrenzyBunnies::Worker do
36
+ it "should start with a clean slate" do
37
+ # check stats, default configuration
38
+ ctx = FrenzyBunnies::Context.new(:logger=> Logger.new(nil))
39
+ with_test_queuefactory(ctx, nil, nil, true)
40
+
41
+
42
+ DummyWorker.start(ctx)
43
+ DummyWorker.jobs_stats[:failed].must_equal 0
44
+ DummyWorker.jobs_stats[:passed].must_equal 0
45
+ q = DummyWorker.queue_opts
46
+ q.must_equal({:prefetch=>10, :durable=>false, :timeout_job_after=>5})
47
+
48
+ end
49
+ it "should respond to configuration tweaks" do
50
+ # check that all params are changed
51
+ ctx = FrenzyBunnies::Context.new(:logger=> Logger.new(nil))
52
+ with_test_queuefactory(ctx, nil, nil, true)
53
+
54
+ CustomWorker.start(ctx)
55
+ CustomWorker.jobs_stats[:failed].must_equal 0
56
+ CustomWorker.jobs_stats[:passed].must_equal 0
57
+ q = CustomWorker.queue_opts
58
+ q.must_equal({:prefetch=>20, :durable=>true, :timeout_job_after=>13, :threads=>25})
59
+ end
60
+ it "should stop when asked to" do
61
+ # validate that a worker stops
62
+ # check stats, default configuration
63
+ ctx = FrenzyBunnies::Context.new(:logger=> Logger.new(nil))
64
+ with_test_queuefactory(ctx, nil, nil, true)
65
+
66
+
67
+ DummyWorker.start(ctx)
68
+ DummyWorker.stop
69
+ end
70
+ it "should be passed a message to work on" do
71
+ ctx = FrenzyBunnies::Context.new(:logger=> Logger.new(nil))
72
+ with_test_queuefactory(ctx, true, "work!")
73
+
74
+ any_instance_of(DummyWorker){ |w| mock(w).work("work!"){ true } }
75
+ DummyWorker.start(ctx)
76
+ end
77
+ it "should acknowledge a unit of work when worker succeeds" do
78
+ ctx = FrenzyBunnies::Context.new(:logger=> Logger.new(nil))
79
+ with_test_queuefactory(ctx)
80
+
81
+ any_instance_of(DummyWorker){ |w| mock(w).work(anything){ true } }
82
+ DummyWorker.start(ctx)
83
+ DummyWorker.jobs_stats[:passed].must_equal 1
84
+ DummyWorker.jobs_stats[:failed].must_equal 0
85
+ end
86
+ it "should reject a unit of work when worker fails" do
87
+ ctx = FrenzyBunnies::Context.new(:logger=> Logger.new(nil))
88
+ with_test_queuefactory(ctx,false)
89
+
90
+ any_instance_of(DummyWorker){ |w| mock(w).work(anything){ false } }
91
+ mock(DummyWorker).error(anything, anything){ |text, _| text.must_match(/^REJECTED/) }
92
+ DummyWorker.start(ctx)
93
+ DummyWorker.jobs_stats[:failed].must_equal 1
94
+ DummyWorker.jobs_stats[:passed].must_equal 0
95
+ end
96
+ it "should reject a unit of work when worker times out" do
97
+ ctx = FrenzyBunnies::Context.new(:logger=> Logger.new(nil))
98
+ with_test_queuefactory(ctx,false)
99
+ DummyWorker.queue_opts[:timeout_job_after] = 1
100
+ any_instance_of(DummyWorker){ |w| mock(w).work(anything){ sleep(2) }}
101
+ mock(DummyWorker).error(anything, anything){ |text, _| text.must_match(/^TIMEOUT/) }
102
+ DummyWorker.start(ctx)
103
+ DummyWorker.jobs_stats[:failed].must_equal 1
104
+ DummyWorker.jobs_stats[:passed].must_equal 0
105
+ DummyWorker.queue_opts[:timeout_job_after] = 5
106
+ end
107
+ it "should reject a unit of work when worker fails exceptionally" do
108
+ ctx = FrenzyBunnies::Context.new(:logger=> Logger.new(nil))
109
+ with_test_queuefactory(ctx,false)
110
+
111
+ any_instance_of(DummyWorker){ |w| mock(w).work(anything){ throw :error } }
112
+ mock(DummyWorker).error(anything, anything){ |text, _| text.must_match(/^ERROR/) }
113
+ DummyWorker.start(ctx)
114
+ DummyWorker.jobs_stats[:failed].must_equal 1
115
+ DummyWorker.jobs_stats[:passed].must_equal 0
116
+ end
117
+ end
@@ -0,0 +1,35 @@
1
+ #require 'simplecov'
2
+ #SimpleCov.start if ENV["COVERAGE"]
3
+
4
+ require 'minitest/autorun'
5
+
6
+
7
+
8
+ require 'rr'
9
+
10
+
11
+ class MiniTest::Unit::TestCase
12
+ include RR::Adapters::MiniTest
13
+ end
14
+
15
+
16
+ require 'thor'
17
+ # This is to silence the 'task' warning for the mocks.
18
+ #
19
+ class Thor
20
+ class << self
21
+ def create_task(meth) #:nodoc:
22
+ if @usage && @desc
23
+ base_class = @hide ? Thor::HiddenTask : Thor::Task
24
+ tasks[meth] = base_class.new(meth, @desc, @long_desc, @usage, method_options)
25
+ @usage, @desc, @long_desc, @method_options, @hide = nil
26
+ true
27
+ elsif self.all_tasks[meth] || meth == "method_missing"
28
+ true
29
+ else
30
+ false
31
+ end
32
+ end
33
+ end
34
+ end
35
+
metadata ADDED
@@ -0,0 +1,197 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: boardintel_frenzy_bunnies
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.9
5
+ platform: ruby
6
+ authors:
7
+ - Dotan Nahum
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-01-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: march_hare
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: thor
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: sinatra
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: atomic
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: json
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: guard-coffeescript
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rr
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: guard-minitest
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: RabbitMQ JRuby based workers on top of march_hare
126
+ email:
127
+ - jondotan@gmail.com
128
+ executables:
129
+ - frenzy_bunnies
130
+ extensions: []
131
+ extra_rdoc_files: []
132
+ files:
133
+ - ".gitignore"
134
+ - Gemfile
135
+ - Guardfile
136
+ - LICENSE
137
+ - README.md
138
+ - Rakefile
139
+ - bin/frenzy_bunnies
140
+ - examples/feed.rb
141
+ - examples/feed_worker.rb
142
+ - examples/feed_workers_bin.rb
143
+ - fb-cap.png
144
+ - frenzy_bunnies.gemspec
145
+ - lib/frenzy_bunnies.rb
146
+ - lib/frenzy_bunnies/cli.rb
147
+ - lib/frenzy_bunnies/context.rb
148
+ - lib/frenzy_bunnies/handlers/maxretry.rb
149
+ - lib/frenzy_bunnies/handlers/oneshot.rb
150
+ - lib/frenzy_bunnies/health.rb
151
+ - lib/frenzy_bunnies/health/collector.rb
152
+ - lib/frenzy_bunnies/health/providers/jvm.rb
153
+ - lib/frenzy_bunnies/queue_factory.rb
154
+ - lib/frenzy_bunnies/version.rb
155
+ - lib/frenzy_bunnies/web.rb
156
+ - lib/frenzy_bunnies/web/public/css/bootstrap.min.css
157
+ - lib/frenzy_bunnies/web/public/img/bunny16.png
158
+ - lib/frenzy_bunnies/web/public/img/bunny32.png
159
+ - lib/frenzy_bunnies/web/public/index.html
160
+ - lib/frenzy_bunnies/web/public/js/app.coffee
161
+ - lib/frenzy_bunnies/web/public/js/app.js
162
+ - lib/frenzy_bunnies/web/public/js/backbone-min.js
163
+ - lib/frenzy_bunnies/web/public/js/bootstrap.js
164
+ - lib/frenzy_bunnies/web/public/js/bootstrap.min.js
165
+ - lib/frenzy_bunnies/web/public/js/jquery-1.8.0.min.js
166
+ - lib/frenzy_bunnies/web/public/js/jquery.filesize.js
167
+ - lib/frenzy_bunnies/web/public/js/jquery.timeago.js
168
+ - lib/frenzy_bunnies/web/public/js/underscore-min.js
169
+ - lib/frenzy_bunnies/worker.rb
170
+ - spec/frenzy_bunnies/worker_spec.rb
171
+ - spec/spec_helper.rb
172
+ homepage: ''
173
+ licenses: []
174
+ metadata: {}
175
+ post_install_message:
176
+ rdoc_options: []
177
+ require_paths:
178
+ - lib
179
+ required_ruby_version: !ruby/object:Gem::Requirement
180
+ requirements:
181
+ - - ">="
182
+ - !ruby/object:Gem::Version
183
+ version: '0'
184
+ required_rubygems_version: !ruby/object:Gem::Requirement
185
+ requirements:
186
+ - - ">="
187
+ - !ruby/object:Gem::Version
188
+ version: '0'
189
+ requirements: []
190
+ rubyforge_project:
191
+ rubygems_version: 2.5.1
192
+ signing_key:
193
+ specification_version: 4
194
+ summary: RabbitMQ JRuby based workers on top of march_hare
195
+ test_files:
196
+ - spec/frenzy_bunnies/worker_spec.rb
197
+ - spec/spec_helper.rb