roadrunner 4.0.3

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/lib/db.rb ADDED
@@ -0,0 +1,51 @@
1
+ #
2
+
3
+ module RoadRunnerModule
4
+
5
+ module Dbhelper
6
+
7
+ def migrate(tables)
8
+ tables.each do |table|
9
+ case table
10
+ when "scenarios" then
11
+ unless ActiveRecord::Base.connection.tables.include?(table.to_s) then
12
+ ActiveRecord::Base.connection.create_table(table.to_sym) do |t|
13
+ t.column :name, :string, :limit => 256
14
+ t.column :create_at,:string, :limit => 32
15
+ t.column :script, :string,:limit => 10240
16
+ t.column :author, :string, :default => 'Anonymous'
17
+ t.column :author, :tps, :int
18
+ t.column :desc, :string
19
+ end
20
+ end
21
+
22
+ when "transactions" then
23
+ unless ActiveRecord::Base.connection.tables.include?(table.to_s) then
24
+ ActiveRecord::Base.connection.create_table(table.to_sym) do |t|
25
+ t.column :name,:string,:limit => 256
26
+ t.column :scenario_id,:string,:limit => 256
27
+ t.column :success_rate,:string, :limit => 8
28
+ t.column :create_at,:string, :limit => 32
29
+
30
+ end
31
+ end
32
+
33
+ when "records" then
34
+ unless ActiveRecord::Base.connection.tables.include?(table.to_s) then
35
+ ActiveRecord::Base.connection.create_table(table.to_sym) do |t|
36
+ t.column :cost, :string, :limit => 32
37
+ t.column :ts, :string, :limit => 32
38
+ t.column :seq, :int
39
+ t.column :stats,:int
40
+ t.column :transaction_id,:string,:limit => 256
41
+ t.column :create_at,:string, :limit => 32
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ module_function :migrate
49
+ end
50
+
51
+ end
data/lib/ended.rb ADDED
@@ -0,0 +1,14 @@
1
+ #Here is LR End function
2
+
3
+ module RoadRunnerModule
4
+ def ended(&blk)
5
+
6
+ if block_given?
7
+ @endBlk=blk
8
+ register_transactions('end',&blk)
9
+ else
10
+ raise ArgumentError, "no block"
11
+ end
12
+
13
+ end
14
+ end
data/lib/init.rb ADDED
@@ -0,0 +1,14 @@
1
+ #Here is LR init function
2
+
3
+ module RoadRunnerModule
4
+ def init(&blk)
5
+
6
+ if block_given?
7
+ @initBlk=blk
8
+ register_transactions('init',&blk)
9
+ else
10
+ raise ArgumentError, "no block"
11
+ end
12
+
13
+ end
14
+ end
data/lib/model.rb ADDED
@@ -0,0 +1,10 @@
1
+ class Scenario < ActiveRecord::Base
2
+ has_many :transactions
3
+ end
4
+ class Transaction < ActiveRecord::Base
5
+ belongs_to :scenario
6
+ has_many :records
7
+ end
8
+ class Record < ActiveRecord::Base
9
+ belongs_to :transaction
10
+ end
data/lib/report.rb ADDED
@@ -0,0 +1,100 @@
1
+ #After roadRunner.run() executed,
2
+ #execute roadRunner.report() show the Performance report
3
+
4
+ module RoadRunnerModule
5
+
6
+ def report(label='',width=0)
7
+ p " Wait for moment,the report is collecting......"
8
+ content = if @rep
9
+ @succRates = getSuccessRate @transactions
10
+ <<-DOC
11
+ #{"Performance Reports".center(50, '*')}
12
+ #{label.ljust(width)}
13
+ #{Benchmark::Tms::CAPTION}
14
+ #{@rep.format}
15
+ #{'--'*32}
16
+ The Virtual User is #{@users}.
17
+ Total Execute #{@iterations*@users} Action(s).
18
+ Total Cost #{@rep.real} Second(s).
19
+ This Scenario's TPS : #{@tps}.
20
+ The longest action cost #{@longest || 0} seconds.
21
+ #{'Transaction Report'.center(50, '-')}
22
+ #{@transactions.inject("") { |str,k|
23
+ str += "#{k[0].to_s} : count => #{k[1].size} time(s) , cost => #{k[1].inject(0){|c,v|c+=v[:cost].to_f}.to_s[0..3]} sec(s) , success rate => #{@succRates[k[0]]}\n "
24
+ }.gsub(/\n$/,'')}
25
+ #{'--'*32}
26
+ User defined params as below:
27
+ #{@global}
28
+ #{"End of Reports".center(50, '*')}
29
+ DOC
30
+ else
31
+ "None Report before RoadRunner run."
32
+ end
33
+ puts content
34
+ self.log.info content
35
+ end
36
+
37
+ def save_report(opts={:author=>'Anonymous',:script=>""})
38
+
39
+ p " Saving report to database......"
40
+ opts={:author=>'Anonymous',:script=>""}.merge opts
41
+ if @data_write then
42
+ unless opts[:name] then
43
+ self.log.warn "scenario name may be needed."
44
+ require 'uuidtools' unless defined?UUID
45
+ # scenario || UUID.random_create.to_s is for debug
46
+ opts[:name] = opts[:name] || UUID.random_create.to_s
47
+ self.log.info "scenario is created as #{opts[:name]}"
48
+ end
49
+ # => scenario = Scenario.find_or_create_by_name(opts.merge({:create_at=>Time.now})).shift
50
+ scenario = Scenario.find_or_create_by_name(opts.merge({:create_at=>Time.now}))
51
+ scenario.tps+=@iterations*@users/@rep.real
52
+
53
+ # => if opts[:script] is set as <your script>.rb 's path
54
+ # => opts[:script] = __FILE__
55
+ # => then scenario.script will be set.
56
+ if FileTest.exist?(opts[:script]) then
57
+ scenario.script = ""
58
+ IO.foreach(opts[:script]){|x|scenario.script << x}
59
+ end
60
+
61
+ @succRates = @succRates || (getSuccessRate @transactions)
62
+ # k is transaction name ,
63
+ # v is reports in every transaction.
64
+ @transactions.each do |k,v|
65
+ transaction = Transaction.new({:name=>k,:create_at=>Time.now,:success_rate=>@succRates[k]})
66
+ v.each_index do |id|
67
+ transaction.records << Record.new(v[id].merge({:seq=>id,:ts=>v[id][:create_at].to_f-@strat_time.to_f}))
68
+ self.log.debug "v[#{id}] = #{v[id].inspect}"
69
+ end
70
+ scenario.transactions << transaction
71
+ end
72
+ begin
73
+ scenario.save!
74
+ p " Saved OK!"
75
+ rescue =>e
76
+ self.log.error e
77
+ p e
78
+ end
79
+ self.log.info "records has saved in DB."
80
+ else
81
+ p ' Error:You didn\'t connect any database.'
82
+ self.log.error 'You didn\'t connect any database.'
83
+ end
84
+ end
85
+
86
+ def getSuccessRate(transactions)
87
+ result = {}
88
+ transactions.each do |h|
89
+ success,faile = 0,0
90
+ h[1].each do |r|
91
+ r[:stats]==0?success+=1:faile+=1
92
+ end
93
+ result[h[0]]=success.to_f/(success+faile)
94
+ end
95
+ result
96
+ end
97
+
98
+ private :getSuccessRate
99
+
100
+ end
data/lib/roadrunner.rb ADDED
@@ -0,0 +1,122 @@
1
+ # Author : zheng.cuizh@gmail.com
2
+ # RoadRunner named lik LoadRunner which is a industry performance test tool of HP.
3
+ # I'm happy to user LR and Ruby,
4
+ # so i coded the RoadRunner.
5
+ # Using RR,you'll find somethings similar to LR.
6
+
7
+ $:.unshift File.dirname(__FILE__)
8
+ require 'rubygems'
9
+ require 'benchmark'
10
+ require 'logger'
11
+ require 'pp'
12
+
13
+ Dir[File.join(File.dirname(__FILE__),"*.rb")].select{|x|not (x =~ /.*model\.rb/ || x =~ /.*db\.rb/ || x =~ /.*roadrunner\.rb/)}.each { |x| require x }
14
+
15
+ class RoadRunner
16
+
17
+ include RoadRunnerModule
18
+ attr :iterations, true
19
+ attr :users,true
20
+ attr :global,true
21
+ attr :iterationId
22
+ attr :record
23
+ attr :userId
24
+ attr :mode,true
25
+ attr :log
26
+ attr :tps
27
+ alias_method :g,:global
28
+ alias_method :g=,:global=
29
+
30
+ def initialize(opts={})
31
+ # => DEBUG < INFO < WARN < ERROR < FATAL < UNKNOWN
32
+ opts = {:out=>File.join(File.dirname(__FILE__),'..','log',"stdout.log"),:frequency=>'daily',:size=>1048576,:level=>Logger::DEBUG}.merge opts
33
+ @users,@iterations,@longest,@userId,@iterationId=1,1,0,0,0
34
+ @initBlk,@actBlk,@endBlk=proc{},proc{},proc{}
35
+ @global,@transactions={},{}
36
+ @log=Logger.new(opts[:out],opts[:frequency],opts[:size])
37
+ @log.level=opts[:level]
38
+ # => mode : sequence<default>,thread|t,process|p
39
+ @mode='sequence'
40
+
41
+ @transaction_blk={}
42
+
43
+ if block_given? then
44
+ begin
45
+ gem 'active_record'
46
+ require 'active_record'
47
+ rescue Exception => e
48
+ require 'activerecord'
49
+ end
50
+
51
+ require 'db'
52
+ require "model"
53
+
54
+ session = {}
55
+ _session = yield session
56
+ session = _session if session == {}
57
+
58
+ begin
59
+ ActiveRecord::Base.establish_connection(
60
+ #
61
+ # === session defined in block :
62
+ #
63
+ # :adapter=>session[:adapter],
64
+ # :encoding=>session[:encoding],
65
+ # :database=>session[:database],
66
+ # :username=>session[:username],
67
+ # :password=>session[:password],
68
+ # :host=>session[:host]
69
+ #
70
+ session
71
+ )
72
+ self.log.info "connect db ok."
73
+ rescue =>ex
74
+ self.log.error "adapter:#{session[:adapter]}.connect db faile."
75
+ end
76
+
77
+ default_report_table = %w"scenarios transactions records"
78
+
79
+ unless @data_write = default_report_table.inject(true){|r,t| r = r && ActiveRecord::Base.connection.tables.include?(t)} then
80
+ self.log.warn "table rreports doesn't defined and will be created."
81
+ Dbhelper::migrate default_report_table
82
+ if @data_write = default_report_table.inject(true){|r,t| r = r && ActiveRecord::Base.connection.tables.include?(t)} then
83
+ self.log.info "create table #{default_report_table.inspect} successful."
84
+ else
85
+ self.log.error "create table #{default_report_table.inspect} fail."
86
+ end
87
+ end
88
+ require 'model' unless defined? Scenario
89
+ self.log.debug 'model Rreport is reqruired.'
90
+ end
91
+ end
92
+
93
+ def transaction(name,&blk)
94
+ @transactions[name] || @transactions[name] = []
95
+ status = "AUTO"
96
+ # => Status is the last expression in the action block
97
+ rcost = Benchmark::realtime{status = yield} if block_given?
98
+ # => status's value is:
99
+ # => 0=>success
100
+ # => -1=>faile
101
+ case status
102
+ when false,'false','-1',-1 then status = -1
103
+ else status = 0
104
+ end
105
+ # {:stats=>status,:cost=>rcost,:create_at=>Time.now} is one record
106
+ @transactions[name] << {:stats=>status,:cost=>rcost,:create_at=>Time.now}
107
+ # => the below sentence cost a lot of system resource
108
+ # => if you run for production result,keep it annotated!!!
109
+ # self.log.debug @transactions[name].inspect
110
+ rcost
111
+ end
112
+
113
+ def register_transactions(name,&blk)
114
+ @transaction_blk[name]=blk
115
+ end
116
+
117
+ def method_missing(name,*args,&blk)
118
+ # self.transaction(name.to_s,&blk)
119
+ register_transactions(name,&blk)
120
+ end
121
+
122
+ end
data/lib/rrhelper.rb ADDED
@@ -0,0 +1,25 @@
1
+ # Rrhelper will include many helpful methods for RoadRunner
2
+
3
+ module RoadRunnerModule
4
+
5
+ def iterationId
6
+ if(@mode=='thread')then
7
+ @thread_pool[Thread.current][:iterationId]
8
+ else
9
+ @iterationId
10
+ end
11
+ end
12
+
13
+ def userId
14
+ if(@mode=='thread')then
15
+ @thread_pool[Thread.current][:userId]
16
+ else
17
+ @userId
18
+ end
19
+ end
20
+
21
+ module Rrhelper
22
+
23
+ end
24
+
25
+ end
data/lib/rrmonitor.rb ADDED
@@ -0,0 +1,174 @@
1
+ require "rubygems"
2
+ require "net/ssh"
3
+ require 'fileutils'
4
+
5
+ module RoadRunnerModule
6
+
7
+ =begin
8
+ RRMonitor is used for Xnix system.
9
+ =end
10
+
11
+ class RRMonitor
12
+ def initialize(opt={:server=>"0.0.0.0", :username=>"admin", :password=>"123456"})
13
+ raise(ArgumentError,"Logger is needed!") unless opt[:log]
14
+ @log=opt[:log]
15
+
16
+ @server=opt[:server]
17
+
18
+ begin
19
+ @sess=Net::SSH.start(opt[:server], opt[:username], :password => opt[:password],:timeout => 120)
20
+ @log.info "#{opt[:server]} ssh connection OK."
21
+ rescue Exception => e
22
+ @log.error e
23
+ @log.error opt.inspect
24
+ raise e
25
+ end
26
+
27
+
28
+ @log.info("monitor initialize.".center(60,"*"))
29
+ @log.debug("monitor => #{self.inspect}")
30
+
31
+ @ts=( opt[:ts] || Time.now.to_i )
32
+
33
+ @path="#{File.join File.dirname(__FILE__),'..','log'}/monitor/#{@ts}"
34
+ @log.info "monitor local log path :#{@path}"
35
+
36
+ @rpath="~/.monit/#{@ts}"
37
+ @log.info "monitor remote log path(rpath) :#{@rpath}"
38
+
39
+ @sess.exec!("rm -rf #{@rpath}")
40
+ @sess.exec!("mkdir -p #{@rpath}")
41
+
42
+ @pids={}
43
+ end
44
+
45
+ =begin
46
+ run!(cmd) =>
47
+ the cmd will be execed at remote server(0),
48
+ and the stdout will write in log file.
49
+
50
+ cmd =>[
51
+ 'ifstat',# network io
52
+ 'iostat 3',# disk io
53
+ 'vmstat 3',# memory info
54
+ 'while((1>0));do sar -u -d 3 10; done',# disk and cpu info
55
+ 'while((1>0));do sar -u -n DEV 3 10; done'# network and cpu info
56
+ ]
57
+
58
+ "while((1>0));do #{cmd}; done" is needed for non-consist output cmd.
59
+
60
+ cmd sample =>
61
+
62
+ run!(["while((1>0));do /home/admin/apsara/build/debug64/bin/ku --command=getallpart --appname=blog1 --interactive=false|tail -n1; done"])
63
+
64
+ when exec!(cmd)
65
+ =>
66
+ exec!("while((1>0));do /home/admin/apsara/build/debug64/bin/ku --command=getallpart --appname=blog1 --interactive=false|tail -n1; done >> #{kupath} 2>/dev/null &")
67
+ =end
68
+
69
+ def run!(cmd=['ifstat','iostat 3','while((1>0));do sar -u -d 3 10; done','vmstat 3','while((1>0));do sar -u -n DEV 3 10; done'])
70
+ (@log.error "run!(cmd) cmd type require array.";exit) unless cmd.is_a?(Array)
71
+ (@log.error "run!(cmd) require commands.";exit) if cmd.none?
72
+
73
+ @cmd=cmd
74
+
75
+ begin
76
+ @log.info("RUN!".center(60,"*"))
77
+ @log.debug @server
78
+ cmd.each { |e| self.exec!(e)}
79
+ rescue Exception => e
80
+ @log.error("#{self.inspect} run error!")
81
+ @log.error(e.to_s)
82
+ # raise e
83
+ end
84
+
85
+ end
86
+
87
+ def exec!(cmd)
88
+ # >> cmd='while((1>0));do sar -u -d 3 10; # done';cmdpath="#{cmd.gsub(/([^a-zA-Z0-9\-])/,'_')}.log"
89
+ # => "while__1_0___do_sar_-u_-d_3_10__done.log"
90
+
91
+ # cmdpath="#{@rpath}/#{cmd.gsub(/[\s|\t|\$|\`|\(|\)|\&|\>|\<|\;|\||\'|\"]/,'_')}.log"
92
+ cmdpath="#{@rpath}/#{cmd.gsub(/[^a-zA-Z0-9\-]/,'_')}.log"
93
+ @cmdpath=cmdpath
94
+
95
+ @log.debug "#{cmd} path => #{cmdpath}"
96
+ @log.info "Start #{cmd}.".center(60,"*")
97
+
98
+ @log.debug("#{cmd} >> #{cmdpath} 2>/dev/null &")
99
+ @sess.exec!("#{cmd} >> #{cmdpath} 2>/dev/null &")
100
+
101
+ @pids[cmd]=@sess.exec!("echo $!")
102
+ @log.debug("#{cmd} pid => #{@pids[:ku]}")
103
+ end
104
+
105
+ def stop!
106
+ @pids.each do |key,value|
107
+ @sess.exec!("kill -9 #{value}")
108
+ @log.info("#{key} => #{value} stop!")
109
+ end
110
+
111
+ # if other user exec the same cmd,
112
+ # below cmd will kill all of the cmd.
113
+
114
+ # @sess.exec!(%q@ps axf|grep while|grep -v grep|awk '{printf "%s\n",$1}'|xargs kill -9@)
115
+ #
116
+ # @cmd.each { |e|
117
+ # @sess.exec!(%q@ps axf|grep "#{e}"|grep -v grep|awk '{printf "%s\n",$1}'|xargs kill -9@)
118
+ # }
119
+
120
+ @log.debug @server
121
+ @log.info("Monitor STOP!".center(60,"*"))
122
+ end
123
+
124
+ def collect
125
+ @log.debug "mkdir -p #{@path}/#{@server}"
126
+ %x{mkdir -p #{@path}/#{@server}}
127
+
128
+ path = @path.gsub('~',`echo ~`.gsub(/[\r\n]/,''))
129
+ @log.info "Collecting...".center(60,"*")
130
+ @log.debug "collect path => #{path}/#{@server}"
131
+ `scp -r admin@#{@server}:#{@rpath} #{path}/#{@server}`
132
+ @log.info "collected files => #{Dir[path+'/'+@server+'/**/*'].join($/)}"
133
+ end
134
+
135
+ #-----------------------------monit method--------------------------------#
136
+
137
+ def self.monit(servers,log,cmd=['ifstat','iostat 3','while((1>0));do sar -u -d 3 10; done','vmstat 3','while((1>0));do sar -u -n DEV 3 10; done'],ts="PerformanceTest")
138
+
139
+ case servers
140
+ when Hash
141
+ # do nothing
142
+ when String
143
+ File.open( servers ) { |yf|
144
+ begin
145
+ servers=YAML::load( yf )
146
+ rescue Exception => e
147
+ log.error("Servers' config YAML Load Error!Plz check your yaml file => #{servers}.#{$/}#{e.to_s}")
148
+ exit 1
149
+ end
150
+ }
151
+ end
152
+
153
+ _servers = {}
154
+
155
+ servers.each do |ip,obj|
156
+ _servers[ip] = RoadRunnerModule::RRMonitor.new({:server=>ip, :log=>log, :username=>obj[:username], :password=>obj[:password], :ts=>obj[:obj]||ts})
157
+
158
+ _servers[ip].run! cmd
159
+ end
160
+
161
+ yield
162
+
163
+ _servers.each do |k,v|
164
+ v.stop!
165
+ v.collect
166
+ end
167
+
168
+ end
169
+
170
+ end
171
+
172
+ end
173
+
174
+
data/lib/run.rb ADDED
@@ -0,0 +1,113 @@
1
+ #After roadRunner.run() executed,
2
+ #execute roadRunner.report() show the Performance report
3
+
4
+ module RoadRunnerModule
5
+ def run
6
+ @strat_time = Time.now
7
+ @initBlk.call
8
+ @thread_pool = {}
9
+ @counter = 0
10
+
11
+ @log.debug "Mode => #{@mode}"
12
+ iterationBlk = case @mode
13
+ when /thread/,/t/ then
14
+ proc {
15
+ @users.times do |userId|
16
+ @thread_pool[Thread.start(){
17
+ @iterations.times do |iterationId|
18
+
19
+ rcost = 0
20
+ @transaction_blk.each_pair{|k,v|
21
+ rcost = self.transaction(k,v)
22
+ }
23
+
24
+ # rcost = self.transaction('Action',&@actBlk)
25
+ self.log.debug "IterationID is #{self.iterationId};UserID is #{self.userId};This Action Cost #{rcost} seconds"
26
+ @longest = (@longest<rcost)?rcost:@longest
27
+ @thread_pool[Thread.current][:iterationId] = iterationId
28
+ end
29
+ @counter += 1
30
+ }]={:userId=>userId}
31
+ end
32
+ # @thread_pool.keys.each{|t|t.join}
33
+ while @counter != @users do
34
+ Thread.pass
35
+ end
36
+ }
37
+ when /process/,/p/ then
38
+ proc {
39
+ ppid=Process.pid
40
+ @log.info("Main Process pid => #{ppid}")
41
+
42
+ pids=[]
43
+ @users.times { |userId|
44
+ pids << Process.fork {
45
+ @userId=userId
46
+ @iterations.times do |iterationId|
47
+ @iterationId=iterationId
48
+
49
+ rcost = 0
50
+ @transaction_blk.each_pair{|k,v|
51
+ rcost = self.transaction(k,v)
52
+ }
53
+
54
+ # rcost = self.transaction('Action',&@actBlk)
55
+ @longest = (@longest<rcost)?rcost:@longest
56
+ end
57
+ @log.info("<PID:#{Process.pid}> going down.Longest job cost #{@longest}")
58
+
59
+ Process.kill("HUP", ppid)
60
+ }
61
+ }
62
+
63
+ switch=true
64
+ c=0
65
+ Signal.trap("HUP", proc { @log.info("One User(Process) done."); (switch = false;p "Main process<#{Process.pid}> going down.") if ((c+=1) == @users) })
66
+
67
+ @log.info "Waiting Processes => #{pids.inspect}"
68
+
69
+ p "Processes<#{pids.inspect}> working."
70
+ while switch
71
+ STDOUT.puts '.'
72
+ sleep 5
73
+ end
74
+ p $/
75
+
76
+ Process.waitall
77
+ @log.info "Processes down."
78
+ }
79
+ else
80
+ proc {
81
+ @iterations.times do |iterationId|
82
+ @iterationId=iterationId
83
+ @users.times do |userId|
84
+ @userId=userId
85
+
86
+ rcost = 0
87
+ @transaction_blk.each_pair{|k,v|
88
+ rcost = self.transaction(k,v)
89
+ }
90
+
91
+ # rcost = self.transaction('Action',&@actBlk)
92
+
93
+ # => the below sentence cost a lot of system resource
94
+ # => if you run for production result,keep it annotated!!!
95
+ # self.log.debug "IterationID is #{self.iterationId};UserID is #{self.userId};This Action Cost #{rcost} seconds"
96
+ @longest = (@longest<rcost)?rcost:@longest
97
+ end
98
+ end
99
+ }
100
+ end
101
+
102
+ p ' '+"RoadRunner".center(50, '*')
103
+ p ' *'+"---Run , on your way.".rjust(48, ' ')+'*'
104
+ p ' '+'*'*50
105
+ p
106
+ p " Running......"
107
+ @rep = Benchmark::measure(&iterationBlk)
108
+ p " Ending......"
109
+ @endBlk.call
110
+
111
+ @tps = @iterations*@users/@rep.real
112
+ end
113
+ end