canvas-jobs 0.9.13 → 0.9.14

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 145b66e2c267b807d327df23ac5c0511a2ddd173
4
- data.tar.gz: 25187fc7c1e9c13e7a50e676c2162e7cd594bfe5
3
+ metadata.gz: cd2ac418daa325d715cb4388d48f7f08c44fdea2
4
+ data.tar.gz: 4cc439b8bea527dd524c45e5e82186af2d5352c0
5
5
  SHA512:
6
- metadata.gz: 9bd66fddb3dd3d6baebc607aa6822bfb139ace0300734855d34ee742c52e283ff937aed054e1125361afdedc202f66022be0e7092b30014ccdc34c48d8bc9729
7
- data.tar.gz: 2759d931447d3254756e033c94d524230b3af4c231386c028dbdb93a96a0adc251986d61211e3cc790357828b84b94ddb1b40a1331332a084ff1da10d445a5c1
6
+ metadata.gz: 6daa755b75bbc555e0d87e6e7a6905462e3bf71ed64a238e99cec05cf583febd53f3d85429afafb2d9247f817302806179ab5444da2f0529571ff9991b2c64fa
7
+ data.tar.gz: 55148fbafe336859c77fce7df57da578653acd15b18f0141c8a8f13817ca23ff9685221b0d0cff758469bc62ff055aef010e298504321153c62830aa84e752eb
@@ -105,7 +105,7 @@ module Delayed
105
105
  # Note: This does not ping the DB to get the time, so all your clients
106
106
  # must have syncronized clocks.
107
107
  def db_time_now
108
- Time.zone.now
108
+ Time.now.utc
109
109
  end
110
110
 
111
111
  def unlock_orphaned_jobs(pid = nil, name = nil)
@@ -22,25 +22,7 @@ class Pool
22
22
  end
23
23
 
24
24
  def run
25
- op = OptionParser.new do |opts|
26
- opts.banner = "Usage #{$0} <command> <options>"
27
- opts.separator %{\nWhere <command> is one of:
28
- start start the jobs daemon
29
- stop stop the jobs daemon
30
- run start and run in the foreground
31
- restart stop and then start the jobs daemon
32
- status show daemon status
33
- }
34
-
35
- opts.separator "\n<options>"
36
- opts.on("-c", "--config", "Use alternate config file (default #{options[:config_file]})") { |c| options[:config_file] = c }
37
- opts.on("-p", "--pid", "Use alternate folder for PID files (default #{options[:pid_folder]})") { |p| options[:pid_folder] = p }
38
- opts.on("--no-tail", "Don't tail the logs (only affects non-daemon mode)") { options[:tail_logs] = false }
39
- opts.on("--with-prejudice", "When stopping, interrupt jobs in progress, instead of letting them drain") { options[:kill] ||= true }
40
- opts.on("--with-extreme-prejudice", "When stopping, immediately kill jobs in progress, instead of letting them drain") { options[:kill] = 9 }
41
- opts.on_tail("-h", "--help", "Show this message") { puts opts; exit }
42
- end
43
- op.parse!(@args)
25
+ parse_cli_options!
44
26
 
45
27
  read_config(options[:config_file])
46
28
 
@@ -80,6 +62,28 @@ class Pool
80
62
  end
81
63
  end
82
64
 
65
+ def parse_cli_options!
66
+ op = OptionParser.new do |opts|
67
+ opts.banner = "Usage #{$0} <command> <options>"
68
+ opts.separator %{\nWhere <command> is one of:
69
+ start start the jobs daemon
70
+ stop stop the jobs daemon
71
+ run start and run in the foreground
72
+ restart stop and then start the jobs daemon
73
+ status show daemon status
74
+ }
75
+
76
+ opts.separator "\n<options>"
77
+ opts.on("-c", "--config [CONFIG_PATH]", "Use alternate config file (default #{options[:config_file]})") { |c| options[:config_file] = c }
78
+ opts.on("-p", "--pid", "Use alternate folder for PID files (default #{options[:pid_folder]})") { |p| options[:pid_folder] = p }
79
+ opts.on("--no-tail", "Don't tail the logs (only affects non-daemon mode)") { options[:tail_logs] = false }
80
+ opts.on("--with-prejudice", "When stopping, interrupt jobs in progress, instead of letting them drain") { options[:kill] ||= true }
81
+ opts.on("--with-extreme-prejudice", "When stopping, immediately kill jobs in progress, instead of letting them drain") { options[:kill] = 9 }
82
+ opts.on_tail("-h", "--help", "Show this message") { puts opts; exit }
83
+ end
84
+ op.parse!(@args)
85
+ end
86
+
83
87
  protected
84
88
 
85
89
  def procname
@@ -0,0 +1,120 @@
1
+ require 'sinatra/base'
2
+ require 'sinatra/json'
3
+ require 'json'
4
+ require 'delayed_job'
5
+
6
+ module Delayed
7
+ class Server < Sinatra::Base
8
+ APP_DIR = File.dirname(File.expand_path(__FILE__))
9
+ set :views, File.join(APP_DIR, 'server', 'views')
10
+ set :public_folder, File.join(APP_DIR, 'server', 'public')
11
+
12
+ def initialize(*args, &block)
13
+ super()
14
+ # Rails will take care of establishing the DB connection for us if there is
15
+ # an application present
16
+ if using_active_record? && !ActiveRecord::Base.connected?
17
+ ActiveRecord::Base.establish_connection(ENV['DATABASE_URL'])
18
+ end
19
+ end
20
+
21
+ def using_active_record?
22
+ Delayed::Job == Delayed::Backend::ActiveRecord::Job
23
+ end
24
+
25
+ # Ensure we're connected to the DB before processing the request
26
+ before do
27
+ if ActiveRecord::Base.respond_to?(:verify_active_connections!) && using_active_record?
28
+ ActiveRecord::Base.verify_active_connections!
29
+ end
30
+ end
31
+
32
+ # Release any used connections back to the pool
33
+ after do
34
+ ActiveRecord::Base.clear_active_connections! if using_active_record?
35
+ end
36
+
37
+ configure :development do
38
+ require 'sinatra/reloader'
39
+ register Sinatra::Reloader
40
+ end
41
+
42
+ helpers do
43
+ # this can't get required until the class has been opened for the first time
44
+ require 'delayed/server/helpers'
45
+ include Delayed::Server::Helpers
46
+ end
47
+
48
+ get '/' do
49
+ erb :index
50
+ end
51
+
52
+ get '/running' do
53
+ content_type :json
54
+ json({
55
+ draw: params['draw'].to_i,
56
+ recordsTotal: Delayed::Job.running.count,
57
+ recordsFiltered: Delayed::Job.running.count,
58
+ data: Delayed::Job.running_jobs.map{ |j|
59
+ j.as_json(include_root: false, except: [:handler, :last_error])
60
+ },
61
+ })
62
+ end
63
+
64
+ get '/tags' do
65
+ content_type :json
66
+ json({
67
+ draw: params['draw'].to_i,
68
+ data: Delayed::Job.tag_counts('current', 10)
69
+ })
70
+ end
71
+
72
+ DEFAULT_PAGE_SIZE = 10
73
+ MAX_PAGE_SIZE = 100
74
+ get '/jobs' do
75
+ content_type :json
76
+ flavor = params['flavor'] || 'current'
77
+ page_size = extract_page_size
78
+ offset = Integer(params['start'] || 0)
79
+ case flavor
80
+ when 'id'
81
+ jobs = Delayed::Job.where(id: params['search_term'])
82
+ total_records = 1
83
+ when 'future', 'current', 'failed'
84
+ jobs = Delayed::Job.list_jobs(flavor, page_size, offset)
85
+ total_records = Delayed::Job.jobs_count(flavor)
86
+ else
87
+ query = params['search_term']
88
+ if query.present?
89
+ jobs = Delayed::Job.list_jobs(flavor, page_size, offset, query)
90
+ else
91
+ jobs = []
92
+ end
93
+ total_records = Delayed::Job.jobs_count(flavor, query)
94
+ end
95
+ json({
96
+ draw: params['draw'].to_i,
97
+ recordsTotal: total_records,
98
+ recordsFiltered: jobs.size,
99
+ data: build_jobs_json(jobs),
100
+ })
101
+ end
102
+
103
+ private
104
+
105
+ def extract_page_size
106
+ page_size = Integer(params['length'] || DEFAULT_PAGE_SIZE)
107
+ # if dataTables wants all of the records it will send us -1 but we don't
108
+ # want the potential to kill our servers with this request so we'll limit it
109
+ page_size = DEFAULT_PAGE_SIZE if page_size == -1
110
+ [page_size, MAX_PAGE_SIZE].min
111
+ end
112
+
113
+
114
+ def build_jobs_json(jobs)
115
+ json = jobs.map{ |j|
116
+ j.as_json(root: false, except: [:handler, :last_error])
117
+ }
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,28 @@
1
+ module Delayed
2
+ class Server
3
+ module Helpers
4
+ def h(text)
5
+ Rack::Utils.escape_html(text)
6
+ end
7
+
8
+ def url_path(*path_parts)
9
+ [path_prefix, path_parts].join('/').squeeze('/')
10
+ end
11
+
12
+ def path_prefix
13
+ request.env['SCRIPT_NAME']
14
+ end
15
+
16
+ def render_javascript_env
17
+ {
18
+ Routes: {
19
+ root: path_prefix,
20
+ running: url_path('running'),
21
+ tags: url_path('tags'),
22
+ jobs: url_path('jobs'),
23
+ }
24
+ }.to_json
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,12 @@
1
+ table.dataTable td {
2
+ word-wrap: break-word;
3
+ }
4
+ table.dataTable td.strand {
5
+ max-width: 100px;
6
+ }
7
+ table.dataTable td.tag {
8
+ max-width: 100px;
9
+ }
10
+ div.job-list-row {
11
+ padding-top: 10px;
12
+ }
@@ -0,0 +1,132 @@
1
+ (function () {
2
+ var Delayed = {};
3
+
4
+ Delayed.Render = {};
5
+ Delayed.Render.attempts = function (previousAttempts, callType, jobData) {
6
+ var thisAttempt = parseInt(previousAttempts, 10) + 1;
7
+ switch(callType) {
8
+ case "display":
9
+ return thisAttempt + "/" + jobData.max_attempts;
10
+ default:
11
+ return thisAttempt;
12
+ }
13
+ };
14
+
15
+ Delayed.Render.runTime = function (lockedAt, callType) {
16
+ var elapsed, days, hours, minutes, seconds, remaining, formattedTime;
17
+ elapsed = Math.round((new Date().getTime() - Date.parse(lockedAt)) / 1000);
18
+ switch(callType) {
19
+ case "display": {
20
+ days = Math.floor(elapsed / 86400);
21
+ remaining = elapsed - (days * 86400);
22
+ hours = Math.floor(remaining / 3600);
23
+ remaining = remaining - (hours * 3600);
24
+ minutes = Math.floor(remaining / 60);
25
+ seconds = remaining - (minutes * 60);
26
+ formattedTime = ""
27
+ if(days > 0) {
28
+ formattedTime = days + "d "
29
+ }
30
+ formattedTime = formattedTime + ("0" + hours).slice(-2) + ":" + ("0" + minutes).slice(-2) + ":" + ("0" + seconds).slice(-2);
31
+ return formattedTime;
32
+ }
33
+ default:
34
+ return elapsed;
35
+ }
36
+ };
37
+
38
+ $(document).ready(function () {
39
+ var runningTable, runningInterval, tagsTable, tagsInterval, jobsTable, jobsInterval
40
+ runningTable = $('#running').DataTable({
41
+ "autoWidth": false,
42
+ "bSort": false,
43
+ "paging": false,
44
+ "processing": true,
45
+ "searching": false,
46
+ "scrollY": "200px",
47
+ "serverSide": true,
48
+ "order": [[5, "desc"]],
49
+ "ajax": ENV.Routes.running,
50
+ "columns": [
51
+ {"data": "id"},
52
+ {"data": "locked_by", "className": "worker"},
53
+ {"data": "tag", "className": "tag"},
54
+ {"data": "attempts", "render": Delayed.Render.attempts, "className": "attempts"},
55
+ {"data": "strand", "className": "strand"},
56
+ {"data": "locked_at", "render": Delayed.Render.runTime}
57
+ ]
58
+ });
59
+ runningInterval = setInterval(function () { runningTable.ajax.reload(); }, 2000);
60
+
61
+ tagsTable = $("#tags").DataTable({
62
+ "autoWidth": false,
63
+ "bSort": false,
64
+ "paging": false,
65
+ "processing": true,
66
+ "searching": false,
67
+ "scrollY": "200px",
68
+ "order": [[1, "desc"]],
69
+ "ajax": ENV.Routes.tags,
70
+ "columns": [
71
+ {"data": "tag", "className": "tag"},
72
+ {"data": "count"}
73
+ ]
74
+ });
75
+ tagsInterval = setInterval(function () { tagsTable.ajax.reload(); }, 10000);
76
+
77
+ jobsTable = $("#jobs").DataTable({
78
+ "autoWidth": false,
79
+ "bSort": false,
80
+ "paging": true,
81
+ "processing": true,
82
+ "searching": false,
83
+ "scrollY": "200px",
84
+ "serverSide": true,
85
+ "order": [[1, "desc"]],
86
+ "ajax": {
87
+ "url": ENV.Routes.jobs,
88
+ "data": function (data) {
89
+ data.flavor = $("select#current_jobs_flavor").val();
90
+ data.search_term = $("input#jobs_search_term").val();
91
+ }
92
+ },
93
+ "columns": [
94
+ {"data": "id"},
95
+ {"data": "tag", "className": "tag"},
96
+ {"data": "attempts", "render": Delayed.Render.attempts},
97
+ {"data": "priority"},
98
+ {"data": "strand"},
99
+ {"data": "run_at"}
100
+ ]
101
+ });
102
+
103
+ $('body').on('click', '.refresh_jobs_link', function(event) {
104
+ event.preventDefault();
105
+ jobsTable.ajax.reload();
106
+ return true
107
+ });
108
+
109
+ $('body').on('change', 'select#current_jobs_flavor', function (event) {
110
+ event.preventDefault();
111
+ $selectBox = $(this);
112
+ $selectedOption = $($selectBox.children(':selected'));
113
+ $searchDiv = $('div#job_search');
114
+
115
+ if ($selectedOption.data('requires-search')) {
116
+ $searchDiv.show();
117
+ } else {
118
+ $searchDiv.hide();
119
+ }
120
+
121
+ jobsTable.ajax.reload();
122
+ return true
123
+ });
124
+
125
+ $('body').on('keyup', '#jobs_search_term', function (event) {
126
+ if (event.keyCode == 13) {
127
+ jobsTable.ajax.reload();
128
+ }
129
+ return true
130
+ });
131
+ });
132
+ })();
@@ -0,0 +1,90 @@
1
+ <div class="row">
2
+ <div class="col-md-8">
3
+ <h2>Running</h2>
4
+ <table id="running" class="table table-striped table-bordered">
5
+ <thead>
6
+ <tr>
7
+ <th>ID</th>
8
+ <th>Worker</th>
9
+ <th>Tag</th>
10
+ <th>Attempt</th>
11
+ <th>Strand</th>
12
+ <th>Runtime</th>
13
+ </tr>
14
+ </thead>
15
+ <tbody>
16
+ </tbody>
17
+ </table>
18
+ </div>
19
+ <div class="col-md-4">
20
+ <h2>Tags</h2>
21
+ <table id="tags" class="table table-striped table-bordered">
22
+ <thead>
23
+ <tr>
24
+ <th>Tag</th>
25
+ <th>Count</th>
26
+ </tr>
27
+ </thead>
28
+ <tbody>
29
+ </tbody>
30
+ </table>
31
+ </div>
32
+ </div>
33
+
34
+ <div class="row">
35
+ <div class="col-md-12">
36
+ <h2>Jobs List</h2>
37
+ </div>
38
+ </div>
39
+
40
+ <div class="row">
41
+ <div class="form-group">
42
+ <div class="col-md-2">
43
+ <select id="current_jobs_flavor" class="form-control">
44
+ <option value="current">Current</option>
45
+ <option value="future">Future</option>
46
+ <option value="failed">Failed</option>
47
+ <option value="id" data-requires-search="true">ID</option>
48
+ <option value="strand" data-requires-search="true">Strand</option>
49
+ <option value="tag" data-requires-search="true">Tag</option>
50
+ </select>
51
+ </div>
52
+ <div class="col-md-3">
53
+ <!-- this inline style makes me sad but doing it in the stylesheet seems to break bootstrap -->
54
+ <div class="input-group" id="job_search" style="display: none;">
55
+ <input type="text" class="form-control" id="jobs_search_term" placeholder="">
56
+ <span class="input-group-btn">
57
+ <a href="" class="btn btn-default refresh_jobs_link" aria-label="Run Search">
58
+ Filter <span class="glyphicon glyphicon-filter"></span>
59
+ </a>
60
+ </span>
61
+ </div>
62
+ </div>
63
+ <div class="col-md-6">
64
+ </div>
65
+ <div class="col-md-1">
66
+ <a href="" class="btn btn-default pull-right refresh_jobs_link" aria-label="Refresh Jobs Table">
67
+ <span class="glyphicon glyphicon-refresh"></span> Refresh
68
+ </a>
69
+ </div>
70
+ </div>
71
+ </div>
72
+
73
+ <div class="row job-list-row">
74
+ <div class="col-md-12">
75
+ <table id="jobs" class="table table-striped table-bordered">
76
+ <thead>
77
+ <tr>
78
+ <th>ID</th>
79
+ <th>Tag</th>
80
+ <th>Attempt</th>
81
+ <th>Priority</th>
82
+ <th>Strand</th>
83
+ <th>Run At</th>
84
+ </tr>
85
+ </thead>
86
+ <tbody>
87
+ </tbody>
88
+ </table>
89
+ </div>
90
+ </div>