discobolo 0.0.1
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/.rspec +2 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +21 -0
- data/README.md +72 -0
- data/Rakefile +7 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/discobolo.gemspec +37 -0
- data/examples/boot.rb +57 -0
- data/examples/config.ru +9 -0
- data/lib/core_ext/string/color.rb +22 -0
- data/lib/discobolo.rb +16 -0
- data/lib/discobolo/actor.rb +43 -0
- data/lib/discobolo/application.rb +17 -0
- data/lib/discobolo/client.rb +47 -0
- data/lib/discobolo/config.rb +38 -0
- data/lib/discobolo/logger.rb +21 -0
- data/lib/discobolo/supervisor.rb +19 -0
- data/lib/discobolo/version.rb +3 -0
- data/lib/discobolo/web.rb +114 -0
- data/lib/discobolo/worker.rb +38 -0
- data/web/assets/css/sticky_footer.css +39 -0
- data/web/views/index.erb +17 -0
- data/web/views/info.erb +7 -0
- data/web/views/jobs/show.erb +33 -0
- data/web/views/layout.erb +63 -0
- data/web/views/partials/main_nav.erb +35 -0
- data/web/views/queues/index.erb +17 -0
- data/web/views/queues/show.erb +64 -0
- data/web/views/workers/index.erb +10 -0
- metadata +161 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 793afc782710e02e01e09644692bd009e160ed00
|
4
|
+
data.tar.gz: 299bf029622d518be53f1485537b45743f5a7e33
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fd8d6ad1fa8a21d90d986a8e9bb6aaf250c4a2b6f525b3297cd76dd7a0e071645ec39c07ba33a1abb79ffb24d7f1a785aaed7a937ef4e94c45b5a502e9149f57
|
7
|
+
data.tar.gz: 63ba95c16ad886fe2f3091746fe8d07ed1d62ae9d8aa69450ac982db331564e1de61e0fdebd5130668b1e0fb50cf20f7c93f03a4c46d22f3bdaf372389ab0576
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
|
4
|
+
|
5
|
+
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
|
6
|
+
|
7
|
+
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
|
8
|
+
|
9
|
+
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
|
10
|
+
|
11
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
|
12
|
+
|
13
|
+
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Miguel Michelson
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# Discobolo (WIP)
|
2
|
+
|
3
|
+
Ruby worker system for [disque](https://github.com/antirez/disque)
|
4
|
+
|
5
|
+
##Usage:
|
6
|
+
|
7
|
+
### Setup application:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
# Configure Discobolo
|
11
|
+
|
12
|
+
Discobolo::Config.setup do |config|
|
13
|
+
config.client = ["127.0.0.1:7711"]
|
14
|
+
config.queues = ["default", "important", "bogus"]
|
15
|
+
config.fetch_options = {count: 10, timeout: 2000}
|
16
|
+
config.actor_concurrency = 5
|
17
|
+
config.logger = $stdout # or filepath.log
|
18
|
+
end
|
19
|
+
|
20
|
+
# Run Application:
|
21
|
+
|
22
|
+
Discobolo::Application.run
|
23
|
+
```
|
24
|
+
|
25
|
+
### Implement a Worker:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
|
29
|
+
class MyWorker < Discobolo::Worker
|
30
|
+
set_queue "bogus"
|
31
|
+
|
32
|
+
def perform(*args)
|
33
|
+
# Do the hard work here
|
34
|
+
end
|
35
|
+
end
|
36
|
+
```
|
37
|
+
|
38
|
+
### Enqueue
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
DefaultWorker.enqueue({foo:bar})
|
42
|
+
```
|
43
|
+
|
44
|
+
## Web interface:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
#config.ru
|
48
|
+
|
49
|
+
require 'discobolo/web'
|
50
|
+
|
51
|
+
require "./your_disque_app"
|
52
|
+
|
53
|
+
map '/disque' do
|
54
|
+
run Discobolo::Web
|
55
|
+
end
|
56
|
+
|
57
|
+
```
|
58
|
+
### Run rack appplication:
|
59
|
+
|
60
|
+
`bundle exec rackup`
|
61
|
+
|
62
|
+
visit http://localhost:4567
|
63
|
+
|
64
|
+
## Contributing
|
65
|
+
|
66
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/michelson/discobolo. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
|
67
|
+
|
68
|
+
|
69
|
+
## License
|
70
|
+
|
71
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
72
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "discobolo"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/discobolo.gemspec
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'discobolo/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "discobolo"
|
8
|
+
spec.version = Discobolo::VERSION
|
9
|
+
spec.authors = ["Miguel Michelson"]
|
10
|
+
spec.email = ["miguelmichelson@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = "Discobolo is a disque adapter for ruby & rails"
|
13
|
+
spec.description = "Discobolo is a disque adapter for ruby & rails"
|
14
|
+
spec.homepage = "https://github.com/discobolo"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
# Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
|
18
|
+
# delete this section to allow pushing this gem to any host.
|
19
|
+
#if spec.respond_to?(:metadata)
|
20
|
+
# spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
|
21
|
+
#else
|
22
|
+
# raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
23
|
+
#end
|
24
|
+
|
25
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
26
|
+
spec.bindir = "exe"
|
27
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
|
+
spec.require_paths = ["lib"]
|
29
|
+
|
30
|
+
spec.add_dependency "disque"
|
31
|
+
spec.add_dependency "sinatra"
|
32
|
+
spec.add_dependency "celluloid"
|
33
|
+
spec.add_dependency "json"
|
34
|
+
|
35
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
36
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
37
|
+
end
|
data/examples/boot.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require "discobolo"
|
2
|
+
require "pry"
|
3
|
+
|
4
|
+
# EXAMPLE WORKERS FOR QUEUES
|
5
|
+
|
6
|
+
class BogusWorker < Discobolo::Worker
|
7
|
+
set_queue "bogus"
|
8
|
+
|
9
|
+
def perform(*args)
|
10
|
+
puts "Performing from BogusWorker"
|
11
|
+
puts "With #{job_id} #{args} "
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class ImportantWorker < Discobolo::Worker
|
16
|
+
set_queue "important"
|
17
|
+
|
18
|
+
def perform(*args)
|
19
|
+
puts "Performing from ImportantWorker"
|
20
|
+
puts "With #{job_id} #{args} "
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class DefaultWorker < Discobolo::Worker
|
25
|
+
set_queue "default"
|
26
|
+
|
27
|
+
def perform(*args)
|
28
|
+
puts "Performing from DefaultWorker"
|
29
|
+
puts "With #{job_id} #{args} "
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
# APPLICATION SETUP
|
35
|
+
|
36
|
+
Discobolo::Config.setup do |config|
|
37
|
+
config.client = ["127.0.0.1:7711"]
|
38
|
+
config.queues = ["default", "important", "bogus"]
|
39
|
+
config.fetch_options = {count: 10, timeout: 2000}
|
40
|
+
end
|
41
|
+
|
42
|
+
@client = Discobolo::Config.client
|
43
|
+
@app = Discobolo::Application
|
44
|
+
|
45
|
+
##USAGE
|
46
|
+
|
47
|
+
# you need 2 terminal sessions
|
48
|
+
|
49
|
+
# TERMINAL 1 execute `bundle exec rake console`
|
50
|
+
# rake console
|
51
|
+
# $> @app.run
|
52
|
+
|
53
|
+
# TERMINAL 2 execute `bundle exec rake console`
|
54
|
+
# rake console
|
55
|
+
# $> DefaultWorker.enqueue("Hello")
|
56
|
+
# $> ImportantWorker.enqueue([1,2,3,4])
|
57
|
+
# $> BogusWorker.enqueue({b:323 , c:"dslihjoikmkkkmkmmjj"})
|
data/examples/config.ru
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
class String
|
2
|
+
# colorization
|
3
|
+
def colorize(color_code)
|
4
|
+
"\e[#{color_code}m#{self}\e[0m"
|
5
|
+
end
|
6
|
+
|
7
|
+
def red
|
8
|
+
colorize(31)
|
9
|
+
end
|
10
|
+
|
11
|
+
def green
|
12
|
+
colorize(32)
|
13
|
+
end
|
14
|
+
|
15
|
+
def yellow
|
16
|
+
colorize(33)
|
17
|
+
end
|
18
|
+
|
19
|
+
def pink
|
20
|
+
colorize(35)
|
21
|
+
end
|
22
|
+
end
|
data/lib/discobolo.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
|
2
|
+
require "disque"
|
3
|
+
require "celluloid"
|
4
|
+
require "json"
|
5
|
+
require 'core_ext/string/color'
|
6
|
+
|
7
|
+
module Discobolo
|
8
|
+
autoload :VERSION, "discobolo/version.rb"
|
9
|
+
autoload :Logger, "discobolo/logger"
|
10
|
+
autoload :Config, "discobolo/config"
|
11
|
+
autoload :Client, "discobolo/client"
|
12
|
+
autoload :Supervisor, "discobolo/supervisor"
|
13
|
+
autoload :Actor, "discobolo/actor"
|
14
|
+
autoload :Worker, "discobolo/worker"
|
15
|
+
autoload :Application,"discobolo/application"
|
16
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Discobolo
|
2
|
+
class Actor
|
3
|
+
include Celluloid
|
4
|
+
|
5
|
+
attr_accessor :queues
|
6
|
+
|
7
|
+
def initialize(*args)
|
8
|
+
args = Hash[*args.flatten] if args.is_a?(Array)
|
9
|
+
Discobolo::Config.logger.info "Initialize actor with: #{args}"
|
10
|
+
@queues = Discobolo::Config.queues
|
11
|
+
async.fetch if args[:fetch]
|
12
|
+
end
|
13
|
+
|
14
|
+
def fetch
|
15
|
+
client = Discobolo::Config.client
|
16
|
+
Discobolo::Config.logger.info "Listen Disque queues: #{self.queues} with concurrency of #{Discobolo::Config.actor_concurrency} workers"
|
17
|
+
options = Discobolo::Config.fetch_options.merge({from: self.queues})
|
18
|
+
loop do
|
19
|
+
jobs = client.fetch(options)
|
20
|
+
jobs.to_a.each do |queue, job_id, options|
|
21
|
+
Discobolo::Config.logger.info "#{queue} queue: received #{job_id} received #{options}"
|
22
|
+
|
23
|
+
#since we are supervising the actor, let it crash
|
24
|
+
#begin
|
25
|
+
# Claims to be still working with the specified job
|
26
|
+
#client.working(job_id)
|
27
|
+
|
28
|
+
options = JSON.parse(options)
|
29
|
+
klass = Object.const_get(options['class'])
|
30
|
+
instance = klass.new
|
31
|
+
instance.job_id = job_id
|
32
|
+
instance.async.perform_async(*options['args'])
|
33
|
+
|
34
|
+
#rescue => e
|
35
|
+
# Discobolo::Config.logger.error "Terrible error happened #{e}"
|
36
|
+
#end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Discobolo
|
2
|
+
class Application
|
3
|
+
|
4
|
+
def self.run
|
5
|
+
@supervisor = Discobolo::Supervisor.new
|
6
|
+
@supervisor.register_queues
|
7
|
+
#@supervisor.actors.each{|o| o.async.fetch }
|
8
|
+
@supervisor.class.run
|
9
|
+
self
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.actors
|
13
|
+
@supervisor
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Discobolo
|
2
|
+
class Client < Disque
|
3
|
+
|
4
|
+
def show(jobid)
|
5
|
+
Hash[ *call("SHOW", jobid)]
|
6
|
+
end
|
7
|
+
|
8
|
+
def working(jobid)
|
9
|
+
call("WORKING", jobid)
|
10
|
+
end
|
11
|
+
|
12
|
+
def queue_lenght(queue)
|
13
|
+
call("QLEN", queue)
|
14
|
+
end
|
15
|
+
|
16
|
+
def dequeue(queue)
|
17
|
+
call("DEQUEUE", queue)
|
18
|
+
end
|
19
|
+
|
20
|
+
def enqueue(queue)
|
21
|
+
call("ENQUEUE", queue)
|
22
|
+
end
|
23
|
+
|
24
|
+
def queue_stat(queue)
|
25
|
+
Discobolo::Config.logger.error("QSTAT not implemented yet")
|
26
|
+
#call("QSTAT", queue)
|
27
|
+
end
|
28
|
+
|
29
|
+
def jscan(queue, page=0, count=30)
|
30
|
+
jobs = []
|
31
|
+
Discobolo::Config.client.call("JSCAN", page, "COUNT", count, "QUEUE" , queue, "REPLY", "all").each do |job|
|
32
|
+
next if job.is_a? String
|
33
|
+
next if job.empty?
|
34
|
+
job.each do |j|
|
35
|
+
jobs << { results: Hash[*j] }
|
36
|
+
end
|
37
|
+
end.compact
|
38
|
+
jobs
|
39
|
+
#binding.pry
|
40
|
+
end
|
41
|
+
|
42
|
+
def info
|
43
|
+
call("INFO")
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
|
2
|
+
module Discobolo
|
3
|
+
|
4
|
+
class Config
|
5
|
+
|
6
|
+
class << self
|
7
|
+
attr_accessor :client, :queues, :fetch_options, :auth, :actor_concurrency
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.setup
|
11
|
+
yield self
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.client=(nodes)
|
15
|
+
#client = Disque.new("127.0.0.1:7711", auth: "e727d1464a...")
|
16
|
+
@client = Client.new(nodes)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.logger=(arg=nil)
|
20
|
+
l = arg.nil? ? arg : $stdout
|
21
|
+
@logger = Discobolo::Logger.new(l)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.logger
|
25
|
+
@logger || Discobolo::Logger.new($stdout)
|
26
|
+
end
|
27
|
+
|
28
|
+
def actor_concurrency
|
29
|
+
@actor_concurrency || 5
|
30
|
+
end
|
31
|
+
|
32
|
+
def fetch_options
|
33
|
+
@fetch_options || {count: 10, timeout: 2000}
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Discobolo
|
2
|
+
|
3
|
+
class Logger < Logger
|
4
|
+
def format_message(severity, timestamp, progname, msg)
|
5
|
+
#puts timestamp
|
6
|
+
#puts progname
|
7
|
+
"#{severity} [#{timestamp}]: #{msg}\n".send(colorize(severity))
|
8
|
+
end
|
9
|
+
|
10
|
+
def colorize(severity)
|
11
|
+
case severity
|
12
|
+
when "ERROR"
|
13
|
+
:red
|
14
|
+
when "INFO"
|
15
|
+
:green
|
16
|
+
when "WARN"
|
17
|
+
:yellow
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Discobolo
|
2
|
+
|
3
|
+
class Supervisor < Celluloid::SupervisionGroup
|
4
|
+
|
5
|
+
def register_queues
|
6
|
+
self.pool Discobolo::Actor, size: self.class.concurrency,
|
7
|
+
as: :discobolo_pool,
|
8
|
+
args: { fetch: true }
|
9
|
+
|
10
|
+
Discobolo::Config.logger.info "#{self.actors.size} registered actors"
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.concurrency
|
15
|
+
Discobolo::Config.actor_concurrency
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'yaml'
|
3
|
+
require 'sinatra/base'
|
4
|
+
|
5
|
+
require 'discobolo'
|
6
|
+
#require 'discobolo/api'
|
7
|
+
#require 'discobolo/paginator'
|
8
|
+
#require 'discobolo/web_helpers'
|
9
|
+
|
10
|
+
require 'sinatra/base'
|
11
|
+
|
12
|
+
|
13
|
+
module Discobolo
|
14
|
+
|
15
|
+
module WebHelpers
|
16
|
+
def client
|
17
|
+
Discobolo::Config.client
|
18
|
+
end
|
19
|
+
|
20
|
+
def url_for(path)
|
21
|
+
base_path = request.script_name
|
22
|
+
[base_path,path].join("/")
|
23
|
+
end
|
24
|
+
|
25
|
+
def link_to(name, path, options={})
|
26
|
+
"<a href='#{url_for(path)}'>#{name}</a>"
|
27
|
+
end
|
28
|
+
|
29
|
+
def relative_time(start_time)
|
30
|
+
#start_time = start_time.is_a?(Integer) ? Time.at(start_time) : start_time
|
31
|
+
diff_seconds = start_time
|
32
|
+
case start_time
|
33
|
+
when 0 .. 59
|
34
|
+
out = "in #{diff_seconds.round(2)} seconds"
|
35
|
+
when 60 .. (3600-1)
|
36
|
+
out = "in #{(diff_seconds/60).round(2)} minutes"
|
37
|
+
when 3600 .. (3600*24-1)
|
38
|
+
out = "in #{(diff_seconds/3600).round(2)} hours"
|
39
|
+
when (3600*24) .. (3600*24*30)
|
40
|
+
out = "in #{diff_seconds/(3600*24).round(2)} days"
|
41
|
+
else
|
42
|
+
out = start_time.strftime("%m/%d/%Y")
|
43
|
+
end
|
44
|
+
out
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
module Discobolo
|
54
|
+
|
55
|
+
|
56
|
+
class Web < Sinatra::Base
|
57
|
+
set :root, File.expand_path(File.dirname(__FILE__) + "/../../web")
|
58
|
+
set :public_folder, proc { "#{root}/assets" }
|
59
|
+
set :views, proc { "#{root}/views" }
|
60
|
+
set :locales, ["#{root}/locales"]
|
61
|
+
#set :erb, escape_html: true,
|
62
|
+
# layout_options: {views: 'app/views/layouts'}
|
63
|
+
|
64
|
+
helpers Discobolo::WebHelpers
|
65
|
+
|
66
|
+
class << self
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
get "/" do
|
71
|
+
erb :index, layout: :layout
|
72
|
+
end
|
73
|
+
|
74
|
+
get "/info" do
|
75
|
+
erb :info, layout: :layout
|
76
|
+
end
|
77
|
+
|
78
|
+
get "/queues" do
|
79
|
+
@queues = Discobolo::Config.queues
|
80
|
+
erb :'queues/index'
|
81
|
+
end
|
82
|
+
|
83
|
+
get "/queues/:queue" do
|
84
|
+
@queue = params[:queue]
|
85
|
+
@page = params[:page].to_i || 0
|
86
|
+
@jobs = client.jscan(@queue, @page.to_i, 30).map{|o| o[:results]}
|
87
|
+
erb :'queues/show'
|
88
|
+
end
|
89
|
+
|
90
|
+
get "/workers" do
|
91
|
+
erb :'workers/index'
|
92
|
+
end
|
93
|
+
|
94
|
+
get "/jobs/:id" do
|
95
|
+
@job = client.show(params[:id])
|
96
|
+
erb :'jobs/show'
|
97
|
+
end
|
98
|
+
|
99
|
+
get "/jobs/:id/dequeue" do
|
100
|
+
@job = client.dequeue(params[:id])
|
101
|
+
redirect url_for("/jobs/#{params[:id]}")
|
102
|
+
end
|
103
|
+
|
104
|
+
get "/jobs/:id/dequeue" do
|
105
|
+
@job = client.enqueue(params[:id])
|
106
|
+
redirect url_for("/jobs/#{params[:id]}")
|
107
|
+
end
|
108
|
+
|
109
|
+
get "/info" do
|
110
|
+
erb :info
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Discobolo
|
2
|
+
class Worker
|
3
|
+
include Celluloid
|
4
|
+
attr_accessor :queue, :job_id
|
5
|
+
|
6
|
+
class << self
|
7
|
+
attr_accessor :queue
|
8
|
+
attr_accessor :enqueue_options
|
9
|
+
end
|
10
|
+
|
11
|
+
def perform_async(*args)
|
12
|
+
perform(*args)
|
13
|
+
Discobolo::Config.client.call('ACKJOB', self.job_id)
|
14
|
+
Discobolo::Config.logger.info "Finished job #{self.job_id}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.enqueue(message, options={})
|
18
|
+
timeout = options.delete(:timeout) || 100
|
19
|
+
opts = self.enqueue_options.merge(options)
|
20
|
+
#Discobolo::Config.logger.info "Enqueue from #{self.name} to queue #{@queue}"
|
21
|
+
Discobolo::Config.client.push(@queue, format_msg(message), timeout, opts)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.set_queue(queue)
|
25
|
+
self.queue = queue
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.enqueue_options
|
29
|
+
@enqueue_options ||= {ttl: 50000, async: true}
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
def self.format_msg(message)
|
34
|
+
{class: self.name, args: message}.to_json
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
/* Sticky footer styles
|
2
|
+
-------------------------------------------------- */
|
3
|
+
html {
|
4
|
+
position: relative;
|
5
|
+
min-height: 100%;
|
6
|
+
}
|
7
|
+
body {
|
8
|
+
/* Margin bottom by footer height */
|
9
|
+
margin-bottom: 60px;
|
10
|
+
}
|
11
|
+
.footer {
|
12
|
+
position: absolute;
|
13
|
+
bottom: 0;
|
14
|
+
width: 100%;
|
15
|
+
/* Set the fixed height of the footer here */
|
16
|
+
height: 60px;
|
17
|
+
background-color: #f5f5f5;
|
18
|
+
}
|
19
|
+
|
20
|
+
|
21
|
+
/* Custom page CSS
|
22
|
+
-------------------------------------------------- */
|
23
|
+
/* Not required for template or sticky footer method. */
|
24
|
+
|
25
|
+
body > .container {
|
26
|
+
padding: 60px 15px 0;
|
27
|
+
}
|
28
|
+
.container .text-muted {
|
29
|
+
margin: 20px 0;
|
30
|
+
}
|
31
|
+
|
32
|
+
.footer > .container {
|
33
|
+
padding-right: 15px;
|
34
|
+
padding-left: 15px;
|
35
|
+
}
|
36
|
+
|
37
|
+
code {
|
38
|
+
font-size: 80%;
|
39
|
+
}
|
data/web/views/index.erb
ADDED
data/web/views/info.erb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
<div class="page-header">
|
2
|
+
<h1><%= @job['id'] %></h1>
|
3
|
+
</div>
|
4
|
+
|
5
|
+
<p class="lead">From queue <%= @job['queue'] %></p>
|
6
|
+
|
7
|
+
|
8
|
+
<div class="pull-right">
|
9
|
+
<a href='<%= url_for("jobs/#{@job['id']}/dequeue") %>' class="btn btn-danger">
|
10
|
+
Dequeue
|
11
|
+
</a>
|
12
|
+
|
13
|
+
<a href='<%= url_for("jobs/#{@job['id']}/enqueue") %>' class="btn btn-info">
|
14
|
+
Enqueue
|
15
|
+
</a>
|
16
|
+
|
17
|
+
</div>
|
18
|
+
<div class="media">
|
19
|
+
|
20
|
+
|
21
|
+
<% @job.each do |k , v| %>
|
22
|
+
<% next if k == "id" or k == "queue" %>
|
23
|
+
<dl>
|
24
|
+
<dt><%= k %></dt>
|
25
|
+
<dd><%= v %></dd>
|
26
|
+
</dl>
|
27
|
+
<% end %>
|
28
|
+
</div>
|
29
|
+
|
30
|
+
|
31
|
+
<div class="col-sm-4">
|
32
|
+
|
33
|
+
</div>
|
@@ -0,0 +1,63 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Discobolo panel</title>
|
5
|
+
<!-- Latest compiled and minified CSS -->
|
6
|
+
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
|
7
|
+
|
8
|
+
<!-- Optional theme -->
|
9
|
+
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css">
|
10
|
+
|
11
|
+
<link rel="stylesheet" type="text/css" href="/disque/css/sticky_footer.css">
|
12
|
+
|
13
|
+
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
|
14
|
+
<!-- Latest compiled and minified JavaScript -->
|
15
|
+
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
|
16
|
+
</head>
|
17
|
+
<body>
|
18
|
+
|
19
|
+
<!-- Fixed navbar -->
|
20
|
+
<%= erb(:'partials/main_nav', layout: false) %>
|
21
|
+
|
22
|
+
|
23
|
+
<!-- Begin page content -->
|
24
|
+
<div class="container">
|
25
|
+
|
26
|
+
<div class="col-sm-12">
|
27
|
+
<% Discobolo::Config.queues.each do |queue| %>
|
28
|
+
<div class="col-sm-1">
|
29
|
+
<div class="media-left hidden">
|
30
|
+
<a href="#">
|
31
|
+
<img class="media-object" data-src="holder.js/64x64" alt="64x64" src="" data-holder-rendered="true" style="width: 64px; height: 64px;">
|
32
|
+
</a>
|
33
|
+
</div>
|
34
|
+
<div class="media-body">
|
35
|
+
<h4 class="media-heading">
|
36
|
+
<%= link_to queue, "queues/#{queue}" %>
|
37
|
+
<%= client.queue_lenght(queue) %>
|
38
|
+
</h4>
|
39
|
+
<% client.queue_lenght(queue) %>
|
40
|
+
</div>
|
41
|
+
</div>
|
42
|
+
<% end %>
|
43
|
+
<hr/>
|
44
|
+
</div>
|
45
|
+
|
46
|
+
<div class="col-sm-12">
|
47
|
+
<%= yield %>
|
48
|
+
</div>
|
49
|
+
|
50
|
+
</div>
|
51
|
+
|
52
|
+
<footer class="footer">
|
53
|
+
<div class="container">
|
54
|
+
<p class="text-muted">
|
55
|
+
Discobolo <%= Time.now.year %>
|
56
|
+
Nodes: <%= Discobolo::Config.client.instance_variable_get("@hosts").join(", ") %>
|
57
|
+
</p>
|
58
|
+
</div>
|
59
|
+
</footer>
|
60
|
+
|
61
|
+
|
62
|
+
</body>
|
63
|
+
</html>
|
@@ -0,0 +1,35 @@
|
|
1
|
+
<nav class="navbar navbar-default navbar-fixed-top">
|
2
|
+
<div class="container">
|
3
|
+
<div class="navbar-header">
|
4
|
+
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
|
5
|
+
<span class="sr-only">Toggle navigation</span>
|
6
|
+
<span class="icon-bar"></span>
|
7
|
+
<span class="icon-bar"></span>
|
8
|
+
<span class="icon-bar"></span>
|
9
|
+
</button>
|
10
|
+
<a class="navbar-brand" href="#">Discobolo</a>
|
11
|
+
</div>
|
12
|
+
|
13
|
+
<div id="navbar" class="collapse navbar-collapse">
|
14
|
+
<ul class="nav navbar-nav">
|
15
|
+
<li class="active">
|
16
|
+
<a href="<%= url_for('') %>">Dashboard</a>
|
17
|
+
</li>
|
18
|
+
|
19
|
+
<li>
|
20
|
+
<a href="<%= url_for('workers') %>">Workers</a>
|
21
|
+
</li>
|
22
|
+
|
23
|
+
<li class="dropdown">
|
24
|
+
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
|
25
|
+
More
|
26
|
+
<span class="caret"></span>
|
27
|
+
</a>
|
28
|
+
<ul class="dropdown-menu">
|
29
|
+
<li><a href="<%= url_for('info') %>">Info</a></li>
|
30
|
+
</ul>
|
31
|
+
</li>
|
32
|
+
</ul>
|
33
|
+
</div><!--/.nav-collapse -->
|
34
|
+
</div>
|
35
|
+
</nav>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<div class="page-header">
|
2
|
+
<h1>Discobolo panel</h1>
|
3
|
+
</div>
|
4
|
+
|
5
|
+
|
6
|
+
<% if @queues.any? %>
|
7
|
+
<ul>
|
8
|
+
<% @queues.each do |queue| %>
|
9
|
+
<li>
|
10
|
+
<h3>
|
11
|
+
<%= link_to queue, "queues/#{queue}" %>
|
12
|
+
</h3>
|
13
|
+
<%= client.queue_lenght(queue) %>
|
14
|
+
</li>
|
15
|
+
<% end %>
|
16
|
+
</ul>
|
17
|
+
<% end %>
|
@@ -0,0 +1,64 @@
|
|
1
|
+
<div class="page-header">
|
2
|
+
<h1><%= @queue.upcase %> QUEUE</h1>
|
3
|
+
</div>
|
4
|
+
|
5
|
+
|
6
|
+
|
7
|
+
<% if @jobs.any? %>
|
8
|
+
<div class="panel panel-default">
|
9
|
+
<!-- Default panel contents -->
|
10
|
+
<div class="panel-heading">Queues</div>
|
11
|
+
<div class="panel-body">
|
12
|
+
<p>...</p>
|
13
|
+
</div>
|
14
|
+
|
15
|
+
<!-- Table -->
|
16
|
+
<table class="table">
|
17
|
+
<thead>
|
18
|
+
<tr>
|
19
|
+
<% keys = @jobs.first.keys %>
|
20
|
+
<% keys.each do |k| %>
|
21
|
+
<th><%= k %></th>
|
22
|
+
<% end %>
|
23
|
+
</tr>
|
24
|
+
</thead>
|
25
|
+
<tbody>
|
26
|
+
<% @jobs.each do |job| %>
|
27
|
+
<tr>
|
28
|
+
<% keys.each do |k| %>
|
29
|
+
<% case k
|
30
|
+
when "next-requeue-within", 'next-awake-within' %>
|
31
|
+
<th><%= relative_time(job[k].to_i) %></th>
|
32
|
+
<% when 'id' %>
|
33
|
+
<th>
|
34
|
+
<%= link_to job['id'][2..10], "jobs/#{job['id']}" %>
|
35
|
+
</th>
|
36
|
+
<% when "state" %>
|
37
|
+
<th><%= job[k] %></th>
|
38
|
+
<% else %>
|
39
|
+
<th><%= job[k] %></th>
|
40
|
+
<% end %>
|
41
|
+
<% end %>
|
42
|
+
</tr>
|
43
|
+
<% end %>
|
44
|
+
</tbody>
|
45
|
+
</table>
|
46
|
+
</div>
|
47
|
+
<% end %>
|
48
|
+
|
49
|
+
<% if @jobs.size > 0 %>
|
50
|
+
|
51
|
+
<a href='<%="?page=#{@page + 1}"%>'>
|
52
|
+
next
|
53
|
+
<a>
|
54
|
+
|
55
|
+
<% else %>
|
56
|
+
|
57
|
+
<div class="alert alert-info">No jobs found</div>
|
58
|
+
|
59
|
+
<% if params[:page].to_i > 0 %>
|
60
|
+
<a href='<%="?page=#{@page - 1}"%>'>
|
61
|
+
prev
|
62
|
+
<a>
|
63
|
+
<% end %>
|
64
|
+
<% end %>
|
metadata
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: discobolo
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Miguel Michelson
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-08-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: disque
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: sinatra
|
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: celluloid
|
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: json
|
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: bundler
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.10'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.10'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '10.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '10.0'
|
97
|
+
description: Discobolo is a disque adapter for ruby & rails
|
98
|
+
email:
|
99
|
+
- miguelmichelson@gmail.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- ".gitignore"
|
105
|
+
- ".rspec"
|
106
|
+
- ".travis.yml"
|
107
|
+
- CODE_OF_CONDUCT.md
|
108
|
+
- Gemfile
|
109
|
+
- LICENSE.txt
|
110
|
+
- README.md
|
111
|
+
- Rakefile
|
112
|
+
- bin/console
|
113
|
+
- bin/setup
|
114
|
+
- discobolo.gemspec
|
115
|
+
- examples/boot.rb
|
116
|
+
- examples/config.ru
|
117
|
+
- lib/core_ext/string/color.rb
|
118
|
+
- lib/discobolo.rb
|
119
|
+
- lib/discobolo/actor.rb
|
120
|
+
- lib/discobolo/application.rb
|
121
|
+
- lib/discobolo/client.rb
|
122
|
+
- lib/discobolo/config.rb
|
123
|
+
- lib/discobolo/logger.rb
|
124
|
+
- lib/discobolo/supervisor.rb
|
125
|
+
- lib/discobolo/version.rb
|
126
|
+
- lib/discobolo/web.rb
|
127
|
+
- lib/discobolo/worker.rb
|
128
|
+
- web/assets/css/sticky_footer.css
|
129
|
+
- web/views/index.erb
|
130
|
+
- web/views/info.erb
|
131
|
+
- web/views/jobs/show.erb
|
132
|
+
- web/views/layout.erb
|
133
|
+
- web/views/partials/main_nav.erb
|
134
|
+
- web/views/queues/index.erb
|
135
|
+
- web/views/queues/show.erb
|
136
|
+
- web/views/workers/index.erb
|
137
|
+
homepage: https://github.com/discobolo
|
138
|
+
licenses:
|
139
|
+
- MIT
|
140
|
+
metadata: {}
|
141
|
+
post_install_message:
|
142
|
+
rdoc_options: []
|
143
|
+
require_paths:
|
144
|
+
- lib
|
145
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
146
|
+
requirements:
|
147
|
+
- - ">="
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
151
|
+
requirements:
|
152
|
+
- - ">="
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: '0'
|
155
|
+
requirements: []
|
156
|
+
rubyforge_project:
|
157
|
+
rubygems_version: 2.4.8
|
158
|
+
signing_key:
|
159
|
+
specification_version: 4
|
160
|
+
summary: Discobolo is a disque adapter for ruby & rails
|
161
|
+
test_files: []
|