pampa 2.0.28 → 2.0.30
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/pampa/app.rb +194 -0
- data/lib/pampa/dispatcher.rb +133 -0
- data/lib/pampa/worker.rb +172 -0
- data/lib/pampa.rb +42 -398
- data/pampa.gemspec +43 -0
- metadata +66 -23
- data/worker.rb +0 -147
data/lib/pampa.rb
CHANGED
@@ -1,104 +1,21 @@
|
|
1
|
-
require 'sequel'
|
2
1
|
require 'blackstack-core'
|
2
|
+
require 'blackstack-db'
|
3
3
|
require 'blackstack-nodes'
|
4
|
-
require 'blackstack-deployer'
|
5
4
|
require 'simple_command_line_parser'
|
6
5
|
require 'simple_cloud_logging'
|
6
|
+
require 'colorize'
|
7
|
+
require 'sinatra'
|
7
8
|
|
8
9
|
module BlackStack
|
9
10
|
module Pampa
|
10
|
-
# activate this flag if you want to add pampa nodes to blackstack-deployer.
|
11
|
-
@@integrate_with_blackstack_deployer = false
|
12
|
-
# setup custom locations for config and worker files.
|
13
|
-
@@config_filename = "config.rb"
|
14
|
-
@@worker_filename = "worker.rb"
|
15
|
-
# setu the directory where the worker.rb file will be lauched, and the log files will be stored.
|
16
|
-
@@working_directory = "$HOME/pampa"
|
17
11
|
# arrays of workers, nodes, and jobs.
|
18
12
|
@@nodes = []
|
19
13
|
@@jobs = []
|
20
|
-
# logger configuration
|
21
|
-
@@log_filename = nil
|
22
14
|
@@logger = BlackStack::DummyLogger.new(nil)
|
23
|
-
# Connection string to the database. Example: mysql2://user:password@localhost:3306/database
|
24
|
-
@@connection_string = nil
|
25
|
-
|
26
|
-
def self.now()
|
27
|
-
tz = 'America/Argentina/Buenos_Aires' #DB["SELECT current_setting('TIMEZONE') AS tz"].first[:tz]
|
28
|
-
DB["SELECT current_timestamp at TIME ZONE '#{tz}' AS now"].first[:now]
|
29
|
-
end
|
30
|
-
|
31
|
-
# @@integrate_with_blackstack_deployer
|
32
|
-
def self.integrate_with_blackstack_deployer()
|
33
|
-
@@integrate_with_blackstack_deployer
|
34
|
-
end
|
35
|
-
|
36
|
-
def self.set_integrate_with_blackstack_deployer(b)
|
37
|
-
@@integrate_with_blackstack_deployer = b
|
38
|
-
end
|
39
|
-
|
40
|
-
# @@config_filename
|
41
|
-
def self.config_filename()
|
42
|
-
@@config_filename
|
43
|
-
end
|
44
|
-
|
45
|
-
def self.set_config_filename(s)
|
46
|
-
@@config_filename = s
|
47
|
-
end
|
48
|
-
|
49
|
-
# @@worker_filename
|
50
|
-
def self.worker_filename()
|
51
|
-
@@worker_filename
|
52
|
-
end
|
53
|
-
|
54
|
-
def self.set_worker_filename(s)
|
55
|
-
@@worker_filename = s
|
56
|
-
end
|
57
|
-
|
58
|
-
## @@working_directory
|
59
|
-
def self.working_directory()
|
60
|
-
@@working_directory
|
61
|
-
end
|
62
|
-
|
63
|
-
def self.set_working_directory(s)
|
64
|
-
@@working_directory = s
|
65
|
-
end
|
66
|
-
|
67
|
-
# define a filename for the log file.
|
68
|
-
def self.set_log_filename(s)
|
69
|
-
@@log_filename = s
|
70
|
-
@@logger = BlackStack::LocalLogger.new(s)
|
71
|
-
end
|
72
|
-
|
73
|
-
# return the logger.
|
74
|
-
def self.logger()
|
75
|
-
@@logger
|
76
|
-
end
|
77
|
-
|
78
|
-
def self.set_logger(l)
|
79
|
-
@@logger = l
|
80
|
-
end
|
81
|
-
|
82
|
-
# return the log filename.
|
83
|
-
def self.log_filename()
|
84
|
-
@@log_filename
|
85
|
-
end
|
86
|
-
|
87
|
-
# define a connection string to the database.
|
88
|
-
def self.set_connection_string(s)
|
89
|
-
@@connection_string = s
|
90
|
-
end
|
91
|
-
|
92
|
-
# return connection string to the database. Example: mysql2://user:password@localhost:3306/database
|
93
|
-
def self.connection_string()
|
94
|
-
@@connection_string
|
95
|
-
end
|
96
15
|
|
97
16
|
# add a node to the cluster.
|
98
17
|
def self.add_node(h)
|
99
18
|
@@nodes << BlackStack::Pampa::Node.new(h)
|
100
|
-
# add to deployer
|
101
|
-
BlackStack::Deployer.add_node(h) if @@integrate_with_blackstack_deployer
|
102
19
|
end # def self.add_node(h)
|
103
20
|
|
104
21
|
# add an array of nodes to the cluster.
|
@@ -119,7 +36,7 @@ module BlackStack
|
|
119
36
|
|
120
37
|
# return the array of all workers, beloning all nodes.
|
121
38
|
def self.workers()
|
122
|
-
|
39
|
+
@@nodes.map { |node| node.workers }.flatten
|
123
40
|
end
|
124
41
|
|
125
42
|
# add a job to the cluster.
|
@@ -143,6 +60,15 @@ module BlackStack
|
|
143
60
|
@@jobs
|
144
61
|
end
|
145
62
|
|
63
|
+
# get and set logger
|
64
|
+
def self.logger()
|
65
|
+
@@logger
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.set_logger(l)
|
69
|
+
@@logger = l
|
70
|
+
end
|
71
|
+
|
146
72
|
# get attached and unassigned workers.
|
147
73
|
# assign and unassign workers to jobs.
|
148
74
|
#
|
@@ -151,70 +77,63 @@ module BlackStack
|
|
151
77
|
# - worker: relative path of the worker.rb file. Example: '../worker.rb'
|
152
78
|
#
|
153
79
|
def self.stretch()
|
154
|
-
# validate: the connection string is not nil
|
155
|
-
raise "The connection string is nil" if @@connection_string.nil?
|
156
|
-
# validate: the connection string is not empty
|
157
|
-
raise "The connection string is empty" if @@connection_string.empty?
|
158
|
-
# validate: the connection string is not blank
|
159
|
-
raise "The connection string is blank" if @@connection_string.strip.empty?
|
160
80
|
# getting logger
|
161
81
|
l = self.logger()
|
162
82
|
# get the job this worker is working with
|
163
83
|
BlackStack::Pampa.jobs.each { |job|
|
164
|
-
l.log ''
|
165
84
|
l.logs "job #{job.name}... "
|
166
85
|
# get attached and unassigned workers
|
167
86
|
l.logs "Getting attached and unassigned workers... "
|
168
87
|
workers = BlackStack::Pampa.workers.select { |w| w.attached && w.assigned_job.nil? }
|
169
|
-
l.logf
|
88
|
+
l.logf 'done'.green + " (#{workers.size.to_s.blue})"
|
170
89
|
# get the workers that match the filter
|
171
90
|
l.logs "Getting workers that match the filter... "
|
172
91
|
workers = workers.select { |w| w.id =~ job.filter_worker_id }
|
173
|
-
l.logf "done (#{workers.size.to_s})"
|
92
|
+
l.logf "done".green + " (#{workers.size.to_s.blue})"
|
174
93
|
# if theere are workers
|
175
94
|
if workers.size > 0
|
176
95
|
l.logs("Gettting assigned workers... ")
|
177
96
|
assigned = BlackStack::Pampa.workers.select { |worker| worker.attached && worker.assigned_job.to_s == job.name.to_s }
|
178
|
-
l.logf
|
97
|
+
l.logf "done ".green + " (#{assigned.size.to_s.blue})"
|
179
98
|
|
180
99
|
l.logs("Getting total pending (pending) tasks... ")
|
181
100
|
pendings = job.pending
|
182
|
-
l.logf
|
101
|
+
l.logf "done".green + " (#{pendings.to_s.blue})"
|
183
102
|
|
184
103
|
l.logs("0 pending tasks?.... ")
|
185
104
|
if pendings.size == 0
|
186
|
-
l.logf
|
105
|
+
l.logf "yes".green
|
187
106
|
|
188
107
|
l.logs("Unassigning all assigned workers... ")
|
189
108
|
assigned.each { |w|
|
190
109
|
l.logs("Unassigning worker... ")
|
191
110
|
w.assigned_job = nil
|
192
111
|
workers << w # add worker back to the list of unassigned
|
193
|
-
l.logf "done (#{w.id})"
|
112
|
+
l.logf "done".green + " (#{w.id.to_s.blue})"
|
194
113
|
}
|
195
114
|
l.done
|
196
115
|
else
|
197
|
-
l.logf
|
116
|
+
l.logf "no".red
|
198
117
|
|
199
118
|
l.logs("Under :max_pending_tasks (#{job.max_pending_tasks}) and more than 1 assigned workers ?... ")
|
200
119
|
if pendings.size < job.max_pending_tasks && assigned.size > 1
|
201
|
-
l.logf
|
120
|
+
l.logf "yes".green
|
202
121
|
|
203
122
|
while assigned.size > 1
|
204
123
|
l.logs("Unassigning worker... ")
|
205
124
|
w = assigned.pop # TODO: find a worker with no pending tasks
|
206
125
|
w.assigned_job = nil
|
207
126
|
workers << w # add worker back to the array of unassigned workers
|
208
|
-
l.logf "done (#{w.id})"
|
127
|
+
l.logf "done".green + " (#{w.id.to_s.blue})"
|
209
128
|
end
|
210
129
|
else
|
211
|
-
l.logf
|
130
|
+
l.logf "no".red
|
212
131
|
|
213
|
-
l.logs("Over :max_assigned_workers (#{job.max_assigned_workers}) and more than 1 assigned workers?... ")
|
132
|
+
l.logs("Over :max_assigned_workers (#{job.max_assigned_workers.to_s.blue}) and more than 1 assigned workers?... ")
|
214
133
|
if assigned.size >= job.max_assigned_workers && assigned.size > 1
|
215
|
-
l.logf("yes")
|
134
|
+
l.logf("yes".green)
|
216
135
|
else
|
217
|
-
l.logf("no")
|
136
|
+
l.logf("no".red)
|
218
137
|
|
219
138
|
i = assigned.size
|
220
139
|
while i < job.max_assigned_workers
|
@@ -222,11 +141,11 @@ module BlackStack
|
|
222
141
|
l.logs("Assigning worker... ")
|
223
142
|
w = workers.pop
|
224
143
|
if w.nil?
|
225
|
-
l.logf("no more workers")
|
144
|
+
l.logf("no more workers".yellow)
|
226
145
|
break
|
227
146
|
else
|
228
147
|
w.assigned_job = job.name.to_sym
|
229
|
-
l.logf "done (#{w.id})"
|
148
|
+
l.logf "done".green + " (#{w.id.to_s.blue})"
|
230
149
|
end
|
231
150
|
end # while i < job.max_assigned_workers
|
232
151
|
end # if assigned.size >= job.max_assigned_workers && assigned.size > 0
|
@@ -235,7 +154,7 @@ module BlackStack
|
|
235
154
|
end # if workers.size > 0
|
236
155
|
l.done
|
237
156
|
}
|
238
|
-
end
|
157
|
+
end # def self.stretch()
|
239
158
|
|
240
159
|
# iterate the jobs.
|
241
160
|
# for each job, get all the tasks to relaunch.
|
@@ -246,12 +165,6 @@ module BlackStack
|
|
246
165
|
# - worker: relative path of the worker.rb file. Example: '../worker.rb'
|
247
166
|
#
|
248
167
|
def self.relaunch(n=10000)
|
249
|
-
# validate: the connection string is not nil
|
250
|
-
raise "The connection string is nil" if @@connection_string.nil?
|
251
|
-
# validate: the connection string is not empty
|
252
|
-
raise "The connection string is empty" if @@connection_string.empty?
|
253
|
-
# validate: the connection string is not blank
|
254
|
-
raise "The connection string is blank" if @@connection_string.strip.empty?
|
255
168
|
# getting logger
|
256
169
|
l = self.logger()
|
257
170
|
# iterate the workers
|
@@ -259,7 +172,7 @@ module BlackStack
|
|
259
172
|
l.logs("job:#{job.name}... ")
|
260
173
|
l.logs("Gettting tasks to relaunch (max #{n})... ")
|
261
174
|
tasks = job.relaunching(n)
|
262
|
-
l.logf("done (#{tasks.size.to_s})")
|
175
|
+
l.logf("done".green + " (#{tasks.size.to_s.blue})")
|
263
176
|
|
264
177
|
tasks.each { |task|
|
265
178
|
l.logs("Relaunching task #{task[job.field_primary_key.to_sym]}... ")
|
@@ -269,7 +182,7 @@ module BlackStack
|
|
269
182
|
|
270
183
|
l.done
|
271
184
|
}
|
272
|
-
end
|
185
|
+
end # def self.relaunch(n=10000)
|
273
186
|
|
274
187
|
# iterate the workers.
|
275
188
|
# for each worker, iterate the job.
|
@@ -279,245 +192,28 @@ module BlackStack
|
|
279
192
|
# - worker: relative path of the worker.rb file. Example: '../worker.rb'
|
280
193
|
#
|
281
194
|
def self.dispatch()
|
282
|
-
# validate: the connection string is not nil
|
283
|
-
raise "The connection string is nil" if @@connection_string.nil?
|
284
|
-
# validate: the connection string is not empty
|
285
|
-
raise "The connection string is empty" if @@connection_string.empty?
|
286
|
-
# validate: the connection string is not blank
|
287
|
-
raise "The connection string is blank" if @@connection_string.strip.empty?
|
288
195
|
# getting logger
|
289
196
|
l = self.logger()
|
290
197
|
# iterate the workers
|
291
198
|
BlackStack::Pampa.workers.each { |worker|
|
292
199
|
l.logs("worker:#{worker.id} (job:#{worker.assigned_job.to_s})... ")
|
293
200
|
if !worker.attached
|
294
|
-
l.logf("detached")
|
201
|
+
l.logf("detached".green)
|
295
202
|
else
|
296
203
|
if worker.assigned_job.nil?
|
297
|
-
l.logf("unassigned")
|
204
|
+
l.logf("unassigned".yellow)
|
298
205
|
else
|
299
206
|
# get the job this worker is assigned to
|
300
207
|
job = BlackStack::Pampa.jobs.select { |j| j.name.to_s == worker.assigned_job.to_s }.first
|
301
208
|
if job.nil?
|
302
|
-
l.logf("job #{job.name} not found")
|
209
|
+
l.logf("job #{job.name} not found".red)
|
303
210
|
else
|
304
|
-
l.logf("done (#{job.run_dispatch(worker).to_s})")
|
211
|
+
l.logf("done".green + " (#{job.run_dispatch(worker).to_s.blue})")
|
305
212
|
end
|
306
213
|
end
|
307
214
|
end
|
308
215
|
} # @@nodes.each do |node|
|
309
|
-
end
|
310
|
-
|
311
|
-
# connect the nodes via ssh.
|
312
|
-
# kill all Ruby processes except this one.
|
313
|
-
# rename any existing folder $HOME/pampa to $HOME/pampa.<current timestamp>.
|
314
|
-
# create a new folder $HOME/pampa.
|
315
|
-
# build the file $HOME/pampa/config.rb in the remote node.
|
316
|
-
# copy the file $HOME/pampa/worker.rb to the remote node.
|
317
|
-
# run the number of workers specified in the configuration of the Pampa module.
|
318
|
-
# return an array with the IDs of the workers.
|
319
|
-
#
|
320
|
-
# Parameters:
|
321
|
-
# - config: relative path of the configuration file. Example: '../config.rb'
|
322
|
-
# - worker: relative path of the worker.rb file. Example: '../worker.rb'
|
323
|
-
#
|
324
|
-
def self.deploy()
|
325
|
-
# validate: the connection string is not nil
|
326
|
-
raise "The connection string is nil" if @@connection_string.nil?
|
327
|
-
# validate: the connection string is not empty
|
328
|
-
raise "The connection string is empty" if @@connection_string.empty?
|
329
|
-
# validate: the connection string is not blank
|
330
|
-
raise "The connection string is blank" if @@connection_string.strip.empty?
|
331
|
-
# getting logger
|
332
|
-
l = self.logger()
|
333
|
-
# iterate the nodes
|
334
|
-
@@nodes.each { |node|
|
335
|
-
l.logs("node:#{node.name()}... ")
|
336
|
-
# connect the node
|
337
|
-
l.logs("Connecting... ")
|
338
|
-
node.connect()
|
339
|
-
l.done
|
340
|
-
# kill all ruby processes except this one
|
341
|
-
l.logs("Killing all Ruby processes except this one... ")
|
342
|
-
node.kill_workers()
|
343
|
-
l.done
|
344
|
-
# rename any existing folder ~/code/pampa to ~/code/pampa.<current timestamp>.
|
345
|
-
l.logs("Renaming old folder... ")
|
346
|
-
node.exec("mv #{BlackStack::Pampa.working_directory} #{BlackStack::Pampa.working_directory}.#{Time.now().to_i.to_s}", false);
|
347
|
-
l.done
|
348
|
-
# create a new folder ~/code. - ignore if it already exists.
|
349
|
-
l.logs("Creating new folder... ")
|
350
|
-
node.exec("mkdir #{BlackStack::Pampa.working_directory}", false);
|
351
|
-
l.done
|
352
|
-
# build the file $HOME/pampa/config.rb in the remote node. - Be sure the BlackStack::Pampa.to_hash.to_s don't have single-quotes (') in the string.
|
353
|
-
l.logs("Building config file... ")
|
354
|
-
s = "echo \"#{File.read(config_filename)}\" > #{BlackStack::Pampa.working_directory}/#{BlackStack::Pampa.config_filename}"
|
355
|
-
node.exec("#{s}", false);
|
356
|
-
l.done
|
357
|
-
# copy the file $HOME/pampa/worker.rb to the remote node. - Be sure the script don't have single-quotes (') in the string.
|
358
|
-
l.logs("Copying worker file... ")
|
359
|
-
s = "echo \"#{File.read(worker_filename)}\" > #{BlackStack::Pampa.working_directory}/#{BlackStack::Pampa.worker_filename}"
|
360
|
-
node.exec("#{s}", false);
|
361
|
-
l.done
|
362
|
-
# run the number of workers specified in the configuration of the Pampa module.
|
363
|
-
node.workers.each { |worker|
|
364
|
-
# run the worker
|
365
|
-
# add these parameters for debug: debug=yes pampa=~/code/pampa/lib/pampa.rb
|
366
|
-
l.logs "Running worker #{worker.id}... "
|
367
|
-
|
368
|
-
# write bash command to initialize bash file
|
369
|
-
s = "echo \"
|
370
|
-
export RUBYLIB=$HOME/code/mysaas;
|
371
|
-
source $HOME/.profile;
|
372
|
-
source /usr/local/rvm/scripts/rvm;
|
373
|
-
cd ~/code/mysaas; rvm install 3.1.2;
|
374
|
-
rvm --default use 3.1.2;
|
375
|
-
cd #{BlackStack::Pampa.working_directory};
|
376
|
-
nohup ruby #{worker_filename} id=#{worker.id} config=#{self.config_filename} >/dev/null 2>&1 &
|
377
|
-
\" > #{BlackStack::Pampa.working_directory}/#{worker.id}.sh"
|
378
|
-
node.exec(s, false);
|
379
|
-
|
380
|
-
#s = "nohup bash #{BlackStack::Pampa.working_directory}/worker.sh >/dev/null 2>&1 &"
|
381
|
-
s = "bash #{BlackStack::Pampa.working_directory}/#{worker.id}.sh"
|
382
|
-
node.exec(s, false);
|
383
|
-
|
384
|
-
l.done
|
385
|
-
}
|
386
|
-
# disconnect the node
|
387
|
-
l.logs("Disconnecting... ")
|
388
|
-
node.disconnect()
|
389
|
-
l.done
|
390
|
-
l.done
|
391
|
-
} # @@nodes.each do |node|
|
392
|
-
end
|
393
|
-
|
394
|
-
# connect the nodes via ssh.
|
395
|
-
# kill all Ruby processes except this one.
|
396
|
-
# run the number of workers specified in the configuration of the Pampa module.
|
397
|
-
# return an array with the IDs of the workers.
|
398
|
-
#
|
399
|
-
def self.start()
|
400
|
-
# validate: the connection string is not nil
|
401
|
-
raise "The connection string is nil" if @@connection_string.nil?
|
402
|
-
# validate: the connection string is not empty
|
403
|
-
raise "The connection string is empty" if @@connection_string.empty?
|
404
|
-
# validate: the connection string is not blank
|
405
|
-
raise "The connection string is blank" if @@connection_string.strip.empty?
|
406
|
-
# getting logger
|
407
|
-
l = self.logger()
|
408
|
-
# iterate the nodes
|
409
|
-
@@nodes.each { |node|
|
410
|
-
l.logs("node:#{node.name()}... ")
|
411
|
-
# connect the node
|
412
|
-
l.logs("Connecting... ")
|
413
|
-
node.connect()
|
414
|
-
l.done
|
415
|
-
# kill all ruby processes except this one
|
416
|
-
l.logs("Killing all Ruby processes except this one... ")
|
417
|
-
node.kill_workers()
|
418
|
-
l.done
|
419
|
-
# run the number of workers specified in the configuration of the Pampa module.
|
420
|
-
node.workers.each { |worker|
|
421
|
-
# run the worker
|
422
|
-
# add these parameters for debug: debug=yes pampa=~/code/pampa/lib/pampa.rb
|
423
|
-
# run a bash command that sources the .profile file and runs the ruby script in the background, returning immediatelly.
|
424
|
-
|
425
|
-
l.logs "Running worker #{worker.id}... "
|
426
|
-
|
427
|
-
# write bash command to initialize bash file
|
428
|
-
s = "echo \"
|
429
|
-
export RUBYLIB=$HOME/code/mysaas;
|
430
|
-
source $HOME/.profile;
|
431
|
-
source /usr/local/rvm/scripts/rvm;
|
432
|
-
cd ~/code/mysaas; rvm install 3.1.2;
|
433
|
-
rvm --default use 3.1.2;
|
434
|
-
cd #{BlackStack::Pampa.working_directory};
|
435
|
-
nohup ruby #{worker_filename} id=#{worker.id} config=#{self.config_filename} >/dev/null 2>&1 &
|
436
|
-
\" > #{BlackStack::Pampa.working_directory}/#{worker.id}.sh"
|
437
|
-
node.exec(s, false);
|
438
|
-
s = "nohup bash #{BlackStack::Pampa.working_directory}/#{worker.id}.sh >/dev/null 2>&1 &"
|
439
|
-
node.exec(s, false);
|
440
|
-
|
441
|
-
l.done
|
442
|
-
}
|
443
|
-
# disconnect the node
|
444
|
-
l.logs("Disconnecting... ")
|
445
|
-
node.disconnect()
|
446
|
-
l.done
|
447
|
-
l.done
|
448
|
-
} # @@nodes.each do |node|
|
449
|
-
end
|
450
|
-
|
451
|
-
# connect the nodes via ssh.
|
452
|
-
# kill all Ruby processes except this one.
|
453
|
-
#
|
454
|
-
# Parameters:
|
455
|
-
# - config: relative path of the configuration file. Example: '../config.rb'
|
456
|
-
#
|
457
|
-
def self.stop()
|
458
|
-
# validate: the connection string is not nil
|
459
|
-
raise "The connection string is nil" if @@connection_string.nil?
|
460
|
-
# validate: the connection string is not empty
|
461
|
-
raise "The connection string is empty" if @@connection_string.empty?
|
462
|
-
# validate: the connection string is not blank
|
463
|
-
raise "The connection string is blank" if @@connection_string.strip.empty?
|
464
|
-
# getting logger
|
465
|
-
l = self.logger()
|
466
|
-
# iterate the nodes
|
467
|
-
@@nodes.each { |node|
|
468
|
-
l.logs("node:#{node.name()}... ")
|
469
|
-
# connect the node
|
470
|
-
l.logs("Connecting... ")
|
471
|
-
node.connect()
|
472
|
-
l.done
|
473
|
-
# kill all ruby processes except this one
|
474
|
-
l.logs("Killing all Ruby processes except this one... ")
|
475
|
-
node.kill_workers()
|
476
|
-
l.done
|
477
|
-
# disconnect the node
|
478
|
-
l.logs("Disconnecting... ")
|
479
|
-
node.disconnect()
|
480
|
-
l.done
|
481
|
-
l.done
|
482
|
-
} # @@nodes.each do |node|
|
483
|
-
end
|
484
|
-
|
485
|
-
# get the node by `node_name`
|
486
|
-
# connect the nodes via ssh.
|
487
|
-
# get how many minutes the worker wrote the log file
|
488
|
-
# close the connection
|
489
|
-
#
|
490
|
-
# DEPRECATED. Use `ps aux | grep "..."` to know if a process is running or not.
|
491
|
-
#
|
492
|
-
def self.log_minutes_ago(node_name, worker_id)
|
493
|
-
# get the node
|
494
|
-
n = self.nodes.select { |n| n.name == node_name }.first
|
495
|
-
return nil if !n
|
496
|
-
# connect the node
|
497
|
-
n.connect()
|
498
|
-
# get the time of the last time the worker wrote the log file
|
499
|
-
code = "cat #{BlackStack::Pampa.working_directory}/worker.#{worker_id}.log | tail -n 1 | cut -b1-19"
|
500
|
-
s = n.exec(code, false).to_s.strip
|
501
|
-
# run bash command to get the difference in minutes beteen now and the last time the worker wrote the log file
|
502
|
-
s = n.exec("echo \"$(($(date +%s) - $(date -d '#{s}' +%s))) / 60\" | bc", false).to_s.strip
|
503
|
-
# disconnect the node
|
504
|
-
n.disconnect
|
505
|
-
# return the number of minutes
|
506
|
-
s
|
507
|
-
end # log_minutes_ago
|
508
|
-
|
509
|
-
# get the node usage of CPU, RAM, DISK, and NETWORK
|
510
|
-
# return a hash with the usage of CPU, RAM, DISK, and NETWORK
|
511
|
-
#
|
512
|
-
# sudo apt install sysstat
|
513
|
-
#
|
514
|
-
def self.node_usage(node_name)
|
515
|
-
ret = {}
|
516
|
-
# get the node
|
517
|
-
n = self.nodes.select { |n| n.name == node_name }.first
|
518
|
-
return nil if !n
|
519
|
-
n.usage
|
520
|
-
end # node_usage
|
216
|
+
end # def self.dispatch()
|
521
217
|
|
522
218
|
# stub worker class
|
523
219
|
class Worker
|
@@ -551,10 +247,6 @@ module BlackStack
|
|
551
247
|
def detach()
|
552
248
|
self.attached = false
|
553
249
|
end
|
554
|
-
# get the latest n lines of the log of this worker
|
555
|
-
def tail(n=10)
|
556
|
-
self.node.tail("#{BlackStack::Pampa.working_directory}/worker.#{self.id}.log", n)
|
557
|
-
end
|
558
250
|
end
|
559
251
|
|
560
252
|
# stub node class
|
@@ -825,7 +517,7 @@ module BlackStack
|
|
825
517
|
q = "
|
826
518
|
SELECT *
|
827
519
|
FROM #{self.table.to_s}
|
828
|
-
WHERE COALESCE(#{self.field_time.to_s}, '1900-01-01') < CAST('#{
|
520
|
+
WHERE COALESCE(#{self.field_time.to_s}, '1900-01-01') < CAST('#{now}' AS TIMESTAMP) - INTERVAL '#{self.max_job_duration_minutes.to_i} minutes'
|
829
521
|
AND #{self.field_id.to_s} IS NOT NULL
|
830
522
|
AND #{self.field_end_time.to_s} IS NULL
|
831
523
|
--AND COALESCE(#{self.field_times.to_s},0) < #{self.max_try_times.to_i}
|
@@ -870,7 +562,7 @@ module BlackStack
|
|
870
562
|
|
871
563
|
def start(o)
|
872
564
|
if self.starter_function.nil?
|
873
|
-
o[self.field_start_time.to_sym] = DB["SELECT CAST('#{
|
565
|
+
o[self.field_start_time.to_sym] = DB["SELECT CAST('#{now}' AS TIMESTAMP) AS dt"].first[:dt] if !self.field_start_time.nil? # IMPORTANT: use DB location to get current time.
|
874
566
|
o[self.field_times.to_sym] = o[self.field_times.to_sym].to_i + 1
|
875
567
|
self.update(o)
|
876
568
|
else
|
@@ -880,7 +572,7 @@ module BlackStack
|
|
880
572
|
|
881
573
|
def finish(o, e=nil)
|
882
574
|
if self.finisher_function.nil?
|
883
|
-
o[self.field_end_time.to_sym] = DB["SELECT CAST('#{
|
575
|
+
o[self.field_end_time.to_sym] = DB["SELECT CAST('#{now}' AS TIMESTAMP) AS dt"].first[:dt] if !self.field_end_time.nil? && e.nil? # IMPORTANT: use DB location to get current time.
|
884
576
|
o[self.field_success.to_sym] = e.nil?
|
885
577
|
o[self.field_error_description.to_sym] = e.to_console if !e.nil?
|
886
578
|
self.update(o)
|
@@ -913,7 +605,7 @@ module BlackStack
|
|
913
605
|
# dispatching n pending records
|
914
606
|
i = 0
|
915
607
|
if n>0
|
916
|
-
ids = self.selecting(n).map { |h| h[
|
608
|
+
ids = self.selecting(n).map { |h| h[self.field_primary_key.to_sym] }
|
917
609
|
|
918
610
|
i = ids.size
|
919
611
|
|
@@ -937,7 +629,7 @@ module BlackStack
|
|
937
629
|
end
|
938
630
|
|
939
631
|
q += "
|
940
|
-
#{self.field_time.to_s} = CAST('#{
|
632
|
+
#{self.field_time.to_s} = CAST('#{now}' AS TIMESTAMP)
|
941
633
|
WHERE #{self.field_primary_key.to_s} IN ('#{ids.join("','")}')
|
942
634
|
"
|
943
635
|
|
@@ -1020,55 +712,7 @@ module BlackStack
|
|
1020
712
|
else
|
1021
713
|
return self.failed_function.call
|
1022
714
|
end
|
1023
|
-
end # def
|
1024
|
-
|
1025
|
-
# reporting method: timeline
|
1026
|
-
# Return an array of hashes with the number of successfull processed taasks in the last period.
|
1027
|
-
# The period is defined by the `scale_unit` and `scale_points` parameters.
|
1028
|
-
# The `scale_unit` can be `minutes`, `hours`, `days`, `weeks`, `months`, `years`.
|
1029
|
-
# The `scale_points` is the number of `scale_unit` to be reported, and it must be an integer higer than 0.
|
1030
|
-
# if the numbr if running tasks is higher than `max_tasks_to_show` then it returns `max_tasks_to_show`+.
|
1031
|
-
def timeline(scale_unit='minutes', scale_points=60)
|
1032
|
-
j = self
|
1033
|
-
a = []
|
1034
|
-
# validate: The period is defined by the `scale_unit` and `scale_points` parameters.
|
1035
|
-
if !['minutes', 'hours', 'days', 'weeks', 'months', 'years'].include?(scale_unit)
|
1036
|
-
raise "Invalid scale_unit: #{scale_unit}"
|
1037
|
-
end
|
1038
|
-
# validate: The `scale_points` is the number of `scale_unit` to be reported, and it must be an integer higer than 0.
|
1039
|
-
if !scale_points.is_a?(Integer) || scale_points<=0
|
1040
|
-
raise "Invalid scale_points: #{scale_points}"
|
1041
|
-
end
|
1042
|
-
# generate report
|
1043
|
-
point = 0
|
1044
|
-
while point<scale_points
|
1045
|
-
point += 1
|
1046
|
-
q = "
|
1047
|
-
SELECT COUNT(*) AS n
|
1048
|
-
FROM #{j.table.to_s}
|
1049
|
-
WHERE COALESCE(#{j.field_success.to_s},false)=true
|
1050
|
-
AND #{j.field_time.to_s} >= CAST('#{BlackStack::Pampa.now - point.send(scale_unit)}' AS TIMESTAMP)
|
1051
|
-
AND #{j.field_time.to_s} < CAST('#{BlackStack::Pampa.now - (point-1).send(scale_unit)}' AS TIMESTAMP)
|
1052
|
-
"
|
1053
|
-
a << { :time => BlackStack::Pampa.now - (point-1).send(scale_unit), :n => DB[q].first[:n].to_i }
|
1054
|
-
end # while point<scale_points
|
1055
|
-
# return
|
1056
|
-
a
|
1057
|
-
end # def timeline
|
1058
|
-
|
1059
|
-
# reporting method: error_descriptions
|
1060
|
-
# return an array of hashes { :id, :error_description } with the tasks that have an the success flag in false, error description.
|
1061
|
-
# if the numbr if running tasks is higher than `max_tasks_to_show` then it returns `max_tasks_to_show` errors.
|
1062
|
-
def error_descriptions(max_tasks_to_show=25)
|
1063
|
-
j = self
|
1064
|
-
q = "
|
1065
|
-
SELECT #{j.field_primary_key.to_s} as id, #{j.field_error_description.to_s} as description
|
1066
|
-
FROM #{j.table.to_s}
|
1067
|
-
WHERE COALESCE(#{j.field_success.to_s},true)=false
|
1068
|
-
LIMIT #{max_tasks_to_show}
|
1069
|
-
"
|
1070
|
-
DB[q].all
|
1071
|
-
end
|
715
|
+
end # def failed
|
1072
716
|
end # class Job
|
1073
717
|
end # module Pampa
|
1074
718
|
end # module BlackStack
|
data/pampa.gemspec
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'pampa'
|
3
|
+
s.version = '2.0.30'
|
4
|
+
s.date = '2023-10-06'
|
5
|
+
s.summary = "Ruby library for async & distributed computing, supporting dynamic reconfiguration, distribution of the computation jobs, error handling, job-retry and fault tolerance, and fast (non-direct) communication to ensure real-time capabilities."
|
6
|
+
s.description = "Pampa is a Ruby library for async & distributing computing providing the following features:
|
7
|
+
|
8
|
+
- cluster-management with dynamic reconfiguration (joining and leaving nodes);
|
9
|
+
- distribution of the computation jobs to the (active) nodes;
|
10
|
+
- error handling, job-retry and fault tolerance;
|
11
|
+
- fast (non-direct) communication to ensure realtime capabilities.
|
12
|
+
|
13
|
+
The Pampa framework may be widely used for:
|
14
|
+
|
15
|
+
- large scale web scraping with what we call a \"bot-farm\";
|
16
|
+
- payments processing for large-scale ecommerce websites;
|
17
|
+
- reports generation for high demanded SaaS platforms;
|
18
|
+
- heavy mathematical model computing;
|
19
|
+
|
20
|
+
and any other tasks that requires a virtually infinite amount of CPU computing and memory resources.
|
21
|
+
|
22
|
+
Find documentation here: https://github.com/leandrosardi/pampa
|
23
|
+
"
|
24
|
+
s.authors = ["Leandro Daniel Sardi"]
|
25
|
+
s.email = 'leandro.sardi@expandedventure.com'
|
26
|
+
s.files = [
|
27
|
+
'lib/pampa/worker.rb',
|
28
|
+
'lib/pampa/dispatcher.rb',
|
29
|
+
'lib/pampa/app.rb',
|
30
|
+
'lib/pampa.rb',
|
31
|
+
'pampa.gemspec'
|
32
|
+
]
|
33
|
+
s.homepage = 'https://github.com/leandrosardi/pampa'
|
34
|
+
s.license = 'MIT'
|
35
|
+
s.add_runtime_dependency 'rubygems-bundler', '~> 1.4.5', '>= 1.4.5'
|
36
|
+
s.add_runtime_dependency 'sinatra', '~> 2.2.4', '>= 2.2.4'
|
37
|
+
s.add_runtime_dependency 'colorize', '~> 0.8.1', '>= 0.8.1'
|
38
|
+
s.add_runtime_dependency 'blackstack-core', '~> 1.2.15', '>= 1.2.15'
|
39
|
+
s.add_runtime_dependency 'blackstack-db', '~> 1.0.1', '>= 1.0.1'
|
40
|
+
s.add_runtime_dependency 'blackstack-nodes', '~> 1.2.12', '>= 1.2.12'
|
41
|
+
s.add_runtime_dependency 'simple_command_line_parser', '~> 1.1.2', '>= 1.1.2'
|
42
|
+
s.add_runtime_dependency 'simple_cloud_logging', '~> 1.2.2', '>= 1.2.2'
|
43
|
+
end
|