kthxbye 1.2.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 => Failure.count,
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
@@ -58,5 +58,9 @@ module Kthxbye
58
58
  redis.hdel( :failed, id )
59
59
  end
60
60
 
61
+ def self.clear_all
62
+ redis.del( :failed )
63
+ end
64
+
61
65
  end
62
66
  end
@@ -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( :faulure, id )
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 = Failure.fails_for_job(@id) # local tracking only, for rerun purposes
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
- @failed_attempts += 1
128
- log "Error occured: #{ex.message}. Try: #{@failed_attempts}/#{Kthxbye::Config.options[:attempts]}"
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
@@ -1,5 +1,5 @@
1
1
  module Kthxbye
2
2
  # Returns current version of Kthxbye
3
- Version = VERSION = "1.2.1"
3
+ Version = VERSION = "1.3.0"
4
4
  end
5
5
 
@@ -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
- - kthxbye.queues.each do |queue|
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 &nbsp;
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
 
@@ -1,7 +1,7 @@
1
1
  - @sub_tabs = %w(kthxbye redis keys)
2
2
 
3
3
  %h2
4
- Stats about #{params[:id] || params[:key]}
4
+ Stats for #{params[:id] || params[:key] || "kthxbye"}
5
5
 
6
6
  %p
7
7
  - if params[:key]
@@ -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 #{queues}
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 Active Workers
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:red"}
16
+ %span{:style => "color:#A6373A"}
15
17
  Currently no active workers.
16
18
 
17
19
  %p
@@ -69,8 +69,7 @@ module Kthxbye
69
69
 
70
70
  @child = fork {
71
71
  log "Forking..."
72
- result = job.perform
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
  #
@@ -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
- job.perform
130
- assert_equal 2, job.instance_variable_get(:@failed_attempts)
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
@@ -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
- - 2
8
- - 1
9
- version: 1.2.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-08 00:00:00 -04:00
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