kthxbye 1.2.1 → 1.3.0

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.
@@ -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