dummer 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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