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.
@@ -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)
@@ -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
+
@@ -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
@@ -0,0 +1,6 @@
1
+ configure 'input' do
2
+ output "dummer.log"
3
+ rate 500
4
+ input "example/input.txt"
5
+ end
6
+
@@ -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
@@ -0,0 +1,5 @@
1
+ configure 'message' do
2
+ output "dummer.log"
3
+ rate 500
4
+ message "time:2013-11-25 00:23:52 +0900\tlevel:ERROR\tmethod:POST\turi:/api/v1/people\treqtime:3.1983877060667103\n"
5
+ end
@@ -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
@@ -0,0 +1,6 @@
1
+ require "dummer/version"
2
+ require "dummer/error"
3
+ require "dummer/setting"
4
+ require "dummer/generator"
5
+ require "dummer/worker"
6
+ require "dummer/dsl"
@@ -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)
@@ -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
@@ -0,0 +1,3 @@
1
+ module Dummer
2
+ class ConfigError < StandardError; end
3
+ end
@@ -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