canvas-jobs 0.9.13 → 0.9.14

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.
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>