dummer 0.3.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.
- 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
|