steve 1.0.4 → 2.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/steve.rb +4 -0
- data/lib/steve/archived_job.rb +33 -0
- data/lib/steve/interface.rb +20 -2
- data/lib/steve/interface/public/style.css +7 -1
- data/lib/steve/interface/views/completed.haml +10 -6
- data/lib/steve/interface/views/failed.haml +14 -8
- data/lib/steve/interface/views/index.haml +20 -9
- data/lib/steve/interface/views/view.haml +3 -3
- data/lib/steve/job.rb +1 -0
- data/lib/steve/queued_job.rb +33 -13
- metadata +22 -5
data/lib/steve.rb
CHANGED
@@ -10,6 +10,9 @@ module Steve
|
|
10
10
|
|
11
11
|
## The name of the table where jobs are stored (default 'jobs')
|
12
12
|
attr_accessor :jobs_table_name
|
13
|
+
|
14
|
+
## The name of the table where completed jobs are stored
|
15
|
+
attr_accessor :archived_jobs_table_name
|
13
16
|
|
14
17
|
## The period of time to wait before looking for new jobs
|
15
18
|
attr_accessor :worker_sleep_time
|
@@ -54,6 +57,7 @@ end
|
|
54
57
|
|
55
58
|
require 'steve/job'
|
56
59
|
require 'steve/queued_job'
|
60
|
+
require 'steve/archived_job'
|
57
61
|
require 'steve/worker'
|
58
62
|
require 'steve/interface'
|
59
63
|
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Steve
|
2
|
+
class ArchivedJob < ActiveRecord::Base
|
3
|
+
|
4
|
+
self.table_name = Steve.archived_jobs_table_name || 'archived_jobs'
|
5
|
+
|
6
|
+
serialize :params
|
7
|
+
|
8
|
+
scope :completed, lambda { where(:status => 'completed') }
|
9
|
+
scope :failed, lambda { where(:status => 'failed') }
|
10
|
+
|
11
|
+
scope :asc, lambda { order('id desc') }
|
12
|
+
|
13
|
+
def self.archive_job(job)
|
14
|
+
return if job.status == 'completed' && Steve.delete_successful_jobs
|
15
|
+
|
16
|
+
job_attributes = job.attributes
|
17
|
+
job_attributes.delete('id')
|
18
|
+
|
19
|
+
archive_job = self.new(job_attributes)
|
20
|
+
archive_job.save
|
21
|
+
ensure
|
22
|
+
job.destroy
|
23
|
+
end
|
24
|
+
|
25
|
+
def retry!
|
26
|
+
job_requeued = Steve::QueuedJob.queue(self.job, self.params) do |job|
|
27
|
+
job.queue = self.queue
|
28
|
+
job.priority = self.priority
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
data/lib/steve/interface.rb
CHANGED
@@ -1,23 +1,41 @@
|
|
1
1
|
require 'haml'
|
2
|
+
require 'will_paginate/view_helpers'
|
3
|
+
require 'will_paginate/view_helpers/link_renderer'
|
2
4
|
|
3
5
|
module Steve
|
4
6
|
class Interface
|
5
|
-
|
7
|
+
include WillPaginate::ViewHelpers
|
8
|
+
|
6
9
|
def call(env)
|
7
10
|
@req = Rack::Request.new(env)
|
8
11
|
case env['PATH_INFO'].to_s
|
9
|
-
when ''
|
12
|
+
when '/', ''
|
10
13
|
[200, {'Content-type' => 'text/html'}, [haml(:index)]]
|
11
14
|
when /failed/
|
15
|
+
@jobs = Steve::ArchivedJob.failed.asc.paginate(:page => @req.params['page'], :per_page => 50)
|
12
16
|
[200, {'Content-type' => 'text/html'}, [haml(:failed)]]
|
13
17
|
when /completed/
|
18
|
+
@jobs = Steve::ArchivedJob.completed.asc.paginate(:page => @req.params['page'], :per_page => 50)
|
14
19
|
[200, {'Content-type' => 'text/html'}, [haml(:completed)]]
|
20
|
+
when /archived\/object/
|
21
|
+
@jobs = Steve::ArchivedJob.where(:associated_object_type => @req.params['type'], :associated_object_id => @req.params['id']).order('created_at desc').limit(25)
|
22
|
+
[200, {'Content-type' => 'text/html'}, [haml(:object)]]
|
23
|
+
when /archived\/view/
|
24
|
+
@job = Steve::ArchivedJob.find(@req.params['id'])
|
25
|
+
[200, {'Content-type' => 'text/html'}, [haml(:view)]]
|
15
26
|
when /object/
|
16
27
|
@jobs = Steve::QueuedJob.where(:associated_object_type => @req.params['type'], :associated_object_id => @req.params['id']).order('created_at desc').limit(25)
|
17
28
|
[200, {'Content-type' => 'text/html'}, [haml(:object)]]
|
18
29
|
when /view/
|
19
30
|
@job = Steve::QueuedJob.find(@req.params['id'])
|
20
31
|
[200, {'Content-type' => 'text/html'}, [haml(:view)]]
|
32
|
+
when /retry/
|
33
|
+
@archived_job = Steve::ArchivedJob.find(@req.params['id'])
|
34
|
+
if @archived_job.retry!
|
35
|
+
[302, {'Location' => '/jobs/?status=retried'}]
|
36
|
+
else
|
37
|
+
[302, {'Location' => '/jobs/?status=retryfailed'}]
|
38
|
+
end
|
21
39
|
|
22
40
|
else
|
23
41
|
path = static_path(env['PATH_INFO'])
|
@@ -27,6 +27,10 @@ body { margin:0;}
|
|
27
27
|
#header h1 span { font-weight:bold; font-size:90%; color:#fff;}
|
28
28
|
#header h1 em { color:yellow; font-weight:bold;}
|
29
29
|
|
30
|
+
#status { font-size:120%; font-weight:bold; text-align:center; }
|
31
|
+
#status span.success { color:#11772d; }
|
32
|
+
#status span.failure { color:#fe3939; }
|
33
|
+
|
30
34
|
#content { margin:15px 2%;}
|
31
35
|
#content h2 { font-size:150%; font-weight:bold; margin:15px 0;}
|
32
36
|
#content table.data {width:100%;}
|
@@ -39,7 +43,9 @@ body { margin:0;}
|
|
39
43
|
#content p { margin:10px 0; line-height:1.5;}
|
40
44
|
#content a { color:#333;}
|
41
45
|
|
46
|
+
#content .pagination-details { float:right; margin-top:10px; }
|
47
|
+
|
42
48
|
#content dl {margin:40px 0; font-size:110%;}
|
43
49
|
#content dl dt { font-weight:bold; float:left; width:150px; text-align:right;}
|
44
50
|
#content dl dd { margin-left:180px; margin-bottom:10px;}
|
45
|
-
#content dl dd pre { background:#000; color:#fff; line-height:1.3; padding:6px;}
|
51
|
+
#content dl dd pre { background:#000; color:#fff; line-height:1.3; padding:6px;}
|
@@ -10,6 +10,7 @@
|
|
10
10
|
#content
|
11
11
|
#recent
|
12
12
|
%h2 Recently completed jobs
|
13
|
+
|
13
14
|
%table.data
|
14
15
|
%thead
|
15
16
|
%tr
|
@@ -19,13 +20,13 @@
|
|
19
20
|
%td{:width => '15%'} Run at
|
20
21
|
%td{:width => '5%'} Retries
|
21
22
|
%tbody
|
22
|
-
-
|
23
|
-
- for job in jobs
|
23
|
+
- for job in @jobs
|
24
24
|
%tr
|
25
25
|
%td
|
26
26
|
%code
|
27
|
-
%a{:href => "view?id=#{job.id}"}= job.job
|
28
|
-
|
27
|
+
%a{:href => "archived/view?id=#{job.id}"}= job.job
|
28
|
+
- unless job.params.empty?
|
29
|
+
%span= job.params.inspect
|
29
30
|
%td
|
30
31
|
- if job.associated_object_type.blank?
|
31
32
|
—
|
@@ -34,8 +35,11 @@
|
|
34
35
|
%td= job.created_at.to_s(:db)
|
35
36
|
%td= job.run_at.to_s(:db)
|
36
37
|
%td= job.retries
|
37
|
-
- if jobs.empty?
|
38
|
+
- if @jobs.empty?
|
38
39
|
%tr
|
39
40
|
%td.none{:colspan => 7} There are no jobs recently completed
|
41
|
+
|
42
|
+
.pagination-details
|
43
|
+
= page_entries_info @jobs
|
40
44
|
%p
|
41
|
-
%a{:href => "/jobs"} Back to overview
|
45
|
+
%a{:href => "/jobs"} Back to overview
|
@@ -12,6 +12,7 @@
|
|
12
12
|
%p
|
13
13
|
The jobs listed below have failed to execute recently. You can clear all the jobs using the button below, this will
|
14
14
|
remove them from the database and you will not be able to retry once removed.
|
15
|
+
|
15
16
|
%table.data
|
16
17
|
%thead
|
17
18
|
%tr
|
@@ -22,22 +23,27 @@
|
|
22
23
|
%td{:width => '35%'} Error
|
23
24
|
%td{:width => '10%'}
|
24
25
|
%tbody
|
25
|
-
-
|
26
|
-
- for job in jobs
|
26
|
+
- for job in @jobs
|
27
27
|
%tr
|
28
28
|
%td= job.finished_at.to_s(:short)
|
29
29
|
%td= job.id
|
30
30
|
%td
|
31
31
|
%code
|
32
|
-
%a{:href => "view?id=#{job.id}"}= job.job
|
33
|
-
|
32
|
+
%a{:href => "archived/view?id=#{job.id}"}= job.job
|
33
|
+
- unless job.params.empty?
|
34
|
+
%span= job.params.inspect
|
34
35
|
%td= job.queue
|
35
36
|
%td
|
36
|
-
%code{:style => 'font-size:90%', :title => job.error}= ERB::Util.html_escape(job.error.split("\n").first)\
|
37
|
+
%code{:style => 'font-size:90%', :title => job.error}= ERB::Util.html_escape(job.error.to_s.split("\n").first)\
|
37
38
|
%td
|
38
|
-
%
|
39
|
-
|
39
|
+
%form{:action => "/jobs/retry?id=#{job.id}", :method => :post}
|
40
|
+
%input{:type => 'submit', :value => 'Retry'}
|
41
|
+
- if @jobs.empty?
|
40
42
|
%tr
|
41
43
|
%td.none{:colspan => 6} There are no jobs currently running
|
44
|
+
|
45
|
+
.pagination-details
|
46
|
+
= page_entries_info @jobs
|
47
|
+
|
42
48
|
%p
|
43
|
-
%a{:href => "/jobs"} Back to overview
|
49
|
+
%a{:href => "/jobs"} Back to overview
|
@@ -8,27 +8,38 @@
|
|
8
8
|
%h1 <span>Steve</span>—Job Queue Monitor
|
9
9
|
|
10
10
|
#content
|
11
|
+
#status
|
12
|
+
- case @req.params['status']
|
13
|
+
- when 'retried'
|
14
|
+
%span.success Job successfully requeued.
|
15
|
+
- when 'retry_failed'
|
16
|
+
%span.failure Job could not be requeued for some reason.
|
17
|
+
|
11
18
|
#active
|
12
19
|
%h2 Jobs being executed at the moment
|
13
20
|
%table.data
|
14
21
|
%thead
|
15
22
|
%tr
|
16
23
|
%td{:width => '25%'} Job
|
17
|
-
%td{:width => '
|
18
|
-
%td{:width => '
|
24
|
+
%td{:width => '15%'} Queue
|
25
|
+
%td{:width => '40%'} Worker
|
26
|
+
%td{:width => '20%'} Started at
|
27
|
+
|
19
28
|
%tbody
|
20
29
|
- jobs = Steve::QueuedJob.running
|
21
30
|
- for job in jobs
|
22
31
|
%tr
|
23
32
|
%td
|
24
33
|
%code
|
25
|
-
%b
|
34
|
+
%b
|
35
|
+
%a{:href => "jobs/view?id=#{job.id}"}= job.job
|
26
36
|
%span= job.params.inspect
|
27
37
|
%td= job.queue
|
28
38
|
%td= job.worker
|
39
|
+
%td= job.started_at.to_s(:db)
|
29
40
|
- if jobs.empty?
|
30
41
|
%tr
|
31
|
-
%td.none{:colspan =>
|
42
|
+
%td.none{:colspan => 4} There are no jobs currently running
|
32
43
|
|
33
44
|
#queues
|
34
45
|
%h2 Jobs which are pending execution
|
@@ -48,7 +59,8 @@
|
|
48
59
|
%tr
|
49
60
|
%td
|
50
61
|
%code
|
51
|
-
%b
|
62
|
+
%b
|
63
|
+
%a{:href => "jobs/view?id=#{job.id}"}= job.job
|
52
64
|
%span= job.params.inspect
|
53
65
|
%td= job.queue
|
54
66
|
%td= job.status
|
@@ -65,11 +77,10 @@
|
|
65
77
|
#stats
|
66
78
|
%h2 Statistics
|
67
79
|
%ul
|
68
|
-
%li== #{Steve::QueuedJob.count} jobs stored
|
69
|
-
%li== #{Steve::QueuedJob.pending.count} pending execution
|
70
80
|
%li== #{Steve::QueuedJob.running.count} running now
|
81
|
+
%li== #{Steve::QueuedJob.pending.count} pending execution
|
71
82
|
- unless Steve.delete_successful_jobs
|
72
83
|
%li
|
73
|
-
%a{:href => 'jobs/completed'}== #{Steve::
|
84
|
+
%a{:href => 'jobs/completed'}== #{Steve::ArchivedJob.completed.count} completed successfully
|
74
85
|
%li
|
75
|
-
%a{:href => 'jobs/failed'}== #{Steve::
|
86
|
+
%a{:href => 'jobs/failed'}== #{Steve::ArchivedJob.failed.count} failed to execute
|
@@ -31,10 +31,10 @@
|
|
31
31
|
%dd= @job.created_at
|
32
32
|
|
33
33
|
%dt Started at
|
34
|
-
%dd= @job.started_at
|
34
|
+
%dd= @job.started_at || " "
|
35
35
|
|
36
36
|
%dt Finished at
|
37
|
-
%dd= @job.finished_at
|
37
|
+
%dd= @job.finished_at || " "
|
38
38
|
|
39
39
|
- unless @job.output.blank?
|
40
40
|
%dt Output
|
@@ -49,4 +49,4 @@
|
|
49
49
|
%dt Worker
|
50
50
|
%dd= @job.worker
|
51
51
|
%p
|
52
|
-
%a{:href => "/jobs"} Back to overview
|
52
|
+
%a{:href => "/jobs"} Back to overview
|
data/lib/steve/job.rb
CHANGED
data/lib/steve/queued_job.rb
CHANGED
@@ -13,10 +13,8 @@ module Steve
|
|
13
13
|
belongs_to :associated_object, :polymorphic => true
|
14
14
|
|
15
15
|
## Scopes
|
16
|
-
scope :pending, where(:status => ['pending', 'delayed'])
|
17
|
-
scope :running, where(:status => 'running')
|
18
|
-
scope :completed, where(:status => 'completed')
|
19
|
-
scope :failed, where(:status => 'failed')
|
16
|
+
scope :pending, lambda { where(:status => ['pending', 'delayed']) }
|
17
|
+
scope :running, lambda { where(:status => 'running') }
|
20
18
|
|
21
19
|
## Automatically set defaults for new jobs
|
22
20
|
before_create do
|
@@ -26,6 +24,11 @@ module Steve
|
|
26
24
|
self.run_at ||= Time.now.utc
|
27
25
|
end
|
28
26
|
|
27
|
+
after_save do
|
28
|
+
Steve::ArchivedJob.archive_job(self) if self.archive_job?
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
29
32
|
## Queue a new job for processing. Returns true or false depending whether
|
30
33
|
## the job has been queued or not.
|
31
34
|
def self.queue(klass, params = {}, &block)
|
@@ -33,6 +36,7 @@ module Steve
|
|
33
36
|
job.job = klass.to_s
|
34
37
|
job.params = params
|
35
38
|
job.queue = klass.instance_variable_get('@queue')
|
39
|
+
job.priority = klass.instance_variable_get('@priority')
|
36
40
|
block.call(job) if block_given?
|
37
41
|
job.save
|
38
42
|
end
|
@@ -77,11 +81,10 @@ module Steve
|
|
77
81
|
## Execute this job, catching any errors if they occur and ensuring the
|
78
82
|
## job is started & finished as appropriate.
|
79
83
|
def execute
|
80
|
-
@output_file = File.join('', 'tmp', "steve-job-#{self.id}")
|
81
84
|
job = self.job.constantize.new(self)
|
82
85
|
if job.respond_to?(:perform)
|
83
86
|
start!
|
84
|
-
STDOUT.reopen(
|
87
|
+
STDOUT.reopen(output_file) && STDERR.reopen(output_file)
|
85
88
|
begin
|
86
89
|
job.perform
|
87
90
|
success!
|
@@ -117,14 +120,21 @@ module Steve
|
|
117
120
|
end
|
118
121
|
ensure
|
119
122
|
STDOUT.flush && STDERR.flush
|
120
|
-
if File.exist?(
|
121
|
-
self.output = File.read(
|
122
|
-
FileUtils.rm(
|
123
|
+
if File.exist?(output_file)
|
124
|
+
self.output = File.read(output_file)
|
125
|
+
FileUtils.rm(output_file)
|
123
126
|
end
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
127
|
+
self.save(:validate => false)
|
128
|
+
end
|
129
|
+
|
130
|
+
## Get the output of a job, even if the job is in progress,
|
131
|
+
## live output won't work very well in a multiple app-server
|
132
|
+
## environment
|
133
|
+
def output
|
134
|
+
if read_attribute(:output)
|
135
|
+
read_attribute(:output)
|
136
|
+
elsif File.exist?(output_file)
|
137
|
+
File.read(output_file)
|
128
138
|
end
|
129
139
|
end
|
130
140
|
|
@@ -164,6 +174,7 @@ module Steve
|
|
164
174
|
self.finished_at = nil
|
165
175
|
self.started_at = Time.now.utc
|
166
176
|
self.status = 'running'
|
177
|
+
self.job_pid = Process.pid
|
167
178
|
self.save(:validate => false)
|
168
179
|
end
|
169
180
|
|
@@ -183,6 +194,15 @@ module Steve
|
|
183
194
|
self.save(:validate => false)
|
184
195
|
end
|
185
196
|
|
197
|
+
## In a state appropriate to be archived?
|
198
|
+
def archive_job?
|
199
|
+
['failed', 'completed'].include?(self.status)
|
200
|
+
end
|
201
|
+
|
202
|
+
def output_file
|
203
|
+
@output_file ||= File.join('', 'tmp', "steve-job-#{self.id}")
|
204
|
+
end
|
205
|
+
|
186
206
|
## Remove old completed jobs from the database
|
187
207
|
def self.cleanup(age = 5.days.ago)
|
188
208
|
self.delete_all(["status = 'completed' and run_at < ?", age])
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: steve
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,14 +9,31 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
13
|
-
dependencies:
|
12
|
+
date: 2014-04-04 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: will_paginate
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.0.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 3.0.0
|
14
30
|
description:
|
15
31
|
email: support@atechmedia.com
|
16
32
|
executables: []
|
17
33
|
extensions: []
|
18
34
|
extra_rdoc_files: []
|
19
35
|
files:
|
36
|
+
- lib/steve/archived_job.rb
|
20
37
|
- lib/steve/interface/public/style.css
|
21
38
|
- lib/steve/interface/views/completed.haml
|
22
39
|
- lib/steve/interface/views/failed.haml
|
@@ -48,8 +65,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
48
65
|
version: '0'
|
49
66
|
requirements: []
|
50
67
|
rubyforge_project:
|
51
|
-
rubygems_version: 1.8.
|
68
|
+
rubygems_version: 1.8.23.2
|
52
69
|
signing_key:
|
53
70
|
specification_version: 3
|
54
|
-
summary:
|
71
|
+
summary: Steve Jobs background job runner
|
55
72
|
test_files: []
|