supervisor 0.0.95

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