enparallel-bin 1.0.4
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.
- checksums.yaml +7 -0
- data/bin/enparallel +96 -0
- data/lib/enparallel/cli.rb +72 -0
- data/lib/enparallel/command.rb +27 -0
- data/lib/enparallel/log_group.rb +35 -0
- data/lib/enparallel/logger.rb +19 -0
- data/lib/enparallel/picker.rb +20 -0
- data/lib/enparallel/task.rb +71 -0
- data/lib/enparallel/thread_pool.rb +64 -0
- data/lib/enparallel/util.rb +15 -0
- data/lib/enparallel/version.rb +3 -0
- data/lib/enparallel.rb +12 -0
- metadata +152 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a40aa3e43e1fac9e594bbaeadb9786f0899cf34bf0849f73f3c0b9f2d3a01e4f
|
4
|
+
data.tar.gz: 366611b5ea88dc0b621bfd5c65af356a0481a045ccfce94dc41e588e0377be61
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7ccc4d563a11b51a84049d521db264129a693f78e92f8173ecdd02b02373e184701b8924baa3c2bc20cb853da8765b2156289456fdb714bc87592626642f9403
|
7
|
+
data.tar.gz: 6f019d7df35f9d48225f1b62173c67eb6a65b3ccf04f763d3897de12be2d98312911a9fa11834250290f9c2361cfd89fb94e3013ddfde170342b2f9c7d77aabd
|
data/bin/enparallel
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative '../lib/enparallel'
|
4
|
+
include Enparallel
|
5
|
+
|
6
|
+
class Main
|
7
|
+
def self.run(cli)
|
8
|
+
tasks = cli.inputs.map { |input| Task.new(cli.command, input) }
|
9
|
+
pool = ThreadPool.new(tasks, cli.workers, cli.pick)
|
10
|
+
new(pool).run
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(pool)
|
14
|
+
@pool = pool
|
15
|
+
@id = 0
|
16
|
+
end
|
17
|
+
|
18
|
+
def run
|
19
|
+
start
|
20
|
+
|
21
|
+
render_progress do
|
22
|
+
@pool.drain_wait
|
23
|
+
end
|
24
|
+
|
25
|
+
finish
|
26
|
+
save_log
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def start
|
32
|
+
puts 'Running %d tasks with %d workers' % [@pool.size, @pool.worker_count]
|
33
|
+
puts
|
34
|
+
end
|
35
|
+
|
36
|
+
def finish
|
37
|
+
puts
|
38
|
+
puts "Tasks complete"
|
39
|
+
puts
|
40
|
+
puts "#{@pool.succeeded_tasks.length.to_s.green.bold} tasks succeeded"
|
41
|
+
puts "#{@pool.failed_tasks.length.to_s.red.bold} tasks failed"
|
42
|
+
puts
|
43
|
+
end
|
44
|
+
|
45
|
+
def render_progress
|
46
|
+
thread = Thread.new { loop { render } }
|
47
|
+
yield
|
48
|
+
thread.terminate
|
49
|
+
render
|
50
|
+
puts
|
51
|
+
end
|
52
|
+
|
53
|
+
def render
|
54
|
+
print "\r" + @pool.render
|
55
|
+
sleep 0.1
|
56
|
+
end
|
57
|
+
|
58
|
+
def all_paths_available(paths)
|
59
|
+
paths.all? { |path| !File.exist?(path) }
|
60
|
+
end
|
61
|
+
|
62
|
+
def next_id
|
63
|
+
@id += 1
|
64
|
+
end
|
65
|
+
|
66
|
+
def generate_paths(types)
|
67
|
+
loop do
|
68
|
+
id = next_id
|
69
|
+
|
70
|
+
paths = types.to_h do |type|
|
71
|
+
[type, '/tmp/enparallel-run-%d-%s.txt' % [id, type]]
|
72
|
+
end
|
73
|
+
|
74
|
+
break paths if all_paths_available(paths.values)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def save_log
|
79
|
+
log_groups = @pool.get_log_groups
|
80
|
+
types = log_groups.map(&:type).uniq
|
81
|
+
paths = generate_paths(types)
|
82
|
+
|
83
|
+
log_groups.each do |log_group|
|
84
|
+
type = log_group.type
|
85
|
+
path = paths[type]
|
86
|
+
puts 'Written %s log: %s (%s)' % [type, *log_group.write(path)]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
begin
|
92
|
+
Main.run(CLI.parse(ARGV, STDIN))
|
93
|
+
rescue Docopt::Exit => e
|
94
|
+
puts e.message
|
95
|
+
exit
|
96
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Enparallel
|
2
|
+
class CLI
|
3
|
+
def initialize(opts, stdin)
|
4
|
+
@opts = opts
|
5
|
+
@stdin = stdin
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.workers_default
|
9
|
+
Util.processor_count
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.pick_default
|
13
|
+
'sequential'
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.parse(argv, stdin)
|
17
|
+
new(Docopt::docopt(usage, argv: argv, version: VERSION), stdin)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.basename
|
21
|
+
File.basename($0)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.usage
|
25
|
+
<<~EOF
|
26
|
+
#{'Usage:'.bold}
|
27
|
+
#{basename} [options] [--] <command>...
|
28
|
+
|
29
|
+
#{'Description:'.bold}
|
30
|
+
#{basename} operates by reading lines from standard input, and executing
|
31
|
+
<command> once per entry, in parallel.
|
32
|
+
|
33
|
+
The placeholder "{}", if present, is replaced with each line of input in turn.
|
34
|
+
|
35
|
+
seq 1 10 | enparallel sleep {}
|
36
|
+
|
37
|
+
To run a more complex command or to make use of shell functions or constructs
|
38
|
+
(enparallel runs its argument as a program) use a call to "bash -c". Note that
|
39
|
+
because of the "-c" you need to prefix the command with "--" to indicate the
|
40
|
+
end of parameters to enparallel.
|
41
|
+
|
42
|
+
seq 1 10 | enparallel -- bash -c "sleep {} && echo Slept for {}"
|
43
|
+
|
44
|
+
#{'Options:'.bold}
|
45
|
+
-w, --workers <n> Batch into a pool of <n> workers [default: #{workers_default}].
|
46
|
+
-p, --pick <type> Task-picking rule (see "Types") [default: #{pick_default}].
|
47
|
+
-v, --version Version.
|
48
|
+
-h, --help Help.
|
49
|
+
|
50
|
+
#{'Types:'.bold}
|
51
|
+
sequential The order in which the tasks were queued.
|
52
|
+
random Random order.
|
53
|
+
EOF
|
54
|
+
end
|
55
|
+
|
56
|
+
def inputs
|
57
|
+
@inputs ||= @stdin.each_line.map(&:chomp)
|
58
|
+
end
|
59
|
+
|
60
|
+
def command
|
61
|
+
Command.from_a(@opts['<command>'])
|
62
|
+
end
|
63
|
+
|
64
|
+
def workers
|
65
|
+
@opts['--workers'].to_i || workers_default
|
66
|
+
end
|
67
|
+
|
68
|
+
def pick
|
69
|
+
@opts['--pick'] || pick_default
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Enparallel
|
2
|
+
class Command
|
3
|
+
def initialize(name, args)
|
4
|
+
@name = name
|
5
|
+
@args = args
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.from_a(a)
|
9
|
+
name, *args = a
|
10
|
+
Command.new(name, args)
|
11
|
+
end
|
12
|
+
|
13
|
+
def interpolate_safe(replacement)
|
14
|
+
[@name, *replace(replacement)].shelljoin
|
15
|
+
end
|
16
|
+
|
17
|
+
def interpolate_unsafe(replacement)
|
18
|
+
[@name, *replace(replacement)].join(' ')
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def replace(replacement)
|
24
|
+
@args.map { |arg| arg.gsub('{}', replacement) }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Enparallel
|
2
|
+
class LogGroup
|
3
|
+
attr_reader :type, :tasks
|
4
|
+
|
5
|
+
def initialize(type, tasks)
|
6
|
+
@type = type
|
7
|
+
@tasks = tasks
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_soml
|
11
|
+
tasks.join("\n\n") + "\n"
|
12
|
+
end
|
13
|
+
|
14
|
+
def has_tasks?
|
15
|
+
tasks.length > 0
|
16
|
+
end
|
17
|
+
|
18
|
+
def write(path)
|
19
|
+
size = File.write(path, to_soml)
|
20
|
+
[path, Util.bytes_to_human(size)]
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.of(type, pool)
|
24
|
+
LogGroup.new(type, pool.tasks_of(type))
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.success(pool)
|
28
|
+
of(:success, pool)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.failure(pool)
|
32
|
+
of(:failure, pool)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Enparallel
|
2
|
+
class Logger
|
3
|
+
def self.from_thread_pool(pool)
|
4
|
+
Logger.new(pool)
|
5
|
+
end
|
6
|
+
|
7
|
+
def initialize(pool)
|
8
|
+
@pool = pool
|
9
|
+
end
|
10
|
+
|
11
|
+
def get_all_log_groups
|
12
|
+
[LogGroup.success(@pool), LogGroup.failure(@pool)]
|
13
|
+
end
|
14
|
+
|
15
|
+
def get_log_groups
|
16
|
+
get_all_log_groups.select(&:has_tasks?)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Enparallel
|
2
|
+
class Picker
|
3
|
+
def initialize(items, rule)
|
4
|
+
if rule == :random
|
5
|
+
items = items.shuffle
|
6
|
+
end
|
7
|
+
|
8
|
+
@items = items
|
9
|
+
@i = -1
|
10
|
+
end
|
11
|
+
|
12
|
+
def next
|
13
|
+
@items[next_index]
|
14
|
+
end
|
15
|
+
|
16
|
+
def next_index
|
17
|
+
@i += 1
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Enparallel
|
2
|
+
class Task
|
3
|
+
attr_accessor :stdout
|
4
|
+
attr_accessor :stderr
|
5
|
+
|
6
|
+
def initialize(command, input)
|
7
|
+
@command = command
|
8
|
+
@input = input
|
9
|
+
@running = false
|
10
|
+
@stdout = ''
|
11
|
+
@stderr = ''
|
12
|
+
@ran_at = nil
|
13
|
+
@exit_status = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
document = SOML::Document.new
|
18
|
+
|
19
|
+
document.add('CommandLine', command_line_unsafe)
|
20
|
+
document.add('ExitStatus', @exit_status)
|
21
|
+
document.add('RanAt', @ran_at)
|
22
|
+
document.add('StandardOutput', @stdout) unless @stdout.empty?
|
23
|
+
document.add('StandardError', @stderr) unless @stderr.empty?
|
24
|
+
|
25
|
+
document.to_s
|
26
|
+
end
|
27
|
+
|
28
|
+
def char
|
29
|
+
if @running
|
30
|
+
'R'
|
31
|
+
elsif @exit_status.nil?
|
32
|
+
'S'
|
33
|
+
elsif has_succeeded?
|
34
|
+
'D'.green
|
35
|
+
else
|
36
|
+
'F'.red
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def run
|
41
|
+
@running = true
|
42
|
+
@ran_at = Time.now
|
43
|
+
|
44
|
+
Open3.popen3(command_line_safe) do |stdin, stdout, stderr, thread|
|
45
|
+
@stdout = stdout.read.chomp
|
46
|
+
@stderr = stderr.read.chomp
|
47
|
+
@exit_status = thread.value.exitstatus
|
48
|
+
end
|
49
|
+
rescue => e
|
50
|
+
@stderr = e.message
|
51
|
+
@exit_status = 1
|
52
|
+
ensure
|
53
|
+
@running = false
|
54
|
+
end
|
55
|
+
|
56
|
+
def has_succeeded?
|
57
|
+
raise 'Task not resolved' if @ran_at.nil?
|
58
|
+
@exit_status == 0
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def command_line_safe
|
64
|
+
@command.interpolate_safe(@input)
|
65
|
+
end
|
66
|
+
|
67
|
+
def command_line_unsafe
|
68
|
+
@command.interpolate_unsafe(@input)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Enparallel
|
2
|
+
class ThreadPool
|
3
|
+
attr_accessor :worker_count
|
4
|
+
|
5
|
+
def initialize(tasks, requested_worker_count, rule)
|
6
|
+
@tasks = tasks
|
7
|
+
@worker_count = [tasks.length, requested_worker_count].min
|
8
|
+
@picker = Picker.new(tasks, rule.to_sym)
|
9
|
+
end
|
10
|
+
|
11
|
+
def drain_wait
|
12
|
+
drain
|
13
|
+
join
|
14
|
+
end
|
15
|
+
|
16
|
+
def drain
|
17
|
+
@workers = @worker_count.times.map do
|
18
|
+
Thread.new do
|
19
|
+
while task = @picker.next
|
20
|
+
task.run
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def size
|
27
|
+
@tasks.length
|
28
|
+
end
|
29
|
+
|
30
|
+
def render
|
31
|
+
@tasks.map(&:char).join.bold
|
32
|
+
end
|
33
|
+
|
34
|
+
def succeeded_tasks
|
35
|
+
@tasks.select(&:has_succeeded?)
|
36
|
+
end
|
37
|
+
|
38
|
+
def failed_tasks
|
39
|
+
@tasks.reject(&:has_succeeded?)
|
40
|
+
end
|
41
|
+
|
42
|
+
def tasks_of(type)
|
43
|
+
if type == :success
|
44
|
+
succeeded_tasks
|
45
|
+
elsif type == :failure
|
46
|
+
failed_tasks
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def get_log_groups
|
51
|
+
logger.get_log_groups
|
52
|
+
end
|
53
|
+
|
54
|
+
def logger
|
55
|
+
@logger ||= Logger.from_thread_pool(self)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def join
|
61
|
+
@workers.each(&:join)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/enparallel.rb
ADDED
metadata
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: enparallel-bin
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- crdx
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-09-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: require_all
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: colorize
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.8.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.8.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: docopt
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.6.1
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.6.1
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: soml
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.0.1
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.0.1
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: simplecov
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.17.0
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.17.0
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.8'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3.8'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rake
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '12.3'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '12.3'
|
111
|
+
description:
|
112
|
+
email:
|
113
|
+
executables:
|
114
|
+
- enparallel
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- bin/enparallel
|
119
|
+
- lib/enparallel.rb
|
120
|
+
- lib/enparallel/cli.rb
|
121
|
+
- lib/enparallel/command.rb
|
122
|
+
- lib/enparallel/log_group.rb
|
123
|
+
- lib/enparallel/logger.rb
|
124
|
+
- lib/enparallel/picker.rb
|
125
|
+
- lib/enparallel/task.rb
|
126
|
+
- lib/enparallel/thread_pool.rb
|
127
|
+
- lib/enparallel/util.rb
|
128
|
+
- lib/enparallel/version.rb
|
129
|
+
homepage: https://github.com/crdx/enparallel
|
130
|
+
licenses:
|
131
|
+
- MIT
|
132
|
+
metadata: {}
|
133
|
+
post_install_message:
|
134
|
+
rdoc_options: []
|
135
|
+
require_paths:
|
136
|
+
- lib
|
137
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
138
|
+
requirements:
|
139
|
+
- - ">="
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - ">="
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: '0'
|
147
|
+
requirements: []
|
148
|
+
rubygems_version: 3.0.6
|
149
|
+
signing_key:
|
150
|
+
specification_version: 4
|
151
|
+
summary: Run many commands enparallel with a colourful overview
|
152
|
+
test_files: []
|