spawncli 1.0.1
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/.gitignore +12 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/README.md +37 -0
- data/Rakefile +1 -0
- data/bin/spawncli +6 -0
- data/lib/spawncli.rb +131 -0
- data/lib/threadinfo.rb +19 -0
- data/lib/version.rb +3 -0
- data/spawncli.gemspec +26 -0
- metadata +98 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 299f86db99d8e30a0b14ca6040fff591db71cfbe
|
|
4
|
+
data.tar.gz: ec23fd6b89dce707ce2c55889606f3a4c734ce10
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 2c19b171c45fb85b16f68981538381ad9dcf142591101ce121868b052e294d791413b3dc3fe31217b3cd48ba37c286686a03ef07460dd3c64a574091e68de19e
|
|
7
|
+
data.tar.gz: 657daddb7e3e1be0203d87e4be8b6a57a23f8a3c7b021ca8fa4a51f9f28739af5e58e406f26a10f692982ac7d695b237367178a53bcfe03384b1b2589fbada47
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Spawncli
|
|
2
|
+
|
|
3
|
+
Spawncli lets you run several copies of a command-line in parallel, capturing their stdout and stderr.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Spawncli doesn't expose any worthwhile functionality as a library. It can be installed as a command-line gem from RubyGems:
|
|
8
|
+
|
|
9
|
+
``gem install spawncli``
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
``spawncli n command-line arg1 arg2 argN``
|
|
14
|
+
|
|
15
|
+
Spawncli takes two arguments: a number and a command to run. Any further arguments are interpreted as being part of the second.
|
|
16
|
+
|
|
17
|
+
To avoid shell interpretation of pipes and redirections, the command-line to run should be quoted.
|
|
18
|
+
|
|
19
|
+
### Example
|
|
20
|
+
|
|
21
|
+
``spawncli 10 "curl http://localhost:8080/foo"``
|
|
22
|
+
|
|
23
|
+
### Return Code
|
|
24
|
+
|
|
25
|
+
Returns 1 if anything spawned process wrote to its stderr, 0 otherwise.
|
|
26
|
+
|
|
27
|
+
## Development
|
|
28
|
+
|
|
29
|
+
TODO
|
|
30
|
+
|
|
31
|
+
## Contributing
|
|
32
|
+
|
|
33
|
+
1. Fork it ( https://github.com/[my-github-username]/spawn/fork )
|
|
34
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
35
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
36
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
37
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/spawncli
ADDED
data/lib/spawncli.rb
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
require 'open4'
|
|
2
|
+
require 'threadinfo'
|
|
3
|
+
|
|
4
|
+
module Spawncli
|
|
5
|
+
|
|
6
|
+
class Spawncli
|
|
7
|
+
|
|
8
|
+
@@help_text = <<-EOF.gsub /^ /, ""
|
|
9
|
+
Spawn: Spawns 'n' copies of a command line.
|
|
10
|
+
John Hawksley <john.hawksley@gmail.com> -*- MIT License
|
|
11
|
+
|
|
12
|
+
See https://github.com/jhawksley/spawn for details.
|
|
13
|
+
|
|
14
|
+
Options:
|
|
15
|
+
spawncli 2 "sleep 1 && curl http://localhost" # Run 2 copies of the command
|
|
16
|
+
EOF
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def run(args)
|
|
20
|
+
|
|
21
|
+
# Proces out the arguments - this is a very simple parse since we don't support any options.
|
|
22
|
+
n = process_args(args)
|
|
23
|
+
|
|
24
|
+
# Parse out the remainder of the args values as a command line to run in the shell.
|
|
25
|
+
argline = generate_arg_line(args) # Gets rid of the trailing space.
|
|
26
|
+
|
|
27
|
+
# An array to hold our ThreadInfo objects.
|
|
28
|
+
threads = spawn_threads(argline, n)
|
|
29
|
+
|
|
30
|
+
puts
|
|
31
|
+
|
|
32
|
+
# Poll the thread array, seeing if any have completed (status false)
|
|
33
|
+
# or there was an exception (nil).
|
|
34
|
+
# Any exceptions are not currently handled.
|
|
35
|
+
any_stderr = poll_threads(threads)
|
|
36
|
+
|
|
37
|
+
# If any thread wrote to stderr, exit 1, otherwise 0.
|
|
38
|
+
exit any_stderr ? 1 : 0
|
|
39
|
+
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def poll_threads(threads)
|
|
45
|
+
any_stderr = false
|
|
46
|
+
|
|
47
|
+
while threads.length > 0 do
|
|
48
|
+
threads.each do |ti|
|
|
49
|
+
if (ti.thread.status == false || ti.thread.status == nil) then
|
|
50
|
+
# Done or exited with exception
|
|
51
|
+
threads.delete(ti)
|
|
52
|
+
|
|
53
|
+
print "#{ti.id}: complete #{ti.thread.status == nil ? '(failed) ' : ''}"
|
|
54
|
+
$stdout.flush
|
|
55
|
+
puts (Time.now - ti.start_time).to_s << 's'
|
|
56
|
+
|
|
57
|
+
if ti.stdout != nil && !ti.stdout.empty?
|
|
58
|
+
ti.stdout.each_line do |l|
|
|
59
|
+
puts "#{ti.id}: out: #{l}"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
if ti.stderr != nil && !ti.stderr.empty?
|
|
63
|
+
any_stderr = true
|
|
64
|
+
ti.stderr.each_line do |l|
|
|
65
|
+
puts "#{ti.id}: err: #{l}"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
puts
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
any_stderr
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def spawn_threads(argline, n)
|
|
77
|
+
threads = Array.new #of ThreadInfos
|
|
78
|
+
|
|
79
|
+
puts "Spawning #{n} copies of #{argline}"
|
|
80
|
+
|
|
81
|
+
# Do all the spawns
|
|
82
|
+
(1..n).each do |i|
|
|
83
|
+
ti = ThreadInfo.new i #... initializes the internal ID with 'i'
|
|
84
|
+
threads << ti
|
|
85
|
+
thread = Thread.new do
|
|
86
|
+
status = Open4::popen4(argline) do |pid, stdin, stdout, stderr|
|
|
87
|
+
ti.stdout = stdout.read.strip
|
|
88
|
+
ti.stderr = stderr.read.strip
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
ti.thread = thread
|
|
92
|
+
puts "#{i}: spawned."
|
|
93
|
+
end
|
|
94
|
+
threads
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def generate_arg_line(args)
|
|
98
|
+
argline=''
|
|
99
|
+
|
|
100
|
+
args.each do |a|
|
|
101
|
+
argline = argline + a + ' '
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
argline.strip!
|
|
105
|
+
argline
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def process_args(args)
|
|
109
|
+
if args.length < 2 then
|
|
110
|
+
do_help
|
|
111
|
+
abort
|
|
112
|
+
else
|
|
113
|
+
# The first argument is interpreted as the number of concurrent thread
|
|
114
|
+
# to run.
|
|
115
|
+
begin
|
|
116
|
+
n = Integer(args.shift)
|
|
117
|
+
rescue Exception => e
|
|
118
|
+
abort "The first argument of a multiple argument invocation must be an integer (#{e})"
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
n
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def do_help
|
|
126
|
+
puts @@help_text
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
end
|
data/lib/threadinfo.rb
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Spawncli
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# A class to hold information about a spawned thread.
|
|
5
|
+
class ThreadInfo
|
|
6
|
+
|
|
7
|
+
attr_accessor :thread, :id, :stdout, :stderr, :exit_code
|
|
8
|
+
attr_reader :start_time
|
|
9
|
+
|
|
10
|
+
# Create a new ThreadInfo holder.
|
|
11
|
+
# @param id [String] the thread ID
|
|
12
|
+
def initialize(id)
|
|
13
|
+
@id = id
|
|
14
|
+
@start_time = Time.now
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
end
|
data/lib/version.rb
ADDED
data/spawncli.gemspec
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
|
|
5
|
+
require 'version'
|
|
6
|
+
|
|
7
|
+
Gem::Specification.new do |spec|
|
|
8
|
+
spec.name = "spawncli"
|
|
9
|
+
spec.version = Spawncli::VERSION
|
|
10
|
+
spec.authors = ["John Hawksley"]
|
|
11
|
+
spec.email = ["john.hawksley@gmail.com"]
|
|
12
|
+
|
|
13
|
+
spec.summary = %q{Run several command-lines in parallel.}
|
|
14
|
+
spec.description = %q{Spawncli forks several processes to run concurrently, and monitors their status.}
|
|
15
|
+
spec.homepage = "https://github.com/jhawksley/spawncli/"
|
|
16
|
+
|
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
18
|
+
spec.bindir = "bin"
|
|
19
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
20
|
+
spec.require_paths = ["lib"]
|
|
21
|
+
|
|
22
|
+
spec.add_runtime_dependency "open4", "~> 1.3"
|
|
23
|
+
|
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.9"
|
|
25
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
|
26
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: spawncli
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- John Hawksley
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2015-05-01 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: open4
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.3'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.3'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: bundler
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '1.9'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '1.9'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rake
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '10.0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '10.0'
|
|
55
|
+
description: Spawncli forks several processes to run concurrently, and monitors their
|
|
56
|
+
status.
|
|
57
|
+
email:
|
|
58
|
+
- john.hawksley@gmail.com
|
|
59
|
+
executables:
|
|
60
|
+
- spawncli
|
|
61
|
+
extensions: []
|
|
62
|
+
extra_rdoc_files: []
|
|
63
|
+
files:
|
|
64
|
+
- ".gitignore"
|
|
65
|
+
- ".travis.yml"
|
|
66
|
+
- Gemfile
|
|
67
|
+
- README.md
|
|
68
|
+
- Rakefile
|
|
69
|
+
- bin/spawncli
|
|
70
|
+
- lib/spawncli.rb
|
|
71
|
+
- lib/threadinfo.rb
|
|
72
|
+
- lib/version.rb
|
|
73
|
+
- spawncli.gemspec
|
|
74
|
+
homepage: https://github.com/jhawksley/spawncli/
|
|
75
|
+
licenses: []
|
|
76
|
+
metadata: {}
|
|
77
|
+
post_install_message:
|
|
78
|
+
rdoc_options: []
|
|
79
|
+
require_paths:
|
|
80
|
+
- lib
|
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
82
|
+
requirements:
|
|
83
|
+
- - ">="
|
|
84
|
+
- !ruby/object:Gem::Version
|
|
85
|
+
version: '0'
|
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
87
|
+
requirements:
|
|
88
|
+
- - ">="
|
|
89
|
+
- !ruby/object:Gem::Version
|
|
90
|
+
version: '0'
|
|
91
|
+
requirements: []
|
|
92
|
+
rubyforge_project:
|
|
93
|
+
rubygems_version: 2.4.5
|
|
94
|
+
signing_key:
|
|
95
|
+
specification_version: 4
|
|
96
|
+
summary: Run several command-lines in parallel.
|
|
97
|
+
test_files: []
|
|
98
|
+
has_rdoc:
|