dummer 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +28 -0
- data/.travis.yml +4 -0
- data/CHANGELOG.md +63 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +279 -0
- data/Rakefile +15 -0
- data/bin/dummer +16 -0
- data/bin/dummer_simple +81 -0
- data/bin/dummer_yes +36 -0
- data/dummer.gemspec +29 -0
- data/example/input.conf +6 -0
- data/example/input.txt +10 -0
- data/example/message.conf +5 -0
- data/example/sample.conf +13 -0
- data/lib/dummer.rb +6 -0
- data/lib/dummer/cli.rb +96 -0
- data/lib/dummer/dsl.rb +28 -0
- data/lib/dummer/error.rb +3 -0
- data/lib/dummer/generator.rb +178 -0
- data/lib/dummer/setting.rb +16 -0
- data/lib/dummer/version.rb +3 -0
- data/lib/dummer/worker.rb +60 -0
- data/lib/ext/hash/except.rb +15 -0
- data/lib/ext/hash/keys.rb +138 -0
- data/lib/ext/hash/slice.rb +42 -0
- metadata +171 -0
data/bin/dummer_simple
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'thor'
|
3
|
+
|
4
|
+
module DummerSimple
|
5
|
+
class CLI < Thor
|
6
|
+
default_command :start
|
7
|
+
desc "start", "Start a dummer"
|
8
|
+
option :sync, :type => :boolean, :desc => 'Set `IO#sync=true`'
|
9
|
+
option :second, :aliases => ["-s"], :type => :numeric, :default => 1, :desc => 'Duration of running in second'
|
10
|
+
option :parallel, :aliases => ["-p"], :type => :numeric, :default => 1, :desc => 'Number of processes to run in parallel'
|
11
|
+
option :output, :aliases => ["-o"], :type => :string, :default => 'dummy.log', :desc => 'Output file'
|
12
|
+
option :input, :aliases => ["-i"], :type => :string, :desc => 'Input file (Output messages by reading lines of the file in rotation)'
|
13
|
+
option :message, :aliases => ["-m"], :type => :string, :desc => 'Output message',
|
14
|
+
:default => "time:2013-11-20 23:39:42 +0900\tlevel:ERROR\tmethod:POST\turi:/api/v1/people\treqtime:3.1983877060667103\n"
|
15
|
+
def start
|
16
|
+
sync = @options[:sync]
|
17
|
+
parallel = @options[:parallel]
|
18
|
+
output = @options[:output]
|
19
|
+
message = "#{@options[:message].chomp}\n"
|
20
|
+
messages = nil
|
21
|
+
if input = @options[:input] and (messages = readlines(input)).nil?
|
22
|
+
STDERR.puts "Input file `#{input}` does not exist or is not readable."
|
23
|
+
exit 1
|
24
|
+
end
|
25
|
+
|
26
|
+
open(output, (File::WRONLY | File::APPEND | File::CREAT)) do |out_file|
|
27
|
+
out_file.sync = true if sync
|
28
|
+
if messages
|
29
|
+
wait_and_kill { write_messages(out_file, messages, parallel) }
|
30
|
+
else
|
31
|
+
wait_and_kill { write_message(out_file, message, parallel) }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
no_commands {
|
37
|
+
def wait_and_kill(&block)
|
38
|
+
second = Time.now + @options[:second]
|
39
|
+
pids = yield # fork
|
40
|
+
while Time.now < second do; sleep 0.01; end
|
41
|
+
pids.each {|pid| Process.kill(:TERM, pid) }
|
42
|
+
end
|
43
|
+
|
44
|
+
def write_message(out_file, message, parallel = 1)
|
45
|
+
parallel.times.map do
|
46
|
+
Process.fork do
|
47
|
+
while true do
|
48
|
+
out_file.write message
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def write_messages(out_file, messages, parallel = 1)
|
55
|
+
size = messages.size
|
56
|
+
parallel.times.map do
|
57
|
+
Process.fork do
|
58
|
+
idx = -1 # create this in child-process not to be shared
|
59
|
+
while true do
|
60
|
+
idx = (idx + 1) % size
|
61
|
+
out_file.write messages[idx]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def readlines(input)
|
68
|
+
messages = nil
|
69
|
+
begin
|
70
|
+
open(input) do |in_file|
|
71
|
+
messages = in_file.readlines
|
72
|
+
end
|
73
|
+
rescue Errno::ENOENT
|
74
|
+
end
|
75
|
+
messages
|
76
|
+
end
|
77
|
+
}
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
DummerSimple::CLI.start(ARGV)
|
data/bin/dummer_yes
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'thor'
|
3
|
+
|
4
|
+
module DummerYes
|
5
|
+
class CLI < Thor
|
6
|
+
default_command :start
|
7
|
+
desc "start", "Start a dummer"
|
8
|
+
option :second, :aliases => ["-s"], :type => :numeric, :default => 1, :desc => 'Duration of running in second'
|
9
|
+
option :parallel, :aliases => ["-p"], :type => :numeric, :default => 1, :desc => 'Number of processes to run in parallel'
|
10
|
+
option :output, :aliases => ["-o"], :type => :string, :default => 'dummy.log', :desc => 'Output file'
|
11
|
+
option :message, :aliases => ["-m"], :type => :string, :desc => 'Output message',
|
12
|
+
:default => "time:2013-11-20 23:39:42 +0900\tlevel:ERROR\tmethod:POST\turi:/api/v1/people\treqtime:3.1983877060667103\n"
|
13
|
+
def start
|
14
|
+
parallel = @options[:parallel]
|
15
|
+
output = @options[:output]
|
16
|
+
message = "#{@options[:message].chomp}\n"
|
17
|
+
wait_and_kill {
|
18
|
+
parallel.times.map do
|
19
|
+
spawn("yes #{message}", :out => [output, (File::WRONLY | File::APPEND | File::CREAT)])
|
20
|
+
end
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
no_commands {
|
25
|
+
def wait_and_kill(&block)
|
26
|
+
second = Time.now + @options[:second]
|
27
|
+
pids = yield # fork
|
28
|
+
while Time.now < second do; sleep 0.01; end
|
29
|
+
pids.each {|pid| Process.kill(:TERM, pid) }
|
30
|
+
end
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
DummerYes::CLI.start(ARGV)
|
36
|
+
|
data/dummer.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'dummer/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "dummer"
|
8
|
+
spec.version = Dummer::VERSION
|
9
|
+
spec.authors = ["sonots"]
|
10
|
+
spec.email = ["sonots@gmail.com"]
|
11
|
+
spec.description = %q{Generates dummy log data for Fluentd benchmark}
|
12
|
+
spec.summary = %q{Generates dummy log data for Fluentd benchmark}
|
13
|
+
spec.homepage = "https://github.com/sonots/dummer"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "thor"
|
22
|
+
spec.add_dependency "serverengine"
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
25
|
+
spec.add_development_dependency "rake"
|
26
|
+
spec.add_development_dependency "rspec"
|
27
|
+
spec.add_development_dependency "pry"
|
28
|
+
spec.add_development_dependency "pry-nav"
|
29
|
+
end
|
data/example/input.conf
ADDED
data/example/input.txt
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
id:0 time:2013-11-26 02:45:33 +0900 level:WARN method:GET uri:/api/v1/people reqtime:2.3955637407914354 foobar:3xwwVG3T
|
2
|
+
id:1 time:2013-11-26 02:45:33 +0900 level:INFO method:POST uri:/api/v1/people reqtime:4.954494736221596 foobar:kRP5SiBW
|
3
|
+
id:2 time:2013-11-26 02:45:33 +0900 level:DEBUG method:POST uri:/api/v1/people reqtime:1.719978260834408 foobar:0EFaal3L
|
4
|
+
id:3 time:2013-11-26 02:45:33 +0900 level:DEBUG method:POST uri:/api/v1/people reqtime:3.2873443102110755 foobar:sGMFl2xs
|
5
|
+
id:4 time:2013-11-26 02:45:33 +0900 level:INFO method:POST uri:/api/v1/textdata reqtime:1.8776513072710397 foobar:dTlmjln3
|
6
|
+
id:5 time:2013-11-26 02:45:33 +0900 level:INFO method:POST uri:/api/v1/people reqtime:2.7644992372264974 foobar:jLL6zTdt
|
7
|
+
id:6 time:2013-11-26 02:45:33 +0900 level:WARN method:POST uri:/api/v1/people reqtime:4.609761906663468 foobar:k2hegeOG
|
8
|
+
id:7 time:2013-11-26 02:45:33 +0900 level:DEBUG method:POST uri:/api/v1/people reqtime:1.7312535151311619 foobar:JpwUkYKs
|
9
|
+
id:8 time:2013-11-26 02:45:33 +0900 level:WARN method:POST uri:/api/v1/people reqtime:2.2088570489185217 foobar:eOSmfjCb
|
10
|
+
id:9 time:2013-11-26 02:45:33 +0900 level:WARN method:POST uri:/api/v1/people reqtime:2.6351789530831637 foobar:cFBI2Gsm
|
data/example/sample.conf
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
configure 'sample' do
|
2
|
+
output "dummer.log"
|
3
|
+
rate 500
|
4
|
+
delimiter "\t"
|
5
|
+
labeled true
|
6
|
+
field :id, type: :integer, countup: true, format: "%04d"
|
7
|
+
field :time, type: :datetime, format: "[%Y-%m-%d %H:%M:%S]", random: false
|
8
|
+
field :level, type: :string, any: %w[DEBUG INFO WARN ERROR]
|
9
|
+
field :method, type: :string, any: %w[GET POST PUT]
|
10
|
+
field :uri, type: :string, any: %w[/api/v1/people /api/v1/textdata /api/v1/messages]
|
11
|
+
field :reqtime, type: :float, range: 0.1..5.0
|
12
|
+
field :foobar, type: :string, length: 8
|
13
|
+
end
|
data/lib/dummer.rb
ADDED
data/lib/dummer/cli.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'dummer'
|
3
|
+
require 'ext/hash/keys'
|
4
|
+
require 'ext/hash/except'
|
5
|
+
|
6
|
+
module Dummer
|
7
|
+
class CLI < Thor
|
8
|
+
# options for serverengine
|
9
|
+
class_option :pid_path, :aliases => ["-p"], :type => :string, :default => 'dummer.pid'
|
10
|
+
default_command :start
|
11
|
+
|
12
|
+
def initialize(args = [], opts = [], config = {})
|
13
|
+
super(args, opts, config)
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "start", "Start a dummer"
|
17
|
+
option :config, :aliases => ["-c"], :type => :string, :default => 'dummer.conf', :desc => 'Config file'
|
18
|
+
option :rate, :aliases => ["-r"], :type => :numeric, :desc => 'Number of generating messages per second'
|
19
|
+
option :output, :aliases => ["-o"], :type => :string, :desc => 'Output file'
|
20
|
+
option :message, :aliases => ["-m"], :type => :string, :desc => 'Output message'
|
21
|
+
# options for serverengine
|
22
|
+
option :daemonize, :aliases => ["-d"], :type => :boolean, :desc => 'Daemonize. Stop with `dummer stop`'
|
23
|
+
option :workers, :aliases => ["-w"], :type => :numeric, :desc => 'Number of parallels'
|
24
|
+
option :worker_type, :type => :string, :default => 'process'
|
25
|
+
def start
|
26
|
+
@options = @options.dup # avoid frozen
|
27
|
+
dsl =
|
28
|
+
if options[:config] && File.exists?(options[:config])
|
29
|
+
instance_eval(File.read(options[:config]), options[:config])
|
30
|
+
else
|
31
|
+
Dummer::Dsl.new
|
32
|
+
end
|
33
|
+
@options[:setting] = dsl.setting
|
34
|
+
dsl.setting.rate = options[:rate] if options[:rate]
|
35
|
+
dsl.setting.output = options[:output] if options[:output]
|
36
|
+
dsl.setting.message = options[:message] if options[:message]
|
37
|
+
# options for serverengine
|
38
|
+
@options[:workers] ||= dsl.setting.workers
|
39
|
+
|
40
|
+
opts = @options.symbolize_keys.except(:config)
|
41
|
+
se = ServerEngine.create(nil, Dummer::Worker, opts)
|
42
|
+
se.run
|
43
|
+
end
|
44
|
+
|
45
|
+
desc "stop", "Stops a dummer"
|
46
|
+
def stop
|
47
|
+
pid = File.read(@options["pid_path"]).to_i
|
48
|
+
|
49
|
+
begin
|
50
|
+
Process.kill("QUIT", pid)
|
51
|
+
puts "Stopped #{pid}"
|
52
|
+
rescue Errno::ESRCH
|
53
|
+
puts "Dummer #{pid} not running"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
desc "graceful_stop", "Gracefully stops a dummer"
|
58
|
+
def graceful_stop
|
59
|
+
pid = File.read(@options["pid_path"]).to_i
|
60
|
+
|
61
|
+
begin
|
62
|
+
Process.kill("TERM", pid)
|
63
|
+
puts "Gracefully stopped #{pid}"
|
64
|
+
rescue Errno::ESRCH
|
65
|
+
puts "Dummer #{pid} not running"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
desc "restart", "Restarts a dummer"
|
70
|
+
def restart
|
71
|
+
pid = File.read(@options["pid_path"]).to_i
|
72
|
+
|
73
|
+
begin
|
74
|
+
Process.kill("HUP", pid)
|
75
|
+
puts "Restarted #{pid}"
|
76
|
+
rescue Errno::ESRCH
|
77
|
+
puts "Dummer #{pid} not running"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
desc "graceful_restart", "Graceful restarts a dummer"
|
82
|
+
def graceful_restart
|
83
|
+
pid = File.read(@options["pid_path"]).to_i
|
84
|
+
|
85
|
+
begin
|
86
|
+
Process.kill("USR1", pid)
|
87
|
+
puts "Gracefully restarted #{pid}"
|
88
|
+
rescue Errno::ESRCH
|
89
|
+
puts "Dummer #{pid} not running"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
Dummer::CLI.start(ARGV)
|
data/lib/dummer/dsl.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
module Dummer
|
2
|
+
class Dsl
|
3
|
+
attr_reader :setting
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@setting = Setting.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def method_missing(name, *args)
|
10
|
+
if @setting.respond_to?("#{name}=")
|
11
|
+
@setting.__send__("#{name}=", *args)
|
12
|
+
else
|
13
|
+
raise ConfigError.new("Config parameter `#{name}` does not exist")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def field(name, opts)
|
18
|
+
setting.fields ||= {}
|
19
|
+
setting.fields[name] = opts
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def configure(title, &block)
|
25
|
+
dsl = Dummer::Dsl.new
|
26
|
+
dsl.instance_eval(&block)
|
27
|
+
dsl
|
28
|
+
end
|
data/lib/dummer/error.rb
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
module Dummer
|
2
|
+
class Generator
|
3
|
+
def initialize(setting)
|
4
|
+
@message_proc =
|
5
|
+
if fields = setting.fields
|
6
|
+
labeled, delimiter = setting.labeled, setting.delimiter
|
7
|
+
prepare_message_proc_for_fields(fields, labeled, delimiter)
|
8
|
+
elsif input = setting.input
|
9
|
+
prepare_message_proc_for_input(input)
|
10
|
+
else
|
11
|
+
message = setting.message
|
12
|
+
prepare_message_proc_for_message(message)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def prepare_message_proc_for_input(input)
|
17
|
+
messages = nil
|
18
|
+
begin
|
19
|
+
open(input) do |in_file|
|
20
|
+
messages = in_file.readlines
|
21
|
+
end
|
22
|
+
rescue Errno::ENOENT
|
23
|
+
raise ConfigError.new("Input file `#{input}` is not readable")
|
24
|
+
end
|
25
|
+
idx = -1
|
26
|
+
size = messages.size
|
27
|
+
Proc.new {
|
28
|
+
idx = (idx + 1) % size
|
29
|
+
messages[idx]
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def prepare_message_proc_for_message(message)
|
34
|
+
message = "#{message.chomp}\n"
|
35
|
+
Proc.new { message }
|
36
|
+
end
|
37
|
+
|
38
|
+
def prepare_message_proc_for_fields(fields, labeled, delimiter)
|
39
|
+
format_proc = prepare_format_proc(labeled, delimiter)
|
40
|
+
field_procs = prepare_field_procs(fields)
|
41
|
+
|
42
|
+
prev_data = {}
|
43
|
+
Proc.new {
|
44
|
+
data = {}
|
45
|
+
field_procs.each do |key, proc|
|
46
|
+
prev = prev_data[key] || -1
|
47
|
+
data[key] = proc.call(prev)
|
48
|
+
end
|
49
|
+
prev_data = data
|
50
|
+
format_proc.call(data)
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
def prepare_format_proc(labeled, delimiter)
|
55
|
+
if labeled
|
56
|
+
Proc.new {|fields| "#{fields.map {|key, val| "#{key}:#{val}" }.join(delimiter)}\n" }
|
57
|
+
else
|
58
|
+
Proc.new {|fields| "#{fields.values.join(delimiter)}\n" }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def prepare_field_procs(fields)
|
63
|
+
rand = ::Dummer::Random.new
|
64
|
+
field_procs = {}
|
65
|
+
fields.each do |key, opts|
|
66
|
+
opts = opts.dup
|
67
|
+
type = opts.delete(:type)
|
68
|
+
if rand.respond_to?(type)
|
69
|
+
field_procs[key] = rand.send(type, opts)
|
70
|
+
else
|
71
|
+
raise ConfigError.new(type)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
field_procs
|
75
|
+
end
|
76
|
+
|
77
|
+
def generate
|
78
|
+
@message_proc.call
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class Random
|
83
|
+
def initialize
|
84
|
+
@rand = ::Random.new(0)
|
85
|
+
@chars = ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a # no symbols and multi-bytes for now
|
86
|
+
end
|
87
|
+
|
88
|
+
def string(length: 8, any: nil, value: nil)
|
89
|
+
if value
|
90
|
+
string = value.to_s
|
91
|
+
Proc.new { string }
|
92
|
+
elsif any
|
93
|
+
Proc.new { self.any(any) }
|
94
|
+
else
|
95
|
+
Proc.new { Array.new(length){@chars[rand(@chars.size-1)]}.join }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def integer(format: nil, range: nil, countup: false, value: nil)
|
100
|
+
if format
|
101
|
+
if value
|
102
|
+
integer = sprintf(format, value.to_i)
|
103
|
+
Proc.new { integer }
|
104
|
+
elsif range
|
105
|
+
Proc.new { sprintf(format, self.range(range)) }
|
106
|
+
elsif countup
|
107
|
+
Proc.new {|prev| sprintf(format, prev.to_i + 1) }
|
108
|
+
else
|
109
|
+
Proc.new { sprintf(format, rand(0..2,147,483,647)) }
|
110
|
+
end
|
111
|
+
else
|
112
|
+
if value
|
113
|
+
integer = value.to_i
|
114
|
+
Proc.new { integer }
|
115
|
+
elsif range
|
116
|
+
Proc.new { self.range(range) }
|
117
|
+
elsif countup
|
118
|
+
Proc.new {|prev| prev + 1 }
|
119
|
+
else
|
120
|
+
Proc.new { rand(0..2,147,483,647) }
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def float(format: nil, range: nil, value: nil)
|
126
|
+
if format
|
127
|
+
if value
|
128
|
+
float = value.to_f
|
129
|
+
Proc.new { sprintf(format, float) }
|
130
|
+
elsif range
|
131
|
+
Proc.new { sprintf(format, self.range(range)) }
|
132
|
+
else
|
133
|
+
Proc.new { r = rand(1..358); sprintf(format, r * Math.cos(r)) }
|
134
|
+
end
|
135
|
+
else
|
136
|
+
if value
|
137
|
+
float = value.to_f
|
138
|
+
Proc.new { float }
|
139
|
+
elsif range
|
140
|
+
Proc.new { self.range(range) }
|
141
|
+
else
|
142
|
+
Proc.new { r = rand(1..358); r * Math.cos(r) }
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def datetime(format: "%Y-%m-%d %H:%M:%S.%3N", random: false, value: nil)
|
148
|
+
if value
|
149
|
+
Proc.new { value.strftime(format) }
|
150
|
+
elsif random
|
151
|
+
Proc.new {
|
152
|
+
y = rand(1970..2037);
|
153
|
+
m = rand(1..12);
|
154
|
+
d = rand(1..27);
|
155
|
+
h = rand(0..23);
|
156
|
+
min = rand(0..59);
|
157
|
+
s = rand(0..59);
|
158
|
+
usec = rand(0..999999);
|
159
|
+
Time.local(y, m, d, h, min, s, usec).strftime(format)
|
160
|
+
}
|
161
|
+
else
|
162
|
+
Proc.new { Time.now.strftime(format) }
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def range(range)
|
167
|
+
rand(range)
|
168
|
+
end
|
169
|
+
|
170
|
+
def any(any)
|
171
|
+
any[rand(any.size-1)]
|
172
|
+
end
|
173
|
+
|
174
|
+
def rand(arg = nil)
|
175
|
+
@rand.rand(arg)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|