documentcloud-cloud-crowd 0.0.5 → 0.0.6
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.
- 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_})()}
|