solutious-stella 0.5.5 → 0.6.0
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/CHANGES.txt +39 -2
- data/LICENSE.txt +19 -0
- data/README.rdoc +85 -0
- data/Rakefile +54 -59
- data/bin/example_test.rb +82 -0
- data/bin/example_webapp.rb +63 -0
- data/lib/{stella/logger.rb → logger.rb} +6 -11
- data/lib/stella.rb +76 -58
- data/lib/stella/clients.rb +161 -0
- data/lib/stella/command/base.rb +4 -24
- data/lib/stella/command/form.rb +36 -0
- data/lib/stella/command/get.rb +44 -0
- data/lib/stella/common.rb +53 -0
- data/lib/stella/crypto.rb +88 -0
- data/lib/stella/data/domain.rb +2 -2
- data/lib/stella/data/http.rb +164 -36
- data/lib/stella/environment.rb +66 -0
- data/lib/stella/functest.rb +105 -0
- data/lib/stella/loadtest.rb +186 -0
- data/lib/{utils → stella}/stats.rb +16 -20
- data/lib/stella/testplan.rb +237 -0
- data/lib/stella/testrunner.rb +64 -0
- data/lib/storable.rb +280 -0
- data/lib/threadify.rb +171 -0
- data/lib/timeunits.rb +65 -0
- data/lib/util/httputil.rb +266 -0
- data/stella.gemspec +69 -0
- data/tryouts/drb/drb_test.rb +65 -0
- data/tryouts/drb/open4.rb +19 -0
- data/tryouts/drb/slave.rb +27 -0
- data/tryouts/oo_tryout.rb +30 -0
- metadata +39 -107
- data/README.textile +0 -162
- data/bin/stella +0 -12
- data/bin/stella.bat +0 -12
- data/lib/daemonize.rb +0 -56
- data/lib/pcaplet.rb +0 -180
- data/lib/stella/adapter/ab.rb +0 -337
- data/lib/stella/adapter/base.rb +0 -106
- data/lib/stella/adapter/httperf.rb +0 -305
- data/lib/stella/adapter/pcap_watcher.rb +0 -221
- data/lib/stella/adapter/proxy_watcher.rb +0 -76
- data/lib/stella/adapter/siege.rb +0 -341
- data/lib/stella/cli.rb +0 -258
- data/lib/stella/cli/agents.rb +0 -73
- data/lib/stella/cli/base.rb +0 -55
- data/lib/stella/cli/language.rb +0 -18
- data/lib/stella/cli/localtest.rb +0 -78
- data/lib/stella/cli/sysinfo.rb +0 -16
- data/lib/stella/cli/watch.rb +0 -278
- data/lib/stella/command/localtest.rb +0 -358
- data/lib/stella/response.rb +0 -85
- data/lib/stella/storable.rb +0 -201
- data/lib/stella/support.rb +0 -276
- data/lib/stella/sysinfo.rb +0 -257
- data/lib/stella/test/definition.rb +0 -79
- data/lib/stella/test/run/summary.rb +0 -70
- data/lib/stella/test/stats.rb +0 -114
- data/lib/stella/text.rb +0 -64
- data/lib/stella/text/resource.rb +0 -38
- data/lib/utils/crypto-key.rb +0 -84
- data/lib/utils/domainutil.rb +0 -47
- data/lib/utils/escape.rb +0 -302
- data/lib/utils/fileutil.rb +0 -78
- data/lib/utils/httputil.rb +0 -266
- data/lib/utils/mathutil.rb +0 -15
- data/lib/utils/textgraph.rb +0 -267
- data/lib/utils/timerutil.rb +0 -58
- data/lib/win32/Console.rb +0 -970
- data/lib/win32/Console/ANSI.rb +0 -305
- data/support/kvm.h +0 -91
- data/support/ruby-pcap-takuma-notes.txt +0 -19
- data/support/ruby-pcap-takuma-patch.txt +0 -30
- data/support/text/en.yaml +0 -80
- data/support/text/nl.yaml +0 -7
- data/support/useragents.txt +0 -75
- data/tests/01-util_test.rb +0 -0
- data/tests/02-stella-util_test.rb +0 -42
- data/tests/10-stella_test.rb +0 -104
- data/tests/11-stella-storable_test.rb +0 -68
- data/tests/60-stella-command_test.rb +0 -248
- data/tests/80-stella-cli_test.rb +0 -45
- data/tests/spec-helper.rb +0 -31
@@ -0,0 +1,64 @@
|
|
1
|
+
# ---
|
2
|
+
# See: http://codeforpeople.com/lib/ruby/flow/flow-2.0.0/sample/a.rb
|
3
|
+
# +++
|
4
|
+
|
5
|
+
#
|
6
|
+
#
|
7
|
+
#
|
8
|
+
module Stella
|
9
|
+
module TestRunner
|
10
|
+
attr_accessor :name
|
11
|
+
# Name or instance of the testplan to execute
|
12
|
+
attr_accessor :testplan
|
13
|
+
# Determines the amount of output. Default: 0
|
14
|
+
attr_accessor :verbose
|
15
|
+
|
16
|
+
def initialize(name=:default)
|
17
|
+
@name = name
|
18
|
+
@verbose = 0
|
19
|
+
init if respond_to? :init
|
20
|
+
end
|
21
|
+
|
22
|
+
def update(*args)
|
23
|
+
what, *args = args
|
24
|
+
self.send("update_#{what}", *args) if respond_to? "update_#{what}"
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
module DSL
|
29
|
+
module TestRunner
|
30
|
+
attr_accessor :current_test
|
31
|
+
|
32
|
+
def plan(testplan)
|
33
|
+
raise "Unknown testplan, '#{testplan}'" unless @plans.has_key?(testplan)
|
34
|
+
return unless @current_test
|
35
|
+
@current_test.testplan = @plans[testplan]
|
36
|
+
end
|
37
|
+
|
38
|
+
def run(env_name=nil, test_name=nil)
|
39
|
+
to_run = test_name.nil? ? @tests : [@tests[test_name]]
|
40
|
+
env = env_name.nil? ? @stella_environments.first : @stella_environments[env_name]
|
41
|
+
to_run.each do |t|
|
42
|
+
puts '='*60
|
43
|
+
puts "RUNNING TEST: #{test_name}"
|
44
|
+
puts " %11s: %s" % ['type', t.type]
|
45
|
+
puts " %11s: %s" % ['testplan', t.testplan.name]
|
46
|
+
puts " %11s: %s" % ['desc', t.testplan.description]
|
47
|
+
puts " %11s: %s" % ['env', env_name]
|
48
|
+
|
49
|
+
|
50
|
+
t.run(env, self)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def verbose(*args)
|
55
|
+
@current_test.verbose += args.first || 1
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
|
data/lib/storable.rb
ADDED
@@ -0,0 +1,280 @@
|
|
1
|
+
#--
|
2
|
+
# TODO: Handle nested hashes and arrays.
|
3
|
+
# TODO: to_xml, see: http://codeforpeople.com/lib/ruby/xx/xx-2.0.0/README
|
4
|
+
# TODO: Rename to Stuffany
|
5
|
+
#++
|
6
|
+
|
7
|
+
require 'yaml'
|
8
|
+
require 'fileutils'
|
9
|
+
|
10
|
+
|
11
|
+
# Storable makes data available in multiple formats and can
|
12
|
+
# re-create objects from files. Fields are defined using the
|
13
|
+
# Storable.field method which tells Storable the order and
|
14
|
+
# name.
|
15
|
+
class Storable
|
16
|
+
VERSION = 2
|
17
|
+
NICE_TIME_FORMAT = "%Y-%m-%d@%H:%M:%S".freeze unless defined? NICE_TIME_FORMAT
|
18
|
+
SUPPORTED_FORMATS = %w{tsv csv yaml json}.freeze unless defined? SUPPORTED_FORMATS
|
19
|
+
|
20
|
+
# This value will be used as a default unless provided on-the-fly.
|
21
|
+
# See SUPPORTED_FORMATS for available values.
|
22
|
+
attr_reader :format
|
23
|
+
|
24
|
+
# See SUPPORTED_FORMATS for available values
|
25
|
+
def format=(v)
|
26
|
+
raise "Unsupported format: #{v}" unless SUPPORTED_FORMATS.member?(v)
|
27
|
+
@format = v
|
28
|
+
end
|
29
|
+
|
30
|
+
# TODO: from_args([HASH or ordered params])
|
31
|
+
|
32
|
+
def init
|
33
|
+
# NOTE: I think this can be removed
|
34
|
+
self.class.send(:class_variable_set, :@@field_names, []) unless class_variable_defined?(:@@field_names)
|
35
|
+
self.class.send(:class_variable_set, :@@field_types, []) unless class_variable_defined?(:@@field_types)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Accepts field definitions in the one of the follow formats:
|
39
|
+
#
|
40
|
+
# field :product
|
41
|
+
# field :product => Integer
|
42
|
+
#
|
43
|
+
# The order they're defined determines the order the will be output. The fields
|
44
|
+
# data is available by the standard accessors, class.product and class.product= etc...
|
45
|
+
# The value of the field will be cast to the type (if provided) when read from a file.
|
46
|
+
# The value is not touched when the type is not provided.
|
47
|
+
def self.field(args={})
|
48
|
+
# TODO: Examine casting from: http://codeforpeople.com/lib/ruby/fattr/fattr-1.0.3/
|
49
|
+
args = {args => nil} unless args.is_a? Hash
|
50
|
+
|
51
|
+
args.each_pair do |m,t|
|
52
|
+
|
53
|
+
[[:@@field_names, m], [:@@field_types, t]].each do |tuple|
|
54
|
+
class_variable_set(tuple[0], []) unless class_variable_defined?(tuple[0])
|
55
|
+
class_variable_set(tuple[0], class_variable_get(tuple[0]) << tuple[1])
|
56
|
+
end
|
57
|
+
|
58
|
+
next if method_defined?(m)
|
59
|
+
|
60
|
+
define_method(m) do instance_variable_get("@#{m}") end
|
61
|
+
define_method("#{m}=") do |val|
|
62
|
+
instance_variable_set("@#{m}",val)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns an array of field names defined by self.field
|
68
|
+
def self.field_names
|
69
|
+
class_variable_get(:@@field_names)
|
70
|
+
end
|
71
|
+
# Ditto.
|
72
|
+
def field_names
|
73
|
+
self.class.send(:class_variable_get, :@@field_names)
|
74
|
+
end
|
75
|
+
# Returns an array of field types defined by self.field. Fields that did
|
76
|
+
# not receive a type are set to nil.
|
77
|
+
def self.field_types
|
78
|
+
class_variable_get(:@@field_types)
|
79
|
+
end
|
80
|
+
# Ditto.
|
81
|
+
def field_types
|
82
|
+
self.class.send(:class_variable_get, :@@field_types)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Dump the object data to the given format.
|
86
|
+
def dump(format=nil, with_titles=true)
|
87
|
+
format ||= @format
|
88
|
+
raise "Format not defined (#{format})" unless SUPPORTED_FORMATS.member?(format)
|
89
|
+
send("to_#{format}", with_titles)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Create a new instance of the object using data from file.
|
93
|
+
def self.from_file(file_path, format='yaml')
|
94
|
+
raise "Cannot read file (#{file_path})" unless File.exists?(file_path)
|
95
|
+
raise "#{self} doesn't support from_#{format}" unless self.respond_to?("from_#{format}")
|
96
|
+
format = format || File.extname(file_path).tr('.', '')
|
97
|
+
me = send("from_#{format}", read_file_to_array(file_path))
|
98
|
+
me.format = format
|
99
|
+
me
|
100
|
+
end
|
101
|
+
# Write the object data to the given file.
|
102
|
+
def to_file(file_path=nil, with_titles=true)
|
103
|
+
raise "Cannot store to nil path" if file_path.nil?
|
104
|
+
format = File.extname(file_path).tr('.', '')
|
105
|
+
format ||= @format
|
106
|
+
Storable.write_file(file_path, dump(format, with_titles))
|
107
|
+
end
|
108
|
+
|
109
|
+
# Create a new instance of the object from a hash.
|
110
|
+
def self.from_hash(from={})
|
111
|
+
me = self.new
|
112
|
+
|
113
|
+
return me if !from || from.empty?
|
114
|
+
|
115
|
+
fnames = field_names
|
116
|
+
fnames.each_with_index do |key,index|
|
117
|
+
|
118
|
+
stored_value = from[key] || from[key.to_s] # support for symbol keys and string keys
|
119
|
+
|
120
|
+
# TODO: Correct this horrible implementation (sorry, me. It's just one of those days.)
|
121
|
+
|
122
|
+
if field_types[index] == Array
|
123
|
+
((value ||= []) << stored_value).flatten
|
124
|
+
elsif field_types[index] == Hash
|
125
|
+
value = stored_value
|
126
|
+
else
|
127
|
+
# SimpleDB stores attribute shit as lists of values
|
128
|
+
value = stored_value.first if stored_value.is_a?(Array) && stored_value.size == 1
|
129
|
+
|
130
|
+
if field_types[index] == Time
|
131
|
+
value = Time.parse(value)
|
132
|
+
elsif field_types[index] == DateTime
|
133
|
+
value = DateTime.parse(value)
|
134
|
+
elsif field_types[index] == TrueClass
|
135
|
+
value = (value.to_s == "true")
|
136
|
+
elsif field_types[index] == Float
|
137
|
+
value = value.to_f
|
138
|
+
elsif field_types[index] == Integer
|
139
|
+
value = value.to_i
|
140
|
+
else
|
141
|
+
value = value.first if value.is_a?(Array) && value.size == 1 # I
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
me.send("#{key}=", value) if self.method_defined?("#{key}=")
|
146
|
+
end
|
147
|
+
|
148
|
+
me
|
149
|
+
end
|
150
|
+
# Return the object data as a hash
|
151
|
+
# +with_titles+ is ignored.
|
152
|
+
def to_hash(with_titles=true)
|
153
|
+
tmp = {}
|
154
|
+
field_names.each do |fname|
|
155
|
+
tmp[fname] = self.send(fname)
|
156
|
+
end
|
157
|
+
tmp
|
158
|
+
end
|
159
|
+
|
160
|
+
# Create a new instance of the object from YAML.
|
161
|
+
# +from+ a YAML string split into an array by line.
|
162
|
+
def self.from_yaml(from=[])
|
163
|
+
# from is an array of strings
|
164
|
+
from_str = from.join('')
|
165
|
+
hash = YAML::load(from_str)
|
166
|
+
hash = from_hash(hash) if hash.is_a? Hash
|
167
|
+
hash
|
168
|
+
end
|
169
|
+
def to_yaml(with_titles=true)
|
170
|
+
to_hash.to_yaml
|
171
|
+
end
|
172
|
+
|
173
|
+
# Create a new instance of the object from a JSON string.
|
174
|
+
# +from+ a JSON string split into an array by line.
|
175
|
+
def self.from_json(from=[])
|
176
|
+
require 'json'
|
177
|
+
# from is an array of strings
|
178
|
+
from_str = from.join('')
|
179
|
+
tmp = JSON::load(from_str)
|
180
|
+
hash_sym = tmp.keys.inject({}) do |hash, key|
|
181
|
+
hash[key.to_sym] = tmp[key]
|
182
|
+
hash
|
183
|
+
end
|
184
|
+
hash_sym = from_hash(hash_sym) if hash_sym.is_a? Hash
|
185
|
+
hash_sym
|
186
|
+
end
|
187
|
+
def to_json(with_titles=true)
|
188
|
+
require 'json'
|
189
|
+
to_hash.to_json
|
190
|
+
end
|
191
|
+
|
192
|
+
# Return the object data as a delimited string.
|
193
|
+
# +with_titles+ specifiy whether to include field names (default: false)
|
194
|
+
# +delim+ is the field delimiter.
|
195
|
+
def to_delimited(with_titles=false, delim=',')
|
196
|
+
values = []
|
197
|
+
field_names.each do |fname|
|
198
|
+
values << self.send(fname.to_s) # TODO: escape values
|
199
|
+
end
|
200
|
+
output = values.join(delim)
|
201
|
+
output = field_names.join(delim) << $/ << output if with_titles
|
202
|
+
output
|
203
|
+
end
|
204
|
+
# Return the object data as a tab delimited string.
|
205
|
+
# +with_titles+ specifiy whether to include field names (default: false)
|
206
|
+
def to_tsv(with_titles=false)
|
207
|
+
to_delimited(with_titles, "\t")
|
208
|
+
end
|
209
|
+
# Return the object data as a comma delimited string.
|
210
|
+
# +with_titles+ specifiy whether to include field names (default: false)
|
211
|
+
def to_csv(with_titles=false)
|
212
|
+
to_delimited(with_titles, ',')
|
213
|
+
end
|
214
|
+
# Create a new instance from tab-delimited data.
|
215
|
+
# +from+ a JSON string split into an array by line.
|
216
|
+
def self.from_tsv(from=[])
|
217
|
+
self.from_delimited(from, "\t")
|
218
|
+
end
|
219
|
+
# Create a new instance of the object from comma-delimited data.
|
220
|
+
# +from+ a JSON string split into an array by line.
|
221
|
+
def self.from_csv(from=[])
|
222
|
+
self.from_delimited(from, ',')
|
223
|
+
end
|
224
|
+
|
225
|
+
# Create a new instance of the object from a delimited string.
|
226
|
+
# +from+ a JSON string split into an array by line.
|
227
|
+
# +delim+ is the field delimiter.
|
228
|
+
def self.from_delimited(from=[],delim=',')
|
229
|
+
return if from.empty?
|
230
|
+
# We grab an instance of the class so we can
|
231
|
+
hash = {}
|
232
|
+
|
233
|
+
fnames = values = []
|
234
|
+
if (from.size > 1 && !from[1].empty?)
|
235
|
+
fnames = from[0].chomp.split(delim)
|
236
|
+
values = from[1].chomp.split(delim)
|
237
|
+
else
|
238
|
+
fnames = self.field_names
|
239
|
+
values = from[0].chomp.split(delim)
|
240
|
+
end
|
241
|
+
|
242
|
+
fnames.each_with_index do |key,index|
|
243
|
+
next unless values[index]
|
244
|
+
hash[key.to_sym] = values[index]
|
245
|
+
end
|
246
|
+
hash = from_hash(hash) if hash.is_a? Hash
|
247
|
+
hash
|
248
|
+
end
|
249
|
+
|
250
|
+
def self.read_file_to_array(path)
|
251
|
+
contents = []
|
252
|
+
return contents unless File.exists?(path)
|
253
|
+
|
254
|
+
open(path, 'r') do |l|
|
255
|
+
contents = l.readlines
|
256
|
+
end
|
257
|
+
|
258
|
+
contents
|
259
|
+
end
|
260
|
+
|
261
|
+
def self.write_file(path, content, flush=true)
|
262
|
+
write_or_append_file('w', path, content, flush)
|
263
|
+
end
|
264
|
+
|
265
|
+
def self.append_file(path, content, flush=true)
|
266
|
+
write_or_append_file('a', path, content, flush)
|
267
|
+
end
|
268
|
+
|
269
|
+
def self.write_or_append_file(write_or_append, path, content = '', flush = true)
|
270
|
+
#STDERR.puts "Writing to #{ path }..."
|
271
|
+
create_dir(File.dirname(path))
|
272
|
+
|
273
|
+
open(path, write_or_append) do |f|
|
274
|
+
f.puts content
|
275
|
+
f.flush if flush;
|
276
|
+
end
|
277
|
+
File.chmod(0600, path)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
data/lib/threadify.rb
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
module Threadify
|
2
|
+
VERSION = '0.0.3'
|
3
|
+
def Threadify.version() Threadify::VERSION end
|
4
|
+
|
5
|
+
require 'thread'
|
6
|
+
|
7
|
+
@threads = 8
|
8
|
+
@abort_on_exception = true
|
9
|
+
|
10
|
+
class << self
|
11
|
+
attr_accessor :threads
|
12
|
+
attr_accessor :abort_on_exception
|
13
|
+
end
|
14
|
+
|
15
|
+
class Error < ::StandardError; end
|
16
|
+
end
|
17
|
+
|
18
|
+
module Enumerable
|
19
|
+
def threadify opts = {}, &block
|
20
|
+
# setup
|
21
|
+
#
|
22
|
+
opts = {:threads => opts} if Numeric === opts
|
23
|
+
threads = Integer(opts[:threads] || opts['threads'] || Threadify.threads)
|
24
|
+
done = Object.new.freeze
|
25
|
+
nothing = done
|
26
|
+
#jobs = Array.new(threads).map{ Queue.new }
|
27
|
+
jobs = Array.new(threads).map{ [] }
|
28
|
+
top = Thread.current
|
29
|
+
|
30
|
+
# produce jobs
|
31
|
+
#
|
32
|
+
#producer = Thread.new do
|
33
|
+
#this = Thread.current
|
34
|
+
#this.abort_on_exception = Threadify.abort_on_exception
|
35
|
+
|
36
|
+
each_with_index{|args, i| jobs[i % threads].push([args, i])}
|
37
|
+
threads.times{|i| jobs[i].push(done)}
|
38
|
+
#end
|
39
|
+
|
40
|
+
# setup consumer list
|
41
|
+
#
|
42
|
+
consumers = Array.new threads
|
43
|
+
|
44
|
+
# setup support for short-circuit bailout via 'throw :threadify'
|
45
|
+
#
|
46
|
+
thrownv = Hash.new
|
47
|
+
thrownq = Queue.new
|
48
|
+
|
49
|
+
caught = false
|
50
|
+
|
51
|
+
catcher = Thread.new do
|
52
|
+
loop do
|
53
|
+
thrown = thrownq.pop
|
54
|
+
break if thrown == done
|
55
|
+
i, thrown = thrown
|
56
|
+
thrownv[i] = thrown
|
57
|
+
caught = true
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# fire off the consumers
|
62
|
+
#
|
63
|
+
threads.times do |i|
|
64
|
+
consumers[i] = Thread.new(jobs[i]) do |jobsi|
|
65
|
+
this = Thread.current
|
66
|
+
this.abort_on_exception = Threadify.abort_on_exception
|
67
|
+
|
68
|
+
job = nil
|
69
|
+
|
70
|
+
thrown =
|
71
|
+
catch(:threadify) do
|
72
|
+
loop{
|
73
|
+
break if caught
|
74
|
+
#job = jobsi.pop
|
75
|
+
job = jobsi.shift
|
76
|
+
break if job == done
|
77
|
+
args = job.first
|
78
|
+
jobsi << (job << block.call(*args))
|
79
|
+
}
|
80
|
+
nothing
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
unless nothing == thrown
|
85
|
+
args, i = job
|
86
|
+
thrownq.push [i, thrown]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# wait for consumers to finish
|
92
|
+
#
|
93
|
+
consumers.map{|t| t.join}
|
94
|
+
|
95
|
+
# nuke the catcher
|
96
|
+
#
|
97
|
+
thrownq.push done
|
98
|
+
catcher.join
|
99
|
+
|
100
|
+
# iff something(s) was thrown return the one which would have been thrown
|
101
|
+
# earliest in non-parallel execution
|
102
|
+
#
|
103
|
+
unless thrownv.empty?
|
104
|
+
key = thrownv.keys.sort.first
|
105
|
+
return thrownv[key]
|
106
|
+
end
|
107
|
+
|
108
|
+
# collect the results and return them
|
109
|
+
#
|
110
|
+
=begin
|
111
|
+
jobs.push done
|
112
|
+
ret = []
|
113
|
+
while((job = jobs.pop) != done)
|
114
|
+
elem, i, value = job
|
115
|
+
ret[i] = value
|
116
|
+
end
|
117
|
+
ret
|
118
|
+
end
|
119
|
+
=end
|
120
|
+
|
121
|
+
ret = []
|
122
|
+
jobs.each do |results|
|
123
|
+
results.each do |result|
|
124
|
+
break if result == done
|
125
|
+
elem, i, value = result
|
126
|
+
ret[i] = value
|
127
|
+
end
|
128
|
+
end
|
129
|
+
ret
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
class Thread
|
135
|
+
def Thread.ify enumerable, *args, &block
|
136
|
+
enumerable.send :threadify, *args, &block
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
class Object
|
141
|
+
def threadify! *values
|
142
|
+
throw :threadify, *values
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
if __FILE__ == $0
|
148
|
+
require 'open-uri'
|
149
|
+
require 'yaml'
|
150
|
+
|
151
|
+
uris = %w( http://google.com http://yahoo.com http://rubyforge.org/ http://ruby-lang.org)
|
152
|
+
|
153
|
+
Thread.ify uris, :threads => 6 do |uri|
|
154
|
+
body = open(uri){|pipe| pipe.read}
|
155
|
+
y uri => body.size
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
|
160
|
+
__END__
|
161
|
+
|
162
|
+
sample output
|
163
|
+
|
164
|
+
---
|
165
|
+
http://yahoo.com: 9562
|
166
|
+
---
|
167
|
+
http://google.com: 6290
|
168
|
+
---
|
169
|
+
http://rubyforge.org/: 22352
|
170
|
+
---
|
171
|
+
http://ruby-lang.org: 9984
|