documentcloud-cloud-crowd 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/README +59 -50
- data/actions/process_pdfs.rb +3 -3
- data/actions/word_count.rb +14 -0
- data/cloud-crowd.gemspec +27 -13
- data/config/config.example.yml +8 -11
- data/examples/graphics_magick_example.rb +40 -44
- data/examples/process_pdfs_example.rb +39 -29
- data/examples/word_count_example.rb +41 -0
- data/lib/cloud-crowd.rb +20 -17
- data/lib/cloud_crowd/action.rb +26 -9
- data/lib/cloud_crowd/app.rb +26 -4
- data/lib/cloud_crowd/asset_store.rb +69 -40
- data/lib/cloud_crowd/command_line.rb +6 -4
- data/lib/cloud_crowd/daemon.rb +65 -25
- data/lib/cloud_crowd/exceptions.rb +5 -0
- data/lib/cloud_crowd/helpers/resources.rb +2 -2
- data/lib/cloud_crowd/models/job.rb +9 -13
- data/lib/cloud_crowd/models/work_unit.rb +23 -15
- data/lib/cloud_crowd/models/worker_record.rb +61 -0
- data/lib/cloud_crowd/models.rb +7 -1
- data/lib/cloud_crowd/schema.rb +12 -3
- data/lib/cloud_crowd/worker.rb +48 -10
- data/public/css/admin_console.css +174 -4
- data/public/css/reset.css +17 -27
- data/public/images/bullet_green.png +0 -0
- data/public/images/bullet_white.png +0 -0
- data/public/images/cloud_hand.png +0 -0
- data/public/images/header_back.png +0 -0
- data/public/images/logo.png +0 -0
- data/public/images/server_error.png +0 -0
- data/public/images/sidebar_bottom.png +0 -0
- data/public/images/sidebar_top.png +0 -0
- data/public/images/worker_info.png +0 -0
- data/public/images/worker_info_loading.gif +0 -0
- data/public/js/admin_console.js +127 -10
- data/public/js/excanvas.pack.js +1 -0
- data/public/js/jquery-1.3.2.min.js +19 -0
- data/public/js/jquery.flot.pack.js +1 -0
- data/test/acceptance/test_word_count.rb +49 -0
- data/test/blueprints.rb +6 -5
- data/test/config/config.yml +1 -4
- data/test/test_helper.rb +1 -0
- data/test/unit/test_job.rb +12 -4
- data/test/unit/test_work_unit.rb +2 -2
- data/views/index.erb +69 -14
- metadata +23 -6
- data/public/js/jquery-1.3.2.js +0 -4376
data/lib/cloud_crowd/worker.rb
CHANGED
@@ -5,10 +5,17 @@ module CloudCrowd
|
|
5
5
|
# units that they are able to handle (for which they have an action in their
|
6
6
|
# actions directory). If communication with the central server is interrupted,
|
7
7
|
# the WorkUnit will repeatedly attempt to complete its unit -- every
|
8
|
-
#
|
8
|
+
# Worker::RETRY_WAIT seconds. Any exceptions that take place during
|
9
9
|
# the course of the Action will cause the Worker to mark the WorkUnit as
|
10
10
|
# having failed.
|
11
11
|
class Worker
|
12
|
+
|
13
|
+
# The time between worker check-ins with the central server, informing
|
14
|
+
# it of the current status, and simply that it's still alive.
|
15
|
+
CHECK_IN_INTERVAL = 60
|
16
|
+
|
17
|
+
# Wait five seconds to retry, after internal communcication errors.
|
18
|
+
RETRY_WAIT = 5
|
12
19
|
|
13
20
|
attr_reader :action
|
14
21
|
|
@@ -18,6 +25,7 @@ module CloudCrowd
|
|
18
25
|
def initialize
|
19
26
|
@id = $$
|
20
27
|
@hostname = Socket.gethostname
|
28
|
+
@name = "#{@id}@#{@hostname}"
|
21
29
|
@store = AssetStore.new
|
22
30
|
@server = CloudCrowd.central_server
|
23
31
|
@enabled_actions = CloudCrowd.actions.keys
|
@@ -37,7 +45,7 @@ module CloudCrowd
|
|
37
45
|
keep_trying_to "complete work unit" do
|
38
46
|
data = completion_params.merge({:status => 'succeeded', :output => result})
|
39
47
|
unit_json = @server["/work/#{data[:id]}"].put(data)
|
40
|
-
log "finished #{
|
48
|
+
log "finished #{display_work_unit} in #{data[:time]} seconds"
|
41
49
|
clear_work_unit
|
42
50
|
setup_work_unit(unit_json)
|
43
51
|
end
|
@@ -46,14 +54,37 @@ module CloudCrowd
|
|
46
54
|
# Mark the current work unit as failed, returning the exception to central.
|
47
55
|
def fail_work_unit(exception)
|
48
56
|
keep_trying_to "mark work unit as failed" do
|
49
|
-
data = completion_params.merge({:status => 'failed', :output => exception.message})
|
57
|
+
data = completion_params.merge({:status => 'failed', :output => {'output' => exception.message}.to_json})
|
50
58
|
unit_json = @server["/work/#{data[:id]}"].put(data)
|
51
|
-
log "failed #{
|
59
|
+
log "failed #{display_work_unit} in #{data[:time]} seconds\n#{exception.message}\n#{exception.backtrace}"
|
52
60
|
clear_work_unit
|
53
61
|
setup_work_unit(unit_json)
|
54
62
|
end
|
55
63
|
end
|
56
64
|
|
65
|
+
# Check in with the central server. Let it know the condition of the work
|
66
|
+
# thread, the action and status we're processing, and our hostname and PID.
|
67
|
+
def check_in(thread_status)
|
68
|
+
keep_trying_to "check in with central" do
|
69
|
+
@server["/worker"].put({
|
70
|
+
:name => @name,
|
71
|
+
:thread_status => thread_status
|
72
|
+
})
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Inform the central server that this worker is finished. This is the only
|
77
|
+
# remote method that doesn't retry on connection errors -- if the worker
|
78
|
+
# can't connect to the central server while it's trying to shutdown, it
|
79
|
+
# should close, regardless.
|
80
|
+
def check_out
|
81
|
+
@server["/worker"].put({
|
82
|
+
:name => @name,
|
83
|
+
:terminated => true
|
84
|
+
})
|
85
|
+
log 'exiting'
|
86
|
+
end
|
87
|
+
|
57
88
|
# We expect and require internal communication between the central server
|
58
89
|
# and the workers to succeed. If it fails for any reason, log it, and then
|
59
90
|
# keep trying the same request.
|
@@ -61,11 +92,10 @@ module CloudCrowd
|
|
61
92
|
begin
|
62
93
|
yield
|
63
94
|
rescue Exception => e
|
64
|
-
|
65
|
-
log "failed to #{title} -- retry in #{wait_time} seconds"
|
95
|
+
log "failed to #{title} -- retry in #{RETRY_WAIT} seconds"
|
66
96
|
log e.message
|
67
97
|
log e.backtrace
|
68
|
-
sleep
|
98
|
+
sleep RETRY_WAIT
|
69
99
|
retry
|
70
100
|
end
|
71
101
|
end
|
@@ -75,6 +105,11 @@ module CloudCrowd
|
|
75
105
|
@action_name && @input && @options
|
76
106
|
end
|
77
107
|
|
108
|
+
# Loggable string of the current work unit.
|
109
|
+
def display_work_unit
|
110
|
+
"unit ##{@options['work_unit_id']} (#{@action_name})"
|
111
|
+
end
|
112
|
+
|
78
113
|
# Executes the current work unit, catching all exceptions as failures.
|
79
114
|
def run_work_unit
|
80
115
|
begin
|
@@ -103,7 +138,10 @@ module CloudCrowd
|
|
103
138
|
|
104
139
|
# Common parameters to send back to central.
|
105
140
|
def base_params
|
106
|
-
@base_params ||= {
|
141
|
+
@base_params ||= {
|
142
|
+
:worker_name => @name,
|
143
|
+
:worker_actions => @enabled_actions.join(',')
|
144
|
+
}
|
107
145
|
end
|
108
146
|
|
109
147
|
# Common parameters to send back to central upon unit completion,
|
@@ -124,13 +162,13 @@ module CloudCrowd
|
|
124
162
|
@options['job_id'] = unit['job_id']
|
125
163
|
@options['work_unit_id'] = unit['id']
|
126
164
|
@options['attempts'] ||= unit['attempts']
|
127
|
-
log "fetched
|
165
|
+
log "fetched #{display_work_unit}"
|
128
166
|
return true
|
129
167
|
end
|
130
168
|
|
131
169
|
# Log a message to the daemon log. Includes PID for identification.
|
132
170
|
def log(message)
|
133
|
-
puts "Worker ##{@id}: #{message}"
|
171
|
+
puts "Worker ##{@id}: #{message}" unless ENV['RACK_ENV'] == 'test'
|
134
172
|
end
|
135
173
|
|
136
174
|
# When we're done with a unit, clear out our instance variables to make way
|
@@ -1,21 +1,74 @@
|
|
1
1
|
body {
|
2
|
-
background: #
|
2
|
+
background: #979797;
|
3
3
|
font-family: Arial;
|
4
4
|
font-size: 12px;
|
5
|
+
color: #252525;
|
5
6
|
}
|
6
7
|
|
8
|
+
.small_caps {
|
9
|
+
font: 11px Arial, sans-serif;
|
10
|
+
text-transform: uppercase;
|
11
|
+
}
|
12
|
+
|
13
|
+
#header {
|
14
|
+
height: 110px;
|
15
|
+
position: absolute;
|
16
|
+
top: 0; left: 0; right: 0;
|
17
|
+
background: url(/images/header_back.png);
|
18
|
+
}
|
19
|
+
#logo {
|
20
|
+
position: absolute;
|
21
|
+
left: 37px; top: 9px;
|
22
|
+
width: 236px; height: 91px;
|
23
|
+
background: url(/images/logo.png);
|
24
|
+
}
|
25
|
+
|
26
|
+
#disconnected {
|
27
|
+
position: absolute;
|
28
|
+
top: 122px; right: 15px;
|
29
|
+
background: #7f7f7f;
|
30
|
+
color: #333;
|
31
|
+
border: 1px solid #555;
|
32
|
+
font-size: 10px;
|
33
|
+
line-height: 18px;
|
34
|
+
padding: 3px 4px 1px 4px;
|
35
|
+
-moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px;
|
36
|
+
}
|
37
|
+
#disconnected .server_error {
|
38
|
+
float: left;
|
39
|
+
width: 16px; height: 16px;
|
40
|
+
background: url(/images/server_error.png);
|
41
|
+
opacity: 0.7;
|
42
|
+
margin-right: 3px;
|
43
|
+
}
|
44
|
+
|
7
45
|
#queue {
|
8
46
|
position: absolute;
|
9
|
-
top:
|
47
|
+
top: 16px; left: 327px; right: 15px;
|
10
48
|
height: 77px;
|
49
|
+
overflow: hidden;
|
11
50
|
}
|
51
|
+
#no_jobs {
|
52
|
+
text-align: left;
|
53
|
+
position: absolute;
|
54
|
+
bottom: 8px; right: 8px;
|
55
|
+
color: #999;
|
56
|
+
display: none;
|
57
|
+
}
|
58
|
+
#queue.no_jobs #no_jobs {
|
59
|
+
display: block;
|
60
|
+
}
|
12
61
|
#queue_fill {
|
13
62
|
position: absolute;
|
14
|
-
|
63
|
+
left: 0; right: 0; top: 0;
|
64
|
+
height: 75px;
|
15
65
|
border: 1px solid #5c5c5c;
|
16
|
-
-moz-border-radius: 10px; -webkit-border-radius: 10px;
|
66
|
+
-moz-border-radius: 10px; -webkit-border-radius: 10px; border-radius: 10px;
|
17
67
|
background: transparent url(/images/queue_fill.png) repeat-x 0px -1px;
|
18
68
|
}
|
69
|
+
#queue.no_jobs #queue_fill {
|
70
|
+
opacity: 0.3;
|
71
|
+
}
|
19
72
|
#queue .job {
|
20
73
|
position: relative;
|
21
74
|
margin-top: 1px;
|
@@ -36,6 +89,9 @@ body {
|
|
36
89
|
opacity: 0.5;
|
37
90
|
overflow: hidden;
|
38
91
|
}
|
92
|
+
#queue .completion.zero {
|
93
|
+
border: 0;
|
94
|
+
}
|
39
95
|
#queue .percent_complete {
|
40
96
|
position: absolute;
|
41
97
|
bottom: 8px; left: 8px;
|
@@ -48,4 +104,118 @@ body {
|
|
48
104
|
position: absolute;
|
49
105
|
top: 8px; left: 8px;
|
50
106
|
z-index: 10;
|
107
|
+
}
|
108
|
+
|
109
|
+
#sidebar {
|
110
|
+
position: absolute;
|
111
|
+
top: 120px; left: 10px; bottom: 10px;
|
112
|
+
width: 300px;
|
113
|
+
overflow: hidden;
|
114
|
+
}
|
115
|
+
#sidebar_background {
|
116
|
+
position: absolute;
|
117
|
+
top: 21px; bottom: 21px;
|
118
|
+
width: 298px;
|
119
|
+
background: #e0e0e0;
|
120
|
+
border: 1px solid #8b8b8b;
|
121
|
+
border-top: 0; border-bottom: 0;
|
122
|
+
}
|
123
|
+
.sidebar_back {
|
124
|
+
position: absolute;
|
125
|
+
height: 21px; width: 300px;
|
126
|
+
}
|
127
|
+
#sidebar_top {
|
128
|
+
top: 0px;
|
129
|
+
background: url(/images/sidebar_top.png);
|
130
|
+
}
|
131
|
+
#sidebar_bottom {
|
132
|
+
bottom: 0px;
|
133
|
+
background: url(/images/sidebar_bottom.png);
|
134
|
+
}
|
135
|
+
#sidebar_header {
|
136
|
+
position: absolute;
|
137
|
+
top: 5px; left: 8px;
|
138
|
+
color: #404040;
|
139
|
+
text-shadow: 0px 1px 1px #eee;
|
140
|
+
}
|
141
|
+
#sidebar_header.no_workers .no_workers,
|
142
|
+
#sidebar_header .has_workers {
|
143
|
+
display: block;
|
144
|
+
}
|
145
|
+
#sidebar_header .no_workers,
|
146
|
+
#sidebar_header.no_workers .has_workers {
|
147
|
+
display: none;
|
148
|
+
}
|
149
|
+
#workers {
|
150
|
+
position: absolute;
|
151
|
+
padding: 2px 0;
|
152
|
+
top: 21px; left: 0; bottom: 21px;
|
153
|
+
width: 298px;
|
154
|
+
overflow-y: auto; overflow-x: hidden;
|
155
|
+
}
|
156
|
+
#workers .worker {
|
157
|
+
border: 1px solid transparent;
|
158
|
+
margin: 1px 7px;
|
159
|
+
padding-left: 18px;
|
160
|
+
font-size: 11px;
|
161
|
+
line-height: 22px;
|
162
|
+
background: url(/images/bullet_white.png) no-repeat left center;
|
163
|
+
cursor: pointer;
|
164
|
+
}
|
165
|
+
#workers .worker.processing,
|
166
|
+
#workers .worker.splitting,
|
167
|
+
#workers .worker.merging {
|
168
|
+
background: url(/images/bullet_green.png) no-repeat left center;
|
169
|
+
}
|
170
|
+
#workers .worker:hover {
|
171
|
+
border: 1px solid #aaa;
|
172
|
+
border-radius: 4px; -moz-border-radius: 4px; -webkit-border-radius: 4px;
|
173
|
+
background-color: #ccc;
|
174
|
+
}
|
175
|
+
|
176
|
+
#worker_info {
|
177
|
+
position: absolute;
|
178
|
+
width: 231px; height: 79px;
|
179
|
+
margin: -9px 0 0 -20px;
|
180
|
+
background: url(/images/worker_info.png);
|
181
|
+
overflow: hidden;
|
182
|
+
cursor: pointer;
|
183
|
+
}
|
184
|
+
#worker_info_inner {
|
185
|
+
margin: 15px 15px 15px 32px;
|
186
|
+
line-height: 15px;
|
187
|
+
color: #333;
|
188
|
+
text-shadow: 0px 1px 1px #eee;
|
189
|
+
}
|
190
|
+
#worker_info.loading #worker_info_inner {
|
191
|
+
background: url(/images/worker_info_loading.gif) no-repeat right bottom;
|
192
|
+
width: 45px; height: 9px;
|
193
|
+
}
|
194
|
+
#worker_info.awake #worker_details,
|
195
|
+
#worker_sleeping {
|
196
|
+
display: block;
|
197
|
+
}
|
198
|
+
#worker_details, #worker_info.loading #worker_details,
|
199
|
+
#worker_info.loading #worker_sleeping, #worker_info.awake #worker_sleeping {
|
200
|
+
display: none;
|
201
|
+
}
|
202
|
+
|
203
|
+
#graphs {
|
204
|
+
position: absolute;
|
205
|
+
padding: 17px 15px 15px 17px;
|
206
|
+
top: 110px; left: 310px; right: 0px; bottom: 0;
|
207
|
+
overflow: hidden;
|
208
|
+
overflow-y: auto;
|
209
|
+
}
|
210
|
+
.graph_container {
|
211
|
+
margin-bottom: 25px;
|
212
|
+
}
|
213
|
+
.graph_title {
|
214
|
+
color: #333;
|
215
|
+
font-size: 16px;
|
216
|
+
text-shadow: 0px 1px 1px #eee;
|
217
|
+
margin-bottom: 10px;
|
218
|
+
}
|
219
|
+
.graph {
|
220
|
+
height: 150px;
|
51
221
|
}
|
data/public/css/reset.css
CHANGED
@@ -10,43 +10,33 @@ a, abbr, acronym, address, big, cite, code,
|
|
10
10
|
del, dfn, em, font, img, ins, kbd, q, s, samp,
|
11
11
|
small, strike, strong, sub, sup, tt, var,
|
12
12
|
dl, dt, dd, ol, ul, li,
|
13
|
-
fieldset, form, label, legend
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
vertical-align: baseline;
|
13
|
+
fieldset, form, label, legend {
|
14
|
+
margin: 0;
|
15
|
+
padding: 0;
|
16
|
+
border: 0;
|
17
|
+
outline: 0;
|
18
|
+
font-weight: inherit;
|
19
|
+
font-style: inherit;
|
20
|
+
font-size: 100%;
|
21
|
+
font-family: inherit;
|
22
|
+
vertical-align: baseline;
|
24
23
|
}
|
25
24
|
/* remember to define focus styles! */
|
26
25
|
:focus {
|
27
|
-
|
26
|
+
outline: 0;
|
28
27
|
}
|
29
28
|
body {
|
30
|
-
|
31
|
-
|
32
|
-
|
29
|
+
line-height: 1;
|
30
|
+
color: black;
|
31
|
+
background: white;
|
33
32
|
}
|
34
33
|
ol, ul {
|
35
|
-
|
36
|
-
}
|
37
|
-
/* tables still need 'cellspacing="0"' in the markup */
|
38
|
-
table {
|
39
|
-
border-collapse: separate;
|
40
|
-
border-spacing: 0;
|
41
|
-
}
|
42
|
-
caption, th, td {
|
43
|
-
text-align: left;
|
44
|
-
font-weight: normal;
|
34
|
+
list-style: none;
|
45
35
|
}
|
46
36
|
blockquote:before, blockquote:after,
|
47
37
|
q:before, q:after {
|
48
|
-
|
38
|
+
content: "";
|
49
39
|
}
|
50
40
|
blockquote, q {
|
51
|
-
|
41
|
+
quotes: "" "";
|
52
42
|
}
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/public/js/admin_console.js
CHANGED
@@ -1,33 +1,86 @@
|
|
1
|
+
// The Console handles all of the Admin interaction with the active workers
|
2
|
+
// and job queue.
|
3
|
+
//
|
1
4
|
// Think about pulling in the DCJS framework, instead of just raw jQuery here.
|
5
|
+
// Leaving it hacked together like this just cries out for templates, dunnit?
|
2
6
|
window.Console = {
|
3
7
|
|
8
|
+
// Maximum number of data points to record and graph.
|
9
|
+
MAX_DATA_POINTS : 100,
|
10
|
+
|
11
|
+
// Milliseconds between polling the central server for updates to Job progress.
|
4
12
|
POLL_INTERVAL : 3000,
|
5
13
|
|
14
|
+
// Default speed for all animations.
|
6
15
|
ANIMATION_SPEED : 300,
|
7
16
|
|
17
|
+
// Keep this in sync with the map in cloud-crowd.rb
|
18
|
+
DISPLAY_STATUS_MAP : ['unknown', 'processing', 'succeeded', 'failed', 'splitting', 'merging'],
|
19
|
+
|
20
|
+
// Images to preload
|
21
|
+
PRELOAD_IMAGES : ['/images/server_error.png'],
|
22
|
+
|
23
|
+
// All options for drawing the system graphs.
|
24
|
+
GRAPH_OPTIONS : {
|
25
|
+
xaxis : {mode : 'time', timeformat : '%M:%S'},
|
26
|
+
yaxis : {tickDecimals : 0},
|
27
|
+
legend : {show : false},
|
28
|
+
grid : {backgroundColor : '#7f7f7f', color : '#555', tickColor : '#666', borderWidth : 2}
|
29
|
+
},
|
30
|
+
JOBS_COLOR : '#db3a0f',
|
31
|
+
WORKERS_COLOR : '#a1003d',
|
32
|
+
WORK_UNITS_COLOR : '#ffba14',
|
33
|
+
|
34
|
+
// Starting the console begins polling the server.
|
8
35
|
initialize : function() {
|
36
|
+
this._jobsHistory = [];
|
37
|
+
this._workersHistory = [];
|
38
|
+
this._workUnitsHistory = [];
|
39
|
+
this._histories = [this._jobsHistory, this._workersHistory, this._workUnitsHistory];
|
9
40
|
this._queue = $('#jobs');
|
10
|
-
this.
|
41
|
+
this._workerInfo = $('#worker_info');
|
42
|
+
this._disconnected = $('#disconnected');
|
43
|
+
$(window).bind('resize', Console.renderGraphs);
|
44
|
+
$('#workers .worker').live('click', Console.getWorkerInfo);
|
45
|
+
this.getStatus();
|
46
|
+
$.each(this.PRELOAD_IMAGES, function(){ var i = new Image(); i.src = this; });
|
11
47
|
},
|
12
48
|
|
13
|
-
|
14
|
-
|
15
|
-
|
49
|
+
// Request the lastest status of all jobs and workers, re-render or update
|
50
|
+
// the DOM to reflect.
|
51
|
+
getStatus : function() {
|
52
|
+
$.ajax({url : '/status', dataType : 'json', success : function(resp) {
|
53
|
+
Console._jobs = resp.jobs;
|
54
|
+
Console._workers = resp.workers;
|
55
|
+
Console._workUnitCount = resp.work_unit_count;
|
56
|
+
Console.recordDataPoint();
|
57
|
+
if (Console._disconnected.is(':visible')) Console._disconnected.fadeOut(Console.ANIMATION_SPEED);
|
58
|
+
$('#queue').toggleClass('no_jobs', Console._jobs.length <= 0);
|
16
59
|
Console.renderJobs();
|
17
|
-
|
18
|
-
|
60
|
+
Console.renderWorkers();
|
61
|
+
Console.renderGraphs();
|
62
|
+
setTimeout(Console.getStatus, Console.POLL_INTERVAL);
|
63
|
+
}, error : function(request, status, errorThrown) {
|
64
|
+
if (!Console._disconnected.is(':visible')) Console._disconnected.fadeIn(Console.ANIMATION_SPEED);
|
65
|
+
setTimeout(Console.getStatus, Console.POLL_INTERVAL);
|
66
|
+
}});
|
19
67
|
},
|
20
68
|
|
69
|
+
// Render an individual job afresh.
|
21
70
|
renderJob : function(job) {
|
22
|
-
this._queue.
|
71
|
+
this._queue.append('<div class="job" id="job_' + job.id + '" style="width:' + job.width + '%; background: #' + job.color + ';"><div class="completion ' + (job.percent_complete <= 0 ? 'zero' : '') + '" style="width:' + job.percent_complete + '%;"></div><div class="percent_complete">' + job.percent_complete + '%</div><div class="job_id">#' + job.id + '</div></div>');
|
23
72
|
},
|
24
73
|
|
74
|
+
// Animate the update to an existing job in the queue.
|
25
75
|
updateJob : function(job, jobEl) {
|
26
76
|
jobEl.animate({width : job.width + '%'}, this.ANIMATION_SPEED);
|
27
|
-
$('.completion', jobEl)
|
77
|
+
var completion = $('.completion', jobEl);
|
78
|
+
if (job.percent_complete > 0) completion.removeClass('zero');
|
79
|
+
completion.animate({width : job.percent_complete + '%'}, this.ANIMATION_SPEED);
|
28
80
|
$('.percent_complete', jobEl).html(job.percent_complete + '%');
|
29
81
|
},
|
30
82
|
|
83
|
+
// Render all jobs, calculating relative widths and completions.
|
31
84
|
renderJobs : function() {
|
32
85
|
var totalUnits = 0;
|
33
86
|
var totalWidth = this._queue.width();
|
@@ -36,14 +89,78 @@ window.Console = {
|
|
36
89
|
jobIds.push(this.id);
|
37
90
|
totalUnits += this.work_units;
|
38
91
|
});
|
92
|
+
$.each($('.job'), function() {
|
93
|
+
var el = this;
|
94
|
+
if (jobIds.indexOf(parseInt(el.id.replace(/\D/g, ''), 10)) < 0) {
|
95
|
+
$(el).animate({width : '0%'}, Console.ANIMATION_SPEED - 50, 'linear', function() {
|
96
|
+
$(el).remove();
|
97
|
+
});
|
98
|
+
}
|
99
|
+
});
|
39
100
|
$.each(this._jobs, function() {
|
40
101
|
this.width = (this.work_units / totalUnits) * 100;
|
41
102
|
var jobEl = $('#job_' + this.id);
|
42
103
|
jobEl[0] ? Console.updateJob(this, jobEl) : Console.renderJob(this);
|
43
104
|
});
|
44
|
-
|
45
|
-
|
105
|
+
},
|
106
|
+
|
107
|
+
// Re-render all workers from scratch each time.
|
108
|
+
renderWorkers : function() {
|
109
|
+
var header = $('#sidebar_header');
|
110
|
+
$('.has_workers', header).html(this._workers.length + " Active Worker Daemons");
|
111
|
+
header.toggleClass('no_workers', this._workers.length <= 0);
|
112
|
+
$('#workers').html($.map(this._workers, function(w) {
|
113
|
+
return '<div class="worker ' + w.status + '" rel="' + w.name + '">' + w.name + '</div>';
|
114
|
+
}).join(''));
|
115
|
+
},
|
116
|
+
|
117
|
+
// Record the current state and re-render all graphs.
|
118
|
+
recordDataPoint : function() {
|
119
|
+
var timestamp = (new Date()).getTime();
|
120
|
+
this._jobsHistory.push([timestamp, this._jobs.length]);
|
121
|
+
this._workersHistory.push([timestamp, this._workers.length]);
|
122
|
+
this._workUnitsHistory.push([timestamp, this._workUnitCount]);
|
123
|
+
$.each(this._histories, function() {
|
124
|
+
if (this.length > Console.MAX_DATA_POINTS) this.shift();
|
46
125
|
});
|
126
|
+
},
|
127
|
+
|
128
|
+
// Convert our recorded data points into a format Flot can understand.
|
129
|
+
renderGraphs : function() {
|
130
|
+
$.plot($('#jobs_graph'), [{label : 'Jobs in Queue', color : Console.JOBS_COLOR, data : Console._jobsHistory}], Console.GRAPH_OPTIONS);
|
131
|
+
$.plot($('#workers_graph'), [{label : 'Active Workers', color : Console.WORKERS_COLOR, data : Console._workersHistory}], Console.GRAPH_OPTIONS);
|
132
|
+
$.plot($('#work_units_graph'), [{label : 'Work Units in Queue', color : Console.WORK_UNITS_COLOR, data : Console._workUnitsHistory}], Console.GRAPH_OPTIONS);
|
133
|
+
},
|
134
|
+
|
135
|
+
// Request the Worker info from the central server.
|
136
|
+
getWorkerInfo : function(e) {
|
137
|
+
e.stopImmediatePropagation();
|
138
|
+
var info = Console._workerInfo;
|
139
|
+
var row = $(this);
|
140
|
+
info.addClass('loading');
|
141
|
+
$.get('/worker/' + row.attr('rel'), null, Console.renderWorkerInfo, 'json');
|
142
|
+
info.css({top : row.offset().top, left : 325});
|
143
|
+
info.fadeIn(Console.ANIMATION_SPEED);
|
144
|
+
$(document).bind('click', Console.hideWorkerInfo);
|
145
|
+
return false;
|
146
|
+
},
|
147
|
+
|
148
|
+
// When we receieve worker info, update the bubble.
|
149
|
+
renderWorkerInfo : function(resp) {
|
150
|
+
var info = Console._workerInfo;
|
151
|
+
info.toggleClass('awake', !!resp.status);
|
152
|
+
info.removeClass('loading');
|
153
|
+
if (!resp.status) return;
|
154
|
+
$('.status', info).html(Console.DISPLAY_STATUS_MAP[resp.status]);
|
155
|
+
$('.action', info).html(resp.action);
|
156
|
+
$('.job_id', info).html(resp.job_id);
|
157
|
+
$('.work_unit_id', info).html(resp.id);
|
158
|
+
},
|
159
|
+
|
160
|
+
// Hide worker info and unbind the global hide handler.
|
161
|
+
hideWorkerInfo : function() {
|
162
|
+
$(document).unbind('click', Console.hideWorkerInfo);
|
163
|
+
Console._workerInfo.fadeOut(Console.ANIMATION_SPEED);
|
47
164
|
}
|
48
165
|
|
49
166
|
};
|
@@ -0,0 +1 @@
|
|
1
|
+
if(!window.CanvasRenderingContext2D){(function(){var m=Math;var mr=m.round;var ms=m.sin;var mc=m.cos;var Z=10;var Z2=Z/2;var G_vmlCanvasManager_={init:function(opt_doc){var doc=opt_doc||document;if(/MSIE/.test(navigator.userAgent)&&!window.opera){var self=this;doc.attachEvent("onreadystatechange",function(){self.init_(doc)})}},init_:function(doc){if(doc.readyState=="complete"){if(!doc.namespaces["g_vml_"]){doc.namespaces.add("g_vml_","urn:schemas-microsoft-com:vml")}var ss=doc.createStyleSheet();ss.cssText="canvas{display:inline-block;overflow:hidden;"+"text-align:left;width:300px;height:150px}"+"g_vml_\\:*{behavior:url(#default#VML)}";var els=doc.getElementsByTagName("canvas");for(var i=0;i<els.length;i++){if(!els[i].getContext){this.initElement(els[i])}}}},fixElement_:function(el){var outerHTML=el.outerHTML;var newEl=el.ownerDocument.createElement(outerHTML);if(outerHTML.slice(-2)!="/>"){var tagName="/"+el.tagName;var ns;while((ns=el.nextSibling)&&ns.tagName!=tagName){ns.removeNode()}if(ns){ns.removeNode()}}el.parentNode.replaceChild(newEl,el);return newEl},initElement:function(el){el=this.fixElement_(el);el.getContext=function(){if(this.context_){return this.context_}return this.context_=new CanvasRenderingContext2D_(this)};el.attachEvent('onpropertychange',onPropertyChange);el.attachEvent('onresize',onResize);var attrs=el.attributes;if(attrs.width&&attrs.width.specified){el.style.width=attrs.width.nodeValue+"px"}else{el.width=el.clientWidth}if(attrs.height&&attrs.height.specified){el.style.height=attrs.height.nodeValue+"px"}else{el.height=el.clientHeight}return el}};function onPropertyChange(e){var el=e.srcElement;switch(e.propertyName){case'width':el.style.width=el.attributes.width.nodeValue+"px";el.getContext().clearRect();break;case'height':el.style.height=el.attributes.height.nodeValue+"px";el.getContext().clearRect();break}}function onResize(e){var el=e.srcElement;if(el.firstChild){el.firstChild.style.width=el.clientWidth+'px';el.firstChild.style.height=el.clientHeight+'px'}}G_vmlCanvasManager_.init();var dec2hex=[];for(var i=0;i<16;i++){for(var j=0;j<16;j++){dec2hex[i*16+j]=i.toString(16)+j.toString(16)}}function createMatrixIdentity(){return[[1,0,0],[0,1,0],[0,0,1]]}function matrixMultiply(m1,m2){var result=createMatrixIdentity();for(var x=0;x<3;x++){for(var y=0;y<3;y++){var sum=0;for(var z=0;z<3;z++){sum+=m1[x][z]*m2[z][y]}result[x][y]=sum}}return result}function copyState(o1,o2){o2.fillStyle=o1.fillStyle;o2.lineCap=o1.lineCap;o2.lineJoin=o1.lineJoin;o2.lineWidth=o1.lineWidth;o2.miterLimit=o1.miterLimit;o2.shadowBlur=o1.shadowBlur;o2.shadowColor=o1.shadowColor;o2.shadowOffsetX=o1.shadowOffsetX;o2.shadowOffsetY=o1.shadowOffsetY;o2.strokeStyle=o1.strokeStyle;o2.arcScaleX_=o1.arcScaleX_;o2.arcScaleY_=o1.arcScaleY_}function processStyle(styleString){var str,alpha=1;styleString=String(styleString);if(styleString.substring(0,3)=="rgb"){var start=styleString.indexOf("(",3);var end=styleString.indexOf(")",start+1);var guts=styleString.substring(start+1,end).split(",");str="#";for(var i=0;i<3;i++){str+=dec2hex[Number(guts[i])]}if((guts.length==4)&&(styleString.substr(3,1)=="a")){alpha=guts[3]}}else{str=styleString}return[str,alpha]}function processLineCap(lineCap){switch(lineCap){case"butt":return"flat";case"round":return"round";case"square":default:return"square"}}function CanvasRenderingContext2D_(surfaceElement){this.m_=createMatrixIdentity();this.mStack_=[];this.aStack_=[];this.currentPath_=[];this.strokeStyle="#000";this.fillStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=Z*1;this.globalAlpha=1;this.canvas=surfaceElement;var el=surfaceElement.ownerDocument.createElement('div');el.style.width=surfaceElement.clientWidth+'px';el.style.height=surfaceElement.clientHeight+'px';el.style.overflow='hidden';el.style.position='absolute';surfaceElement.appendChild(el);this.element_=el;this.arcScaleX_=1;this.arcScaleY_=1}var contextPrototype=CanvasRenderingContext2D_.prototype;contextPrototype.clearRect=function(){this.element_.innerHTML="";this.currentPath_=[]};contextPrototype.beginPath=function(){this.currentPath_=[]};contextPrototype.moveTo=function(aX,aY){this.currentPath_.push({type:"moveTo",x:aX,y:aY});this.currentX_=aX;this.currentY_=aY};contextPrototype.lineTo=function(aX,aY){this.currentPath_.push({type:"lineTo",x:aX,y:aY});this.currentX_=aX;this.currentY_=aY};contextPrototype.bezierCurveTo=function(aCP1x,aCP1y,aCP2x,aCP2y,aX,aY){this.currentPath_.push({type:"bezierCurveTo",cp1x:aCP1x,cp1y:aCP1y,cp2x:aCP2x,cp2y:aCP2y,x:aX,y:aY});this.currentX_=aX;this.currentY_=aY};contextPrototype.quadraticCurveTo=function(aCPx,aCPy,aX,aY){var cp1x=this.currentX_+2.0/3.0*(aCPx-this.currentX_);var cp1y=this.currentY_+2.0/3.0*(aCPy-this.currentY_);var cp2x=cp1x+(aX-this.currentX_)/3.0;var cp2y=cp1y+(aY-this.currentY_)/3.0;this.bezierCurveTo(cp1x,cp1y,cp2x,cp2y,aX,aY)};contextPrototype.arc=function(aX,aY,aRadius,aStartAngle,aEndAngle,aClockwise){aRadius*=Z;var arcType=aClockwise?"at":"wa";var xStart=aX+(mc(aStartAngle)*aRadius)-Z2;var yStart=aY+(ms(aStartAngle)*aRadius)-Z2;var xEnd=aX+(mc(aEndAngle)*aRadius)-Z2;var yEnd=aY+(ms(aEndAngle)*aRadius)-Z2;if(xStart==xEnd&&!aClockwise){xStart+=0.125}this.currentPath_.push({type:arcType,x:aX,y:aY,radius:aRadius,xStart:xStart,yStart:yStart,xEnd:xEnd,yEnd:yEnd})};contextPrototype.rect=function(aX,aY,aWidth,aHeight){this.moveTo(aX,aY);this.lineTo(aX+aWidth,aY);this.lineTo(aX+aWidth,aY+aHeight);this.lineTo(aX,aY+aHeight);this.closePath()};contextPrototype.strokeRect=function(aX,aY,aWidth,aHeight){this.beginPath();this.moveTo(aX,aY);this.lineTo(aX+aWidth,aY);this.lineTo(aX+aWidth,aY+aHeight);this.lineTo(aX,aY+aHeight);this.closePath();this.stroke()};contextPrototype.fillRect=function(aX,aY,aWidth,aHeight){this.beginPath();this.moveTo(aX,aY);this.lineTo(aX+aWidth,aY);this.lineTo(aX+aWidth,aY+aHeight);this.lineTo(aX,aY+aHeight);this.closePath();this.fill()};contextPrototype.createLinearGradient=function(aX0,aY0,aX1,aY1){var gradient=new CanvasGradient_("gradient");return gradient};contextPrototype.createRadialGradient=function(aX0,aY0,aR0,aX1,aY1,aR1){var gradient=new CanvasGradient_("gradientradial");gradient.radius1_=aR0;gradient.radius2_=aR1;gradient.focus_.x=aX0;gradient.focus_.y=aY0;return gradient};contextPrototype.drawImage=function(image,var_args){var dx,dy,dw,dh,sx,sy,sw,sh;var oldRuntimeWidth=image.runtimeStyle.width;var oldRuntimeHeight=image.runtimeStyle.height;image.runtimeStyle.width='auto';image.runtimeStyle.height='auto';var w=image.width;var h=image.height;image.runtimeStyle.width=oldRuntimeWidth;image.runtimeStyle.height=oldRuntimeHeight;if(arguments.length==3){dx=arguments[1];dy=arguments[2];sx=sy=0;sw=dw=w;sh=dh=h}else if(arguments.length==5){dx=arguments[1];dy=arguments[2];dw=arguments[3];dh=arguments[4];sx=sy=0;sw=w;sh=h}else if(arguments.length==9){sx=arguments[1];sy=arguments[2];sw=arguments[3];sh=arguments[4];dx=arguments[5];dy=arguments[6];dw=arguments[7];dh=arguments[8]}else{throw"Invalid number of arguments";}var d=this.getCoords_(dx,dy);var w2=sw/2;var h2=sh/2;var vmlStr=[];var W=10;var H=10;vmlStr.push(' <g_vml_:group',' coordsize="',Z*W,',',Z*H,'"',' coordorigin="0,0"',' style="width:',W,';height:',H,';position:absolute;');if(this.m_[0][0]!=1||this.m_[0][1]){var filter=[];filter.push("M11='",this.m_[0][0],"',","M12='",this.m_[1][0],"',","M21='",this.m_[0][1],"',","M22='",this.m_[1][1],"',","Dx='",mr(d.x/Z),"',","Dy='",mr(d.y/Z),"'");var max=d;var c2=this.getCoords_(dx+dw,dy);var c3=this.getCoords_(dx,dy+dh);var c4=this.getCoords_(dx+dw,dy+dh);max.x=Math.max(max.x,c2.x,c3.x,c4.x);max.y=Math.max(max.y,c2.y,c3.y,c4.y);vmlStr.push("padding:0 ",mr(max.x/Z),"px ",mr(max.y/Z),"px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",filter.join(""),", sizingmethod='clip');")}else{vmlStr.push("top:",mr(d.y/Z),"px;left:",mr(d.x/Z),"px;")}vmlStr.push(' ">','<g_vml_:image src="',image.src,'"',' style="width:',Z*dw,';',' height:',Z*dh,';"',' cropleft="',sx/w,'"',' croptop="',sy/h,'"',' cropright="',(w-sx-sw)/w,'"',' cropbottom="',(h-sy-sh)/h,'"',' />','</g_vml_:group>');this.element_.insertAdjacentHTML("BeforeEnd",vmlStr.join(""))};contextPrototype.stroke=function(aFill){var lineStr=[];var lineOpen=false;var a=processStyle(aFill?this.fillStyle:this.strokeStyle);var color=a[0];var opacity=a[1]*this.globalAlpha;var W=10;var H=10;lineStr.push('<g_vml_:shape',' fillcolor="',color,'"',' filled="',Boolean(aFill),'"',' style="position:absolute;width:',W,';height:',H,';"',' coordorigin="0 0" coordsize="',Z*W,' ',Z*H,'"',' stroked="',!aFill,'"',' strokeweight="',this.lineWidth,'"',' strokecolor="',color,'"',' path="');var newSeq=false;var min={x:null,y:null};var max={x:null,y:null};for(var i=0;i<this.currentPath_.length;i++){var p=this.currentPath_[i];if(p.type=="moveTo"){lineStr.push(" m ");var c=this.getCoords_(p.x,p.y);lineStr.push(mr(c.x),",",mr(c.y))}else if(p.type=="lineTo"){lineStr.push(" l ");var c=this.getCoords_(p.x,p.y);lineStr.push(mr(c.x),",",mr(c.y))}else if(p.type=="close"){lineStr.push(" x ")}else if(p.type=="bezierCurveTo"){lineStr.push(" c ");var c=this.getCoords_(p.x,p.y);var c1=this.getCoords_(p.cp1x,p.cp1y);var c2=this.getCoords_(p.cp2x,p.cp2y);lineStr.push(mr(c1.x),",",mr(c1.y),",",mr(c2.x),",",mr(c2.y),",",mr(c.x),",",mr(c.y))}else if(p.type=="at"||p.type=="wa"){lineStr.push(" ",p.type," ");var c=this.getCoords_(p.x,p.y);var cStart=this.getCoords_(p.xStart,p.yStart);var cEnd=this.getCoords_(p.xEnd,p.yEnd);lineStr.push(mr(c.x-this.arcScaleX_*p.radius),",",mr(c.y-this.arcScaleY_*p.radius)," ",mr(c.x+this.arcScaleX_*p.radius),",",mr(c.y+this.arcScaleY_*p.radius)," ",mr(cStart.x),",",mr(cStart.y)," ",mr(cEnd.x),",",mr(cEnd.y))}if(c){if(min.x==null||c.x<min.x){min.x=c.x}if(max.x==null||c.x>max.x){max.x=c.x}if(min.y==null||c.y<min.y){min.y=c.y}if(max.y==null||c.y>max.y){max.y=c.y}}}lineStr.push(' ">');if(typeof this.fillStyle=="object"){var focus={x:"50%",y:"50%"};var width=(max.x-min.x);var height=(max.y-min.y);var dimension=(width>height)?width:height;focus.x=mr((this.fillStyle.focus_.x/width)*100+50)+"%";focus.y=mr((this.fillStyle.focus_.y/height)*100+50)+"%";var colors=[];if(this.fillStyle.type_=="gradientradial"){var inside=(this.fillStyle.radius1_/dimension*100);var expansion=(this.fillStyle.radius2_/dimension*100)-inside}else{var inside=0;var expansion=100}var insidecolor={offset:null,color:null};var outsidecolor={offset:null,color:null};this.fillStyle.colors_.sort(function(cs1,cs2){return cs1.offset-cs2.offset});for(var i=0;i<this.fillStyle.colors_.length;i++){var fs=this.fillStyle.colors_[i];colors.push((fs.offset*expansion)+inside,"% ",fs.color,",");if(fs.offset>insidecolor.offset||insidecolor.offset==null){insidecolor.offset=fs.offset;insidecolor.color=fs.color}if(fs.offset<outsidecolor.offset||outsidecolor.offset==null){outsidecolor.offset=fs.offset;outsidecolor.color=fs.color}}colors.pop();lineStr.push('<g_vml_:fill',' color="',outsidecolor.color,'"',' color2="',insidecolor.color,'"',' type="',this.fillStyle.type_,'"',' focusposition="',focus.x,', ',focus.y,'"',' colors="',colors.join(""),'"',' opacity="',opacity,'" />')}else if(aFill){lineStr.push('<g_vml_:fill color="',color,'" opacity="',opacity,'" />')}else{lineStr.push('<g_vml_:stroke',' opacity="',opacity,'"',' joinstyle="',this.lineJoin,'"',' miterlimit="',this.miterLimit,'"',' endcap="',processLineCap(this.lineCap),'"',' weight="',this.lineWidth,'px"',' color="',color,'" />')}lineStr.push("</g_vml_:shape>");this.element_.insertAdjacentHTML("beforeEnd",lineStr.join(""))};contextPrototype.fill=function(){this.stroke(true)};contextPrototype.closePath=function(){this.currentPath_.push({type:"close"})};contextPrototype.getCoords_=function(aX,aY){return{x:Z*(aX*this.m_[0][0]+aY*this.m_[1][0]+this.m_[2][0])-Z2,y:Z*(aX*this.m_[0][1]+aY*this.m_[1][1]+this.m_[2][1])-Z2}};contextPrototype.save=function(){var o={};copyState(this,o);this.aStack_.push(o);this.mStack_.push(this.m_);this.m_=matrixMultiply(createMatrixIdentity(),this.m_)};contextPrototype.restore=function(){copyState(this.aStack_.pop(),this);this.m_=this.mStack_.pop()};contextPrototype.translate=function(aX,aY){var m1=[[1,0,0],[0,1,0],[aX,aY,1]];this.m_=matrixMultiply(m1,this.m_)};contextPrototype.rotate=function(aRot){var c=mc(aRot);var s=ms(aRot);var m1=[[c,s,0],[-s,c,0],[0,0,1]];this.m_=matrixMultiply(m1,this.m_)};contextPrototype.scale=function(aX,aY){this.arcScaleX_*=aX;this.arcScaleY_*=aY;var m1=[[aX,0,0],[0,aY,0],[0,0,1]];this.m_=matrixMultiply(m1,this.m_)};contextPrototype.clip=function(){};contextPrototype.arcTo=function(){};contextPrototype.createPattern=function(){return new CanvasPattern_};function CanvasGradient_(aType){this.type_=aType;this.radius1_=0;this.radius2_=0;this.colors_=[];this.focus_={x:0,y:0}}CanvasGradient_.prototype.addColorStop=function(aOffset,aColor){aColor=processStyle(aColor);this.colors_.push({offset:1-aOffset,color:aColor})};function CanvasPattern_(){}G_vmlCanvasManager=G_vmlCanvasManager_;CanvasRenderingContext2D=CanvasRenderingContext2D_;CanvasGradient=CanvasGradient_;CanvasPattern=CanvasPattern_})()}
|