supervisor 0.0.95

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 (36) hide show
  1. data/.DS_Store +0 -0
  2. data/.gitignore +18 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +57 -0
  6. data/Rakefile +1 -0
  7. data/config.yml +37 -0
  8. data/lib/supervisor.rb +87 -0
  9. data/lib/supervisor/.DS_Store +0 -0
  10. data/lib/supervisor/application/.DS_Store +0 -0
  11. data/lib/supervisor/application/app.rb +186 -0
  12. data/lib/supervisor/application/public/images/poll.png +0 -0
  13. data/lib/supervisor/application/public/javascripts/application.js +64 -0
  14. data/lib/supervisor/application/public/javascripts/jquery-1.7.1.min.js +4 -0
  15. data/lib/supervisor/application/public/javascripts/jquery.relatize_date.js +117 -0
  16. data/lib/supervisor/application/public/stylesheets/reset.css +44 -0
  17. data/lib/supervisor/application/public/stylesheets/style.css +150 -0
  18. data/lib/supervisor/application/views/enqueued.haml +10 -0
  19. data/lib/supervisor/application/views/error.haml +2 -0
  20. data/lib/supervisor/application/views/failed.haml +14 -0
  21. data/lib/supervisor/application/views/job.haml +43 -0
  22. data/lib/supervisor/application/views/layout.haml +23 -0
  23. data/lib/supervisor/application/views/next_more.haml +6 -0
  24. data/lib/supervisor/application/views/overview.haml +111 -0
  25. data/lib/supervisor/application/views/pending.haml +9 -0
  26. data/lib/supervisor/application/views/queue.haml +13 -0
  27. data/lib/supervisor/application/views/servers.haml +16 -0
  28. data/lib/supervisor/application/views/workers.haml +15 -0
  29. data/lib/supervisor/application/views/working.haml +9 -0
  30. data/lib/supervisor/job.rb +7 -0
  31. data/lib/supervisor/server.rb +44 -0
  32. data/lib/supervisor/version.rb +3 -0
  33. data/lib/supervisor/worker.rb +5 -0
  34. data/supervisor.gemspec +27 -0
  35. data/supervisor_app.rb +6 -0
  36. metadata +224 -0
Binary file
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ configbak.yml
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in supervisor.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Dan Barrett
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,57 @@
1
+ ========
2
+ Supervisor
3
+ ========
4
+
5
+ **WARNING!!!!**
6
+ * This gem is extremely rough. There are no tests of any kind. Use at your own risk!
7
+
8
+ Delayed Jobs Administration for the Masses!
9
+
10
+ Built for/by [Contently](http://www.contently.com) by [thoughpunch](http://www.about.me/thoughtpunch)
11
+
12
+ ## Description
13
+
14
+ supervisor is a Sinatra-based web front-end for managing [Delayed Jobs](https://github.com/collectiveidea/delayed_job) heavily inspired by the excellent [delayed_job_web](https://github.com/ejschmitt/delayed_job_web) gem.
15
+
16
+ What makes supervisor unique is that it can be run in one of two modes. 'Gem' mode runs the mounted Sinatra app inside your Rails app via a route. 'Stand Alone' mode can be run from the same server or even a different location, provided that you configure the database settings to connect to the instance that is storing the delayed jobs. It's a Sinatra app, it's a Gem, it's a Sinatra app inside a gem!
17
+
18
+ ## Installation
19
+
20
+ gem install 'supervisor'
21
+
22
+ ## Usage
23
+
24
+ **Gem Mode**
25
+ * Mount the Sinatra app in your route.rb file like so:
26
+ * ```match "/jobs" => Supervisor::App, :anchor => false```
27
+
28
+ **Stand Alone Mode**
29
+ * Cloning the entire repo into a directory and run the following command will start the stand-alone Sinatra app.
30
+ * ``ruby supervisor_app.rb```
31
+
32
+ ## RoadMap
33
+
34
+ The overall goal is to allow full management of Delayed Jobs workers and tasks from both inside and outside a Rails App.
35
+ To do so *might* require stubbing enough Rails functionality that Delayed::Worker and Delayed::Command can run without error.
36
+
37
+ 1. Make fully functional as stand alone app
38
+ 1. Ensure can use any ActiveRecord-supported database
39
+ 2. Add PID/Daemon monitoring for Delayed Job Deamons
40
+ 1. Ability to restart jobs if not running from outside Rails app
41
+ 3. Delayed Job log parsing/tailing
42
+ 1. Tail all delayed_job logs in the view via JS [like so](http://dojo4.com/blog/easy-cheasy-realtime-log-tailing-in-a-rails-admin-view)
43
+ 4. Ability to edit jobs on the fly, individually or in bulk
44
+ 1. Change PID, queue, run_at
45
+ 5. Delayed Jobs statistics and processing numbers
46
+ 1. Store jobs total and jobs per second while app is running, maybe store in YAML?
47
+ 2. Use request-log-analyzer for past dj stats (Note: Current version can not parse logs from Delayed Job v3.0 and later)
48
+ 3. Realtime Graphs and charts
49
+ 6. Write Tests
50
+ 1. Integration tests for Sinatra App, unit tests for all modules and classes.
51
+ 2. Version lock all gems after testing
52
+
53
+ ## Contributing
54
+
55
+ 1. Fork it
56
+ 2. Writer tests or extend functionality from the roadmap
57
+ 3. Submit a pull request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,37 @@
1
+ #SUPERVISOR CONFIGURATION
2
+
3
+ #SUPERVISOR APP CONFIG (application)
4
+ # - If running in app mode (i.e. outside of a Rails app), configure settings here
5
+ #DELAYED JOB WORKER MACHINES (hosts)
6
+ # - Configure SSH connection info for the seperates hosts that are working delayed jobs
7
+ # - To add additional host connections, simply add another nested entry below
8
+ # - The DelayedJobMonitor::Worker module will establish SSH connections to each
9
+ # one to monitor, start, and stop delayed_job workers.
10
+ #DELAYED JOB STORE (database)
11
+ # - Configure an ActiveRecord connection to the database that is hosting the delay_jobs database
12
+ # - Only 1 DB connection is supported at this time.
13
+
14
+ application:
15
+ name: #Your production Rails app "nickname". Used to parse Rails path, log paths, etc
16
+ email: #email upon failures,changes,etc
17
+ hosts:
18
+ -
19
+ name: # 'nickname' of host
20
+ rails_path: # path of the Rails app on host machine, assumes '/var/www/RAILS_APP' by default
21
+ host: # domain name of host for SSH connection
22
+ username: # SSH Username
23
+ password: # SSH Password
24
+ -
25
+ name: # 'nickname' of host
26
+ rails_path: # path of the Rails app on host machine, assumes '/var/www/RAILS_APP' by default
27
+ host: # domain name of host for SSH connection
28
+ username: # SSH Username
29
+ password: # SSH Password
30
+ database:
31
+ host:
32
+ port:
33
+ adapter:
34
+ database:
35
+ username:
36
+ password:
37
+ template:
@@ -0,0 +1,87 @@
1
+ require "supervisor/job"
2
+ require "supervisor/server"
3
+ require "supervisor/worker"
4
+ require "supervisor/version"
5
+ require "supervisor/application/app"
6
+
7
+ module Supervisor
8
+ def self.[](key)
9
+ unless @config
10
+ @config = YAML.load_file("config.yml").symbolize_keys
11
+ end
12
+ @config[key.to_sym]
13
+ end
14
+
15
+ def self.[]=(key, value)
16
+ @config[key.to_sym] = value
17
+ end
18
+
19
+ def self.app_mode?
20
+ return !ENV["RAILS_ENV"].present?
21
+ end
22
+
23
+ def self.connected?
24
+ if defined? Supervisor.connection
25
+ return true
26
+ else
27
+ return false
28
+ end
29
+ end
30
+
31
+ def self.establish_connection
32
+ if Supervisor[:database]["host"].nil?
33
+ raise "No job database is configured. Please add one in the config.yml file."
34
+ else
35
+ if defined? connection
36
+ p "There is already a DB connection locally...closing"
37
+ Supervisor.connection.disconnect!
38
+ Supervisor.connection.connection.close
39
+ ActiveRecord::Base.clear_active_connections!
40
+ ActiveRecord::Base.clear_reloadable_connections!
41
+ end
42
+ begin
43
+ db = Supervisor[:database]
44
+ connection = ActiveRecord::Base.establish_connection(:adapter => db["adapter"],:database => db["database"],:host=>db["host"],:port=>db["port"],:username=>db["username"],:password=>db["password"],:encoding => "utf8",:template=>"template0")
45
+ return connection
46
+ rescue
47
+ raise "Could not connect to the job database"
48
+ end
49
+ end
50
+ end
51
+
52
+ def self.delayed_job_running_locally?
53
+ local_delayed_jobs = %x{ps aux}.split(/\n/).map{|x| x.split(/\s+/) if x.split(/\s+/).last.match("job")}.compact
54
+ if local_delayed_jobs.empty?
55
+ return false
56
+ else
57
+ return true
58
+ end
59
+ end
60
+
61
+ def self.initialize_servers
62
+ if Supervisor[:hosts].first["host"].nil? && !delayed_job_running_locally?
63
+ raise "No job worker machines (host) configured and no workers running locally. Please add host in config.yml"
64
+ elsif Supervisor[:hosts].first["host"].nil? && delayed_job_running_locally?
65
+ Supervisor::Server.new
66
+ else
67
+ Supervisor[:hosts].each do |host|
68
+ Supervisor::Server.new(host["name"],host["host"],host["rails_path"],host["username"],host["password"])
69
+ end
70
+ if delayed_job_running_locally?
71
+ Supervisor::Server.new #init a default local server
72
+ end
73
+ end
74
+ return Supervisor::Server.servers
75
+ end
76
+
77
+ def self.start!
78
+ begin
79
+ Supervisor.establish_connection
80
+ Supervisor.initialize_servers
81
+ Supervisor::App.run!
82
+ rescue
83
+ raise "Could not start Supervisor App: #{$!}"
84
+ end
85
+ end
86
+
87
+ end
Binary file
@@ -0,0 +1,186 @@
1
+ require 'sinatra'
2
+ require 'active_support'
3
+ require 'active_record'
4
+ require 'delayed_job'
5
+ require 'haml'
6
+
7
+ module Supervisor
8
+ class App < Sinatra::Base
9
+ require "supervisor"
10
+ set :root, File.dirname(__FILE__)
11
+ set :static, true
12
+ set :public_folder, File.expand_path('../public', __FILE__)
13
+ set :views, File.expand_path('../views', __FILE__)
14
+ set :haml, { :format => :html5 }
15
+ set :port, 4567
16
+
17
+ if (Supervisor.methods - Object.methods).count > 1
18
+ begin
19
+ Supervisor.initialize_servers
20
+ Supervisor.establish_connection
21
+ rescue
22
+ p "Supervisor libraries not loaded. Partial functionality"
23
+ end
24
+ end
25
+
26
+ def delayed_job
27
+ begin
28
+ if (Supervisor.methods - Object.methods).count > 1
29
+ Supervisor::Job
30
+ else
31
+ Delayed::Job
32
+ end
33
+ rescue
34
+ nil
35
+ end
36
+ end
37
+
38
+ ############################## SINTRA APP ################################
39
+ def current_page
40
+ url_path request.path_info.sub('/','')
41
+ end
42
+
43
+ def start
44
+ params[:start].to_i
45
+ end
46
+
47
+ def per_page
48
+ 25
49
+ end
50
+
51
+ def url_path(*path_parts)
52
+ [ path_prefix, path_parts ].join("/").squeeze('/')
53
+ end
54
+ alias_method :u, :url_path
55
+
56
+ def path_prefix
57
+ request.env['SCRIPT_NAME']
58
+ end
59
+
60
+ def delayed_jobs(type)
61
+ delayed_job.where(delayed_job_sql(type))
62
+ end
63
+
64
+ def delayed_job_sql(type)
65
+ case type
66
+ when :enqueued
67
+ ''
68
+ when :working
69
+ 'locked_at is not null'
70
+ when :failed
71
+ 'last_error is not null'
72
+ when :pending
73
+ 'attempts = 0'
74
+ end
75
+ end
76
+
77
+ def partial(template, local_vars = {})
78
+ @partial = true
79
+ haml(template.to_sym, {:layout => false}, local_vars)
80
+ ensure
81
+ @partial = false
82
+ end
83
+
84
+ def poll
85
+ if @polling
86
+ text = "Last Updated: #{Time.now.strftime("%H:%M:%S")}"
87
+ else
88
+ text = "<a href='#{u(request.path_info)}.poll' rel='poll'>Live Poll</a>"
89
+ end
90
+ "<p class='poll'>#{text}</p>"
91
+ end
92
+
93
+ def show_for_polling(page)
94
+ content_type "text/html"
95
+ @polling = true
96
+ # show(page.to_sym, false).gsub(/\s{1,}/, ' ')
97
+ @jobs = delayed_jobs(page.to_sym)
98
+ haml(page.to_sym, {:layout => false})
99
+ end
100
+
101
+ ####################### SINATRA ROUTES/ACTIONS ##########################
102
+ def tabs
103
+ [
104
+ {:name => 'Overview', :path => '/overview'},
105
+ {:name => 'Enqueued', :path => '/enqueued'},
106
+ {:name => 'Working', :path => '/working'},
107
+ {:name => 'Pending', :path => '/pending'},
108
+ {:name => 'Failed', :path => '/failed'}
109
+ ]
110
+ end
111
+
112
+ get "/?" do
113
+ redirect u(:overview)
114
+ end
115
+
116
+ #Static Page Rendering
117
+ %w(enqueued working pending failed).each do |page|
118
+ get "/#{page}" do
119
+ @jobs = delayed_jobs(page.to_sym).order('created_at desc, id desc').offset(start).limit(per_page)
120
+ @all_jobs = delayed_jobs(page.to_sym)
121
+ haml page.to_sym
122
+ end
123
+ end
124
+
125
+ #Polling Page Rendering
126
+ %w(overview enqueued working pending failed stats) .each do |page|
127
+ get "/#{page}.poll" do
128
+ show_for_polling(page)
129
+ end
130
+
131
+ get "/#{page}/:id.poll" do
132
+ show_for_polling(page)
133
+ end
134
+ end
135
+
136
+ get '/overview' do
137
+ if delayed_job
138
+ haml :overview
139
+ else
140
+ @message = "Unable to connected to Delayed::Job database"
141
+ haml :error
142
+ end
143
+ end
144
+
145
+ get "/queue/:queue" do
146
+ @jobs = delayed_job.where(:queue=>params[:queue]).order('created_at desc, id desc').offset(start).limit(per_page)
147
+ @all_jobs = delayed_job.where(:queue=>params[:queue]).count
148
+ haml :queue
149
+ end
150
+
151
+ get "/remove/:id" do
152
+ delayed_job.find(params[:id]).delete
153
+ redirect back
154
+ end
155
+
156
+ get "/requeue/:id" do
157
+ job = delayed_job.find(params[:id])
158
+ job.update_attributes(:run_at =>Time.now,:failed_at=>nil,:locked_at=>nil,:attempts=>0)
159
+ redirect back
160
+ end
161
+
162
+ post "/failed/clear" do
163
+ delayed_job.where("last_error IS NOT NULL").destroy_all
164
+ redirect back
165
+ end
166
+
167
+ post "/requeue/all" do
168
+ delayed_job.where("last_error IS NOT NULL").update_all(
169
+ :run_at=>Time.now,
170
+ :failed_at => nil,
171
+ :attempts=>0,
172
+ :last_error=>nil,
173
+ :locked_at=>nil)
174
+ redirect back
175
+ end
176
+
177
+ get '/update/:id' do
178
+ "#{params[:id]}"
179
+ end
180
+
181
+ post '/update/all' do
182
+ "#{params}"
183
+ end
184
+
185
+ end
186
+ end
@@ -0,0 +1,64 @@
1
+ $(function() {
2
+ var poll_interval = 3;
3
+
4
+ var relatizer = function(){
5
+ var dt = $(this).text(), relatized = $.relatizeDate(this)
6
+ if ($(this).parents("a").length > 0 || $(this).is("a")) {
7
+ $(this).relatizeDate()
8
+ if (!$(this).attr('title')) {
9
+ $(this).attr('title', dt)
10
+ }
11
+ } else {
12
+ $(this)
13
+ .text('')
14
+ .append( $('<a href="#" class="toggle_format" title="' + dt + '" />')
15
+ .append('<span class="date_time">' + dt +
16
+ '</span><span class="relatized_time">' +
17
+ relatized + '</span>') )
18
+ }
19
+ };
20
+
21
+ $('.time').each(relatizer);
22
+
23
+ $('.time a.toggle_format .date_time').hide();
24
+
25
+ var format_toggler = function(){
26
+ $('.time a.toggle_format span').toggle();
27
+ $(this).attr('title', $('span:hidden',this).text());
28
+ return false;
29
+ };
30
+
31
+ $('.time a.toggle_format').click(format_toggler);
32
+
33
+ $('ul li.job').hover(function() {
34
+ $(this).addClass('hover');
35
+ }, function() {
36
+ $(this).removeClass('hover');
37
+ })
38
+
39
+ $('a.backtrace').click(function (e) {
40
+ e.preventDefault();
41
+ if($(this).prev('div.backtrace:visible').length > 0) {
42
+ $(this).next('div.backtrace').show();
43
+ $(this).prev('div.backtrace').hide();
44
+ } else {
45
+ $(this).next('div.backtrace').hide();
46
+ $(this).prev('div.backtrace').show();
47
+ }
48
+ });
49
+
50
+ $('a[rel=poll]').click(function(e) {
51
+ e.preventDefault();
52
+ var href = $(this).attr('href')
53
+ $(this).parent().text('Starting...')
54
+ $("#main").addClass('polling')
55
+
56
+ setInterval(function() {
57
+ $.ajax({dataType: 'text', type: 'get', url: href, success: function(data) {
58
+ $('#main').html(data);
59
+ }})
60
+ }, poll_interval * 1000)
61
+
62
+ return false
63
+ })
64
+ })