steve 1.0.4 → 2.0.2
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.
- 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: []
|