resque-telework 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -5,10 +5,12 @@ Resque Telework
5
5
 
6
6
  Telework depends on Resque 1.20+ and Redis 2.2+
7
7
 
8
+ Telework 0.3 has a new auto feature that is in beta and currently under testing - please report bugs to [gip.github@gmail.com](gip.github@gmail.com)
9
+
8
10
  Description
9
11
  -----------
10
12
 
11
- Telework is a [Resque](https://github.com/defunkt/resque) plugin aimed at controlling Resque workers from the web UI. It makes it easy to manage workers on a complex systems that includes several hosts, different queue(s) and an evolving source code that is deployed several times a day. Beyond starting and stopping workers on remote hosts, the plugin makes it easy to switch between code revisions, gives a partial view of each worker's log (stdout and stderr) and maintains a status of each workers.
13
+ Telework is a [Resque](https://github.com/defunkt/resque) plugin aimed at controlling Resque workers from the web UI. It makes it easy to manage workers on a complex systems that includes several hosts, different queue(s) and an evolving source code that is deployed several times a day. Beyond starting and stopping workers on remote hosts, the plugin makes it easy to switch between code revisions, gives a partial view of each worker's log (stdout and stderr) and maintains a status of each workers. Version 0.3 adds an auto mode that is able to start workers depending on how full a given queue (or several queues) are. The workers are stopped once all the jobs are processed, allowing a better memory usage.
12
14
 
13
15
  Telework comes with three main components
14
16
 
@@ -16,7 +18,9 @@ Telework comes with three main components
16
18
  * A daemon process to be started on each host (`rake telework:start_daemon` starts a new daemon and returns while `rake telework:daemon` runs the daemon interactively)
17
19
  * A registration command (`rake telework:register_revision`) to be called by the deployment script when a new revision is added on the host
18
20
 
19
- Note that currently (Telework 0.0.1), the daemon process is included in the main app, which is not really elegant as the full Rails environment needs to be loaded to run the daemon. A light-weight daemon is currently being developed and should be ready in the coming weeks.
21
+ Note that currently (Telework 0.3), the daemon process is included in the main app, which is not really elegant as the full Rails environment needs to be loaded to run the daemon. A light-weight daemon is currently being developed and should be ready in the coming weeks.
22
+
23
+ Telework has been successfully used in production at Entelo for more that a year with up to 10 servers.
20
24
 
21
25
  Overview of the WebUI
22
26
  ---------------------
@@ -120,12 +124,20 @@ gilles@myworkhost0 $ rake telework:register_revision
120
124
  ```
121
125
  Note that it is not necessary to stop and restart the daemon. Restarting the daemon is only required when the Telework gem is updated.
122
126
 
127
+ Auto Mode
128
+ ---------
129
+
130
+ The auto mode is still under testing as of version 0.3. Starting/stopping workers in auto mode is done by the daemon using a simple heuristic. Parameters may be modified as the task page. The main parameters controlling the auto mode are
131
+ * `Auto_delay`: this is the minimum amount of time, in second, that the daemon has to wait before to make a new change to the workers (e.g. start or stop workers). A large number prevents the overhead of stopping/starting workers too often
132
+ * `Worker_count`, `Auto_job_per_worker` and `Auto_min_worker`: these parameters control the number of workers needed to process jobs from a given queue. Given a queue with `Q` pending jobs, the number of workers started by the daemon at a given time will be:
133
+
134
+ min( `Worker_count`, max( `Auto_min_worker`, ceil( `Q` / `Auto_job_per_worker` ) ) )
135
+
123
136
  Known Issues
124
137
  ------------
125
138
 
126
- For version 0.2:
139
+ For version 0.3:
127
140
 
128
- * Buttons are not aligned in the web-UI
129
141
  * The daemon crashes if any of the log directories do not exist
130
142
 
131
143
  Bugs
@@ -136,10 +148,6 @@ Please report bugs on [github](https://github.com/gip/resque-telework/issues) or
136
148
  Todo
137
149
  ----
138
150
 
139
- The following features are are being developed and should be available shortly:
140
-
141
- * Improved window layout
142
-
143
151
  The following features are planned for future releases:
144
152
 
145
153
  * Light-weight daemon in Haskell
@@ -3,7 +3,7 @@ module Resque
3
3
  module Telework
4
4
  Name = NAME = 'resque-telework'
5
5
  Nickname = NICKNAME = 'telework'
6
- Version = VERSION = '0.2.1'
6
+ Version = VERSION = '0.3.0'
7
7
  RedisInterfaceVersion = REDIS_INTERFACE_VERSION = '2'
8
8
  end
9
9
  end
@@ -11,6 +11,7 @@ module Resque
11
11
  @SLEEP= cfg['daemon_pooling_interval']
12
12
  @WORKERS= {}
13
13
  @STOPPED= []
14
+ @AUTO= {}
14
15
  end
15
16
 
16
17
  # The manager (e.g. daemon) main loop
@@ -33,6 +34,7 @@ module Resque
33
34
  while cmd= cmds_pop( @HOST ) do # Pop a command in the command queue
34
35
  do_command(cmd) # Execute it
35
36
  end
37
+ check_auto # Deal with the task in auto mode
36
38
  sleep @SLEEP # Sleep
37
39
  end
38
40
  # A stop request has been received
@@ -41,6 +43,7 @@ module Resque
41
43
  send_status( 'Error', "A stop request has been received by the #{@HOST} daemon but there are still running worker(s) so it will keep running") unless @WORKERS.empty?
42
44
  @RUN_DAEMON= true
43
45
  end
46
+ i_am_dead
44
47
  rescue Interrupt # Control-C
45
48
  send_status( 'Info', "Interruption for #{@HOST} daemon, exiting gracefully") if @WORKERS.empty?
46
49
  send_status( 'Error', "Interruption for #{@HOST} daemon, exiting, running workers may now unexpectedly terminate") unless @WORKERS.empty?
@@ -79,18 +82,108 @@ module Resque
79
82
  start_worker( cmd, find_revision(cmd['revision']) )
80
83
  when 'signal_worker'
81
84
  manage_worker( cmd )
85
+ when 'start_auto'
86
+ start_auto( cmd, find_revision(cmd['revision']) )
87
+ when 'stop_auto'
88
+ stop_auto( cmd )
82
89
  when 'stop_daemon'
83
90
  @RUN_DAEMON= false
84
91
  when 'kill_daemon'
85
92
  send_status( 'Error', "A kill request has been received, the daemon on #{@HOST} is now brutally terminating by calling exit()")
93
+ i_am_dead
86
94
  exit # Bye
87
95
  else
88
96
  send_status( 'Error', "Unknown command '#{cmd['command']}'" )
89
97
  end
90
98
  end
99
+
100
+ def stop_auto( auto )
101
+ id= auto['task_id']
102
+ auto['worker_id'].each do |wid|
103
+ if @WORKERS[wid]
104
+ manage_worker( { 'worker_id' => wid, 'action' => 'QUIT'} )
105
+ end
106
+ end
107
+ @AUTO.delete(auto['task_id'])
108
+ autos_rem( @HOST, id )
109
+ send_status( 'Info', "Task #{id} is now in manual mode")
110
+ end
111
+
112
+ def status_auto( id, auto )
113
+ n= nvoid= nrun= 0
114
+ auto['worker_status']= []
115
+ auto['worker_id'].each do |id|
116
+ s= @WORKERS[id] ? @WORKERS[id]['status'] : 'VOID'
117
+ nvoid+= 1 if 'VOID'==s
118
+ nrun+= 1 if 'RUN'==s
119
+ n+= 1
120
+ auto['worker_status'] << s
121
+ end
122
+ auto['worker_run']= nrun
123
+ auto['worker_void']= nvoid
124
+ auto['worker_unknown']= n-nrun-nvoid
125
+ @AUTO[id]= auto
126
+ auto
127
+ end
128
+
129
+ def manage_auto( auto, status, action, n0 )
130
+ n= 0
131
+ auto['worker_status'].each_with_index do |s, i|
132
+ if s==status
133
+ cmd= auto.clone
134
+ cmd['worker_id']= auto['worker_id'][i]
135
+ if 'START'==action
136
+ start_worker( cmd, cmd['rev_info'], true )
137
+ else
138
+ cmd['action']= action
139
+ manage_worker( cmd )
140
+ end
141
+ n+= 1
142
+ end
143
+ break if n==n0
144
+ end
145
+ end
146
+
147
+ def check_auto
148
+ @AUTO.keys.each do |id|
149
+ auto= @AUTO[id]
150
+ autos_add( @HOST, id, auto )
151
+ next unless auto['last_action']+auto['auto_delay'].to_i <= Time.now
152
+ auto= status_auto( id, @AUTO[id] ) # Compute the new status..
153
+ ql= get_queue_length( auto['queue'] )
154
+ ideal= [(ql.to_f / auto['auto_max_waiting_job_per_worker'].to_f).ceil, auto['auto_worker_min'].to_i].max
155
+ count= auto['worker_count'].to_i
156
+ case ideal <=> (count-auto['worker_void'])
157
+ when 0 # Do nothing
158
+ when 1 # Increase number of workers if possible
159
+ inc= [ideal-auto['worker_run'], auto['worker_void']].min
160
+ manage_auto( auto, 'VOID', 'START', inc ) if inc>0
161
+ when -1 # Decrease number of workers if possible
162
+ dec= [auto['worker_run']-ideal, auto['worker_run']].min
163
+ manage_auto( auto, 'RUN', 'QUIT', dec ) if dec>0
164
+ end
165
+ end
166
+ end
167
+
168
+ # Start auto session
169
+ def start_auto( cmd0, rev_info )
170
+ auto_def= { 'auto_max_waiting_job_per_worker' => 1,'auto_worker_min' => 0, 'auto_delay' => 15 }
171
+ cmd= auto_def.merge( cmd0 )
172
+ id= cmd['task_id']
173
+ if @AUTO[id]
174
+ send_status( 'Error', "Task #{id} is already running in auto mode")
175
+ return
176
+ end
177
+ send_status( 'Info', "Task #{id} is now in auto mode")
178
+ auto= cmd # Should be defined in cmd: task_id, worker_count, worker_id, queue, rails_env, exec
179
+ auto['rev_info']= rev_info
180
+ # Get status for the workers
181
+ auto['last_action']= Time.now - auto['auto_delay'].to_i
182
+ @AUTO[id]= auto
183
+ end
91
184
 
92
185
  # Start a task
93
- def start_worker( cmd, rev_info )
186
+ def start_worker( cmd, rev_info, auto=false )
94
187
  # Retrieving args
95
188
  path= rev_info['revision_path']
96
189
  log_path= rev_info['revision_log_path']
@@ -113,8 +206,9 @@ module Resque
113
206
  pid= spawn( env, exec, opt) # Start it!
114
207
  info= { 'pid' => pid, 'status' => 'RUN', 'environment' => env, 'options' => opt, 'revision_info' => rev_info }
115
208
  # Log snapshot
116
- info['log_snapshot_period']= cmd['log_snapshot_period'] if cmd['log_snapshot_period']
117
- info['log_snapshort_lines']= cmd['log_snapshot_lines'] if cmd['log_snapshot_lines']
209
+ info['log_snapshot_period']= cmd['log_snapshot_period'].to_i if cmd['log_snapshot_period']
210
+ info['log_snapshort_lines']= cmd['log_snapshot_lines'].to_i if cmd['log_snapshot_lines']
211
+ info['mode']= auto ? 'Auto' : 'Manual'
118
212
  @WORKERS[id]= info
119
213
  workers_add( @HOST, id, info )
120
214
  send_status( 'Info', "Starting worker #{id} (PID #{pid})" )
@@ -175,6 +269,12 @@ module Resque
175
269
  end
176
270
  end
177
271
  end
272
+
273
+ def get_queue_length( qs )
274
+ ql= qs.split(",")
275
+ l= ql.include?("*") ? queue_list : ql
276
+ l.inject(0) { |a,e| a+queue_length(e) }
277
+ end
178
278
 
179
279
  def update_log_snapshot( id )
180
280
  ls= @WORKERS[id]['log_snapshot_period']
@@ -7,6 +7,10 @@ module Resque
7
7
  "plugins:#{Resque::Plugins::Telework::Nickname}"
8
8
  end
9
9
 
10
+ def queue_prefix
11
+ "queue:"
12
+ end
13
+
10
14
  def redis_interface_key # String
11
15
  "#{key_prefix}:redisif"
12
16
  end
@@ -27,6 +31,10 @@ module Resque
27
31
  "#{key_prefix}:host:#{h}:workers"
28
32
  end
29
33
 
34
+ def autos_key( h ) # Hash
35
+ "#{key_prefix}:host:#{h}:autos"
36
+ end
37
+
30
38
  def tasks_key( h ) # Hash
31
39
  "#{key_prefix}:host:#{h}:tasks"
32
40
  end
@@ -74,14 +82,20 @@ module Resque
74
82
  # Clients (hosts) side
75
83
 
76
84
  def i_am_alive( info= {}, ttl=10 )
77
- h= @HOST
78
85
  t= Time.now
79
86
  info= info.merge( { 'date' => t, 'version' => Resque::Plugins::Telework::Version } )
80
- k= alive_key(h)
81
- hosts_add(h)
87
+ k= alive_key(@HOST)
88
+ hosts_add(@HOST)
82
89
  Resque.redis.set(k, info.to_json )
83
90
  Resque.redis.expire(k, ttl)
84
- Resque.redis.set(last_seen_key(h), t)
91
+ Resque.redis.set(last_seen_key(@HOST), t)
92
+ end
93
+
94
+ def i_am_dead
95
+ t= Time.now
96
+ hosts_add( @HOST )
97
+ Resque.redis.del( alive_key( @HOST ) )
98
+ Resque.redis.set(last_seen_key( @HOST ), t)
85
99
  end
86
100
 
87
101
  def register_revision( h, rev, lim=9 )
@@ -129,12 +143,23 @@ module Resque
129
143
  Resque.redis.hset(k, id, info.to_json )
130
144
  Resque.redis.expire(k, ttl)
131
145
  end
146
+
147
+ def autos_add( h, id, info, ttl=10 )
148
+ k= autos_key(h)
149
+ Resque.redis.hset(k, id, info.to_json )
150
+ Resque.redis.expire(k, ttl)
151
+ end
132
152
 
133
153
  def workers_rem( h , id )
134
154
  k= workers_key(h)
135
155
  Resque.redis.hdel(k, id)
136
156
  end
137
157
 
158
+ def autos_rem( h , id )
159
+ k= autos_key(h)
160
+ Resque.redis.hdel(k, id)
161
+ end
162
+
138
163
  def tasks_add( h, id, info)
139
164
  k= tasks_key(h)
140
165
  Resque.redis.hset(k, id, info.to_json )
@@ -195,7 +220,8 @@ module Resque
195
220
  # This function must be idempotent
196
221
  def reconcile
197
222
  hosts.each do |h|
198
- tasks(h).each do |id, info|
223
+ tasks(h).each do |tid, info|
224
+ auto= autos_by_id( h, tid )
199
225
  statuses= []
200
226
  pids= []
201
227
  tstatus= info['worker_status'] # Task status
@@ -217,17 +243,22 @@ module Resque
217
243
  "Running"
218
244
  when "STOP"
219
245
  "Stopped"
246
+ when "AUTO"
247
+ "Auto"
220
248
  else
221
249
  "Unknown"
222
250
  end
223
251
  statuses << ws
224
252
  pids << worker['pid'] if worker
225
253
  end
226
- ts= statuses.uniq * ","
227
- if ts!=tstatus #&& (tstatus!="Starting" || wstatus!="STOP")
254
+ ts= if statuses.uniq.length==1 then statuses[0] else statuses * "," end
255
+ mode= auto ? 'Auto' : 'Manual'
256
+ if ts!=tstatus || info['mode']!=mode #&& (tstatus!="Starting" || wstatus!="STOP")
257
+ info['last_changed']= Time.now
228
258
  info['worker_status']= ts
229
259
  info['worker_pid']= pids
230
- tasks_add( h, id, info )
260
+ info['mode']= mode
261
+ tasks_add( h, tid, info )
231
262
  end
232
263
  end
233
264
  end
@@ -236,11 +267,18 @@ module Resque
236
267
  def workers( h )
237
268
  Resque.redis.hgetall(workers_key(h)).collect { |id, info| [id, ActiveSupport::JSON.decode(info)] }
238
269
  end
239
-
240
- def workers_by_id( h, id )
241
- k= workers_key(h)
270
+
271
+ def get_by_id( k, id)
242
272
  info= Resque.redis.hget(k, id)
243
- info ? ActiveSupport::JSON.decode(info) : nil
273
+ info ? ActiveSupport::JSON.decode(info) : nil
274
+ end
275
+
276
+ def autos_by_id( h, id )
277
+ get_by_id( autos_key(h), id)
278
+ end
279
+
280
+ def workers_by_id( h, id )
281
+ get_by_id( workers_key(h), id)
244
282
  end
245
283
 
246
284
  def tasks( h )
@@ -248,15 +286,11 @@ module Resque
248
286
  end
249
287
 
250
288
  def tasks_by_id( h, id )
251
- k= tasks_key(h)
252
- info= Resque.redis.hget(k, id)
253
- info ? ActiveSupport::JSON.decode(info) : nil
289
+ get_by_id( tasks_key(h), id)
254
290
  end
255
291
 
256
292
  def logs_by_id( h, id )
257
- k= logs_key(h)
258
- info= Resque.redis.hget(k, id)
259
- info ? ActiveSupport::JSON.decode(info) : nil
293
+ get_by_id( logs_key(h), id)
260
294
  end
261
295
 
262
296
  def unique_id
@@ -326,6 +360,14 @@ module Resque
326
360
  Resque.redis.keys("#{key_prefix}:*").length
327
361
  end
328
362
 
363
+ def queue_length( q )
364
+ Resque.redis.llen("#{queue_prefix}#{q}")
365
+ end
366
+
367
+ def queue_list
368
+ Resque.redis.smembers("queues")
369
+ end
370
+
329
371
  def fmt_date( t, rel=false ) # This is not redis-specific and should be moved to another class!
330
372
  begin
331
373
  if rel
@@ -0,0 +1,20 @@
1
+ <h1>Task <%= @task_id %></h1>
2
+
3
+ <% task= redis.tasks_by_id( @host, @task_id) %>
4
+
5
+ <form style="margin:0px;float:center;" id="taskm" name="taskm" method="post" action="/resque/telework_mod_task/<%= @task_id %>" >
6
+ <table>
7
+ <tr><th>Parameter</th><th>Value</th><th>Description</th>
8
+ <tr><td>Mode</td><td><%= "#{task['mode']}" %></td><td>Current task mode</td>
9
+ <tr><td>Environment</td><td><%= "#{task['rails_env']}" %></td><td>Rails environment</td>
10
+ <tr><td>Queues</td><td><%= "#{task['queue']}" %></td><td>Queues</td>
11
+ <tr><td>Worker count</td><td><input id="worker_count" name="worker_count" type="number" value="<%= "#{task['worker_count']}" %>" /></td><td>In manual mode, this is the the number of workers started when the task in on<br/>In auto mode, this is the maximum number of workers that can be started</td>
12
+ <tr><td>Exec</td><td><input id="exec" name="exec" size=60 type="text" value="<%= "#{task['exec']}" %>" /></td><td>Command to be executed to start a worker</td>
13
+ <tr><td>Log period</td><td><input id="log_snapshot_period" name="log_snapshot_period" type="number" value="<%= "#{task['log_snapshot_period']}" %>" /></td><td>Period in second between two worker log capture</td>
14
+ <tr><td>Log lines</td><td><input id="log_snapshot_lines" name="log_snapshot_lines" type="number" value="<%= "#{task['log_snapshot_lines']}" %>" /></td><td>Number of lines captured at the end of the worker log</td>
15
+ <tr><td>Auto delay</td><td><input id="auto_delay" name="auto_delay" type="number" value="<%= "#{task['auto_delay']}" %>" /></td><td>Reaction time in second for the auto mode</td>
16
+ <tr><td>Auto job per worker</td><td><input id="auto_max_waiting_job_per_worker" name="auto_max_waiting_job_per_worker" type="number" value="<%= "#{task['auto_max_waiting_job_per_worker']}" %>" /></td><td>Max number of waiting job for earch worker in auto mode</td>
17
+ <tr><td>Auto min worker</td><td><input id="auto_worker_min" name="auto_worker_min" type="number" value="<%= "#{task['auto_worker_min']}" %>" /></td><td>Min number of worker in auto mode</td>
18
+ </table>
19
+ <input type="submit" value= "Save" />
20
+ </form>
@@ -35,7 +35,8 @@ function action(val) {
35
35
  var url = "";
36
36
  switch(params[0]) {
37
37
  case "start":
38
- url = "/start?task="+params[1]+"&host="+params[2]+"&rev="+document.getElementById("rev_filter_"+params[1]).value;
38
+ url = "/start?task="+params[1]+"&host="+params[2]+"&rev="+document.getElementById("rev_filter_"+params[1]).value+
39
+ "&count="+document.getElementById("count_filter_"+params[1]).value;
39
40
  break;
40
41
  case "pause":
41
42
  url = "/pause?task="+params[1]+"&host="+params[2];
@@ -51,7 +52,14 @@ function action(val) {
51
52
  break;
52
53
  case "delete":
53
54
  url = "/delete?task="+params[1]+"&host="+params[2];
54
- break;
55
+ break;
56
+ case "auto":
57
+ url = "/start_auto?task="+params[1]+"&host="+params[2]+"&rev="+document.getElementById("rev_filter_"+params[1]).value+
58
+ "&count="+document.getElementById("count_filter_"+params[1]).value;
59
+ break;
60
+ case "autostop":
61
+ url = "/stop_auto?kill=true&task="+params[1]+"&host="+params[2];
62
+ break;
55
63
  default:
56
64
  return true;
57
65
  }
@@ -87,7 +95,7 @@ $(document).ready(function() {
87
95
  <td><%= "#{redis.fmt_date(note['date'], true)}" %></td>
88
96
  <td><%= note['note'] %></td>
89
97
  <td>
90
- <form id="deln" name="deln" method="post" action="/resque/telework_del_note/<%= id %>" ><input type="submit" value= "Delete" /></form>
98
+ <form style="margin:0px;float:center;" id="deln" name="deln" method="post" action="/resque/telework_del_note/<%= id %>" ><input type="submit" value= "Delete" /></form>
91
99
  </td>
92
100
  </tr>
93
101
  <% end %>
@@ -141,6 +149,11 @@ $(document).ready(function() {
141
149
  <% end %>
142
150
 
143
151
  <h1>Hosts, Revisions, Tasks and Workers</h1>
152
+ <form id="stopping_all" name="stopping_all" method="post" action="/resque/telework/stop_all">
153
+ <%= generic_filter("mode_filter", "m", ["Stop", "Kill"]) %>
154
+ all workers on host <%= generic_filter("host_filter", "h", redis.hosts << "[All hosts]") %>
155
+ <span class="stopping_all_submit"><input type="submit" value="Go"/></span>
156
+ </form></br>
144
157
  <table>
145
158
  <tr>
146
159
  <th>Host</th>
@@ -156,11 +169,9 @@ $(document).ready(function() {
156
169
  <% end %>
157
170
  </td>
158
171
  <% if 'Alive'==status %>
159
- <td> <table><tr><td><%= "Alive (v#{info['version']})" %></td></tr>
160
- <tr><td>
161
- <form id="stopd" name="stopd" method="post" action="/resque/telework_stopitd/<%= host %>" ><input type="submit" value= <%="\"Stop\""%> /></form>
162
- </td></tr>
163
- </table>
172
+ <td> <center><%= "Alive (v#{info['version']})" %><br/><br/>
173
+ <form style="margin:0px;float:center;" id="stopd" name="stopd" method="post" action="/resque/telework_stopitd/<%= host %>" align="middle"><input type="submit" value= <%="\"Stop\""%> /></form></center>
174
+
164
175
  </td>
165
176
  <% else %>
166
177
  <td><%= status%></td>
@@ -191,14 +202,25 @@ $(document).ready(function() {
191
202
  <th>Worker(s)</th>
192
203
  <tr>
193
204
  <% for id, info in redis.tasks(host) %>
205
+ <% auto= info['mode']=='Auto' %>
194
206
  <% status= info['worker_status'] %>
195
207
  <% status= 'Stopped' if status.blank? %>
196
208
  <% running= status!='Stopped' %>
197
209
  <% paused= status=='Paused' %>
198
- <td><%= "<a hreftbd=\"/resque/telework/task/#{host}/#{id}\">#{id}</a>" %>
199
- </td><td><%= "#{status}" %>
200
- </td><td><%= "#{info['queue'].gsub(/,/,'<br/>')}" %>
201
- </td><td><%= "#{info['worker_count']}" %>
210
+ <td><%= "<a href=\"/resque/telework/task/#{host}/#{id}\">#{id}</a>" %>
211
+ </td><td><center><%= auto ? "<b>Auto</b><br/><br/>" : "" %><%= "#{status.gsub(/,/,'<br/>')}" %> </center>
212
+ </td><td><% for q in info['queue'].split(",") %>
213
+ <% if q=="*" %>*
214
+ <% else %> <% ql= redis.queue_length(q) %>
215
+ <% if ql==0 %><font color="grey"><span title="Queue is empty"><%= q %></span></font>
216
+ <% else %><span title="<%= "#{ql} pending jobs" %>" ><%= q %></span>
217
+ <% end %><% end %><br/><% end %>
218
+ </td><td><% if running%><%= "#{info['worker_count']}" %>
219
+ <% else %><select id= <%= "\"count_filter_#{id}\"" %> name="c"/>
220
+ <% for co in (1..8).to_a %>
221
+ <option <%= co==info['worker_count'].to_i ? 'selected="selected"' : '' %>value=<%= "#{co}"%> ><%= co %></option>
222
+ <% end %>
223
+ </select><% end %>
202
224
  </td><td><% if running %><a href= <%= "/resque/telework/revision/#{info['revision']}" %> ><%= "#{info['revision_small']}" %></a>
203
225
  <% else %> <select id= <%= "\"rev_filter_#{id}\"" %> name="r"/>
204
226
  <% for rev in redis.revisions(host) %>
@@ -209,18 +231,20 @@ $(document).ready(function() {
209
231
  </td><td>
210
232
  <select id="action" name="a" onchange="javascript: action(this.options[this.selectedIndex].value);" >
211
233
  <option select="selected" value="select">Select</option>
212
- <option <%= 'disabled="disabled"' if running %> value=<%= "\"start,#{id},#{host}\"" %> >Start worker</option>
213
- <option <%= 'disabled="disabled"' unless running %> value=<%= "\"stop,#{id},#{host}\"" %> >Stop worker</option>
214
- <option <%= 'disabled="disabled"' unless running %> value=<%= "\"kill,#{id},#{host}\"" %> >Kill worker</option>
215
- <option <%= 'disabled="disabled"' if (!running || paused) %> value=<%= "\"pause,#{id},#{host}\"" %> >Pause worker</option>
216
- <option <%= 'disabled="disabled"' unless paused %> value=<%= "\"cont,#{id},#{host}\"" %> >Resume worker</option>
217
- <option <%= 'disabled="disabled"' if running %> value=<%= "\"delete,#{id},#{host}\"" %> >Delete</option>
234
+ <option <%= 'disabled="disabled"' if running || auto %> value=<%= "\"start,#{id},#{host}\"" %> >Start worker</option>
235
+ <option <%= 'disabled="disabled"' unless running && !auto %> value=<%= "\"stop,#{id},#{host}\"" %> >Stop worker</option>
236
+ <option <%= 'disabled="disabled"' unless running && !auto %> value=<%= "\"kill,#{id},#{host}\"" %> >Kill worker</option>
237
+ <option <%= 'disabled="disabled"' if (!running || paused || auto) %> value=<%= "\"pause,#{id},#{host}\"" %> >Pause worker</option>
238
+ <option <%= 'disabled="disabled"' unless paused && !auto %> value=<%= "\"cont,#{id},#{host}\"" %> >Resume worker</option>
239
+ <option <%= 'disabled="disabled"' if auto || running %> value=<%= "\"auto,#{id},#{host}\"" %> >Start auto</option>
240
+ <option <%= 'disabled="disabled"' unless auto %> value=<%= "\"autostop,#{id},#{host}\"" %> >Stop auto</option>
241
+ <option <%= 'disabled="disabled"' if running || auto %> value=<%= "\"delete,#{id},#{host}\"" %> >Delete</option>
218
242
  </select>
219
243
  </td><td align="center"><p align="center"><font size="1">
220
- <% info['worker_id'].zip(info['worker_pid']) do |id,pid| %>
244
+ <% info['worker_id'].zip(info['worker_pid']) do |id,pid| %><% unless pid.blank? %>
221
245
  <%= "<a href=\"/resque/telework/worker/#{host}/#{id}\">Worker #{id}</a>" if running %><br/>
222
246
  <font size=1><% l= "#{host}:#{pid}" %><%= "<a href=\"/resque/workers/#{l}:#{info['queue']}\">#{l}</a>" if running %></font><br/>
223
- <% end %>
247
+ <% end %><% end %>
224
248
  </font></p>
225
249
  </td></tr>
226
250
  <%end%>
@@ -38,7 +38,11 @@ module Resque
38
38
  end
39
39
  html += "</select>"
40
40
  end
41
-
41
+ def task_default
42
+ { 'auto_max_waiting_job_per_worker' => 1,'auto_worker_min' => 0, 'auto_delay' => 15,
43
+ 'log_snapshot_period' => 30, 'log_snapshot_lines' => 40, 'exec' => "bundle exec rake resque:work --trace"
44
+ }
45
+ end
42
46
  end
43
47
 
44
48
  app.get "/#{appn.downcase}" do
@@ -46,8 +50,12 @@ module Resque
46
50
  end
47
51
 
48
52
  app.get "/#{appn.downcase}/Overview" do
49
- @status_messages= 100
50
53
  @refresh= 10
54
+ if params[:refresh]
55
+ @refresh= params[:refresh].to_i
56
+ @refresh= nil if @refresh==0
57
+ end
58
+ @status_messages= 100
51
59
  @scheduling= nil
52
60
  my_show appn.downcase
53
61
  end
@@ -72,6 +80,12 @@ module Resque
72
80
  @host= params[:host]
73
81
  my_show 'worker'
74
82
  end
83
+
84
+ app.get "/#{appn.downcase}/task/:host/:task_id" do
85
+ @task_id= params[:task_id]
86
+ @host= params[:host]
87
+ my_show 'task'
88
+ end
75
89
 
76
90
  app.get "/#{appn.downcase}/config" do
77
91
  content_type :json
@@ -108,6 +122,24 @@ module Resque
108
122
  my_show 'stopit'
109
123
  end
110
124
 
125
+ app.post "/#{appn.downcase}_mod_task/:task" do
126
+ @task_id= params[:task]
127
+ @host= nil
128
+ redis.hosts.each do |h|
129
+ redis.tasks(h).each do |id, info|
130
+ @host= h if id==@task_id # TODO: break nested loops
131
+ end
132
+ end
133
+ @task= redis.tasks_by_id( @host, @task_id )
134
+ all= ['log_snapshot_period', 'log_snapshot_lines', 'exec', 'worker_count',
135
+ 'auto_delay', 'auto_max_waiting_job_per_worker', 'auto_worker_min' ]
136
+ all.each do |a|
137
+ @task[a]= params[a]
138
+ end
139
+ redis.tasks_add( @host , @task_id, @task )
140
+ redirect "/resque/#{appn.downcase}"
141
+ end
142
+
111
143
  app.post "/#{appn.downcase}_killit/:worker" do
112
144
  @worker= params[:worker]
113
145
  @host= nil
@@ -145,12 +177,10 @@ module Resque
145
177
  @env= params[:e]
146
178
  @q= @qmanual.blank? ? @queue : @qmanual
147
179
  id= redis.unique_id.to_s
148
- redis.tasks_add( @host , id, { 'task_id' => id, 'worker_count' => @count,
149
- 'rails_env' => @env, 'queue' => @q,
150
- 'exec' => "bundle exec rake resque:work --trace",
151
- 'worker_id' => [], 'worker_status' => 'Stopped',
152
- 'log_snapshot_period' => 30,
153
- 'log_snapshot_lines' => 40 } )
180
+ t= task_default
181
+ redis.tasks_add( @host , id, t.merge( { 'task_id' => id, 'worker_count' => @count,
182
+ 'rails_env' => @env, 'queue' => @q,
183
+ 'worker_id' => [], 'worker_status' => 'Stopped'} ) )
154
184
  redirect "/resque/#{appn.downcase}"
155
185
  end
156
186
 
@@ -167,7 +197,7 @@ module Resque
167
197
  @host= params[:host]
168
198
  @rev= params[:rev].split(',')
169
199
  @task= redis.tasks_by_id(@host, @task_id)
170
- count= @task['worker_count'] || 1
200
+ count= params[:count]
171
201
  id= []
172
202
  for i in 1..count.to_i do
173
203
  w= @task
@@ -181,10 +211,46 @@ module Resque
181
211
  redis.cmds_push( @host, w )
182
212
  end
183
213
  @task['worker_id']= id
214
+ @task['worker_count']= count
184
215
  redis.tasks_add( @host, @task_id, @task )
185
216
  redirect "/resque/#{appn.downcase}"
186
217
  end
187
218
 
219
+ app.post "/#{appn.downcase}/start_auto" do
220
+ @task_id= params[:task]
221
+ @host= params[:host]
222
+ @rev= params[:rev].split(',')
223
+ @task= redis.tasks_by_id(@host, @task_id)
224
+ count= params[:count]
225
+ wid= []
226
+ for i in 1..count.to_i do
227
+ wid << redis.unique_id.to_s
228
+ #redis.cmds_push( @host, w )
229
+ end
230
+ @task['worker_id']= wid
231
+ @task['worker_count']= count
232
+ @task['mode']= 'auto'
233
+ cmd= @task
234
+ cmd['task_id']= @task_id
235
+ cmd['revision']= @rev[0]
236
+ cmd['revision_small']= @rev[1]
237
+ cmd['command']= 'start_auto'
238
+ redis.cmds_push( @host, cmd )
239
+ redis.tasks_add( @host, @task_id, @task )
240
+ redirect "/resque/#{appn.downcase}"
241
+ end
242
+
243
+ app.post "/#{appn.downcase}/stop_auto" do
244
+ @task_id= params[:task]
245
+ @host= params[:host]
246
+ @task= redis.tasks_by_id(@host, @task_id)
247
+ cmd= @task
248
+ cmd['command']= 'stop_auto'
249
+ redis.cmds_push( @host, cmd )
250
+ redirect "/resque/#{appn.downcase}"
251
+ end
252
+
253
+
188
254
  app.post "/#{appn.downcase}/pause" do
189
255
  @task_id= params[:task]
190
256
  @host= params[:host]
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 2
8
- - 1
9
- version: 0.2.1
7
+ - 3
8
+ - 0
9
+ version: 0.3.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Gilles Pirio
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2013-02-04 00:00:00 -08:00
17
+ date: 2013-02-22 00:00:00 -08:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -68,6 +68,7 @@ files:
68
68
  - lib/resque-telework/server/views/misc.erb
69
69
  - lib/resque-telework/server/views/revision.erb
70
70
  - lib/resque-telework/server/views/stopit.erb
71
+ - lib/resque-telework/server/views/task.erb
71
72
  - lib/resque-telework/server/views/telework.erb
72
73
  - lib/resque-telework/server/views/worker.erb
73
74
  - lib/tasks/telework.rake