kthxbye 1.2.1 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/kthxbye.rb +3 -1
- data/lib/kthxbye/failure.rb +4 -0
- data/lib/kthxbye/job.rb +12 -7
- data/lib/kthxbye/stats.rb +39 -0
- data/lib/kthxbye/version.rb +1 -1
- data/lib/kthxbye/web_interface.rb +10 -0
- data/lib/kthxbye/web_interface/public/style.css +10 -1
- data/lib/kthxbye/web_interface/views/failed.haml +1 -1
- data/lib/kthxbye/web_interface/views/queue.haml +14 -0
- data/lib/kthxbye/web_interface/views/queues.haml +26 -27
- data/lib/kthxbye/web_interface/views/stats.haml +1 -1
- data/lib/kthxbye/web_interface/views/workers.haml +11 -2
- data/lib/kthxbye/web_interface/views/working.haml +4 -2
- data/lib/kthxbye/worker.rb +33 -2
- data/test/test_kthxbye.rb +5 -5
- data/test/test_worker.rb +24 -1
- metadata +9 -4
data/lib/kthxbye.rb
CHANGED
@@ -11,6 +11,7 @@ require 'kthxbye/helper'
|
|
11
11
|
require 'kthxbye/job'
|
12
12
|
require 'kthxbye/worker'
|
13
13
|
require 'kthxbye/failure'
|
14
|
+
require 'kthxbye/stats'
|
14
15
|
|
15
16
|
require 'kthxbye/version'
|
16
17
|
|
@@ -213,7 +214,8 @@ module Kthxbye
|
|
213
214
|
:workers => workers.size,
|
214
215
|
:working => working.size,
|
215
216
|
:queues => queues.size,
|
216
|
-
:failed =>
|
217
|
+
:failed => Stats["failures"],
|
218
|
+
:jobs_processed => Stats["processed"],
|
217
219
|
:pending => queues.inject(0) {|m,o| m + size(o)}
|
218
220
|
}
|
219
221
|
end
|
data/lib/kthxbye/failure.rb
CHANGED
data/lib/kthxbye/job.rb
CHANGED
@@ -57,7 +57,7 @@ module Kthxbye
|
|
57
57
|
# remove the job's data as well
|
58
58
|
redis.hdel("data-store:#{queue}", id)
|
59
59
|
redis.hdel("result-store:#{queue}", id)
|
60
|
-
redis.hdel( :
|
60
|
+
redis.hdel( :failure, id )
|
61
61
|
|
62
62
|
return ret
|
63
63
|
end
|
@@ -69,7 +69,7 @@ module Kthxbye
|
|
69
69
|
@id = id.to_i
|
70
70
|
@queue = queue
|
71
71
|
@data = data
|
72
|
-
@failed_attempts =
|
72
|
+
@failed_attempts = Failure.fails_for_job(@id) # local tracking only, for rerun purposes
|
73
73
|
end
|
74
74
|
|
75
75
|
# Simply requeues the job to be rerun.
|
@@ -124,14 +124,19 @@ module Kthxbye
|
|
124
124
|
redis.hset( "result-store:#{@queue}", @id, encode( result ) )
|
125
125
|
return result
|
126
126
|
rescue Object => ex
|
127
|
-
|
128
|
-
|
129
|
-
redis.publish("job.failed", @id)
|
130
|
-
return Kthxbye::Failure.create( self, ex ) if @failed_attempts >= Kthxbye::Config.options[:attempts]
|
131
|
-
perform
|
127
|
+
# handled by worker
|
128
|
+
raise ex
|
132
129
|
end
|
133
130
|
end
|
134
131
|
|
132
|
+
def fail(ex)
|
133
|
+
@failed_attempts += 1
|
134
|
+
log "Error occured: #{ex.message}. Try: #{@failed_attempts}/#{Kthxbye::Config.options[:attempts]}"
|
135
|
+
redis.publish("job.failed", @id)
|
136
|
+
Stats.incr("failures")
|
137
|
+
Kthxbye::Failure.create( self, ex )
|
138
|
+
end
|
139
|
+
|
135
140
|
# Removes the job from the inactive queue.
|
136
141
|
def active
|
137
142
|
redis.srem("jobs:inactive", @id)
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# Simple library to aid in tracking statistics regarding Kthxbye and its workers.
|
2
|
+
# Submits jobs processed stats, total failures, total uptime, etc.
|
3
|
+
# Puts all stats in stats:x vars in Redis. Super flexible, made to allow tracking
|
4
|
+
# of any kind of count internally
|
5
|
+
#
|
6
|
+
# Built in stats (tracked by Kthxbye internals) include the following
|
7
|
+
#
|
8
|
+
# processed - the count of processed jobs
|
9
|
+
# <worker>:processed - the count a given worker has processed
|
10
|
+
# failures - the count of failed jobs
|
11
|
+
|
12
|
+
module Kthxbye
|
13
|
+
module Stats
|
14
|
+
extend self
|
15
|
+
extend Helper
|
16
|
+
|
17
|
+
# get count for a given stat (can also use [] method)
|
18
|
+
def get( stat )
|
19
|
+
redis.get( "stat:#{stat}" ).to_i
|
20
|
+
end
|
21
|
+
alias_method :[], :get
|
22
|
+
|
23
|
+
# increment a count by a given value (defaults to 1)
|
24
|
+
def incr( stat, by=1 )
|
25
|
+
redis.incrby("stat:#{stat}", by)
|
26
|
+
end
|
27
|
+
|
28
|
+
# decrement a count by a given value (defaults to 1)
|
29
|
+
def decr( stat, by=1 )
|
30
|
+
redis.decrby("stat:#{stat}", by)
|
31
|
+
end
|
32
|
+
|
33
|
+
# reset this stat to 0
|
34
|
+
def reset( stat )
|
35
|
+
redis.del( "stat:#{stat}" )
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
data/lib/kthxbye/version.rb
CHANGED
@@ -99,6 +99,11 @@ module Kthxbye
|
|
99
99
|
redirect url(:overview)
|
100
100
|
end
|
101
101
|
|
102
|
+
get "/queues/:q" do
|
103
|
+
@queue = params[:q]
|
104
|
+
haml :queue, {:layout => :layout}
|
105
|
+
end
|
106
|
+
|
102
107
|
post "/failed/:job/rerun" do
|
103
108
|
Kthxbye::Failure.rerun(params[:job])
|
104
109
|
redirect url(:failed)
|
@@ -109,6 +114,11 @@ module Kthxbye
|
|
109
114
|
redirect url(:failed)
|
110
115
|
end
|
111
116
|
|
117
|
+
post "/failed/clear" do
|
118
|
+
Kthxbye::Failure.clear_all
|
119
|
+
redirect url(:queues)
|
120
|
+
end
|
121
|
+
|
112
122
|
post "/toggle_polling" do
|
113
123
|
redirect url(:overview)
|
114
124
|
end
|
@@ -5,6 +5,11 @@ body {
|
|
5
5
|
font-family: Helvetica, Arial, sans-serif;
|
6
6
|
}
|
7
7
|
|
8
|
+
h2 {
|
9
|
+
margin-bottom: 0;
|
10
|
+
color:#5d6f72;
|
11
|
+
}
|
12
|
+
|
8
13
|
#header {
|
9
14
|
/*height: 50px;*/
|
10
15
|
background-color: #b5d3d5;
|
@@ -37,7 +42,6 @@ body {
|
|
37
42
|
}
|
38
43
|
|
39
44
|
a {
|
40
|
-
text-decoration: none;
|
41
45
|
color: #619793;
|
42
46
|
}
|
43
47
|
|
@@ -126,3 +130,8 @@ table tr td.result, table tr th.result {
|
|
126
130
|
#backtrace {
|
127
131
|
padding:20px;
|
128
132
|
}
|
133
|
+
|
134
|
+
.t8 {
|
135
|
+
font-size: 8pt;
|
136
|
+
color: #5e5f5f;
|
137
|
+
}
|
@@ -17,7 +17,7 @@
|
|
17
17
|
%a{:href => "/view_backtrace?id=#{f['job']}"}
|
18
18
|
View Backtrace
|
19
19
|
%td #{f['time']}
|
20
|
-
%td
|
20
|
+
%td{:style => "white-space:nowrap;"}
|
21
21
|
%form{:action => "/failed/#{f['job']}/rerun", :method => "post", :style => "display:inline"}
|
22
22
|
%input{:class => "awesome button small", :value => "Retry", :type => "submit"}
|
23
23
|
%span.spacer
|
@@ -0,0 +1,14 @@
|
|
1
|
+
%h2 The #{params[:q]} queue
|
2
|
+
%table.queue_job
|
3
|
+
%tr
|
4
|
+
%th.id ID
|
5
|
+
%th.klass Class
|
6
|
+
%th.payload Data
|
7
|
+
%th.klass Status
|
8
|
+
- Kthxbye.data_peek(@queue).each do |id,job|
|
9
|
+
%tr
|
10
|
+
%td.id #{id}
|
11
|
+
%td.klass #{job['klass']}
|
12
|
+
%td.payload #{job['payload']}
|
13
|
+
%td.klass #{Kthxbye::Job.find(id,@queue).status}
|
14
|
+
|
@@ -1,31 +1,30 @@
|
|
1
|
-
%h2 Queues
|
1
|
+
%h2 Queues
|
2
|
+
.t8 The queue job count only shows jobs that are either waiting for processing or being processed
|
2
3
|
|
3
|
-
|
4
|
-
#queue{:style => "margin-top:20px"}
|
5
|
-
#queue_head
|
6
|
-
Pending jobs on
|
7
|
-
%b #{queue}
|
8
|
-
(#{Kthxbye.size(queue)})
|
9
|
-
%span.spacer
|
10
|
-
|
|
11
|
-
%form{:action => "/queues/#{queue}/remove", :style => "display:inline", :method => "post" }
|
12
|
-
%input{:type => "submit", :class => "awesome button small", :value => "Remove Queue"}
|
13
|
-
|
14
|
-
|
15
|
-
%br
|
16
|
-
|
17
|
-
%table.queue_job
|
18
|
-
%tr
|
19
|
-
%th.id ID
|
20
|
-
%th.klass Class
|
21
|
-
%th.payload Data
|
22
|
-
%th.klass Status
|
23
|
-
- Kthxbye.data_peek(queue).each do |id,job|
|
24
|
-
%tr
|
25
|
-
%td.id #{id}
|
26
|
-
%td.klass #{job['klass']}
|
27
|
-
%td.payload #{job['payload']}
|
28
|
-
%td.klass #{Kthxbye::Job.find(id,queue).status}
|
4
|
+
%br
|
29
5
|
|
6
|
+
%table
|
7
|
+
%tr
|
8
|
+
%th Name
|
9
|
+
%th Jobs
|
10
|
+
%th
|
11
|
+
- kthxbye.queues.each do |queue|
|
12
|
+
%tr
|
13
|
+
%td
|
14
|
+
%a{:href => "/queues/#{queue}"}
|
15
|
+
%b #{queue}
|
16
|
+
%td
|
17
|
+
= Kthxbye.size(queue)
|
18
|
+
%td
|
19
|
+
%form{:action => "/queues/#{queue}/remove", :style => "display:inline", :method => "post" }
|
20
|
+
%input{:type => "submit", :class => "awesome button small", :value => "Remove Queue"}
|
21
|
+
%tr{:style => "background-color:#F4A8AA"}
|
22
|
+
%td
|
23
|
+
%a{:href => "/failed"}
|
24
|
+
%b Failed
|
25
|
+
%td= Kthxbye::Failure.count
|
26
|
+
%td
|
27
|
+
%form{:action => "/failed/clear", :style => "display:inline", :method => "post" }
|
28
|
+
%input{:type => "submit", :class => "awesome button small", :value => "Clear"}
|
30
29
|
|
31
30
|
|
@@ -5,16 +5,25 @@
|
|
5
5
|
%th PID
|
6
6
|
%th Server
|
7
7
|
%th Queues
|
8
|
+
%th Uptime
|
9
|
+
%th Jobs Run
|
8
10
|
%th Working Job
|
9
11
|
- kthxbye.workers.each do |worker|
|
10
12
|
- host,pid,queues = worker.to_s.split(":")
|
11
13
|
%tr
|
12
14
|
%td #{pid}
|
13
15
|
%td #{host}
|
14
|
-
%td
|
16
|
+
%td
|
17
|
+
- queues.split(",").each do |queue|
|
18
|
+
%a{:href => "/queues/#{queue}"}
|
19
|
+
#{queues}
|
20
|
+
%td
|
21
|
+
= Time.parse(worker.started).strftime("%m/%d/%Y at %I:%M%p") if worker.started
|
22
|
+
%td
|
23
|
+
= worker.processed
|
15
24
|
%td
|
16
25
|
- job = Kthxbye::Worker.working_on(worker)
|
17
|
-
= job ? "<b>ID</b>: #{job['job_id']} - <b>Started</b>: #{job['started']}" : "***"
|
26
|
+
= job ? "<b>ID</b>: #{job['job_id']} - <b>Started</b>: #{Time.parse(job['started']).strftime("%I:%M%p")}" : "***"
|
18
27
|
|
19
28
|
- else
|
20
29
|
%span{:style => "color:red"}
|
@@ -1,4 +1,6 @@
|
|
1
|
-
%h2
|
1
|
+
%h2 #{Kthxbye.working.size} of #{Kthxbye.workers.size} workers active:
|
2
|
+
|
3
|
+
%br
|
2
4
|
|
3
5
|
- unless Kthxbye.working.empty?
|
4
6
|
%table
|
@@ -11,7 +13,7 @@
|
|
11
13
|
%td #{job['job_id']}
|
12
14
|
%td #{job['started']}
|
13
15
|
- else
|
14
|
-
%span{:style => "color
|
16
|
+
%span{:style => "color:#A6373A"}
|
15
17
|
Currently no active workers.
|
16
18
|
|
17
19
|
%p
|
data/lib/kthxbye/worker.rb
CHANGED
@@ -69,8 +69,7 @@ module Kthxbye
|
|
69
69
|
|
70
70
|
@child = fork {
|
71
71
|
log "Forking..."
|
72
|
-
|
73
|
-
yield job if block_given?
|
72
|
+
perform(job)
|
74
73
|
exit!
|
75
74
|
}
|
76
75
|
|
@@ -86,6 +85,19 @@ module Kthxbye
|
|
86
85
|
unregister_worker
|
87
86
|
end
|
88
87
|
|
88
|
+
def perform(job)
|
89
|
+
begin
|
90
|
+
result = job.perform
|
91
|
+
rescue Object => ex
|
92
|
+
job.fail(ex)
|
93
|
+
log "Error running job #{job}"
|
94
|
+
Stats.incr("failures:#{self}")
|
95
|
+
perform(job) if job.failed_attempts < Kthxbye::Config.options[:attempts]
|
96
|
+
ensure
|
97
|
+
yield job if block_given?
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
89
101
|
# Returns the queues this worker is attached toin alphabetical order.
|
90
102
|
def queues
|
91
103
|
@queues.sort
|
@@ -117,6 +129,7 @@ module Kthxbye
|
|
117
129
|
def register_worker
|
118
130
|
log "Registered worker #{self}"
|
119
131
|
redis.sadd( :workers, self ) if !exists?
|
132
|
+
redis.set( "worker:#{self}:started", Time.now.to_s )
|
120
133
|
end
|
121
134
|
|
122
135
|
# Removes the worker from our worker registry
|
@@ -130,6 +143,10 @@ module Kthxbye
|
|
130
143
|
|
131
144
|
redis.del "worker:#{self}"
|
132
145
|
redis.srem :workers, self
|
146
|
+
redis.del "worker:#{self}:started"
|
147
|
+
|
148
|
+
Stats.reset("processed:#{self}")
|
149
|
+
Stats.reset("failures:#{self}")
|
133
150
|
end
|
134
151
|
|
135
152
|
# Gets the current job this worker is working.
|
@@ -163,9 +180,23 @@ module Kthxbye
|
|
163
180
|
redis.del( "worker:#{self}" )
|
164
181
|
log "Completed job #{@current_job}"
|
165
182
|
redis.publish("job.completed", @current_job.id)
|
183
|
+
Stats.incr("processed")
|
184
|
+
Stats.incr("processed:#{self}")
|
166
185
|
@current_job = nil
|
167
186
|
end
|
168
187
|
|
188
|
+
def processed
|
189
|
+
Stats["processed:#{self}"]
|
190
|
+
end
|
191
|
+
|
192
|
+
def failed
|
193
|
+
Stats["failures:#{self}"]
|
194
|
+
end
|
195
|
+
|
196
|
+
def started
|
197
|
+
redis.get( "worker:#{self}:started" )
|
198
|
+
end
|
199
|
+
|
169
200
|
#
|
170
201
|
# thanks to http://github.com/defunkt/resque/blob/master/lib/resque/worker.rb for these signals
|
171
202
|
#
|
data/test/test_kthxbye.rb
CHANGED
@@ -123,12 +123,12 @@ class TestKthxbye < Test::Unit::TestCase
|
|
123
123
|
|
124
124
|
should "retry a job if attempts setting is > 1" do
|
125
125
|
Kthxbye::Config.options[:attempts] = 2
|
126
|
+
worker = Kthxbye::Worker.new("test", 0)
|
126
127
|
id = Kthxbye.enqueue( "test", BadJob )
|
127
|
-
job = Kthxbye::Job.find(id, "test")
|
128
128
|
|
129
|
-
|
130
|
-
|
131
|
-
|
129
|
+
worker.run
|
130
|
+
job = Kthxbye::Job.find(id, "test")
|
131
|
+
assert_equal 2, job.failed_attempts
|
132
132
|
end
|
133
133
|
|
134
134
|
should "delete a job" do
|
@@ -191,8 +191,8 @@ class TestKthxbye < Test::Unit::TestCase
|
|
191
191
|
worker1.run
|
192
192
|
assert_equal :succeeded, job.status
|
193
193
|
|
194
|
-
job = Kthxbye::Job.find(bad_job, "bad")
|
195
194
|
worker2.run
|
195
|
+
job = Kthxbye::Job.find(bad_job, "bad")
|
196
196
|
|
197
197
|
assert_equal :failed, job.status
|
198
198
|
end
|
data/test/test_worker.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
class TestWorker < Test::Unit::TestCase
|
3
3
|
context "See Kthxbye::Worker" do
|
4
4
|
setup do
|
5
|
-
Kthxbye::Config.setup(:verbose => false, :redis_port => 9876)
|
5
|
+
Kthxbye::Config.setup(:verbose => false, :redis_port => 9876, :attempts => 1)
|
6
6
|
Kthxbye.redis.flushall
|
7
7
|
|
8
8
|
@id = Kthxbye.enqueue( "test", SimpleJob, 1, 2 )
|
@@ -130,6 +130,29 @@ class TestWorker < Test::Unit::TestCase
|
|
130
130
|
end
|
131
131
|
end
|
132
132
|
|
133
|
+
should "report system stats" do
|
134
|
+
Kthxbye.enqueue( "test", BadJob )
|
135
|
+
assert_equal 0, @worker.processed
|
136
|
+
assert_equal 0, @worker.failed
|
137
|
+
|
138
|
+
@worker.run
|
139
|
+
|
140
|
+
assert_equal 2, Kthxbye::Stats["processed"]
|
141
|
+
assert_equal 1, Kthxbye::Stats["failures"]
|
142
|
+
end
|
143
|
+
|
144
|
+
should "track failures" do
|
145
|
+
Kthxbye.enqueue( "test", BadJob )
|
146
|
+
Kthxbye.enqueue( "test", BadJob )
|
147
|
+
|
148
|
+
3.times do
|
149
|
+
job = @worker.grab_job
|
150
|
+
@worker.perform(job)
|
151
|
+
end
|
152
|
+
|
153
|
+
assert_equal 2, @worker.failed
|
154
|
+
end
|
155
|
+
|
133
156
|
should "requeue a job and report error if worker is killed mid-process" do
|
134
157
|
@worker.working( Kthxbye::Job.find( @id, "test" ) )
|
135
158
|
@worker.unregister_worker
|
metadata
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kthxbye
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
4
5
|
prerelease: false
|
5
6
|
segments:
|
6
7
|
- 1
|
7
|
-
-
|
8
|
-
-
|
9
|
-
version: 1.
|
8
|
+
- 3
|
9
|
+
- 0
|
10
|
+
version: 1.3.0
|
10
11
|
platform: ruby
|
11
12
|
authors:
|
12
13
|
- Luke van der Hoeven
|
@@ -14,7 +15,7 @@ autorequire:
|
|
14
15
|
bindir: bin
|
15
16
|
cert_chain: []
|
16
17
|
|
17
|
-
date: 2010-10-
|
18
|
+
date: 2010-10-11 00:00:00 -04:00
|
18
19
|
default_executable: kthxbye-monitor
|
19
20
|
dependencies: []
|
20
21
|
|
@@ -42,6 +43,7 @@ files:
|
|
42
43
|
- lib/kthxbye/web_interface/views/overview.haml
|
43
44
|
- lib/kthxbye/web_interface/views/failed.haml
|
44
45
|
- lib/kthxbye/web_interface/views/set.haml
|
46
|
+
- lib/kthxbye/web_interface/views/queue.haml
|
45
47
|
- lib/kthxbye/web_interface/views/view_backtrace.haml
|
46
48
|
- lib/kthxbye/web_interface/views/layout.haml
|
47
49
|
- lib/kthxbye/web_interface/views/queues.haml
|
@@ -51,6 +53,7 @@ files:
|
|
51
53
|
- lib/kthxbye/failure.rb
|
52
54
|
- lib/kthxbye/config.rb
|
53
55
|
- lib/kthxbye/railtie.rb
|
56
|
+
- lib/kthxbye/stats.rb
|
54
57
|
- lib/kthxbye/helper.rb
|
55
58
|
- lib/kthxbye/worker.rb
|
56
59
|
- lib/kthxbye/railties/kthxbye.rake
|
@@ -85,6 +88,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
85
88
|
requirements:
|
86
89
|
- - ">="
|
87
90
|
- !ruby/object:Gem::Version
|
91
|
+
hash: 3
|
88
92
|
segments:
|
89
93
|
- 0
|
90
94
|
version: "0"
|
@@ -93,6 +97,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
93
97
|
requirements:
|
94
98
|
- - ">="
|
95
99
|
- !ruby/object:Gem::Version
|
100
|
+
hash: 23
|
96
101
|
segments:
|
97
102
|
- 1
|
98
103
|
- 3
|