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 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
@@ -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
- - jobs = Steve::QueuedJob.completed.limit(15).order('run_at desc')
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
- %span= job.params.inspect
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
  &mdash;
@@ -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
- - jobs = Steve::QueuedJob.failed
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
- %span= job.params.inspect
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
- %a{:href => '#'} Retry
39
- - if jobs.empty?
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>&mdash;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 => '25%'} Queue
18
- %td{:width => '50%'} Worker
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= job.job
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 => 3} There are no jobs currently running
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= job.job
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::QueuedJob.completed.count} completed successfully
84
+ %a{:href => 'jobs/completed'}== #{Steve::ArchivedJob.completed.count} completed successfully
74
85
  %li
75
- %a{:href => 'jobs/failed'}== #{Steve::QueuedJob.failed.count} failed to execute
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 || "&nbsp;"
35
35
 
36
36
  %dt Finished at
37
- %dd= @job.finished_at
37
+ %dd= @job.finished_at || "&nbsp;"
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
@@ -6,6 +6,7 @@ module Steve
6
6
 
7
7
  class Delay < StandardError; end
8
8
  class Error < StandardError; end
9
+ class Aborted < StandardError; end
9
10
 
10
11
  attr_reader :job
11
12
  attr_reader :params
@@ -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(@output_file) && STDERR.reopen(@output_file)
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?(@output_file)
121
- self.output = File.read(@output_file)
122
- FileUtils.rm(@output_file)
123
+ if File.exist?(output_file)
124
+ self.output = File.read(output_file)
125
+ FileUtils.rm(output_file)
123
126
  end
124
- if self.status == 'completed' && Steve.delete_successful_jobs
125
- self.destroy
126
- else
127
- self.save(:validate => false)
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: 1.0.4
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: 2012-07-18 00:00:00.000000000Z
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.10
68
+ rubygems_version: 1.8.23.2
52
69
  signing_key:
53
70
  specification_version: 3
54
- summary: Deployment Recipes for Appli
71
+ summary: Steve Jobs background job runner
55
72
  test_files: []